@fitchmultz/pi-mcp-adapter 8.0.5 → 8.0.7
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 +140 -7
- package/OAUTH.md +13 -16
- package/README.md +8 -8
- package/cli.js +67 -28
- package/dist/abort.js +18 -9
- package/dist/client-operations.js +4 -2
- package/dist/commands.js +77 -43
- package/dist/config.js +194 -105
- package/dist/connection-runtime.d.ts +2 -2
- package/dist/connection-runtime.js +29 -18
- package/dist/consent-manager.d.ts +2 -2
- package/dist/consent-manager.js +5 -2
- package/dist/direct-tools.d.ts +1 -1
- package/dist/direct-tools.js +13 -7
- package/dist/elicitation-handler.d.ts +2 -3
- package/dist/elicitation-handler.js +113 -58
- package/dist/gateway-arguments.d.ts +1 -1
- package/dist/gateway-arguments.js +90 -23
- package/dist/glimpse-ui.js +10 -5
- package/dist/host-html-template.d.ts +3 -1
- package/dist/host-html-template.js +9 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +264 -119
- package/dist/init.d.ts +1 -3
- package/dist/init.js +91 -51
- package/dist/legacy-migration.d.ts +1 -1
- package/dist/legacy-migration.js +17 -12
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +33 -17
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +4 -2
- package/dist/mcp-auth-flow.js +150 -58
- package/dist/mcp-auth.d.ts +7 -7
- package/dist/mcp-auth.js +189 -138
- package/dist/mcp-callback-server.js +19 -8
- package/dist/mcp-code.js +168 -54
- package/dist/mcp-keyring-helper.cjs +57 -23
- package/dist/mcp-oauth-provider.d.ts +6 -7
- package/dist/mcp-oauth-provider.js +146 -72
- package/dist/mcp-oauth-runtime.js +29 -16
- package/dist/mcp-output-guard.js +193 -65
- package/dist/mcp-output-preview.js +41 -20
- package/dist/mcp-panel.d.ts +9 -9
- package/dist/mcp-panel.js +141 -70
- package/dist/mcp-script-worker.mjs +135 -43
- package/dist/mcp-setup-panel.d.ts +8 -8
- package/dist/mcp-setup-panel.js +130 -44
- package/dist/mcp-status.js +14 -7
- package/dist/mcp-trace.d.ts +10 -5
- package/dist/mcp-trace.js +127 -61
- package/dist/metadata-cache.d.ts +3 -4
- package/dist/metadata-cache.js +83 -43
- package/dist/npx-resolver.js +101 -60
- package/dist/onboarding-state.js +4 -2
- package/dist/plain-result-reader.js +78 -31
- package/dist/prompts.js +36 -20
- package/dist/proxy-modes.js +670 -193
- package/dist/resource-tools.js +30 -9
- package/dist/runtime-owner.js +36 -18
- package/dist/sampling-handler.d.ts +1 -2
- package/dist/sampling-handler.js +41 -21
- package/dist/search-ranking.d.ts +1 -1
- package/dist/search-ranking.js +67 -34
- package/dist/server-manager.d.ts +11 -11
- package/dist/server-manager.js +283 -166
- package/dist/session-recovery.js +113 -61
- package/dist/tool-approval.js +9 -3
- package/dist/tool-loader.js +154 -55
- package/dist/tool-metadata.d.ts +1 -1
- package/dist/tool-metadata.js +51 -23
- package/dist/tool-registrar.js +32 -10
- package/dist/tool-result-renderer.d.ts +3 -3
- package/dist/tool-result-renderer.js +65 -44
- package/dist/types.d.ts +2 -2
- package/dist/types.js +39 -18
- package/dist/ui-message-history.js +33 -13
- package/dist/ui-resource-handler.d.ts +3 -3
- package/dist/ui-resource-handler.js +37 -21
- package/dist/ui-server.js +101 -43
- package/dist/ui-session.js +66 -29
- package/dist/ui-stream-types.js +14 -4
- package/dist/ui-tool-visibility.js +12 -6
- package/dist/unix-socket-transport.d.ts +1 -2
- package/dist/unix-socket-transport.js +19 -12
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +60 -35
- package/package.json +29 -5
- package/skills/mcp-scripting/SKILL.md +6 -6
package/OAUTH.md
CHANGED
|
@@ -42,6 +42,7 @@ For most MCP servers, you only need the URL:
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
OAuth is automatically enabled for HTTP servers. The SDK will:
|
|
45
|
+
|
|
45
46
|
- Auto-detect if the server requires OAuth
|
|
46
47
|
- Discover OAuth endpoints from the server
|
|
47
48
|
- Reuse a configured/stored client, use CIMD when supported, or register dynamically
|
|
@@ -69,8 +70,6 @@ You can optionally provide a pre-registered client:
|
|
|
69
70
|
}
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
73
|
### Configuration Options
|
|
75
74
|
|
|
76
75
|
- `url` - The MCP server URL (required)
|
|
@@ -145,13 +144,13 @@ Set `oauth.privateKeyJwt` to authenticate native token requests with `private_ke
|
|
|
145
144
|
}
|
|
146
145
|
```
|
|
147
146
|
|
|
148
|
-
| Option
|
|
149
|
-
|
|
|
150
|
-
| `privateKey`
|
|
151
|
-
| `algorithm`
|
|
152
|
-
| `audience`
|
|
153
|
-
| `lifetimeSeconds` | Optional positive integer; native default is 300 seconds.
|
|
154
|
-
| `claims`
|
|
147
|
+
| Option | Meaning |
|
|
148
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
149
|
+
| `privateKey` | PKCS#8 PEM string, JWK object or JWK JSON string. Strings support `${VAR}`, `$env:VAR`, `{env:VAR}` and existing `!command` / `!!` conventions, resolved only when authenticating. |
|
|
150
|
+
| `algorithm` | Required asymmetric JOSE algorithm. Native PEM import supports RS, PS and ES families; EdDSA / Ed25519 require a JWK. The SDK validates supported key/algorithm combinations. HMAC (`HS*`) and `none` are not private-key authentication. |
|
|
151
|
+
| `audience` | Optional nonempty JWT audience string; defaults to the discovered authorization-server issuer, then token URL. It does not change the request destination. |
|
|
152
|
+
| `lifetimeSeconds` | Optional positive integer; native default is 300 seconds. |
|
|
153
|
+
| `claims` | Optional object of additional JWT claims. Native `iss`, `sub`, `aud`, `iat`, `exp` and `jti` take precedence over overlapping entries. |
|
|
155
154
|
|
|
156
155
|
Keys must belong to an externally registered `clientId`, or a custom `clientMetadataUrl` whose document describes `private_key_jwt` and provides the matching public verification key. The SDK signs issuer and subject as the actual selected client ID. Configured and usable stored clients retain their existing priority. If a previous login uses the shared method-`none` browser identity or a shared secret, private-key authentication fails before key resolution instead of silently migrating or erasing that login. `clientSecret` and `privateKeyJwt` cannot be configured together. The shared Fitch document cannot identify a private-key client. Explicit pre-registration and opaque DCR client IDs are not classified as shared CIMD merely because their strings resemble a document URL.
|
|
157
156
|
|
|
@@ -197,6 +196,7 @@ Run the `/mcp-auth` command with the server name:
|
|
|
197
196
|
Manual `/mcp-auth` is the default flow. With `settings.autoAuth: true`, proxy/direct/script tool execution may authenticate when a server needs auth or rejects a call for insufficient scope. Browser authorization still requires an interactive host, and each invocation gets at most one automatic auth attempt and one post-auth retry. Initial sign-in does not grant a second automatic consent attempt in the same invocation.
|
|
198
197
|
|
|
199
198
|
For browser authorization, this will:
|
|
199
|
+
|
|
200
200
|
1. Start the callback server lazily on an OS-assigned local port, or on the exact `oauth.redirectUri` port for pre-registered callbacks
|
|
201
201
|
2. Discover OAuth endpoints automatically
|
|
202
202
|
3. Select a configured/stored client, CIMD, or dynamic registration
|
|
@@ -253,6 +253,7 @@ mcp({ action: "call", server: "my-oauth-server", tool: "my-tool", args: { key: "
|
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
The SDK automatically:
|
|
256
|
+
|
|
256
257
|
- Adds the access token to requests
|
|
257
258
|
- Refreshes expired tokens automatically
|
|
258
259
|
- Re-authenticates if tokens are invalid
|
|
@@ -434,12 +435,8 @@ The default remains strict, including when `protocolVersion` is `"legacy"`. The
|
|
|
434
435
|
The implementation uses the official split MCP SDK v2 client and OAuth APIs. MCP Apps also uses this split SDK graph; SDK v1 is not a runtime dependency:
|
|
435
436
|
|
|
436
437
|
```typescript
|
|
437
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client"
|
|
438
|
-
import {
|
|
439
|
-
auth,
|
|
440
|
-
UnauthorizedError,
|
|
441
|
-
type OAuthClientProvider,
|
|
442
|
-
} from "@modelcontextprotocol/client"
|
|
438
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
439
|
+
import { auth, UnauthorizedError, type OAuthClientProvider } from "@modelcontextprotocol/client";
|
|
443
440
|
```
|
|
444
441
|
|
|
445
442
|
The `McpOAuthProvider` class implements `OAuthClientProvider` and is passed to `StreamableHTTPClientTransport`:
|
|
@@ -447,7 +444,7 @@ The `McpOAuthProvider` class implements `OAuthClientProvider` and is passed to `
|
|
|
447
444
|
```typescript
|
|
448
445
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
449
446
|
authProvider: new McpOAuthProvider(serverName, serverUrl, config, callbacks),
|
|
450
|
-
})
|
|
447
|
+
});
|
|
451
448
|
```
|
|
452
449
|
|
|
453
450
|
## References
|
package/README.md
CHANGED
|
@@ -44,14 +44,14 @@ By default, servers connect when Pi discovers an uncached catalog or calls a too
|
|
|
44
44
|
|
|
45
45
|
## Controls and settings
|
|
46
46
|
|
|
47
|
-
| Command
|
|
48
|
-
|
|
|
49
|
-
| `/mcp`
|
|
50
|
-
| `/mcp status`
|
|
51
|
-
| `/mcp reconnect <server>` | Reconnect and refresh the catalog
|
|
52
|
-
| `/mcp-auth <server>`
|
|
53
|
-
| `/mcp disable <server>`
|
|
54
|
-
| `/mcp enable <server>`
|
|
47
|
+
| Command | Purpose |
|
|
48
|
+
| ------------------------- | --------------------------------------------------------- |
|
|
49
|
+
| `/mcp` | Show servers and pin tools |
|
|
50
|
+
| `/mcp status` | Show connection state and tool/resource counts |
|
|
51
|
+
| `/mcp reconnect <server>` | Reconnect and refresh the catalog |
|
|
52
|
+
| `/mcp-auth <server>` | Start OAuth in an interactive session |
|
|
53
|
+
| `/mcp disable <server>` | Disable the server for this project; apply with `/reload` |
|
|
54
|
+
| `/mcp enable <server>` | Enable the server for this project; apply with `/reload` |
|
|
55
55
|
|
|
56
56
|
Use [`directTools`](docs/reference.md#typed-discovery-and-pinned-tools) to pin tools at startup. Other tools remain available for discovery. Global search covers known catalogs; select a server to discover an unknown catalog.
|
|
57
57
|
|
package/cli.js
CHANGED
|
@@ -8,9 +8,14 @@ import stripJsonComments from "strip-json-comments";
|
|
|
8
8
|
|
|
9
9
|
const HOME = os.homedir();
|
|
10
10
|
|
|
11
|
+
/** @param {string} input @returns {string} */
|
|
11
12
|
function expandHome(input) {
|
|
12
|
-
if (input === "~")
|
|
13
|
-
|
|
13
|
+
if (input === "~") {
|
|
14
|
+
return HOME;
|
|
15
|
+
}
|
|
16
|
+
if (input.startsWith("~/")) {
|
|
17
|
+
return path.resolve(HOME, input.slice(2));
|
|
18
|
+
}
|
|
14
19
|
return path.resolve(input);
|
|
15
20
|
}
|
|
16
21
|
|
|
@@ -31,11 +36,10 @@ const IMPORT_PATHS = {
|
|
|
31
36
|
path.join(HOME, ".claude.json"),
|
|
32
37
|
path.join(HOME, ".claude", "claude_desktop_config.json"),
|
|
33
38
|
],
|
|
34
|
-
"claude-desktop": [
|
|
35
|
-
|
|
36
|
-
path.join(HOME, ".codex", "config.toml"),
|
|
37
|
-
path.join(HOME, ".codex", "config.json"),
|
|
39
|
+
"claude-desktop": [
|
|
40
|
+
path.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
38
41
|
],
|
|
42
|
+
codex: [path.join(HOME, ".codex", "config.toml"), path.join(HOME, ".codex", "config.json")],
|
|
39
43
|
opencode: [
|
|
40
44
|
path.join(HOME, ".config", "opencode", "opencode.json"),
|
|
41
45
|
path.resolve(process.cwd(), "opencode.json"),
|
|
@@ -55,8 +59,11 @@ function printHelp(log = console.log) {
|
|
|
55
59
|
log(" fitch-mcp-adapter migrate [--dry-run] Copy pre-v5 state without changing its sources");
|
|
56
60
|
}
|
|
57
61
|
|
|
62
|
+
/** @param {string} filePath */
|
|
58
63
|
function readJsonFile(filePath) {
|
|
59
|
-
return JSON.parse(
|
|
64
|
+
return JSON.parse(
|
|
65
|
+
stripJsonComments(fs.readFileSync(filePath, "utf-8"), { trailingCommas: true }),
|
|
66
|
+
);
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
function loadPiConfig() {
|
|
@@ -67,13 +74,17 @@ function loadPiConfig() {
|
|
|
67
74
|
const raw = readJsonFile(PI_CONFIG_PATH);
|
|
68
75
|
const mcpServers = raw.mcpServers ?? raw["mcp-servers"] ?? {};
|
|
69
76
|
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
70
|
-
throw new Error(
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Invalid MCP config at ${PI_CONFIG_PATH}: expected "mcpServers" to be an object`,
|
|
79
|
+
);
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
const normalized = { ...raw };
|
|
74
83
|
delete normalized["mcp-servers"];
|
|
75
84
|
|
|
76
|
-
const imports = Array.isArray(raw.imports)
|
|
85
|
+
const imports = Array.isArray(raw.imports)
|
|
86
|
+
? raw.imports.filter((/** @type {unknown} */ value) => typeof value === "string")
|
|
87
|
+
: undefined;
|
|
77
88
|
return {
|
|
78
89
|
...normalized,
|
|
79
90
|
mcpServers,
|
|
@@ -94,6 +105,7 @@ function findAvailableImports() {
|
|
|
94
105
|
return found;
|
|
95
106
|
}
|
|
96
107
|
|
|
108
|
+
/** @param {(line: string) => void} log @param {{ kind: string; path: string }[]} imports */
|
|
97
109
|
function printDiscovery(log, imports) {
|
|
98
110
|
log("Config discovery:\n");
|
|
99
111
|
|
|
@@ -107,6 +119,9 @@ function printDiscovery(log, imports) {
|
|
|
107
119
|
];
|
|
108
120
|
|
|
109
121
|
for (const [label, filePath] of paths) {
|
|
122
|
+
if (filePath === undefined) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
110
125
|
const prefix = fs.existsSync(filePath) ? "✓" : "-";
|
|
111
126
|
log(`${prefix} ${label}: ${filePath}`);
|
|
112
127
|
}
|
|
@@ -122,11 +137,13 @@ function printDiscovery(log, imports) {
|
|
|
122
137
|
}
|
|
123
138
|
}
|
|
124
139
|
|
|
140
|
+
/** @param {{ imports?: unknown }} config */
|
|
125
141
|
function writePiConfig(config) {
|
|
126
142
|
fs.mkdirSync(path.dirname(PI_CONFIG_PATH), { recursive: true });
|
|
127
143
|
fs.writeFileSync(PI_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
128
144
|
}
|
|
129
145
|
|
|
146
|
+
/** @param {string[]} argv @param {(line: string) => void} [log] */
|
|
130
147
|
async function runInit(argv, log = console.log) {
|
|
131
148
|
const dryRun = argv.includes("--dry-run");
|
|
132
149
|
const discoverHostConfigs = argv.includes("--discover-host-configs");
|
|
@@ -139,16 +156,21 @@ async function runInit(argv, log = console.log) {
|
|
|
139
156
|
|
|
140
157
|
printDiscovery(log, foundImports);
|
|
141
158
|
|
|
142
|
-
const discoverySettingChanged =
|
|
159
|
+
const discoverySettingChanged =
|
|
160
|
+
discoverHostConfigs && existingConfig.settings?.hostConfigDiscovery !== "on";
|
|
143
161
|
if (importsToAdd.length === 0 && !discoverySettingChanged) {
|
|
144
162
|
log("\nNo Pi config changes needed.");
|
|
145
|
-
log(
|
|
163
|
+
log(
|
|
164
|
+
"Standard MCP configs are discovered automatically, and host-specific imports are already configured or unavailable.",
|
|
165
|
+
);
|
|
146
166
|
return 0;
|
|
147
167
|
}
|
|
148
168
|
|
|
149
169
|
const nextConfig = {
|
|
150
170
|
...existingConfig,
|
|
151
|
-
...(discoverySettingChanged
|
|
171
|
+
...(discoverySettingChanged
|
|
172
|
+
? { settings: { ...existingConfig.settings, hostConfigDiscovery: "on" } }
|
|
173
|
+
: {}),
|
|
152
174
|
...(importsToAdd.length > 0 ? { imports: [...existingImports, ...importsToAdd] } : {}),
|
|
153
175
|
mcpServers: existingConfig.mcpServers ?? {},
|
|
154
176
|
};
|
|
@@ -157,7 +179,9 @@ async function runInit(argv, log = console.log) {
|
|
|
157
179
|
log(`\nDetected host configs to import into Pi: ${importsToAdd.join(", ")}`);
|
|
158
180
|
}
|
|
159
181
|
if (discoverySettingChanged) {
|
|
160
|
-
log(
|
|
182
|
+
log(
|
|
183
|
+
"Opting in to host-specific fallback discovery (standard and Pi-owned configs still take precedence).",
|
|
184
|
+
);
|
|
161
185
|
}
|
|
162
186
|
|
|
163
187
|
if (dryRun) {
|
|
@@ -167,9 +191,13 @@ async function runInit(argv, log = console.log) {
|
|
|
167
191
|
|
|
168
192
|
writePiConfig(nextConfig);
|
|
169
193
|
log(`Updated ${PI_CONFIG_PATH}`);
|
|
170
|
-
log(
|
|
194
|
+
log(
|
|
195
|
+
"Pi will now keep reading standard MCP configs automatically, while these imports cover host-specific config formats.",
|
|
196
|
+
);
|
|
171
197
|
if (discoverySettingChanged) {
|
|
172
|
-
log(
|
|
198
|
+
log(
|
|
199
|
+
"Host config discovery is explicit and does not write to or execute commands from external host files.",
|
|
200
|
+
);
|
|
173
201
|
}
|
|
174
202
|
return 0;
|
|
175
203
|
}
|
|
@@ -184,7 +212,9 @@ export async function main(argv = process.argv.slice(2), log = console.log, erro
|
|
|
184
212
|
|
|
185
213
|
if (command === "install") {
|
|
186
214
|
error("The custom downloader has been retired.");
|
|
187
|
-
error(
|
|
215
|
+
error(
|
|
216
|
+
"Use `pi install npm:@fitchmultz/pi-mcp-adapter` instead, then optionally run `fitch-mcp-adapter init`.",
|
|
217
|
+
);
|
|
188
218
|
return 1;
|
|
189
219
|
}
|
|
190
220
|
|
|
@@ -196,11 +226,17 @@ export async function main(argv = process.argv.slice(2), log = console.log, erro
|
|
|
196
226
|
const { migrateLegacyState } = await import("./dist/legacy-migration.js");
|
|
197
227
|
const dryRun = rest.includes("--dry-run");
|
|
198
228
|
const result = migrateLegacyState({ dryRun });
|
|
199
|
-
for (const file of result.files)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
229
|
+
for (const file of result.files) {
|
|
230
|
+
log(`${file.status}: ${file.source} -> ${file.destination}`);
|
|
231
|
+
}
|
|
232
|
+
for (const credential of result.credentials) {
|
|
233
|
+
log(`OAuth ${credential.server}: ${credential.status}`);
|
|
234
|
+
}
|
|
235
|
+
log(
|
|
236
|
+
dryRun
|
|
237
|
+
? "Dry run: no files changed and no credential store accessed."
|
|
238
|
+
: "Migration complete. Existing destinations and all source files/credentials were left intact. Restart Pi to use the independent adapter.",
|
|
239
|
+
);
|
|
204
240
|
return 0;
|
|
205
241
|
}
|
|
206
242
|
|
|
@@ -210,13 +246,16 @@ export async function main(argv = process.argv.slice(2), log = console.log, erro
|
|
|
210
246
|
}
|
|
211
247
|
|
|
212
248
|
const resolvedEntrypoint = process.argv[1] ? fs.realpathSync(process.argv[1]) : undefined;
|
|
213
|
-
const isEntrypoint =
|
|
249
|
+
const isEntrypoint =
|
|
250
|
+
resolvedEntrypoint && import.meta.url === pathToFileURL(resolvedEntrypoint).href;
|
|
214
251
|
|
|
215
252
|
if (isEntrypoint) {
|
|
216
|
-
main()
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
253
|
+
main()
|
|
254
|
+
.then((code) => {
|
|
255
|
+
process.exitCode = code;
|
|
256
|
+
})
|
|
257
|
+
.catch((err) => {
|
|
258
|
+
console.error(`\nHelper failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
});
|
|
222
261
|
}
|
package/dist/abort.js
CHANGED
|
@@ -1,32 +1,41 @@
|
|
|
1
1
|
export function throwIfAborted(signal) {
|
|
2
|
-
if (!signal?.aborted)
|
|
2
|
+
if (!signal?.aborted) {
|
|
3
3
|
return;
|
|
4
|
-
|
|
4
|
+
}
|
|
5
|
+
throw signal.reason instanceof Error
|
|
6
|
+
? signal.reason
|
|
7
|
+
: new Error(String(signal.reason ?? "MCP request aborted"));
|
|
5
8
|
}
|
|
6
9
|
export async function abortable(promise, signal) {
|
|
7
|
-
if (!signal)
|
|
10
|
+
if (!signal) {
|
|
8
11
|
return promise;
|
|
12
|
+
}
|
|
9
13
|
throwIfAborted(signal);
|
|
10
14
|
return await new Promise((resolve, reject) => {
|
|
11
15
|
let settled = false;
|
|
12
16
|
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
13
17
|
const onAbort = () => {
|
|
14
|
-
if (settled)
|
|
18
|
+
if (settled) {
|
|
15
19
|
return;
|
|
20
|
+
}
|
|
16
21
|
settled = true;
|
|
17
22
|
cleanup();
|
|
18
|
-
reject(signal.reason instanceof Error
|
|
23
|
+
reject(signal.reason instanceof Error
|
|
24
|
+
? signal.reason
|
|
25
|
+
: new Error(String(signal.reason ?? "MCP request aborted")));
|
|
19
26
|
};
|
|
20
27
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
21
|
-
promise.then(value => {
|
|
22
|
-
if (settled)
|
|
28
|
+
promise.then((value) => {
|
|
29
|
+
if (settled) {
|
|
23
30
|
return;
|
|
31
|
+
}
|
|
24
32
|
settled = true;
|
|
25
33
|
cleanup();
|
|
26
34
|
resolve(value);
|
|
27
|
-
}, error => {
|
|
28
|
-
if (settled)
|
|
35
|
+
}, (error) => {
|
|
36
|
+
if (settled) {
|
|
29
37
|
return;
|
|
38
|
+
}
|
|
30
39
|
settled = true;
|
|
31
40
|
cleanup();
|
|
32
41
|
reject(error);
|
|
@@ -4,16 +4,18 @@ export async function drainClientCallbacks(client) {
|
|
|
4
4
|
const pending = [...(clientOperations.get(client) ?? [])]
|
|
5
5
|
.filter(([, kind]) => kind === "sampling callback" || kind === "elicitation callback")
|
|
6
6
|
.map(([operation]) => operation);
|
|
7
|
-
if (!pending.length)
|
|
7
|
+
if (!pending.length) {
|
|
8
8
|
return;
|
|
9
|
+
}
|
|
9
10
|
await Promise.allSettled(pending);
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
export async function drainClientOperations(client) {
|
|
13
14
|
for (;;) {
|
|
14
15
|
const pending = [...(clientOperations.get(client)?.keys() ?? [])];
|
|
15
|
-
if (!pending.length)
|
|
16
|
+
if (!pending.length) {
|
|
16
17
|
return;
|
|
18
|
+
}
|
|
17
19
|
await Promise.allSettled(pending);
|
|
18
20
|
}
|
|
19
21
|
}
|