@netbirdio/explain 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netbirdio/explain",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Full-stack AI assistant library with React frontend components and Node.js backend handler",
5
5
  "license": "BSD-3-Clause",
6
6
  "main": "./dist/client/index.js",
@@ -2,7 +2,7 @@ import type { Message } from "../types";
2
2
  import { AnthropicProvider } from "./providers/anthropic";
3
3
  import { DifyProvider } from "./providers/dify";
4
4
  import { OpenAIProvider } from "./providers/openai";
5
- import type { LLMProvider, Middleware, Tool } from "./providers/types";
5
+ import type { LLMProvider, McpServer, Middleware, Tool } from "./providers/types";
6
6
 
7
7
  export type AssistantConfig = {
8
8
  provider: "anthropic" | "openai" | "dify" | LLMProvider;
@@ -12,6 +12,7 @@ export type AssistantConfig = {
12
12
  systemPrompt?: string;
13
13
  middleware?: Middleware[];
14
14
  tools?: Tool[];
15
+ mcpServers?: McpServer[];
15
16
  };
16
17
 
17
18
  type HandlerOptions = {
@@ -133,7 +134,7 @@ export function createAssistant(config: AssistantConfig) {
133
134
  systemPrompt = result.systemPrompt;
134
135
  }
135
136
 
136
- const reply = await provider.chat(messages, systemPrompt, config.tools);
137
+ const reply = await provider.chat(messages, systemPrompt, config.tools, config.mcpServers);
137
138
  return { reply };
138
139
  }
139
140
 
@@ -3,4 +3,4 @@ export type { AssistantConfig } from "./handler";
3
3
  export { AnthropicProvider } from "./providers/anthropic";
4
4
  export { DifyProvider } from "./providers/dify";
5
5
  export { OpenAIProvider } from "./providers/openai";
6
- export type { LLMProvider, ProviderConfig, Middleware, Tool } from "./providers/types";
6
+ export type { LLMProvider, McpServer, ProviderConfig, Middleware, Tool } from "./providers/types";
@@ -1,5 +1,5 @@
1
1
  import type { Message } from "../../types";
2
- import type { LLMProvider, ProviderConfig, Tool } from "./types";
2
+ import type { LLMProvider, McpServer, ProviderConfig, Tool } from "./types";
3
3
 
4
4
  type AnthropicMessage = {
5
5
  role: "user" | "assistant";
@@ -20,17 +20,36 @@ export class AnthropicProvider implements LLMProvider {
20
20
  this.model = config.model || "claude-sonnet-4-20250514";
21
21
  }
22
22
 
23
- async chat(messages: Message[], systemPrompt?: string, tools?: Tool[]): Promise<string> {
23
+ async chat(messages: Message[], systemPrompt?: string, tools?: Tool[], mcpServers?: McpServer[]): Promise<string> {
24
24
  const anthropicMessages: AnthropicMessage[] = messages.map((m) => ({
25
25
  role: m.role === "context" || m.role === "system" ? ("user" as const) : m.role,
26
26
  content: m.role === "context" ? `[Context]: ${m.content}` : m.content,
27
27
  }));
28
28
 
29
- const toolDefs = tools?.map((t) => ({
30
- name: t.name,
31
- description: t.description,
32
- input_schema: t.input_schema,
33
- }));
29
+ const toolDefs: Record<string, unknown>[] = [];
30
+
31
+ if (tools?.length) {
32
+ for (const t of tools) {
33
+ toolDefs.push({
34
+ name: t.name,
35
+ description: t.description,
36
+ input_schema: t.input_schema,
37
+ });
38
+ }
39
+ }
40
+
41
+ if (mcpServers?.length) {
42
+ for (const mcp of mcpServers) {
43
+ const mcpTool: Record<string, unknown> = {
44
+ type: "mcp",
45
+ server_label: mcp.server_label,
46
+ server_url: mcp.server_url,
47
+ };
48
+ if (mcp.headers) mcpTool.headers = mcp.headers;
49
+ if (mcp.allowed_tools) mcpTool.allowed_tools = mcp.allowed_tools;
50
+ toolDefs.push(mcpTool);
51
+ }
52
+ }
34
53
 
35
54
  const toolsByName = new Map(tools?.map((t) => [t.name, t]));
36
55
 
@@ -52,13 +71,19 @@ export class AnthropicProvider implements LLMProvider {
52
71
  body.tools = toolDefs;
53
72
  }
54
73
 
74
+ const headers: Record<string, string> = {
75
+ "Content-Type": "application/json",
76
+ "x-api-key": this.apiKey,
77
+ "anthropic-version": "2023-06-01",
78
+ };
79
+
80
+ if (mcpServers?.length) {
81
+ headers["anthropic-beta"] = "mcp-client-2025-04-04";
82
+ }
83
+
55
84
  const response = await fetch("https://api.anthropic.com/v1/messages", {
56
85
  method: "POST",
57
- headers: {
58
- "Content-Type": "application/json",
59
- "x-api-key": this.apiKey,
60
- "anthropic-version": "2023-06-01",
61
- },
86
+ headers,
62
87
  body: JSON.stringify(body),
63
88
  });
64
89
 
@@ -76,6 +101,12 @@ export class AnthropicProvider implements LLMProvider {
76
101
  lastTextResponse = textBlock.text;
77
102
  }
78
103
 
104
+ // MCP server-side tools hit iteration limit — re-send to continue
105
+ if (data.stop_reason === "pause_turn") {
106
+ anthropicMessages.push({ role: "assistant", content: data.content });
107
+ continue;
108
+ }
109
+
79
110
  if (data.stop_reason !== "tool_use") {
80
111
  if (!lastTextResponse) {
81
112
  throw new Error("No text content in Anthropic response");
@@ -83,7 +114,7 @@ export class AnthropicProvider implements LLMProvider {
83
114
  return lastTextResponse;
84
115
  }
85
116
 
86
- // Handle tool calls
117
+ // Handle client-side tool calls
87
118
  const toolUseBlocks = data.content.filter(
88
119
  (block: { type: string }) => block.type === "tool_use",
89
120
  );
@@ -7,8 +7,15 @@ export type Tool = {
7
7
  execute: (input: Record<string, unknown>) => Promise<string>;
8
8
  };
9
9
 
10
+ export type McpServer = {
11
+ server_label: string;
12
+ server_url: string;
13
+ headers?: Record<string, string>;
14
+ allowed_tools?: string[];
15
+ };
16
+
10
17
  export interface LLMProvider {
11
- chat(messages: Message[], systemPrompt?: string, tools?: Tool[]): Promise<string>;
18
+ chat(messages: Message[], systemPrompt?: string, tools?: Tool[], mcpServers?: McpServer[]): Promise<string>;
12
19
  }
13
20
 
14
21
  export type ProviderConfig = {