Add MCP server-side tools support for Anthropic provider

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
braginini
2026-04-03 18:34:06 +02:00
co-authored by Claude Opus 4.6
parent 33bc9fb789
commit d8fac71d18
5 changed files with 57 additions and 18 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@netbirdio/explain",
"version": "0.1.5",
"version": "0.1.6",
"description": "Full-stack AI assistant library with React frontend components and Node.js backend handler",
"license": "BSD-3-Clause",
"main": "./dist/client/index.js",
+3 -2
View File
@@ -2,7 +2,7 @@ import type { Message } from "../types";
import { AnthropicProvider } from "./providers/anthropic";
import { DifyProvider } from "./providers/dify";
import { OpenAIProvider } from "./providers/openai";
import type { LLMProvider, Middleware, Tool } from "./providers/types";
import type { LLMProvider, McpServer, Middleware, Tool } from "./providers/types";
export type AssistantConfig = {
provider: "anthropic" | "openai" | "dify" | LLMProvider;
@@ -12,6 +12,7 @@ export type AssistantConfig = {
systemPrompt?: string;
middleware?: Middleware[];
tools?: Tool[];
mcpServers?: McpServer[];
};
type HandlerOptions = {
@@ -133,7 +134,7 @@ export function createAssistant(config: AssistantConfig) {
systemPrompt = result.systemPrompt;
}
const reply = await provider.chat(messages, systemPrompt, config.tools);
const reply = await provider.chat(messages, systemPrompt, config.tools, config.mcpServers);
return { reply };
}
+1 -1
View File
@@ -3,4 +3,4 @@ export type { AssistantConfig } from "./handler";
export { AnthropicProvider } from "./providers/anthropic";
export { DifyProvider } from "./providers/dify";
export { OpenAIProvider } from "./providers/openai";
export type { LLMProvider, ProviderConfig, Middleware, Tool } from "./providers/types";
export type { LLMProvider, McpServer, ProviderConfig, Middleware, Tool } from "./providers/types";
+44 -13
View File
@@ -1,5 +1,5 @@
import type { Message } from "../../types";
import type { LLMProvider, ProviderConfig, Tool } from "./types";
import type { LLMProvider, McpServer, ProviderConfig, Tool } from "./types";
type AnthropicMessage = {
role: "user" | "assistant";
@@ -20,17 +20,36 @@ export class AnthropicProvider implements LLMProvider {
this.model = config.model || "claude-sonnet-4-20250514";
}
async chat(messages: Message[], systemPrompt?: string, tools?: Tool[]): Promise<string> {
async chat(messages: Message[], systemPrompt?: string, tools?: Tool[], mcpServers?: McpServer[]): Promise<string> {
const anthropicMessages: AnthropicMessage[] = messages.map((m) => ({
role: m.role === "context" || m.role === "system" ? ("user" as const) : m.role,
content: m.role === "context" ? `[Context]: ${m.content}` : m.content,
}));
const toolDefs = tools?.map((t) => ({
name: t.name,
description: t.description,
input_schema: t.input_schema,
}));
const toolDefs: Record<string, unknown>[] = [];
if (tools?.length) {
for (const t of tools) {
toolDefs.push({
name: t.name,
description: t.description,
input_schema: t.input_schema,
});
}
}
if (mcpServers?.length) {
for (const mcp of mcpServers) {
const mcpTool: Record<string, unknown> = {
type: "mcp",
server_label: mcp.server_label,
server_url: mcp.server_url,
};
if (mcp.headers) mcpTool.headers = mcp.headers;
if (mcp.allowed_tools) mcpTool.allowed_tools = mcp.allowed_tools;
toolDefs.push(mcpTool);
}
}
const toolsByName = new Map(tools?.map((t) => [t.name, t]));
@@ -52,13 +71,19 @@ export class AnthropicProvider implements LLMProvider {
body.tools = toolDefs;
}
const headers: Record<string, string> = {
"Content-Type": "application/json",
"x-api-key": this.apiKey,
"anthropic-version": "2023-06-01",
};
if (mcpServers?.length) {
headers["anthropic-beta"] = "mcp-client-2025-04-04";
}
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": this.apiKey,
"anthropic-version": "2023-06-01",
},
headers,
body: JSON.stringify(body),
});
@@ -76,6 +101,12 @@ export class AnthropicProvider implements LLMProvider {
lastTextResponse = textBlock.text;
}
// MCP server-side tools hit iteration limit — re-send to continue
if (data.stop_reason === "pause_turn") {
anthropicMessages.push({ role: "assistant", content: data.content });
continue;
}
if (data.stop_reason !== "tool_use") {
if (!lastTextResponse) {
throw new Error("No text content in Anthropic response");
@@ -83,7 +114,7 @@ export class AnthropicProvider implements LLMProvider {
return lastTextResponse;
}
// Handle tool calls
// Handle client-side tool calls
const toolUseBlocks = data.content.filter(
(block: { type: string }) => block.type === "tool_use",
);
+8 -1
View File
@@ -7,8 +7,15 @@ export type Tool = {
execute: (input: Record<string, unknown>) => Promise<string>;
};
export type McpServer = {
server_label: string;
server_url: string;
headers?: Record<string, string>;
allowed_tools?: string[];
};
export interface LLMProvider {
chat(messages: Message[], systemPrompt?: string, tools?: Tool[]): Promise<string>;
chat(messages: Message[], systemPrompt?: string, tools?: Tool[], mcpServers?: McpServer[]): Promise<string>;
}
export type ProviderConfig = {