pi-mcp-adapter 2.2.0 → 2.2.2
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/CHANGELOG.md +14 -0
- package/direct-tools.ts +4 -3
- package/index.ts +94 -39
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.2.2] - 2026-04-03
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Session lifecycle teardown now handles repeated `session_start` transitions safely and prevents stale async init results from replacing newer state.
|
|
14
|
+
- Shutdown now still runs `gracefulShutdown()` even if metadata cache flushing throws, avoiding leaked MCP processes.
|
|
15
|
+
- Proxy/direct tool init error paths now preserve and surface underlying error messages instead of returning generic failures.
|
|
16
|
+
- Invalid `mcp` tool `args` now fail by throwing with parse/type context instead of returning non-failing tool payloads.
|
|
17
|
+
- Added focused lifecycle regressions tests for stale init cleanup and init-error visibility.
|
|
18
|
+
|
|
19
|
+
## [2.2.1] - 2026-03-23
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Added `promptSnippet` to MCP proxy tool and direct MCP tools so they appear in the system prompt's Available tools section (required since pi 0.59.0)
|
|
23
|
+
|
|
10
24
|
## [2.2.0] - 2026-03-16
|
|
11
25
|
|
|
12
26
|
### Added
|
package/direct-tools.ts
CHANGED
|
@@ -181,10 +181,11 @@ export function createDirectToolExecutor(
|
|
|
181
181
|
if (!state && initPromise) {
|
|
182
182
|
try {
|
|
183
183
|
state = await initPromise;
|
|
184
|
-
} catch {
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
185
186
|
return {
|
|
186
|
-
content: [{ type: "text" as const, text:
|
|
187
|
-
details: { error: "init_failed" },
|
|
187
|
+
content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }],
|
|
188
|
+
details: { error: "init_failed", message },
|
|
188
189
|
};
|
|
189
190
|
}
|
|
190
191
|
}
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI,
|
|
1
|
+
import type { ExtensionAPI, ToolInfo } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.js";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import { showStatus, showTools, reconnectServers, authenticateServer, openMcpPanel } from "./commands.js";
|
|
@@ -7,11 +7,42 @@ import { buildProxyDescription, createDirectToolExecutor, resolveDirectTools } f
|
|
|
7
7
|
import { flushMetadataCache, initializeMcp, updateStatusBar } from "./init.js";
|
|
8
8
|
import { loadMetadataCache } from "./metadata-cache.js";
|
|
9
9
|
import { executeCall, executeConnect, executeDescribe, executeList, executeSearch, executeStatus, executeUiMessages } from "./proxy-modes.js";
|
|
10
|
-
import { getConfigPathFromArgv } from "./utils.js";
|
|
10
|
+
import { getConfigPathFromArgv, truncateAtWord } from "./utils.js";
|
|
11
11
|
|
|
12
12
|
export default function mcpAdapter(pi: ExtensionAPI) {
|
|
13
13
|
let state: McpExtensionState | null = null;
|
|
14
14
|
let initPromise: Promise<McpExtensionState> | null = null;
|
|
15
|
+
let lifecycleGeneration = 0;
|
|
16
|
+
|
|
17
|
+
async function shutdownState(currentState: McpExtensionState | null, reason: string): Promise<void> {
|
|
18
|
+
if (!currentState) return;
|
|
19
|
+
|
|
20
|
+
if (currentState.uiServer) {
|
|
21
|
+
currentState.uiServer.close(reason);
|
|
22
|
+
currentState.uiServer = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let flushError: unknown;
|
|
26
|
+
try {
|
|
27
|
+
flushMetadataCache(currentState);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
flushError = error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await currentState.lifecycle.gracefulShutdown();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (flushError) {
|
|
36
|
+
console.error("MCP: graceful shutdown failed after metadata flush error", error);
|
|
37
|
+
} else {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (flushError) {
|
|
43
|
+
throw flushError;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
15
46
|
|
|
16
47
|
const earlyConfigPath = getConfigPathFromArgv();
|
|
17
48
|
const earlyConfig = loadMcpConfig(earlyConfigPath);
|
|
@@ -33,6 +64,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
33
64
|
name: spec.prefixedName,
|
|
34
65
|
label: `MCP: ${spec.originalName}`,
|
|
35
66
|
description: spec.description || "(no description)",
|
|
67
|
+
promptSnippet: truncateAtWord(spec.description, 100) || `MCP tool from ${spec.serverName}`,
|
|
36
68
|
parameters: Type.Unsafe<Record<string, unknown>>(spec.inputSchema || { type: "object", properties: {} }),
|
|
37
69
|
execute: createDirectToolExecutor(() => state, () => initPromise, spec),
|
|
38
70
|
});
|
|
@@ -46,35 +78,59 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
46
78
|
});
|
|
47
79
|
|
|
48
80
|
pi.on("session_start", async (_event, ctx) => {
|
|
49
|
-
|
|
81
|
+
const generation = ++lifecycleGeneration;
|
|
82
|
+
const previousState = state;
|
|
83
|
+
state = null;
|
|
84
|
+
initPromise = null;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await shutdownState(previousState, "session_restart");
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error("MCP: failed to shut down previous session state", error);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (generation !== lifecycleGeneration) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const promise = initializeMcp(pi, ctx);
|
|
97
|
+
initPromise = promise;
|
|
50
98
|
|
|
51
|
-
|
|
52
|
-
|
|
99
|
+
promise.then(async (nextState) => {
|
|
100
|
+
if (generation !== lifecycleGeneration || initPromise !== promise) {
|
|
101
|
+
try {
|
|
102
|
+
await shutdownState(nextState, "stale_session_start");
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error("MCP: failed to clean stale session state", error);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
state = nextState;
|
|
110
|
+
updateStatusBar(nextState);
|
|
53
111
|
initPromise = null;
|
|
54
|
-
updateStatusBar(s);
|
|
55
112
|
}).catch(err => {
|
|
113
|
+
if (generation !== lifecycleGeneration) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (initPromise !== promise && initPromise !== null) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
56
119
|
console.error("MCP initialization failed:", err);
|
|
57
120
|
initPromise = null;
|
|
58
121
|
});
|
|
59
122
|
});
|
|
60
123
|
|
|
61
124
|
pi.on("session_shutdown", async () => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Initialization failed, nothing to clean up
|
|
67
|
-
}
|
|
68
|
-
}
|
|
125
|
+
++lifecycleGeneration;
|
|
126
|
+
const currentState = state;
|
|
127
|
+
state = null;
|
|
128
|
+
initPromise = null;
|
|
69
129
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
flushMetadataCache(state);
|
|
76
|
-
await state.lifecycle.gracefulShutdown();
|
|
77
|
-
state = null;
|
|
130
|
+
try {
|
|
131
|
+
await shutdownState(currentState, "session_shutdown");
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("MCP: session shutdown cleanup failed", error);
|
|
78
134
|
}
|
|
79
135
|
});
|
|
80
136
|
|
|
@@ -84,8 +140,9 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
84
140
|
if (!state && initPromise) {
|
|
85
141
|
try {
|
|
86
142
|
state = await initPromise;
|
|
87
|
-
} catch {
|
|
88
|
-
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
145
|
+
if (ctx.hasUI) ctx.ui.notify(`MCP initialization failed: ${message}`, "error");
|
|
89
146
|
return;
|
|
90
147
|
}
|
|
91
148
|
}
|
|
@@ -130,8 +187,9 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
130
187
|
if (!state && initPromise) {
|
|
131
188
|
try {
|
|
132
189
|
state = await initPromise;
|
|
133
|
-
} catch {
|
|
134
|
-
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
192
|
+
if (ctx.hasUI) ctx.ui.notify(`MCP initialization failed: ${message}`, "error");
|
|
135
193
|
return;
|
|
136
194
|
}
|
|
137
195
|
}
|
|
@@ -148,6 +206,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
148
206
|
name: "mcp",
|
|
149
207
|
label: "MCP",
|
|
150
208
|
description: buildProxyDescription(earlyConfig, earlyCache, directSpecs),
|
|
209
|
+
promptSnippet: "MCP gateway - connect to MCP servers and call their tools",
|
|
151
210
|
parameters: Type.Object({
|
|
152
211
|
tool: Type.Optional(Type.String({ description: "Tool name to call (e.g., 'xcodebuild_list_sims')" })),
|
|
153
212
|
args: Type.Optional(Type.String({ description: "Arguments as JSON string (e.g., '{\"key\": \"value\"}')" })),
|
|
@@ -176,28 +235,24 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
176
235
|
parsedArgs = JSON.parse(params.args);
|
|
177
236
|
if (typeof parsedArgs !== "object" || parsedArgs === null || Array.isArray(parsedArgs)) {
|
|
178
237
|
const gotType = Array.isArray(parsedArgs) ? "array" : parsedArgs === null ? "null" : typeof parsedArgs;
|
|
179
|
-
|
|
180
|
-
content: [{ type: "text" as const, text: `Invalid args: expected a JSON object, got ${gotType}` }],
|
|
181
|
-
isError: true,
|
|
182
|
-
details: { error: "invalid_args_type" },
|
|
183
|
-
};
|
|
238
|
+
throw new Error(`Invalid args: expected a JSON object, got ${gotType}`);
|
|
184
239
|
}
|
|
185
|
-
} catch (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error instanceof SyntaxError) {
|
|
242
|
+
throw new Error(`Invalid args JSON: ${error.message}`, { cause: error });
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
191
245
|
}
|
|
192
246
|
}
|
|
193
247
|
|
|
194
248
|
if (!state && initPromise) {
|
|
195
249
|
try {
|
|
196
250
|
state = await initPromise;
|
|
197
|
-
} catch {
|
|
251
|
+
} catch (error) {
|
|
252
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
198
253
|
return {
|
|
199
|
-
content: [{ type: "text" as const, text:
|
|
200
|
-
details: { error: "init_failed" },
|
|
254
|
+
content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }],
|
|
255
|
+
details: { error: "init_failed", message },
|
|
201
256
|
};
|
|
202
257
|
}
|
|
203
258
|
}
|