@pencil-agent/nano-pencil 1.1.0 → 1.2.0
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/README.md +10 -0
- package/core/index.ts +87 -61
- package/core/mcp/index.ts +23 -0
- package/core/mcp/mcp-adapter.ts +134 -0
- package/core/mcp/mcp-client.ts +296 -0
- package/core/mcp/mcp-config.ts +217 -0
- package/core/mcp/mcp-guidance.ts +188 -0
- package/core/mcp-manager.ts +61 -0
- package/core/sdk.ts +418 -366
- package/core/slash-commands.ts +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -24,6 +24,16 @@ nanopencil
|
|
|
24
24
|
|
|
25
25
|
进入后可直接输入需求,模型会使用 read、write、edit、bash 等工具完成任务。
|
|
26
26
|
|
|
27
|
+
### MCP 支持
|
|
28
|
+
|
|
29
|
+
NanoPencil 内置支持 MCP (Model Context Protocol),**默认启用**免费工具(Filesystem、Fetch、Puppeteer、SQLite、Git)。
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
nanopencil # MCP 默认启用
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
详见 [MCP 文档](docs/MCP_GUIDE.md) 和 [内置工具列表](docs/BUILTIN_MCP_TOOLS.md)。
|
|
36
|
+
|
|
27
37
|
## 常用命令与快捷键
|
|
28
38
|
|
|
29
39
|
在输入框输入 `/` 可触发命令,例如:
|
package/core/index.ts
CHANGED
|
@@ -1,61 +1,87 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core modules shared between all run modes.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "./agent-session.js";
|
|
14
|
-
export {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Core modules shared between all run modes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
AgentSession,
|
|
7
|
+
type AgentSessionConfig,
|
|
8
|
+
type AgentSessionEvent,
|
|
9
|
+
type AgentSessionEventListener,
|
|
10
|
+
type ModelCycleResult,
|
|
11
|
+
type PromptOptions,
|
|
12
|
+
type SessionStats,
|
|
13
|
+
} from "./agent-session.js";
|
|
14
|
+
export {
|
|
15
|
+
type BashExecutorOptions,
|
|
16
|
+
type BashResult,
|
|
17
|
+
executeBash,
|
|
18
|
+
executeBashWithOperations,
|
|
19
|
+
} from "./bash-executor.js";
|
|
20
|
+
export type { CompactionResult } from "./compaction/index.js";
|
|
21
|
+
export {
|
|
22
|
+
createEventBus,
|
|
23
|
+
type EventBus,
|
|
24
|
+
type EventBusController,
|
|
25
|
+
} from "./event-bus.js";
|
|
26
|
+
|
|
27
|
+
// Extensions system
|
|
28
|
+
export {
|
|
29
|
+
type AgentEndEvent,
|
|
30
|
+
type AgentStartEvent,
|
|
31
|
+
type AgentToolResult,
|
|
32
|
+
type AgentToolUpdateCallback,
|
|
33
|
+
type BeforeAgentStartEvent,
|
|
34
|
+
type ContextEvent,
|
|
35
|
+
discoverAndLoadExtensions,
|
|
36
|
+
type ExecOptions,
|
|
37
|
+
type ExecResult,
|
|
38
|
+
type Extension,
|
|
39
|
+
type ExtensionAPI,
|
|
40
|
+
type ExtensionCommandContext,
|
|
41
|
+
type ExtensionContext,
|
|
42
|
+
type ExtensionError,
|
|
43
|
+
type ExtensionEvent,
|
|
44
|
+
type ExtensionFactory,
|
|
45
|
+
type ExtensionFlag,
|
|
46
|
+
type ExtensionHandler,
|
|
47
|
+
ExtensionRunner,
|
|
48
|
+
type ExtensionShortcut,
|
|
49
|
+
type ExtensionUIContext,
|
|
50
|
+
type LoadExtensionsResult,
|
|
51
|
+
type MessageRenderer,
|
|
52
|
+
type RegisteredCommand,
|
|
53
|
+
type SessionBeforeCompactEvent,
|
|
54
|
+
type SessionBeforeForkEvent,
|
|
55
|
+
type SessionBeforeSwitchEvent,
|
|
56
|
+
type SessionBeforeTreeEvent,
|
|
57
|
+
type SessionCompactEvent,
|
|
58
|
+
type SessionForkEvent,
|
|
59
|
+
type SessionShutdownEvent,
|
|
60
|
+
type SessionStartEvent,
|
|
61
|
+
type SessionSwitchEvent,
|
|
62
|
+
type SessionTreeEvent,
|
|
63
|
+
type ToolCallEvent,
|
|
64
|
+
type ToolDefinition,
|
|
65
|
+
type ToolRenderResultOptions,
|
|
66
|
+
type ToolResultEvent,
|
|
67
|
+
type TurnEndEvent,
|
|
68
|
+
type TurnStartEvent,
|
|
69
|
+
wrapToolsWithExtensions,
|
|
70
|
+
} from "./extensions/index.js";
|
|
71
|
+
|
|
72
|
+
// MCP (Model Context Protocol) support
|
|
73
|
+
export { MCPManager } from "./mcp-manager.js";
|
|
74
|
+
export type {
|
|
75
|
+
MCPServerConfig,
|
|
76
|
+
MCPTool,
|
|
77
|
+
MCPToolResult,
|
|
78
|
+
} from "./mcp/mcp-client.js";
|
|
79
|
+
export {
|
|
80
|
+
loadMCPConfig,
|
|
81
|
+
saveMCPConfig,
|
|
82
|
+
addMCPServer,
|
|
83
|
+
removeMCPServer,
|
|
84
|
+
getMCPServer,
|
|
85
|
+
listMCPServers,
|
|
86
|
+
listEnabledMCPServers,
|
|
87
|
+
} from "./mcp/mcp-config.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) Module
|
|
3
|
+
*
|
|
4
|
+
* Exports MCP client and adapter functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { MCPClient } from "./mcp-client.js";
|
|
8
|
+
export type { MCPServerConfig, MCPTool, MCPToolResult } from "./mcp-client.js";
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
createMCPTool,
|
|
12
|
+
getMCPToolDisplayName,
|
|
13
|
+
loadMCPTools,
|
|
14
|
+
} from "./mcp-adapter.js";
|
|
15
|
+
export {
|
|
16
|
+
API_KEY_GUIDANCE,
|
|
17
|
+
formatGuidanceMessage,
|
|
18
|
+
getAPIKeyGuidance,
|
|
19
|
+
getMissingKeyServers,
|
|
20
|
+
getOptionalAPIKeyServers,
|
|
21
|
+
requiresAPIKey,
|
|
22
|
+
} from "./mcp-guidance.js";
|
|
23
|
+
export type { APIKeyGuidance } from "./mcp-guidance.js";
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Adapter
|
|
3
|
+
*
|
|
4
|
+
* Adapts MCP tools to work with NanoPencil's tool system.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ToolDefinition } from "../extensions/index.js";
|
|
8
|
+
import type { MCPClient, MCPTool } from "./mcp-client.js";
|
|
9
|
+
import { formatGuidanceMessage, getAPIKeyGuidance } from "./mcp-guidance.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a NanoPencil ToolDefinition from an MCP tool definition
|
|
13
|
+
*/
|
|
14
|
+
export function createMCPTool(
|
|
15
|
+
mcpClient: MCPClient,
|
|
16
|
+
mcpTool: MCPTool,
|
|
17
|
+
): ToolDefinition {
|
|
18
|
+
const toolName = mcpTool.name; // Full name like "filesystem/read"
|
|
19
|
+
const [serverId] = toolName.split("/");
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
name: toolName,
|
|
23
|
+
label: toolName,
|
|
24
|
+
description: mcpTool.description,
|
|
25
|
+
// Use TypeBox Object schema with any properties since MCP tools have dynamic schemas
|
|
26
|
+
parameters: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {},
|
|
29
|
+
additionalProperties: true,
|
|
30
|
+
} as any,
|
|
31
|
+
|
|
32
|
+
async execute(
|
|
33
|
+
toolCallId: string,
|
|
34
|
+
params: Record<string, any>,
|
|
35
|
+
signal: AbortSignal | undefined,
|
|
36
|
+
onUpdate: ((details: any) => void) | undefined,
|
|
37
|
+
ctx: any,
|
|
38
|
+
) {
|
|
39
|
+
try {
|
|
40
|
+
const result = await mcpClient.callTool(toolName, params);
|
|
41
|
+
|
|
42
|
+
if (result.error) {
|
|
43
|
+
// Check if error is due to missing API key and provide guidance
|
|
44
|
+
const guidance = getAPIKeyGuidance(serverId);
|
|
45
|
+
if (guidance && result.error?.toLowerCase().includes("key")) {
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{ type: "text", text: formatGuidanceMessage(guidance, true) },
|
|
49
|
+
],
|
|
50
|
+
details: { error: result.error },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: result.content.map((c) => c.text || "").join("\n"),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
details: { error: result.error },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Format tool result for NanoPencil
|
|
66
|
+
const output = result.content
|
|
67
|
+
.map((c) => {
|
|
68
|
+
if (c.type === "text") {
|
|
69
|
+
return c.text || "";
|
|
70
|
+
} else if (c.type === "image") {
|
|
71
|
+
return `[Image: ${c.data?.uri || "unknown"}]`;
|
|
72
|
+
} else if (c.type === "resource") {
|
|
73
|
+
return `[Resource: ${JSON.stringify(c.data)}]`;
|
|
74
|
+
}
|
|
75
|
+
return "";
|
|
76
|
+
})
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.join("\n");
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: output }],
|
|
82
|
+
details: undefined,
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// Check if error is related to missing API key and provide guidance
|
|
86
|
+
const guidance = getAPIKeyGuidance(serverId);
|
|
87
|
+
if (guidance && String(error).toLowerCase().includes("key")) {
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{ type: "text", text: formatGuidanceMessage(guidance, true) },
|
|
91
|
+
],
|
|
92
|
+
details: {
|
|
93
|
+
error: error instanceof Error ? error.message : String(error),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
content: [
|
|
100
|
+
{ type: "text", text: `Failed to call MCP tool ${toolName}` },
|
|
101
|
+
],
|
|
102
|
+
details: {
|
|
103
|
+
error: error instanceof Error ? error.message : String(error),
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Load all MCP tools as NanoPencil ToolDefinitions
|
|
113
|
+
*/
|
|
114
|
+
export async function loadMCPTools(
|
|
115
|
+
mcpClient: MCPClient,
|
|
116
|
+
): Promise<ToolDefinition[]> {
|
|
117
|
+
const mcpTools = await mcpClient.listTools();
|
|
118
|
+
|
|
119
|
+
return mcpTools.map((mcpTool) => createMCPTool(mcpClient, mcpTool));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get a human-readable display name for an MCP tool
|
|
124
|
+
*/
|
|
125
|
+
export function getMCPToolDisplayName(mcpTool: MCPTool): string {
|
|
126
|
+
if (mcpTool.displayName) {
|
|
127
|
+
return mcpTool.displayName;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const [serverId, ...nameParts] = mcpTool.name.split("/");
|
|
131
|
+
const toolName = nameParts.join("/");
|
|
132
|
+
|
|
133
|
+
return `${serverId}/${toolName}`;
|
|
134
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) Client
|
|
3
|
+
*
|
|
4
|
+
* Provides client functionality to connect to MCP servers and call their tools.
|
|
5
|
+
* Supports both stdio and SSE transport types.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
import { existsSync, readFileSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import { getAgentDir } from "../../config.js";
|
|
12
|
+
|
|
13
|
+
export interface MCPServerConfig {
|
|
14
|
+
/** Unique identifier for this server */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Display name */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Command to start the server (e.g., "npx", "uvx") */
|
|
19
|
+
command: string;
|
|
20
|
+
/** Arguments to pass to the command */
|
|
21
|
+
args: string[];
|
|
22
|
+
/** Environment variables to pass */
|
|
23
|
+
env?: Record<string, string>;
|
|
24
|
+
/** Transport type: "stdio" or "sse" */
|
|
25
|
+
transport?: "stdio" | "sse";
|
|
26
|
+
/** Whether this server is enabled */
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface MCPTool {
|
|
31
|
+
/** Tool name (server_id/tool_name format) */
|
|
32
|
+
name: string;
|
|
33
|
+
/** Display name */
|
|
34
|
+
displayName?: string;
|
|
35
|
+
/** Tool description */
|
|
36
|
+
description: string;
|
|
37
|
+
/** JSON Schema for input */
|
|
38
|
+
inputSchema: any;
|
|
39
|
+
/** Server ID */
|
|
40
|
+
serverId: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MCPToolResult {
|
|
44
|
+
/** Tool result content */
|
|
45
|
+
content: Array<{
|
|
46
|
+
type: "text" | "image" | "resource";
|
|
47
|
+
text?: string;
|
|
48
|
+
data?: any;
|
|
49
|
+
}>;
|
|
50
|
+
/** Error message if call failed */
|
|
51
|
+
error?: string;
|
|
52
|
+
/** Whether result is partial (hasMore=true) */
|
|
53
|
+
isPartial?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* MCP Client class
|
|
58
|
+
* Manages connections to MCP servers and tool calls
|
|
59
|
+
*/
|
|
60
|
+
export class MCPClient {
|
|
61
|
+
private servers = new Map<string, MCPServerConfig>();
|
|
62
|
+
private serverProcesses = new Map<string, any>();
|
|
63
|
+
private serverTools = new Map<string, MCPTool[]>();
|
|
64
|
+
|
|
65
|
+
constructor() {
|
|
66
|
+
this.loadServersFromConfig();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load MCP server configurations from config file
|
|
71
|
+
*/
|
|
72
|
+
private loadServersFromConfig(): void {
|
|
73
|
+
const configDir = getAgentDir();
|
|
74
|
+
const configPath = join(configDir, "mcp.json");
|
|
75
|
+
|
|
76
|
+
if (!existsSync(configPath)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
82
|
+
const servers: MCPServerConfig[] = config.mcpServers || [];
|
|
83
|
+
|
|
84
|
+
for (const server of servers) {
|
|
85
|
+
if (server.enabled !== false) {
|
|
86
|
+
this.servers.set(server.id, server);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(`Failed to load MCP config: ${error}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get all configured servers
|
|
96
|
+
*/
|
|
97
|
+
getServers(): MCPServerConfig[] {
|
|
98
|
+
return Array.from(this.servers.values());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get a specific server by ID
|
|
103
|
+
*/
|
|
104
|
+
getServer(id: string): MCPServerConfig | undefined {
|
|
105
|
+
return this.servers.get(id);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Add or update a server configuration
|
|
110
|
+
*/
|
|
111
|
+
addServer(server: MCPServerConfig): void {
|
|
112
|
+
this.servers.set(server.id, server);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Remove a server
|
|
117
|
+
*/
|
|
118
|
+
removeServer(id: string): void {
|
|
119
|
+
this.servers.delete(id);
|
|
120
|
+
this.serverTools.delete(id);
|
|
121
|
+
this.stopServer(id);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Start an MCP server (for stdio transport)
|
|
126
|
+
*/
|
|
127
|
+
async startServer(serverId: string): Promise<boolean> {
|
|
128
|
+
const server = this.servers.get(serverId);
|
|
129
|
+
if (!server) {
|
|
130
|
+
throw new Error(`Server ${serverId} not found`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (server.transport === "sse") {
|
|
134
|
+
// SSE servers don't need to be started as separate processes
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check if already running
|
|
139
|
+
if (this.serverProcesses.has(serverId)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const serverProcess = spawn(server.command, server.args, {
|
|
145
|
+
env: { ...process.env, ...server.env },
|
|
146
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
this.serverProcesses.set(serverId, serverProcess);
|
|
150
|
+
|
|
151
|
+
// TODO: Initialize MCP handshake, list tools
|
|
152
|
+
// For now, we'll mark it as started
|
|
153
|
+
return true;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(`Failed to start MCP server ${serverId}:`, error);
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Stop an MCP server
|
|
162
|
+
*/
|
|
163
|
+
stopServer(serverId: string): void {
|
|
164
|
+
const process = this.serverProcesses.get(serverId);
|
|
165
|
+
if (process) {
|
|
166
|
+
process.kill();
|
|
167
|
+
this.serverProcesses.delete(serverId);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Stop all running servers
|
|
173
|
+
*/
|
|
174
|
+
stopAllServers(): void {
|
|
175
|
+
for (const serverId of this.serverProcesses.keys()) {
|
|
176
|
+
this.stopServer(serverId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* List available tools from all servers
|
|
182
|
+
*/
|
|
183
|
+
async listTools(serverId?: string): Promise<MCPTool[]> {
|
|
184
|
+
const tools: MCPTool[] = [];
|
|
185
|
+
|
|
186
|
+
if (serverId) {
|
|
187
|
+
const serverTools = this.serverTools.get(serverId) || [];
|
|
188
|
+
tools.push(...serverTools);
|
|
189
|
+
} else {
|
|
190
|
+
for (const [_id, serverTools] of this.serverTools) {
|
|
191
|
+
tools.push(...serverTools);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return tools;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Call an MCP tool
|
|
200
|
+
*/
|
|
201
|
+
async callTool(
|
|
202
|
+
toolName: string,
|
|
203
|
+
args: Record<string, any>,
|
|
204
|
+
): Promise<MCPToolResult> {
|
|
205
|
+
// Parse tool name: server_id/tool_name
|
|
206
|
+
const [serverId, ...nameParts] = toolName.split("/");
|
|
207
|
+
const toolNameOnly = nameParts.join("/");
|
|
208
|
+
|
|
209
|
+
const server = this.servers.get(serverId);
|
|
210
|
+
if (!server) {
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: "text", text: `Server ${serverId} not found` }],
|
|
213
|
+
error: `Server ${serverId} not found`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// For SSE transport, make HTTP request
|
|
218
|
+
if (server.transport === "sse") {
|
|
219
|
+
return this.callSSETool(server, toolNameOnly, args);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// For stdio transport, send JSON-RPC message
|
|
223
|
+
return this.callStdioTool(server, toolNameOnly, args);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Call tool via stdio (JSON-RPC)
|
|
228
|
+
*/
|
|
229
|
+
private async callStdioTool(
|
|
230
|
+
server: MCPServerConfig,
|
|
231
|
+
toolName: string,
|
|
232
|
+
args: Record<string, any>,
|
|
233
|
+
): Promise<MCPToolResult> {
|
|
234
|
+
const process = this.serverProcesses.get(server.id);
|
|
235
|
+
if (!process) {
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: `Server ${server.id} is not running` }],
|
|
238
|
+
error: `Server ${server.id} is not running`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
// Send JSON-RPC request
|
|
244
|
+
const request = {
|
|
245
|
+
jsonrpc: "2.0",
|
|
246
|
+
id: Date.now(),
|
|
247
|
+
method: "tools/call",
|
|
248
|
+
params: {
|
|
249
|
+
name: toolName,
|
|
250
|
+
arguments: args,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
process.stdin.write(JSON.stringify(request) + "\n");
|
|
255
|
+
|
|
256
|
+
// Read response (simplified - in production, use proper message handling)
|
|
257
|
+
// For now, return a placeholder
|
|
258
|
+
return {
|
|
259
|
+
content: [
|
|
260
|
+
{
|
|
261
|
+
type: "text",
|
|
262
|
+
text: `Tool ${toolName} called (TODO: implement response handling)`,
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
};
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return {
|
|
268
|
+
content: [{ type: "text", text: `Failed to call tool: ${error}` }],
|
|
269
|
+
error: String(error),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Call tool via SSE (HTTP)
|
|
276
|
+
*/
|
|
277
|
+
private async callSSETool(
|
|
278
|
+
server: MCPServerConfig,
|
|
279
|
+
toolName: string,
|
|
280
|
+
args: Record<string, any>,
|
|
281
|
+
): Promise<MCPToolResult> {
|
|
282
|
+
// TODO: Implement SSE tool calls
|
|
283
|
+
return {
|
|
284
|
+
content: [{ type: "text", text: `SSE tool calls not yet implemented` }],
|
|
285
|
+
error: "SSE transport not yet supported",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Check if a tool exists
|
|
291
|
+
*/
|
|
292
|
+
hasTool(toolName: string): boolean {
|
|
293
|
+
const [serverId] = toolName.split("/");
|
|
294
|
+
return this.servers.has(serverId);
|
|
295
|
+
}
|
|
296
|
+
}
|