pi-mcp-adapter 1.1.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/ARCHITECTURE.md +572 -0
- package/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/config.ts +132 -0
- package/index.ts +907 -0
- package/lifecycle.ts +59 -0
- package/oauth-handler.ts +57 -0
- package/package.json +51 -0
- package/resource-tools.ts +45 -0
- package/server-manager.ts +242 -0
- package/tool-registrar.ts +77 -0
- package/types.ts +112 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
# Pi MCP Adapter - Architecture
|
|
2
|
+
|
|
3
|
+
## High-Level Overview
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
7
|
+
│ PI CODING AGENT │
|
|
8
|
+
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
9
|
+
│ │ Tool Registry │ │
|
|
10
|
+
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
|
|
11
|
+
│ │ │ read │ │ write │ │ bash │ │ mcp │ │ │
|
|
12
|
+
│ │ │ (builtin) │ │ (builtin) │ │ (builtin) │ │ (MCP proxy) │ │ │
|
|
13
|
+
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
|
|
14
|
+
│ │ │ │
|
|
15
|
+
│ │ Only ONE tool registered for all MCP servers! │ │
|
|
16
|
+
│ │ ~200 tokens vs ~15,000 tokens for 75 individual tools │ │
|
|
17
|
+
│ │ │ │
|
|
18
|
+
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
19
|
+
│ ▲ │
|
|
20
|
+
│ │ pi.registerTool("mcp", ...) │
|
|
21
|
+
│ ┌──────────────────────────────────┴────────────────────────────────────┐ │
|
|
22
|
+
│ │ PI MCP ADAPTER EXTENSION │ │
|
|
23
|
+
│ │ ┌────────────┐ ┌─────────────────┐ ┌───────────────────────────┐ │ │
|
|
24
|
+
│ │ │ Config │ │ Server Manager │ │ Tool Metadata │ │ │
|
|
25
|
+
│ │ │ Loader │──│ (connections) │──│ (for search/lookup) │ │ │
|
|
26
|
+
│ │ └────────────┘ └─────────────────┘ └───────────────────────────┘ │ │
|
|
27
|
+
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
28
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
29
|
+
│
|
|
30
|
+
┌─────────────────┼─────────────────┐
|
|
31
|
+
│ │ │
|
|
32
|
+
▼ ▼ ▼
|
|
33
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
34
|
+
│ MCP Server │ │ MCP Server │ │ MCP Server │
|
|
35
|
+
│ (stdio) │ │ (HTTP) │ │ (stdio) │
|
|
36
|
+
│ │ │ │ │ │
|
|
37
|
+
│ xcodebuild │ │ remote-api │ │ github │
|
|
38
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Token Efficiency Design
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
45
|
+
│ OLD APPROACH (rejected) │
|
|
46
|
+
│ │
|
|
47
|
+
│ Register each MCP tool individually with Pi: │
|
|
48
|
+
│ │
|
|
49
|
+
│ - xcodebuild_list_sims (~200 tokens) │
|
|
50
|
+
│ - xcodebuild_build_sim (~200 tokens) │
|
|
51
|
+
│ - xcodebuild_tap (~200 tokens) │
|
|
52
|
+
│ - ... 72 more tools ... │
|
|
53
|
+
│ │
|
|
54
|
+
│ Total: ~15,000 tokens just for tool definitions! │
|
|
55
|
+
│ Problem: Burns context window, slow, expensive │
|
|
56
|
+
│ │
|
|
57
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
58
|
+
|
|
59
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
60
|
+
│ NEW APPROACH (implemented) │
|
|
61
|
+
│ │
|
|
62
|
+
│ Single unified `mcp` proxy tool: │
|
|
63
|
+
│ │
|
|
64
|
+
│ - mcp({ }) → Show server status │
|
|
65
|
+
│ - mcp({ server: "name" }) → List tools from server │
|
|
66
|
+
│ - mcp({ search: "search" }) → Search for tools │
|
|
67
|
+
│ - mcp({ tool: "name", args }) → Call a tool │
|
|
68
|
+
│ │
|
|
69
|
+
│ Total: ~200 tokens for the proxy tool! │
|
|
70
|
+
│ LLM discovers tools on-demand via search/list │
|
|
71
|
+
│ │
|
|
72
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Config Loading Flow
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
┌─────────────────────────┐
|
|
79
|
+
│ loadMcpConfig() │
|
|
80
|
+
└───────────┬─────────────┘
|
|
81
|
+
│
|
|
82
|
+
┌─────────────────────┼─────────────────────┐
|
|
83
|
+
│ │ │
|
|
84
|
+
▼ ▼ ▼
|
|
85
|
+
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
|
|
86
|
+
│ Global Config │ │ Import Sources │ │ Project Config │
|
|
87
|
+
│ │ │ │ │ │
|
|
88
|
+
│ ~/.pi/agent/ │ │ ~/.cursor/ │ │ .pi/mcp.json │
|
|
89
|
+
│ mcp.json │ │ mcp.json │ │ (in project) │
|
|
90
|
+
│ │ │ │ │ │
|
|
91
|
+
│ PRIORITY: 2 │ │ ~/.claude/ │ │ PRIORITY: 1 │
|
|
92
|
+
│ (base config) │ │ claude_desktop │ │ (overrides all) │
|
|
93
|
+
│ │ │ _config.json │ │ │
|
|
94
|
+
└─────────┬─────────┘ │ │ └─────────┬─────────┘
|
|
95
|
+
│ │ ~/.windsurf/ │ │
|
|
96
|
+
│ │ mcp.json │ │
|
|
97
|
+
│ │ │ │
|
|
98
|
+
│ │ .vscode/mcp.json │ │
|
|
99
|
+
│ │ │ │
|
|
100
|
+
│ │ PRIORITY: 3 │ │
|
|
101
|
+
│ │ (only if not in │ │
|
|
102
|
+
│ │ global config) │ │
|
|
103
|
+
│ └─────────┬─────────┘ │
|
|
104
|
+
│ │ │
|
|
105
|
+
└──────────┬──────────┴──────────┬──────────┘
|
|
106
|
+
│ │
|
|
107
|
+
▼ ▼
|
|
108
|
+
┌─────────────────────────────────────┐
|
|
109
|
+
│ Merged McpConfig │
|
|
110
|
+
│ │
|
|
111
|
+
│ { │
|
|
112
|
+
│ mcpServers: { │
|
|
113
|
+
│ "xcodebuild": {...}, │
|
|
114
|
+
│ "github": {...}, │
|
|
115
|
+
│ "imported-server": {...} │
|
|
116
|
+
│ }, │
|
|
117
|
+
│ settings: { │
|
|
118
|
+
│ toolPrefix: "server" │
|
|
119
|
+
│ } │
|
|
120
|
+
│ } │
|
|
121
|
+
└─────────────────────────────────────┘
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Connection Establishment
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
128
|
+
│ McpServerManager.connect() │
|
|
129
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
130
|
+
│
|
|
131
|
+
▼
|
|
132
|
+
┌─────────────────────────────────┐
|
|
133
|
+
│ Check: Already connecting? │
|
|
134
|
+
│ (dedupe concurrent attempts) │
|
|
135
|
+
└─────────────────┬───────────────┘
|
|
136
|
+
│ No
|
|
137
|
+
▼
|
|
138
|
+
┌─────────────────────────────────┐
|
|
139
|
+
│ Check: Existing healthy │
|
|
140
|
+
│ connection? (reuse if so) │
|
|
141
|
+
└─────────────────┬───────────────┘
|
|
142
|
+
│ No
|
|
143
|
+
▼
|
|
144
|
+
┌─────────────────────────────────┐
|
|
145
|
+
│ Has command? ──────────────── │ ─── Yes ──┐
|
|
146
|
+
└─────────────────┬───────────────┘ │
|
|
147
|
+
│ No │
|
|
148
|
+
▼ ▼
|
|
149
|
+
┌─────────────────────────────────┐ ┌────────────────────┐
|
|
150
|
+
│ Has URL? │ │ Create Stdio │
|
|
151
|
+
└─────────────────┬───────────────┘ │ Transport │
|
|
152
|
+
│ Yes │ │
|
|
153
|
+
▼ │ - spawn process │
|
|
154
|
+
┌───────────────────────────────────────┐ │ - connect stdin/ │
|
|
155
|
+
│ HTTP Transport Selection │ │ stdout │
|
|
156
|
+
│ │ └─────────┬──────────┘
|
|
157
|
+
│ ┌─────────────────────────────────┐ │ │
|
|
158
|
+
│ │ Try StreamableHTTP first │ │ │
|
|
159
|
+
│ │ (modern MCP servers) │ │ │
|
|
160
|
+
│ └───────────────┬─────────────────┘ │ │
|
|
161
|
+
│ │ │ │
|
|
162
|
+
│ Success? │ │ │
|
|
163
|
+
│ │ │ │ │
|
|
164
|
+
│ ┌─────┴──────┴──────┐ │ │
|
|
165
|
+
│ │ Yes │ No │ │
|
|
166
|
+
│ ▼ ▼ │ │
|
|
167
|
+
│ ┌────────┐ ┌──────────────┐ │ │
|
|
168
|
+
│ │ Use │ │ Fallback to │ │ │
|
|
169
|
+
│ │ Stream │ │ SSE Transport│ │ │
|
|
170
|
+
│ │ able │ │ (legacy) │ │ │
|
|
171
|
+
│ │ HTTP │ └──────┬───────┘ │ │
|
|
172
|
+
│ └───┬────┘ │ │ │
|
|
173
|
+
│ │ │ │ │
|
|
174
|
+
└──────┼────────────────┼───────────────┘ │
|
|
175
|
+
│ │ │
|
|
176
|
+
└────────┬───────┴────────────────────────────┘
|
|
177
|
+
│
|
|
178
|
+
▼
|
|
179
|
+
┌───────────────────────────────────────┐
|
|
180
|
+
│ client.connect(transport) │
|
|
181
|
+
└───────────────────┬───────────────────┘
|
|
182
|
+
│
|
|
183
|
+
┌─────────────┴─────────────┐
|
|
184
|
+
│ │
|
|
185
|
+
▼ ▼
|
|
186
|
+
┌───────────────────┐ ┌───────────────────┐
|
|
187
|
+
│ listTools() │ │ listResources() │
|
|
188
|
+
│ (with cursor │ │ (with cursor │
|
|
189
|
+
│ pagination) │ │ pagination) │
|
|
190
|
+
└─────────┬─────────┘ └─────────┬─────────┘
|
|
191
|
+
│ │
|
|
192
|
+
└─────────────┬─────────────┘
|
|
193
|
+
│
|
|
194
|
+
▼
|
|
195
|
+
┌───────────────────────────────────────┐
|
|
196
|
+
│ ServerConnection │
|
|
197
|
+
│ { │
|
|
198
|
+
│ client, │
|
|
199
|
+
│ transport, │
|
|
200
|
+
│ tools: McpTool[], │
|
|
201
|
+
│ resources: McpResource[], │
|
|
202
|
+
│ status: "connected" | "closed" │
|
|
203
|
+
│ } │
|
|
204
|
+
└───────────────────────────────────────┘
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Tool Metadata Collection (NOT Registration)
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
211
|
+
│ MCP Server Tool Definition │
|
|
212
|
+
│ │
|
|
213
|
+
│ { │
|
|
214
|
+
│ name: "list_sims", │
|
|
215
|
+
│ description: "Lists available iOS simulators", │
|
|
216
|
+
│ inputSchema: { ... } ◄─── NOT converted (MCP server validates) │
|
|
217
|
+
│ } │
|
|
218
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
219
|
+
│
|
|
220
|
+
▼
|
|
221
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
222
|
+
│ Tool Name Formatting │
|
|
223
|
+
│ formatToolName() │
|
|
224
|
+
│ │
|
|
225
|
+
│ Server: "xcodebuild" Tool: "list_sims" │
|
|
226
|
+
│ │
|
|
227
|
+
│ prefix: "server" ──► "xcodebuild_list_sims" │
|
|
228
|
+
│ prefix: "short" ──► "xcodebuild_list_sims" (strips -mcp suffix) │
|
|
229
|
+
│ prefix: "none" ──► "list_sims" (collision risk!) │
|
|
230
|
+
│ │
|
|
231
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
232
|
+
│
|
|
233
|
+
▼
|
|
234
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
235
|
+
│ Tool Metadata (stored in Map) │
|
|
236
|
+
│ NOT registered with Pi! │
|
|
237
|
+
│ │
|
|
238
|
+
│ toolMetadata.set("xcodebuild", [ │
|
|
239
|
+
│ { │
|
|
240
|
+
│ name: "xcodebuild_list_sims", ◄─── Prefixed name (for lookup) │
|
|
241
|
+
│ originalName: "list_sims", ◄─── Original MCP tool name │
|
|
242
|
+
│ description: "Lists available iOS simulators", │
|
|
243
|
+
│ }, │
|
|
244
|
+
│ { │
|
|
245
|
+
│ name: "xcodebuild_get_simulators", │
|
|
246
|
+
│ originalName: "get_simulators", │
|
|
247
|
+
│ description: "Read resource: xcodebuildmcp://simulators", │
|
|
248
|
+
│ resourceUri: "xcodebuildmcp://simulators", ◄─── Resource tools │
|
|
249
|
+
│ }, │
|
|
250
|
+
│ // ... more tools │
|
|
251
|
+
│ ]); │
|
|
252
|
+
│ │
|
|
253
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## How the LLM Uses MCP Tools
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
260
|
+
│ LLM SEES (single tool in system prompt): │
|
|
261
|
+
│ │
|
|
262
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
263
|
+
│ │ Tool: mcp │ │
|
|
264
|
+
│ │ Description: MCP gateway - connect to MCP servers and call tools. │ │
|
|
265
|
+
│ │ │ │
|
|
266
|
+
│ │ Usage: │ │
|
|
267
|
+
│ │ mcp({ }) → Show server status │ │
|
|
268
|
+
│ │ mcp({ server: "name" }) → List tools from server │ │
|
|
269
|
+
│ │ mcp({ search: "query" }) → Search for tools │ │
|
|
270
|
+
│ │ mcp({ describe: "tool_name" }) → Show tool parameters │ │
|
|
271
|
+
│ │ mcp({ tool: "name", args: {...} })→ Call a tool │ │
|
|
272
|
+
│ │ │ │
|
|
273
|
+
│ │ Parameters: │ │
|
|
274
|
+
│ │ tool?: string - Tool name to call │ │
|
|
275
|
+
│ │ args?: object - Arguments for tool call │ │
|
|
276
|
+
│ │ describe?: string - Tool name to describe (shows parameters) │ │
|
|
277
|
+
│ │ search?: string - Search (space-separated words OR'd) │ │
|
|
278
|
+
│ │ server?: string - Filter to specific server │ │
|
|
279
|
+
│ │ regex?: boolean - Treat as regex instead of OR'd words │ │
|
|
280
|
+
│ │ includeSchemas?: boolean - Include schemas (default: true) │ │
|
|
281
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
282
|
+
│ │
|
|
283
|
+
│ ~200 tokens total (vs ~15,000 for 75 individual tools) │
|
|
284
|
+
│ │
|
|
285
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
286
|
+
│
|
|
287
|
+
│ LLM workflow:
|
|
288
|
+
▼
|
|
289
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
290
|
+
│ │
|
|
291
|
+
│ 1. LLM calls mcp({}) to see what servers are available │
|
|
292
|
+
│ → Returns: "MCP: 1/1 servers, 75 tools\n✓ xcodebuild (75 tools)" │
|
|
293
|
+
│ │
|
|
294
|
+
│ 2. LLM calls mcp({ search: "simulator" }) to find relevant tools │
|
|
295
|
+
│ → Returns: "Found 5 tools matching 'simulator':\n- xcodebuild_..." │
|
|
296
|
+
│ │
|
|
297
|
+
│ 3. LLM calls mcp({ describe: "xcodebuild_boot_sim" }) to see parameters │
|
|
298
|
+
│ → Returns: "Parameters:\n simulatorId (string) *required*\n ..." │
|
|
299
|
+
│ │
|
|
300
|
+
│ 4. LLM calls mcp({ tool: "xcodebuild_boot_sim", args: {...} }) to execute │
|
|
301
|
+
│ → Returns: "Simulator booted successfully" │
|
|
302
|
+
│ │
|
|
303
|
+
│ Note: Step 3 is optional - LLM can skip it and learn from error messages │
|
|
304
|
+
│ which include the expected parameter schema. │
|
|
305
|
+
│ │
|
|
306
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Tool Execution Flow
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
┌────────────────────────┐
|
|
313
|
+
│ LLM decides to call: │
|
|
314
|
+
│ mcp({ │
|
|
315
|
+
│ tool: "xcodebuild_ │
|
|
316
|
+
│ list_sims" │
|
|
317
|
+
│ }) │
|
|
318
|
+
└───────────┬────────────┘
|
|
319
|
+
│
|
|
320
|
+
▼
|
|
321
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
322
|
+
│ Pi Tool Executor │
|
|
323
|
+
│ │
|
|
324
|
+
│ Looks up "mcp" in Tool Registry │
|
|
325
|
+
│ Finds the unified MCP proxy tool │
|
|
326
|
+
└───────────────────────────┬───────────────────────────────────┘
|
|
327
|
+
│
|
|
328
|
+
▼
|
|
329
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
330
|
+
│ mcp tool execute() - executeCall() │
|
|
331
|
+
│ │
|
|
332
|
+
│ 1. Look up tool in toolMetadata │
|
|
333
|
+
│ for (const [server, metadata] of toolMetadata) { │
|
|
334
|
+
│ const found = metadata.find(m => m.name === toolName); │
|
|
335
|
+
│ if (found) { serverName = server; toolMeta = found; } │
|
|
336
|
+
│ } │
|
|
337
|
+
│ │
|
|
338
|
+
│ 2. Get connection from ServerManager │
|
|
339
|
+
│ const connection = manager.getConnection(serverName); │
|
|
340
|
+
│ if (!connection || connection.status !== "connected") │
|
|
341
|
+
│ return error; │
|
|
342
|
+
│ │
|
|
343
|
+
│ 3. Call MCP server │
|
|
344
|
+
│ if (toolMeta.resourceUri) { │
|
|
345
|
+
│ // Resource tool - use readResource │
|
|
346
|
+
│ connection.client.readResource({ uri: resourceUri }); │
|
|
347
|
+
│ } else { │
|
|
348
|
+
│ // Regular tool - use callTool │
|
|
349
|
+
│ connection.client.callTool({ │
|
|
350
|
+
│ name: toolMeta.originalName, ◄── Original name! │
|
|
351
|
+
│ arguments: args ?? {} │
|
|
352
|
+
│ }); │
|
|
353
|
+
│ } │
|
|
354
|
+
└───────────────────────────┬───────────────────────────────────┘
|
|
355
|
+
│
|
|
356
|
+
▼
|
|
357
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
358
|
+
│ MCP Protocol │
|
|
359
|
+
│ │
|
|
360
|
+
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
|
|
361
|
+
│ │ Pi MCP Client │ ──────► │ MCP Server (xcodebuild) │ │
|
|
362
|
+
│ │ │ JSON │ │ │
|
|
363
|
+
│ │ callTool() │ RPC │ Validates args (JSON Schema)│ │
|
|
364
|
+
│ │ │ ◄────── │ Executes list_sims │ │
|
|
365
|
+
│ └─────────────────┘ └─────────────────────────────┘ │
|
|
366
|
+
│ │
|
|
367
|
+
│ Transport: stdio (stdin/stdout) or HTTP (StreamableHTTP/SSE) │
|
|
368
|
+
│ Validation: MCP server validates args, not Pi │
|
|
369
|
+
└───────────────────────────┬───────────────────────────────────┘
|
|
370
|
+
│
|
|
371
|
+
▼
|
|
372
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
373
|
+
│ Content Transformation │
|
|
374
|
+
│ transformMcpContent() │
|
|
375
|
+
│ │
|
|
376
|
+
│ MCP Content Types Pi Content Types │
|
|
377
|
+
│ ───────────────── ──────────────── │
|
|
378
|
+
│ { type: "text", ──► { type: "text", │
|
|
379
|
+
│ text: "..." } text: "..." } │
|
|
380
|
+
│ │
|
|
381
|
+
│ { type: "image", ──► { type: "image", │
|
|
382
|
+
│ data: "base64", data: "base64", │
|
|
383
|
+
│ mimeType: "..." } mimeType: "..." } │
|
|
384
|
+
│ │
|
|
385
|
+
│ { type: "resource", ──► { type: "text", │
|
|
386
|
+
│ resource: {...} } text: "[Resource: uri]\n..." } │
|
|
387
|
+
│ │
|
|
388
|
+
│ { type: "resource ──► { type: "text", │
|
|
389
|
+
│ _link", text: "[Resource Link: name]\n │
|
|
390
|
+
│ uri: "..." } URI: uri" } │
|
|
391
|
+
│ │
|
|
392
|
+
│ { type: "audio", ──► { type: "text", │
|
|
393
|
+
│ ... } text: "[Audio content: mime]" } │
|
|
394
|
+
│ │
|
|
395
|
+
└───────────────────────────┬───────────────────────────────────┘
|
|
396
|
+
│
|
|
397
|
+
▼
|
|
398
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
399
|
+
│ Back to LLM │
|
|
400
|
+
│ │
|
|
401
|
+
│ { │
|
|
402
|
+
│ content: [ │
|
|
403
|
+
│ { type: "text", text: "Available iOS Simulators:..." } │
|
|
404
|
+
│ ], │
|
|
405
|
+
│ details: { mode: "call", server: "xcodebuild", ... } │
|
|
406
|
+
│ } │
|
|
407
|
+
│ │
|
|
408
|
+
│ LLM receives the result and continues conversation │
|
|
409
|
+
└───────────────────────────────────────────────────────────────┘
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Lifecycle & Health Checks
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
416
|
+
│ Session Start │
|
|
417
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
418
|
+
│
|
|
419
|
+
▼
|
|
420
|
+
┌─────────────────────────────────┐
|
|
421
|
+
│ initializeMcp() │
|
|
422
|
+
│ │
|
|
423
|
+
│ 1. Load config │
|
|
424
|
+
│ 2. Create ServerManager │
|
|
425
|
+
│ 3. Create LifecycleManager │
|
|
426
|
+
│ 4. Connect to each server │
|
|
427
|
+
│ 5. Collect tool metadata │
|
|
428
|
+
│ 6. Mark keep-alive servers │
|
|
429
|
+
│ 7. Start health checks │
|
|
430
|
+
│ 8. Set reconnect callback │
|
|
431
|
+
└─────────────────┬───────────────┘
|
|
432
|
+
│
|
|
433
|
+
▼
|
|
434
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
435
|
+
│ Normal Operation │
|
|
436
|
+
│ │
|
|
437
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
438
|
+
│ │ Health Check Loop (30s) │ │
|
|
439
|
+
│ │ │ │
|
|
440
|
+
│ │ for each keep-alive server: │ │
|
|
441
|
+
│ │ if (status !== "connected"): │ │
|
|
442
|
+
│ │ try reconnect │ │
|
|
443
|
+
│ │ if success: call onReconnect callback │ │
|
|
444
|
+
│ │ → updates toolMetadata │ │
|
|
445
|
+
│ │ │ │
|
|
446
|
+
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
|
|
447
|
+
│ │ │ Note: Reconnect callback updates tool metadata so │ │ │
|
|
448
|
+
│ │ │ the mcp proxy tool can find tools after reconnection. │ │ │
|
|
449
|
+
│ │ └──────────────────────────────────────────────────────────┘ │ │
|
|
450
|
+
│ │ │ │
|
|
451
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
452
|
+
│ │
|
|
453
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
454
|
+
│ │ /mcp Commands │ │
|
|
455
|
+
│ │ │ │
|
|
456
|
+
│ │ /mcp status - Show all servers and their connection status │ │
|
|
457
|
+
│ │ /mcp tools - List all available MCP tools │ │
|
|
458
|
+
│ │ /mcp reconnect - Force reconnect all servers, update metadata │ │
|
|
459
|
+
│ │ │ │
|
|
460
|
+
│ │ /mcp-auth <server> - Show OAuth setup instructions │ │
|
|
461
|
+
│ │ │ │
|
|
462
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
463
|
+
│ │
|
|
464
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
465
|
+
│
|
|
466
|
+
│ session_shutdown event
|
|
467
|
+
▼
|
|
468
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
469
|
+
│ Graceful Shutdown │
|
|
470
|
+
│ │
|
|
471
|
+
│ 1. Clear health check interval │
|
|
472
|
+
│ 2. Close all MCP connections (client + transport) │
|
|
473
|
+
│ 3. Tool calls via mcp proxy return "not connected" error │
|
|
474
|
+
│ │
|
|
475
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## File Structure
|
|
479
|
+
|
|
480
|
+
```
|
|
481
|
+
~/.pi/agent/extensions/pi-mcp-adapter/
|
|
482
|
+
│
|
|
483
|
+
├── index.ts Entry point: unified mcp tool, commands, event handlers
|
|
484
|
+
│ - mcp({}) status, search, list, call modes
|
|
485
|
+
│ - /mcp and /mcp-auth commands
|
|
486
|
+
│ - tool metadata management
|
|
487
|
+
│
|
|
488
|
+
├── types.ts Type definitions, formatToolName()
|
|
489
|
+
│ - McpTool, McpResource, McpContent
|
|
490
|
+
│ - ServerEntry, McpConfig, McpSettings
|
|
491
|
+
│
|
|
492
|
+
├── config.ts Config loading, import merging
|
|
493
|
+
│ - Global, project, and imported configs
|
|
494
|
+
│ - Priority: project > global > imports
|
|
495
|
+
│
|
|
496
|
+
├── server-manager.ts MCP connection management
|
|
497
|
+
│ - stdio transport
|
|
498
|
+
│ - HTTP transport (StreamableHTTP + SSE fallback)
|
|
499
|
+
│ - connection pooling and deduplication
|
|
500
|
+
│
|
|
501
|
+
├── tool-registrar.ts Tool name collection (NOT registration!)
|
|
502
|
+
│ - collectToolNames() - builds name list
|
|
503
|
+
│ - transformMcpContent() - MCP → Pi content
|
|
504
|
+
│
|
|
505
|
+
├── resource-tools.ts Resource tool name collection
|
|
506
|
+
│ - collectResourceToolNames()
|
|
507
|
+
│ - resourceNameToToolName()
|
|
508
|
+
│
|
|
509
|
+
├── lifecycle.ts Health checks, reconnection
|
|
510
|
+
│ - keep-alive server tracking
|
|
511
|
+
│ - reconnect callback for metadata updates
|
|
512
|
+
│
|
|
513
|
+
├── oauth-handler.ts OAuth token file reading
|
|
514
|
+
│ - getStoredTokens() from ~/.pi/agent/mcp-oauth/
|
|
515
|
+
│
|
|
516
|
+
├── package.json Dependencies (@modelcontextprotocol/sdk)
|
|
517
|
+
│
|
|
518
|
+
└── tsconfig.json TypeScript configuration
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Key Design Decisions
|
|
522
|
+
|
|
523
|
+
```
|
|
524
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
525
|
+
│ │
|
|
526
|
+
│ 1. SINGLE PROXY TOOL (token efficiency) │
|
|
527
|
+
│ ──────────────────────────────────── │
|
|
528
|
+
│ Only ONE tool ("mcp") is registered with Pi. │
|
|
529
|
+
│ LLM discovers MCP tools on-demand via search/list. │
|
|
530
|
+
│ Saves ~15,000 tokens for a server with 75 tools. │
|
|
531
|
+
│ │
|
|
532
|
+
│ mcp({ tool: "xcodebuild_list_sims" }) // call │
|
|
533
|
+
│ mcp({ search: "simulator" }) // search │
|
|
534
|
+
│ mcp({ server: "xcodebuild" }) // list │
|
|
535
|
+
│ │
|
|
536
|
+
│ 2. SCHEMA ON-DEMAND (describe mode + error enhancement) │
|
|
537
|
+
│ ──────────────────────────────────────────────────── │
|
|
538
|
+
│ Schemas stored in metadata, formatted to human-readable on request. │
|
|
539
|
+
│ - mcp({ describe: "tool" }) returns full description + parameters │
|
|
540
|
+
│ - Error responses include expected parameters to help self-correct │
|
|
541
|
+
│ MCP server still does final validation - we just help the LLM. │
|
|
542
|
+
│ │
|
|
543
|
+
│ 3. METADATA-BASED LOOKUP │
|
|
544
|
+
│ ──────────────────────── │
|
|
545
|
+
│ Tool metadata stored in Map<server, ToolMetadata[]> │
|
|
546
|
+
│ executeCall() looks up tool by prefixed name → finds server + original │
|
|
547
|
+
│ name → calls MCP server with original name. │
|
|
548
|
+
│ │
|
|
549
|
+
│ 4. HTTP TRANSPORT FALLBACK │
|
|
550
|
+
│ ──────────────────────── │
|
|
551
|
+
│ Try StreamableHTTP first (modern), fall back to SSE (legacy). │
|
|
552
|
+
│ Probe with a test connection, close it, create fresh for real use. │
|
|
553
|
+
│ │
|
|
554
|
+
│ 5. TOOL PREFIXING │
|
|
555
|
+
│ ─────────────── │
|
|
556
|
+
│ Default "server" prefix prevents tool name collisions. │
|
|
557
|
+
│ "short" removes -mcp suffix for cleaner names. │
|
|
558
|
+
│ "none" is risky but available for single-server setups. │
|
|
559
|
+
│ │
|
|
560
|
+
│ 6. CONFIG IMPORT │
|
|
561
|
+
│ ───────────── │
|
|
562
|
+
│ Can import from Cursor, Claude, VSCode, etc. │
|
|
563
|
+
│ Allows reusing existing MCP configurations. │
|
|
564
|
+
│ Priority: project > global > imports │
|
|
565
|
+
│ │
|
|
566
|
+
│ 7. RECONNECT CALLBACK │
|
|
567
|
+
│ ────────────────── │
|
|
568
|
+
│ Lifecycle manager notifies extension after auto-reconnect. │
|
|
569
|
+
│ Extension updates tool metadata so proxy can find tools. │
|
|
570
|
+
│ │
|
|
571
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
572
|
+
```
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.1.0] - 2026-01-19
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Search includes schemas by default** - Search results now include parameter schemas, reducing tool calls needed (search + call instead of search + describe + call)
|
|
13
|
+
- **Space-separated search terms match as OR** - `"navigate screenshot"` finds tools matching either word (like most search engines)
|
|
14
|
+
- **Suppress server stderr by default** - MCP server logs no longer clutter terminal on startup
|
|
15
|
+
- Use `includeSchemas: false` for compact output without schemas
|
|
16
|
+
- Use `debug: true` per-server to show stderr when troubleshooting
|
|
17
|
+
|
|
18
|
+
## [1.0.0] - 2026-01-19
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Single unified `mcp` tool** with token-efficient architecture (~200 tokens vs ~15,000 for individual tools)
|
|
23
|
+
- **Five operation modes:**
|
|
24
|
+
- `mcp({})` - Show server status
|
|
25
|
+
- `mcp({ server: "name" })` - List tools from a server
|
|
26
|
+
- `mcp({ search: "query" })` - Search tools by name/description
|
|
27
|
+
- `mcp({ describe: "tool_name" })` - Show tool details and parameter schema
|
|
28
|
+
- `mcp({ tool: "name", args: {...} })` - Call a tool
|
|
29
|
+
- **Stdio transport** for local MCP servers (command + args)
|
|
30
|
+
- **HTTP transport** with automatic fallback (StreamableHTTP → SSE)
|
|
31
|
+
- **Config imports** from Cursor, Claude Code, Claude Desktop, VS Code, Windsurf, Codex
|
|
32
|
+
- **Resource tools** - MCP resources exposed as callable tools
|
|
33
|
+
- **OAuth support** - Token file-based authentication
|
|
34
|
+
- **Bearer token auth** - Static or environment variable tokens
|
|
35
|
+
- **Keep-alive connections** with automatic health checks and reconnection
|
|
36
|
+
- **Schema on-demand** - Parameter schemas shown in `describe` mode and error responses
|
|
37
|
+
- **Commands:**
|
|
38
|
+
- `/mcp` or `/mcp status` - Show server status
|
|
39
|
+
- `/mcp tools` - List all tools
|
|
40
|
+
- `/mcp reconnect` - Force reconnect all servers
|
|
41
|
+
- `/mcp-auth <server>` - Show OAuth setup instructions
|
|
42
|
+
|
|
43
|
+
### Architecture
|
|
44
|
+
|
|
45
|
+
- Tools stored in metadata map, not registered individually with Pi
|
|
46
|
+
- MCP server validates arguments (no client-side schema conversion)
|
|
47
|
+
- Reconnect callback updates metadata after auto-reconnect
|
|
48
|
+
- Human-readable schema formatting for LLM consumption
|