@sammorrowdrums/mcpi-ext 0.1.0 → 0.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/.github/workflows/ci.yml +1 -1
- package/README.md +109 -139
- package/docs/code-mode.md +38 -0
- package/docs/skills.md +57 -0
- package/docs/tool-cli.md +54 -0
- package/images/code-mode-histogram.png +0 -0
- package/images/skills-enabling-mcp-tools.png +0 -0
- package/images/tool-cli-grep.png +0 -0
- package/mise.toml +1 -13
- package/package.json +5 -7
- package/scripts/test-tool-cli-smoke.mjs +40 -9
- package/src/index.ts +24 -3
- package/src/tool-cli/index.ts +2 -2
- package/dist/code-mode/code-mode-integration.test.d.ts +0 -2
- package/dist/code-mode/code-mode-integration.test.d.ts.map +0 -1
- package/dist/code-mode/code-mode-integration.test.js +0 -116
- package/dist/code-mode/code-mode-integration.test.js.map +0 -1
- package/dist/code-mode/eligibility.d.ts +0 -10
- package/dist/code-mode/eligibility.d.ts.map +0 -1
- package/dist/code-mode/eligibility.js +0 -14
- package/dist/code-mode/eligibility.js.map +0 -1
- package/dist/code-mode/eligibility.test.d.ts +0 -2
- package/dist/code-mode/eligibility.test.d.ts.map +0 -1
- package/dist/code-mode/eligibility.test.js +0 -53
- package/dist/code-mode/eligibility.test.js.map +0 -1
- package/dist/code-mode/executor.d.ts +0 -35
- package/dist/code-mode/executor.d.ts.map +0 -1
- package/dist/code-mode/executor.js +0 -120
- package/dist/code-mode/executor.js.map +0 -1
- package/dist/code-mode/executor.test.d.ts +0 -2
- package/dist/code-mode/executor.test.d.ts.map +0 -1
- package/dist/code-mode/executor.test.js +0 -130
- package/dist/code-mode/executor.test.js.map +0 -1
- package/dist/code-mode/index.d.ts +0 -65
- package/dist/code-mode/index.d.ts.map +0 -1
- package/dist/code-mode/index.js +0 -175
- package/dist/code-mode/index.js.map +0 -1
- package/dist/code-mode/tools.d.ts +0 -45
- package/dist/code-mode/tools.d.ts.map +0 -1
- package/dist/code-mode/tools.js +0 -70
- package/dist/code-mode/tools.js.map +0 -1
- package/dist/code-mode/type-hints.d.ts +0 -20
- package/dist/code-mode/type-hints.d.ts.map +0 -1
- package/dist/code-mode/type-hints.js +0 -189
- package/dist/code-mode/type-hints.js.map +0 -1
- package/dist/code-mode/type-hints.test.d.ts +0 -2
- package/dist/code-mode/type-hints.test.d.ts.map +0 -1
- package/dist/code-mode/type-hints.test.js +0 -157
- package/dist/code-mode/type-hints.test.js.map +0 -1
- package/dist/docker-e2e.d.ts +0 -55
- package/dist/docker-e2e.d.ts.map +0 -1
- package/dist/docker-e2e.js +0 -235
- package/dist/docker-e2e.js.map +0 -1
- package/dist/docker-e2e.test.d.ts +0 -2
- package/dist/docker-e2e.test.d.ts.map +0 -1
- package/dist/docker-e2e.test.js +0 -176
- package/dist/docker-e2e.test.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -128
- package/dist/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -8
- package/dist/index.test.js.map +0 -1
- package/dist/mcp/client-manager.d.ts +0 -46
- package/dist/mcp/client-manager.d.ts.map +0 -1
- package/dist/mcp/client-manager.js +0 -148
- package/dist/mcp/client-manager.js.map +0 -1
- package/dist/mcp/client-manager.test.d.ts +0 -2
- package/dist/mcp/client-manager.test.d.ts.map +0 -1
- package/dist/mcp/client-manager.test.js +0 -204
- package/dist/mcp/client-manager.test.js.map +0 -1
- package/dist/mcp/config-loader.d.ts +0 -8
- package/dist/mcp/config-loader.d.ts.map +0 -1
- package/dist/mcp/config-loader.js +0 -43
- package/dist/mcp/config-loader.js.map +0 -1
- package/dist/mcp/config-loader.test.d.ts +0 -2
- package/dist/mcp/config-loader.test.d.ts.map +0 -1
- package/dist/mcp/config-loader.test.js +0 -93
- package/dist/mcp/config-loader.test.js.map +0 -1
- package/dist/mcp/config.d.ts +0 -46
- package/dist/mcp/config.d.ts.map +0 -1
- package/dist/mcp/config.js +0 -28
- package/dist/mcp/config.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -5
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -4
- package/dist/mcp/index.js.map +0 -1
- package/dist/skills/discover.d.ts +0 -10
- package/dist/skills/discover.d.ts.map +0 -1
- package/dist/skills/discover.js +0 -60
- package/dist/skills/discover.js.map +0 -1
- package/dist/skills/format.d.ts +0 -10
- package/dist/skills/format.d.ts.map +0 -1
- package/dist/skills/format.js +0 -38
- package/dist/skills/format.js.map +0 -1
- package/dist/skills/format.test.d.ts +0 -2
- package/dist/skills/format.test.d.ts.map +0 -1
- package/dist/skills/format.test.js +0 -63
- package/dist/skills/format.test.js.map +0 -1
- package/dist/skills/index.d.ts +0 -6
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -6
- package/dist/skills/index.js.map +0 -1
- package/dist/skills/load-skill-tool.d.ts +0 -40
- package/dist/skills/load-skill-tool.d.ts.map +0 -1
- package/dist/skills/load-skill-tool.js +0 -104
- package/dist/skills/load-skill-tool.js.map +0 -1
- package/dist/skills/mcp-tool-proxy.d.ts +0 -11
- package/dist/skills/mcp-tool-proxy.d.ts.map +0 -1
- package/dist/skills/mcp-tool-proxy.js +0 -116
- package/dist/skills/mcp-tool-proxy.js.map +0 -1
- package/dist/skills/skill-integration.test.d.ts +0 -2
- package/dist/skills/skill-integration.test.d.ts.map +0 -1
- package/dist/skills/skill-integration.test.js +0 -119
- package/dist/skills/skill-integration.test.js.map +0 -1
- package/dist/skills/skill-registry.d.ts +0 -39
- package/dist/skills/skill-registry.d.ts.map +0 -1
- package/dist/skills/skill-registry.js +0 -48
- package/dist/skills/skill-registry.js.map +0 -1
- package/dist/skills/skill-registry.test.d.ts +0 -2
- package/dist/skills/skill-registry.test.d.ts.map +0 -1
- package/dist/skills/skill-registry.test.js +0 -67
- package/dist/skills/skill-registry.test.js.map +0 -1
- package/dist/test-servers/weather-server.d.ts +0 -12
- package/dist/test-servers/weather-server.d.ts.map +0 -1
- package/dist/test-servers/weather-server.js +0 -139
- package/dist/test-servers/weather-server.js.map +0 -1
- package/dist/test-servers/weather-stdio.d.ts +0 -2
- package/dist/test-servers/weather-stdio.d.ts.map +0 -1
- package/dist/test-servers/weather-stdio.js +0 -11
- package/dist/test-servers/weather-stdio.js.map +0 -1
- package/dist/tool-cli/cli.d.ts +0 -3
- package/dist/tool-cli/cli.d.ts.map +0 -1
- package/dist/tool-cli/cli.js +0 -179
- package/dist/tool-cli/cli.js.map +0 -1
- package/dist/tool-cli/constants.d.ts +0 -7
- package/dist/tool-cli/constants.d.ts.map +0 -1
- package/dist/tool-cli/constants.js +0 -15
- package/dist/tool-cli/constants.js.map +0 -1
- package/dist/tool-cli/format.d.ts +0 -8
- package/dist/tool-cli/format.d.ts.map +0 -1
- package/dist/tool-cli/format.js +0 -57
- package/dist/tool-cli/format.js.map +0 -1
- package/dist/tool-cli/format.test.d.ts +0 -2
- package/dist/tool-cli/format.test.d.ts.map +0 -1
- package/dist/tool-cli/format.test.js +0 -30
- package/dist/tool-cli/format.test.js.map +0 -1
- package/dist/tool-cli/index.d.ts +0 -4
- package/dist/tool-cli/index.d.ts.map +0 -1
- package/dist/tool-cli/index.js +0 -4
- package/dist/tool-cli/index.js.map +0 -1
- package/dist/tool-cli/rpc-client.d.ts +0 -6
- package/dist/tool-cli/rpc-client.d.ts.map +0 -1
- package/dist/tool-cli/rpc-client.js +0 -25
- package/dist/tool-cli/rpc-client.js.map +0 -1
- package/dist/tool-cli/rpc-server.d.ts +0 -48
- package/dist/tool-cli/rpc-server.d.ts.map +0 -1
- package/dist/tool-cli/rpc-server.js +0 -220
- package/dist/tool-cli/rpc-server.js.map +0 -1
- package/dist/tool-cli/rpc-server.test.d.ts +0 -2
- package/dist/tool-cli/rpc-server.test.d.ts.map +0 -1
- package/dist/tool-cli/rpc-server.test.js +0 -154
- package/dist/tool-cli/rpc-server.test.js.map +0 -1
- package/src/tool-cli/cli.ts +0 -232
- package/src/tool-cli/constants.ts +0 -15
- package/src/tool-cli/rpc-client.ts +0 -39
- package/src/tool-cli/rpc-server.test.ts +0 -193
- package/src/tool-cli/rpc-server.ts +0 -287
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
import http from "node:http";
|
|
2
|
-
import type { McpClientManager, McpTool } from "../mcp/index.js";
|
|
3
|
-
import { resolvePort } from "./constants.js";
|
|
4
|
-
|
|
5
|
-
interface JsonRpcRequest {
|
|
6
|
-
jsonrpc: "2.0";
|
|
7
|
-
method: string;
|
|
8
|
-
params?: Record<string, unknown>;
|
|
9
|
-
id: number | string | null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface JsonRpcResponse {
|
|
13
|
-
jsonrpc: "2.0";
|
|
14
|
-
result?: unknown;
|
|
15
|
-
error?: { code: number; message: string; data?: unknown };
|
|
16
|
-
id: number | string | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface ServerInfo {
|
|
20
|
-
name: string;
|
|
21
|
-
toolCount: number;
|
|
22
|
-
examples: string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface ToolSummary {
|
|
26
|
-
name: string;
|
|
27
|
-
description: string;
|
|
28
|
-
hasStructuredOutput: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface ToolDetails {
|
|
32
|
-
name: string;
|
|
33
|
-
description: string;
|
|
34
|
-
inputSchema: Record<string, unknown>;
|
|
35
|
-
outputSchema?: Record<string, unknown>;
|
|
36
|
-
annotations?: Record<string, unknown>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export type { ServerInfo, ToolSummary, ToolDetails };
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* JSON-RPC 2.0 server that exposes MCP tool discovery and execution.
|
|
43
|
-
*
|
|
44
|
-
* Methods:
|
|
45
|
-
* - listServers → server names + tool counts + random examples
|
|
46
|
-
* - listTools(server) → tool names + descriptions
|
|
47
|
-
* - describeTool(server, tool) → full schema
|
|
48
|
-
* - callTool(server, tool, arguments) → execute and return result
|
|
49
|
-
*/
|
|
50
|
-
export class ToolCliRpcServer {
|
|
51
|
-
private server: http.Server | null = null;
|
|
52
|
-
private port: number;
|
|
53
|
-
|
|
54
|
-
constructor(
|
|
55
|
-
private mcpManager: McpClientManager,
|
|
56
|
-
port?: number,
|
|
57
|
-
) {
|
|
58
|
-
this.port = port ?? resolvePort();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Start the HTTP server. Resolves once listening. */
|
|
62
|
-
async start(log?: (msg: string) => void): Promise<void> {
|
|
63
|
-
if (this.server) return;
|
|
64
|
-
|
|
65
|
-
const MAX_BODY_SIZE = 1024 * 1024; // 1 MB
|
|
66
|
-
|
|
67
|
-
const server = http.createServer((req, res) => {
|
|
68
|
-
if (req.method !== "POST") {
|
|
69
|
-
res.writeHead(405, { "Content-Type": "application/json" });
|
|
70
|
-
res.end(JSON.stringify(rpcError(null, -32600, "Only POST accepted")));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let body = "";
|
|
75
|
-
let aborted = false;
|
|
76
|
-
|
|
77
|
-
req.on("error", () => {
|
|
78
|
-
aborted = true;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
req.on("data", (chunk: Buffer) => {
|
|
82
|
-
if (aborted) return;
|
|
83
|
-
body += chunk.toString();
|
|
84
|
-
if (body.length > MAX_BODY_SIZE) {
|
|
85
|
-
aborted = true;
|
|
86
|
-
res.writeHead(413, { "Content-Type": "application/json" });
|
|
87
|
-
res.end(JSON.stringify(rpcError(null, -32600, "Request too large")));
|
|
88
|
-
req.destroy();
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
req.on("end", () => {
|
|
93
|
-
if (aborted) return;
|
|
94
|
-
void this.handleBody(body)
|
|
95
|
-
.then((response) => {
|
|
96
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
97
|
-
res.end(JSON.stringify(response));
|
|
98
|
-
})
|
|
99
|
-
.catch(() => {
|
|
100
|
-
try {
|
|
101
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
102
|
-
res.end(JSON.stringify(rpcError(null, -32603, "Internal error")));
|
|
103
|
-
} catch {
|
|
104
|
-
res.end();
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return new Promise<void>((resolve, reject) => {
|
|
111
|
-
server.once("error", (err) => {
|
|
112
|
-
this.server = null;
|
|
113
|
-
reject(err);
|
|
114
|
-
});
|
|
115
|
-
server.listen(this.port, "127.0.0.1", () => {
|
|
116
|
-
this.server = server;
|
|
117
|
-
log?.(`[tool-cli] RPC server listening on 127.0.0.1:${this.port}`);
|
|
118
|
-
resolve();
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Stop the HTTP server. */
|
|
124
|
-
async stop(): Promise<void> {
|
|
125
|
-
const server = this.server;
|
|
126
|
-
if (!server) return;
|
|
127
|
-
this.server = null;
|
|
128
|
-
|
|
129
|
-
return new Promise<void>((resolve) => {
|
|
130
|
-
server.close(() => resolve());
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Returns the port the server is listening on (resolved after start for port 0). */
|
|
135
|
-
getPort(): number {
|
|
136
|
-
if (this.server) {
|
|
137
|
-
const addr = this.server.address();
|
|
138
|
-
if (addr && typeof addr === "object") return addr.port;
|
|
139
|
-
}
|
|
140
|
-
return this.port;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private async handleBody(body: string): Promise<JsonRpcResponse> {
|
|
144
|
-
let parsed: JsonRpcRequest;
|
|
145
|
-
try {
|
|
146
|
-
parsed = JSON.parse(body) as JsonRpcRequest;
|
|
147
|
-
} catch {
|
|
148
|
-
return rpcError(null, -32700, "Parse error");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (parsed.jsonrpc !== "2.0" || !parsed.method) {
|
|
152
|
-
return rpcError(parsed.id ?? null, -32600, "Invalid Request");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const result = await this.dispatch(parsed.method, parsed.params ?? {});
|
|
157
|
-
return { jsonrpc: "2.0", result, id: parsed.id ?? null };
|
|
158
|
-
} catch (err) {
|
|
159
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
160
|
-
const code = err instanceof RpcMethodError ? err.code : -32603;
|
|
161
|
-
return rpcError(parsed.id ?? null, code, message);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private async dispatch(method: string, params: Record<string, unknown>): Promise<unknown> {
|
|
166
|
-
switch (method) {
|
|
167
|
-
case "listServers":
|
|
168
|
-
return this.listServers();
|
|
169
|
-
case "listTools":
|
|
170
|
-
return this.listTools(params);
|
|
171
|
-
case "describeTool":
|
|
172
|
-
return this.describeTool(params);
|
|
173
|
-
case "callTool":
|
|
174
|
-
return this.callTool(params);
|
|
175
|
-
default:
|
|
176
|
-
throw new RpcMethodError(-32601, `Method not found: ${method}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
private listServers(): { servers: ServerInfo[] } {
|
|
181
|
-
const serverNames = this.mcpManager.getConnectedServers();
|
|
182
|
-
const servers: ServerInfo[] = serverNames.map((name) => {
|
|
183
|
-
const tools = this.mcpManager.getToolsForServer(name);
|
|
184
|
-
return {
|
|
185
|
-
name,
|
|
186
|
-
toolCount: tools.length,
|
|
187
|
-
examples: pickRandomExamples(tools, 3),
|
|
188
|
-
};
|
|
189
|
-
});
|
|
190
|
-
return { servers };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private listTools(params: Record<string, unknown>): { server: string; tools: ToolSummary[] } {
|
|
194
|
-
const server = requireString(params, "server");
|
|
195
|
-
this.assertServerExists(server);
|
|
196
|
-
const tools = this.mcpManager.getToolsForServer(server);
|
|
197
|
-
return {
|
|
198
|
-
server,
|
|
199
|
-
tools: tools.map((t) => ({
|
|
200
|
-
name: t.name,
|
|
201
|
-
description: t.description ?? "(no description)",
|
|
202
|
-
hasStructuredOutput: t.outputSchema != null,
|
|
203
|
-
})),
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private describeTool(params: Record<string, unknown>): ToolDetails {
|
|
208
|
-
const server = requireString(params, "server");
|
|
209
|
-
const toolName = requireString(params, "tool");
|
|
210
|
-
this.assertServerExists(server);
|
|
211
|
-
|
|
212
|
-
const tool = this.mcpManager.getToolsForServer(server).find((t) => t.name === toolName);
|
|
213
|
-
if (!tool) {
|
|
214
|
-
throw new RpcMethodError(-32602, `Tool "${toolName}" not found on server "${server}"`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
name: tool.name,
|
|
219
|
-
description: tool.description ?? "(no description)",
|
|
220
|
-
inputSchema: tool.inputSchema,
|
|
221
|
-
outputSchema: tool.outputSchema,
|
|
222
|
-
annotations: tool.annotations,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private async callTool(params: Record<string, unknown>): Promise<{
|
|
227
|
-
content: unknown[];
|
|
228
|
-
isError?: boolean;
|
|
229
|
-
structuredContent?: Record<string, unknown>;
|
|
230
|
-
}> {
|
|
231
|
-
const server = requireString(params, "server");
|
|
232
|
-
const toolName = requireString(params, "tool");
|
|
233
|
-
this.assertServerExists(server);
|
|
234
|
-
|
|
235
|
-
const client = this.mcpManager.getClient(server);
|
|
236
|
-
if (!client) {
|
|
237
|
-
throw new RpcMethodError(-32603, `No client for server "${server}"`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const toolArgs = (params.arguments as Record<string, unknown>) ?? {};
|
|
241
|
-
|
|
242
|
-
const result = await client.callTool({ name: toolName, arguments: toolArgs });
|
|
243
|
-
return {
|
|
244
|
-
content: result.content as unknown[],
|
|
245
|
-
isError: result.isError === true ? true : undefined,
|
|
246
|
-
structuredContent: result.structuredContent as Record<string, unknown> | undefined,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private assertServerExists(server: string): void {
|
|
251
|
-
if (!this.mcpManager.getConnectedServers().includes(server)) {
|
|
252
|
-
throw new RpcMethodError(
|
|
253
|
-
-32602,
|
|
254
|
-
`Server "${server}" not found. Connected: ${this.mcpManager.getConnectedServers().join(", ") || "(none)"}`,
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
class RpcMethodError extends Error {
|
|
261
|
-
constructor(
|
|
262
|
-
public code: number,
|
|
263
|
-
message: string,
|
|
264
|
-
) {
|
|
265
|
-
super(message);
|
|
266
|
-
this.name = "RpcMethodError";
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function rpcError(id: number | string | null, code: number, message: string): JsonRpcResponse {
|
|
271
|
-
return { jsonrpc: "2.0", error: { code, message }, id };
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function requireString(params: Record<string, unknown>, key: string): string {
|
|
275
|
-
const val = params[key];
|
|
276
|
-
if (typeof val !== "string" || val.length === 0) {
|
|
277
|
-
throw new RpcMethodError(-32602, `Missing or invalid parameter: "${key}" (string required)`);
|
|
278
|
-
}
|
|
279
|
-
return val;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/** Pick up to `n` random tool names from a list. */
|
|
283
|
-
function pickRandomExamples(tools: McpTool[], n: number): string[] {
|
|
284
|
-
if (tools.length <= n) return tools.map((t) => t.name);
|
|
285
|
-
const shuffled = [...tools].sort(() => Math.random() - 0.5);
|
|
286
|
-
return shuffled.slice(0, n).map((t) => t.name);
|
|
287
|
-
}
|