pi-mcp-adapter 3.1.0 → 3.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/CHANGELOG.md +25 -0
- package/OAUTH.md +7 -7
- package/README.md +52 -4
- package/dist/mcp-callback-server.js +1 -2
- package/dist/mcp-callback-server.js.map +1 -1
- package/dist/mcp-oauth-provider.d.ts +2 -1
- package/dist/mcp-oauth-provider.js +5 -2
- package/dist/mcp-oauth-provider.js.map +1 -1
- package/dist/state.d.ts +2 -1
- package/host-managed.ts +547 -0
- package/index.ts +19 -22
- package/init.ts +15 -9
- package/mcp-callback-server.ts +1 -1
- package/mcp-code.ts +2 -2
- package/mcp-oauth-provider.ts +5 -2
- package/mcp-script-wasm.ts +18 -1
- package/mcp-script-worker.mjs +9 -2
- package/namespace-tools.ts +22 -2
- package/package.json +8 -1
- package/state.ts +2 -1
- package/tool-approval.ts +11 -6
- package/tool-parameters.ts +11 -0
package/init.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { McpExtensionState } from "./state.ts";
|
|
|
3
3
|
import { formatToolName, isServerDisabled, resolveToolPrefix, type McpAdapterOptions, type PromptMetadata, type ToolMetadata, type ToolSelectorCandidateIndex } from "./types.ts";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { cloneMcpConfig, loadMcpConfig, resolveConfiguredClaudePluginMcp } from "./config.ts";
|
|
6
|
-
import { applyProjectServerTrustToConfig, describeProjectServerBlock } from "./project-server-trust.ts";
|
|
6
|
+
import { applyProjectServerTrustToConfig, describeProjectServerBlock, excludeProjectServersAtLoadTime } from "./project-server-trust.ts";
|
|
7
7
|
import { ConsentManager } from "./consent-manager.ts";
|
|
8
8
|
import { McpLifecycleManager } from "./lifecycle.ts";
|
|
9
9
|
import {
|
|
@@ -104,6 +104,8 @@ type McpInitializationOptions = McpAdapterOptions & {
|
|
|
104
104
|
oauthRuntime?: McpOAuthRuntime;
|
|
105
105
|
statusEvents?: McpExtensionState["statusEvents"];
|
|
106
106
|
onProjectTrustResolved?: () => void;
|
|
107
|
+
/** Load-time runs have no Pi context, so project trust is unknown; session_start decides later. */
|
|
108
|
+
excludeProjectServers?: boolean;
|
|
107
109
|
};
|
|
108
110
|
|
|
109
111
|
export async function initializeMcp(
|
|
@@ -142,7 +144,9 @@ export async function initializeMcp(
|
|
|
142
144
|
const runtimeSignal = combineAbortSignals(owner.signal, initialSignal);
|
|
143
145
|
const trustResult = options.config !== undefined
|
|
144
146
|
? { config: resolveConfiguredClaudePluginMcp(cloneMcpConfig(options.config), cwd), blockedServers: new Map() }
|
|
145
|
-
:
|
|
147
|
+
: options.excludeProjectServers
|
|
148
|
+
? { config: excludeProjectServersAtLoadTime(loadMcpConfig(configPath, cwd)), blockedServers: new Map() }
|
|
149
|
+
: await applyProjectServerTrustToConfig(loadMcpConfig(configPath, cwd), ctx);
|
|
146
150
|
options.onProjectTrustResolved?.();
|
|
147
151
|
const config = trustResult.config;
|
|
148
152
|
const authStorageOptions = getAuthStorageOptions(config.settings?.oauthDir, cwd, config.settings?.oauthCredentialStore);
|
|
@@ -593,6 +597,7 @@ export function updateMetadataCache(
|
|
|
593
597
|
if (!definition || isServerDisabled(definition)) return;
|
|
594
598
|
|
|
595
599
|
const configHash = computeServerHash(definition);
|
|
600
|
+
if (connection.definition && computeServerHash(connection.definition) !== configHash) return;
|
|
596
601
|
const existing = loadMetadataCache();
|
|
597
602
|
const existingEntry = existing?.servers?.[serverName];
|
|
598
603
|
|
|
@@ -602,13 +607,13 @@ export function updateMetadataCache(
|
|
|
602
607
|
? existingEntry?.configHash === configHash ? existingEntry.prompts : undefined
|
|
603
608
|
: serializePrompts(connection.prompts ?? []);
|
|
604
609
|
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
isServerCacheValid(existingEntry, definition)
|
|
610
|
-
|
|
611
|
-
|
|
610
|
+
if (definition.exposeResources !== false && connection.resourceDiscoveryFailed === true) {
|
|
611
|
+
const sessionEntry = state.sessionMetadata?.get(serverName);
|
|
612
|
+
if (sessionEntry?.configHash === configHash) {
|
|
613
|
+
resources = sessionEntry.resources ?? [];
|
|
614
|
+
} else if (existingEntry?.resources?.length && isServerCacheValid(existingEntry, definition)) {
|
|
615
|
+
resources = existingEntry.resources;
|
|
616
|
+
}
|
|
612
617
|
}
|
|
613
618
|
|
|
614
619
|
const entry: ServerCacheEntry = {
|
|
@@ -622,6 +627,7 @@ export function updateMetadataCache(
|
|
|
622
627
|
cachedAt: Date.now(),
|
|
623
628
|
};
|
|
624
629
|
|
|
630
|
+
(state.sessionMetadata ??= new Map()).set(serverName, entry);
|
|
625
631
|
saveMetadataCache({ version: 1, servers: { [serverName]: entry } });
|
|
626
632
|
}
|
|
627
633
|
|
package/mcp-callback-server.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { createServer, type Server, type IncomingMessage, type ServerResponse } from "http"
|
|
9
9
|
import { getAppName } from "./agent-dir.ts"
|
|
10
10
|
import {
|
|
11
|
+
DEFAULT_OAUTH_CALLBACK_HOST,
|
|
11
12
|
DEFAULT_OAUTH_CALLBACK_PATH,
|
|
12
13
|
getConfiguredOAuthCallbackPort,
|
|
13
14
|
getOAuthCallbackPath,
|
|
@@ -197,7 +198,6 @@ interface EnsureCallbackServerOptions {
|
|
|
197
198
|
reserveState?: boolean
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
const DEFAULT_OAUTH_CALLBACK_HOST = "localhost"
|
|
201
201
|
let callbackServerHost = DEFAULT_OAUTH_CALLBACK_HOST
|
|
202
202
|
|
|
203
203
|
/**
|
package/mcp-code.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { formatWithOptions } from "node:util";
|
|
|
3
3
|
import { Worker } from "node:worker_threads";
|
|
4
4
|
import { throwIfAborted } from "./abort.ts";
|
|
5
5
|
import { guardMcpOutput, guardedMcpDetails, resolveMcpOutputGuardOptions } from "./mcp-output-guard.ts";
|
|
6
|
-
import { loadMcpScriptWasm } from "./mcp-script-wasm.ts";
|
|
6
|
+
import { loadMcpScriptWasm, resolveMcpScriptQuickJsUrl } from "./mcp-script-wasm.ts";
|
|
7
7
|
import { evaluateJev, validateJevSettings } from "./jev-client.ts";
|
|
8
8
|
import type { JevErrorCode, JevEvaluateInput, JevEvaluationEnvelope } from "./jev-contracts.ts";
|
|
9
9
|
import { executeCall } from "./proxy-modes.ts";
|
|
@@ -408,7 +408,7 @@ export async function runMcpScript(
|
|
|
408
408
|
try {
|
|
409
409
|
const wasm = await Promise.race([loadMcpScriptWasm(), timeout, aborted]);
|
|
410
410
|
worker = new Worker(new URL("./mcp-script-worker.mjs", import.meta.url), {
|
|
411
|
-
workerData: { code, wasm, interrupt, outputMaxBytes: MCP_SCRIPT_OUTPUT_MAX_BYTES },
|
|
411
|
+
workerData: { code, wasm, quickjsUrl: resolveMcpScriptQuickJsUrl(), interrupt, outputMaxBytes: MCP_SCRIPT_OUTPUT_MAX_BYTES },
|
|
412
412
|
env: {},
|
|
413
413
|
// The sandbox cannot open files, and the host always terminates this worker.
|
|
414
414
|
// Disable Node's unmanaged FD bookkeeping, which emits false warnings when
|
package/mcp-oauth-provider.ts
CHANGED
|
@@ -89,6 +89,9 @@ function toOAuthTokens(tokens: StoredTokens): IssuerBoundTokens {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// Callback server configuration
|
|
92
|
+
// RFC 8252 section 7.3: use the loopback IP literal, not "localhost". The
|
|
93
|
+
// callback listener binds this same host so the advertised redirect reaches it.
|
|
94
|
+
const DEFAULT_OAUTH_CALLBACK_HOST = "127.0.0.1"
|
|
92
95
|
const DEFAULT_OAUTH_CALLBACK_PORT = 19876
|
|
93
96
|
const DEFAULT_OAUTH_CALLBACK_PATH = "/callback"
|
|
94
97
|
|
|
@@ -287,7 +290,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
287
290
|
this.flowState = initialState
|
|
288
291
|
this.redirectUrlSnapshot = config.grantType === "client_credentials"
|
|
289
292
|
? undefined
|
|
290
|
-
: config.redirectUri ?? `http
|
|
293
|
+
: config.redirectUri ?? `http://${DEFAULT_OAUTH_CALLBACK_HOST}:${getOAuthCallbackPort()}${getOAuthCallbackPath()}`
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
setAuthFetch(fetchFn: OAuthFetch): void {
|
|
@@ -816,4 +819,4 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
816
819
|
}
|
|
817
820
|
}
|
|
818
821
|
|
|
819
|
-
export { DEFAULT_OAUTH_CALLBACK_PORT, DEFAULT_OAUTH_CALLBACK_PATH }
|
|
822
|
+
export { DEFAULT_OAUTH_CALLBACK_HOST, DEFAULT_OAUTH_CALLBACK_PORT, DEFAULT_OAUTH_CALLBACK_PATH }
|
package/mcp-script-wasm.ts
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
3
4
|
|
|
4
5
|
export type McpScriptWasmModule = object;
|
|
5
6
|
|
|
6
7
|
const cached = new Map<string, Promise<McpScriptWasmModule>>();
|
|
7
8
|
|
|
9
|
+
function resolveQuickJsWasmPath(): string {
|
|
10
|
+
return createRequire(import.meta.url).resolve("quickjs-wasi/quickjs.wasm");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the quickjs-wasi entry on the host thread. The worker imports this
|
|
15
|
+
* file URL because Bun-compiled executables cannot resolve bare package names
|
|
16
|
+
* from a worker file loaded from disk.
|
|
17
|
+
* Those executables also fail to resolve the bare `quickjs-wasi` root on the
|
|
18
|
+
* host thread, while package subpaths resolve. So the entry is located next to
|
|
19
|
+
* the wasm file, which is how quickjs-wasi's `"."` export lays it out.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveMcpScriptQuickJsUrl(): string {
|
|
22
|
+
return new URL("./dist/index.js", pathToFileURL(resolveQuickJsWasmPath())).href;
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
/** Load and compile the packaged QuickJS runtime once per host process. */
|
|
9
|
-
export function loadMcpScriptWasm(path =
|
|
26
|
+
export function loadMcpScriptWasm(path = resolveQuickJsWasmPath()): Promise<McpScriptWasmModule> {
|
|
10
27
|
let module = cached.get(path);
|
|
11
28
|
if (!module) {
|
|
12
29
|
const webAssembly = (globalThis as unknown as {
|
package/mcp-script-worker.mjs
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { parentPort, workerData } from "node:worker_threads";
|
|
2
|
-
import { JSException, MAX_STACK_SIZE, QuickJS } from "quickjs-wasi";
|
|
3
2
|
|
|
4
3
|
const MEMORY_LIMIT_BYTES = 64 * 1024 * 1024;
|
|
5
4
|
const ERROR_MAX_BYTES = 64 * 1024;
|
|
6
5
|
const ERROR_TRUNCATION_MARKER = "\n...[mcpScript error truncated]";
|
|
7
6
|
|
|
7
|
+
// Set once main() imports quickjs-wasi from the host-resolved file URL.
|
|
8
|
+
let JSException;
|
|
9
|
+
|
|
8
10
|
function post(message) {
|
|
9
11
|
parentPort?.postMessage(message);
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
function errorText(error) {
|
|
13
|
-
if (error instanceof JSException) {
|
|
15
|
+
if (JSException && error instanceof JSException) {
|
|
14
16
|
const head = error.message ? `${error.name}: ${error.message}` : error.name;
|
|
15
17
|
return error.stack ? `${head}\n${error.stack}` : head;
|
|
16
18
|
}
|
|
@@ -172,6 +174,11 @@ const PRELUDE_SOURCE = `(function (bridge) {
|
|
|
172
174
|
})`;
|
|
173
175
|
|
|
174
176
|
async function main() {
|
|
177
|
+
// Bun-compiled executables cannot resolve bare package names from this
|
|
178
|
+
// worker file, so import quickjs-wasi from the URL the host resolved.
|
|
179
|
+
const quickjs = await import(workerData.quickjsUrl);
|
|
180
|
+
JSException = quickjs.JSException;
|
|
181
|
+
const { MAX_STACK_SIZE, QuickJS } = quickjs;
|
|
175
182
|
const interrupt = new Int32Array(workerData.interrupt);
|
|
176
183
|
let outputBytes = 0;
|
|
177
184
|
let outputExceeded = false;
|
package/namespace-tools.ts
CHANGED
|
@@ -190,6 +190,10 @@ export interface SyncNamespaceProxyToolsInput {
|
|
|
190
190
|
activeDirectNames?: ReadonlySet<string>;
|
|
191
191
|
existingNamespaceNames: Set<string>;
|
|
192
192
|
unavailableServers?: ReadonlySet<string>;
|
|
193
|
+
// Names the adapter removed from Pi's active tools because Pi could not
|
|
194
|
+
// unregister them. Pi does not re-activate a name it already knows, so a
|
|
195
|
+
// later re-registration must add the name back.
|
|
196
|
+
fallbackDeactivatedNames?: Set<string>;
|
|
193
197
|
pi: ExtensionAPI;
|
|
194
198
|
getState: GetState;
|
|
195
199
|
getInitPromise: GetInitPromise;
|
|
@@ -265,10 +269,19 @@ function registerNamespaceProxyTool(
|
|
|
265
269
|
),
|
|
266
270
|
});
|
|
267
271
|
input.guardReentrant?.();
|
|
272
|
+
if (input.fallbackDeactivatedNames?.delete(spec.toolName)) {
|
|
273
|
+
// Only undo a fallback deactivation the adapter still owns.
|
|
274
|
+
input.guardReentrant?.();
|
|
275
|
+
const activeTools = getActiveToolsIfReady(input.pi);
|
|
276
|
+
input.guardReentrant?.();
|
|
277
|
+
if (activeTools && !activeTools.includes(spec.toolName)) {
|
|
278
|
+
input.pi.setActiveTools([...activeTools, spec.toolName]);
|
|
279
|
+
input.guardReentrant?.();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
268
282
|
}
|
|
269
283
|
|
|
270
|
-
function
|
|
271
|
-
if (staleNames.length === 0) return undefined;
|
|
284
|
+
function getActiveToolsIfReady(pi: ExtensionAPI): string[] | undefined {
|
|
272
285
|
try {
|
|
273
286
|
return pi.getActiveTools?.();
|
|
274
287
|
} catch (error) {
|
|
@@ -277,6 +290,11 @@ function getActiveToolsForStaleCleanup(pi: ExtensionAPI, staleNames: string[]):
|
|
|
277
290
|
}
|
|
278
291
|
}
|
|
279
292
|
|
|
293
|
+
function getActiveToolsForStaleCleanup(pi: ExtensionAPI, staleNames: string[]): string[] | undefined {
|
|
294
|
+
if (staleNames.length === 0) return undefined;
|
|
295
|
+
return getActiveToolsIfReady(pi);
|
|
296
|
+
}
|
|
297
|
+
|
|
280
298
|
function deactivateStaleNamespaceTools(input: SyncNamespaceProxyToolsInput, nextNames: Set<string>): string[] {
|
|
281
299
|
const activeDirectNames = input.activeDirectNames ?? new Set<string>();
|
|
282
300
|
const staleNames = [...input.existingNamespaceNames].filter(
|
|
@@ -298,12 +316,14 @@ function deactivateStaleNamespaceTools(input: SyncNamespaceProxyToolsInput, next
|
|
|
298
316
|
if (!activeTools) return deactivated;
|
|
299
317
|
|
|
300
318
|
const stale = new Set(staleNames);
|
|
319
|
+
const fallbackRemoved = staleNames.filter((name) => !deactivated.includes(name) && activeTools.includes(name));
|
|
301
320
|
const nextActiveTools = activeTools.filter((name) => !stale.has(name));
|
|
302
321
|
if (nextActiveTools.length === activeTools.length) return deactivated;
|
|
303
322
|
|
|
304
323
|
input.guardReentrant?.();
|
|
305
324
|
input.pi.setActiveTools(nextActiveTools);
|
|
306
325
|
input.guardReentrant?.();
|
|
326
|
+
for (const name of fallbackRemoved) input.fallbackDeactivatedNames?.add(name);
|
|
307
327
|
for (const name of staleNames) {
|
|
308
328
|
if (!deactivated.includes(name)) deactivated.push(name);
|
|
309
329
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-adapter",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"import": "./oauth.ts",
|
|
21
21
|
"default": "./oauth.ts"
|
|
22
22
|
},
|
|
23
|
+
"./host-managed": {
|
|
24
|
+
"types": "./host-managed.ts",
|
|
25
|
+
"import": "./host-managed.ts",
|
|
26
|
+
"default": "./host-managed.ts"
|
|
27
|
+
},
|
|
23
28
|
"./config": {
|
|
24
29
|
"types": "./dist/config.d.ts",
|
|
25
30
|
"import": "./dist/config.js",
|
|
@@ -86,6 +91,8 @@
|
|
|
86
91
|
"ts-shape.ts",
|
|
87
92
|
"search-ranking.ts",
|
|
88
93
|
"tool-approval.ts",
|
|
94
|
+
"tool-parameters.ts",
|
|
95
|
+
"host-managed.ts",
|
|
89
96
|
"session-approvals.ts",
|
|
90
97
|
"project-server-trust.ts",
|
|
91
98
|
"init.ts",
|
package/state.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ConsentManager } from "./consent-manager.ts";
|
|
|
3
3
|
import type { McpLifecycleManager } from "./lifecycle.ts";
|
|
4
4
|
import type { McpServerManager } from "./server-manager.ts";
|
|
5
5
|
import type { AuthStorageOptions } from "./mcp-auth.ts";
|
|
6
|
-
import type { ServerDefinition, ToolMetadata, PromptMetadata, McpConfig, UiSessionMessages, UiStreamSummary, McpStatusEventBus, UiServerHandle, ProjectServerBlock } from "./types.ts";
|
|
6
|
+
import type { ServerDefinition, ToolMetadata, PromptMetadata, ServerCacheEntry, McpConfig, UiSessionMessages, UiStreamSummary, McpStatusEventBus, UiServerHandle, ProjectServerBlock } from "./types.ts";
|
|
7
7
|
import type { UiResourceHandler } from "./ui-resource-handler.ts";
|
|
8
8
|
import type { McpRuntimeOwner } from "./runtime-owner.ts";
|
|
9
9
|
import type { McpOAuthRuntime } from "./mcp-auth-flow.ts";
|
|
@@ -33,6 +33,7 @@ export interface McpExtensionState {
|
|
|
33
33
|
manager: McpServerManager;
|
|
34
34
|
lifecycle: McpLifecycleManager;
|
|
35
35
|
toolMetadata: Map<string, ToolMetadata[]>;
|
|
36
|
+
sessionMetadata?: Map<string, ServerCacheEntry>;
|
|
36
37
|
/** Number of tools currently registered directly with Pi, by server. */
|
|
37
38
|
directToolCounts: Map<string, number>;
|
|
38
39
|
/** Resource counts retained separately because tool metadata includes resource tools. */
|
package/tool-approval.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { abortable } from "./abort.ts";
|
|
3
4
|
import { combineAbortSignals } from "./runtime-owner.ts";
|
|
4
5
|
import type { McpExtensionState } from "./state.ts";
|
|
@@ -88,15 +89,19 @@ function isMcpToolApprovalDecision(value: unknown): value is McpToolApprovalDeci
|
|
|
88
89
|
|| value === "abstain";
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Returns "abstain" when no handler claims the request. A handler that throws
|
|
94
|
+
* or returns an unknown value counts as "deny". Rethrows only when `signal` aborted.
|
|
95
|
+
*/
|
|
96
|
+
export async function requestBrokerApproval(
|
|
97
|
+
events: ExtensionAPI["events"] | undefined,
|
|
93
98
|
serverName: string,
|
|
94
|
-
toolMeta: ToolMetadata,
|
|
99
|
+
toolMeta: Pick<ToolMetadata, "name" | "originalName">,
|
|
95
100
|
args: Record<string, unknown> | undefined,
|
|
96
101
|
origin: McpToolApprovalOrigin,
|
|
97
102
|
signal?: AbortSignal,
|
|
98
103
|
): Promise<McpToolApprovalDecision> {
|
|
99
|
-
if (!
|
|
104
|
+
if (!events) return "abstain";
|
|
100
105
|
|
|
101
106
|
let acceptingClaim = true;
|
|
102
107
|
let handler: McpToolApprovalHandler | undefined;
|
|
@@ -115,7 +120,7 @@ async function requestBrokerApproval(
|
|
|
115
120
|
},
|
|
116
121
|
};
|
|
117
122
|
|
|
118
|
-
|
|
123
|
+
events.emit(MCP_TOOL_APPROVAL_REQUEST_EVENT, request);
|
|
119
124
|
acceptingClaim = false;
|
|
120
125
|
if (!handler) return "abstain";
|
|
121
126
|
|
|
@@ -140,7 +145,7 @@ export async function ensureToolCallApproved(
|
|
|
140
145
|
const ownedSignal = combineAbortSignals(state.owner?.signal, signal);
|
|
141
146
|
const approvedServers = state.approvedServers ??= new Map();
|
|
142
147
|
const serverIdentity = getServerApprovalIdentity(state, serverName);
|
|
143
|
-
const brokerDecision = await requestBrokerApproval(state, serverName, toolMeta, args, origin, signal);
|
|
148
|
+
const brokerDecision = await requestBrokerApproval(state.approvalEvents, serverName, toolMeta, args, origin, signal);
|
|
144
149
|
if (brokerDecision === "allow_once") return { ok: true };
|
|
145
150
|
if (brokerDecision === "allow_for_session") {
|
|
146
151
|
rememberToolApproval(state, serverName, toolMeta, args);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
|
|
3
|
+
// OMP remaps `typebox` to a host shim that historically lacked Type.Unsafe.
|
|
4
|
+
// Prefer Unsafe when present (real TypeBox / fixed OMP shim); otherwise pass
|
|
5
|
+
// the normalized JSON Schema through as a plain object so toolWireSchema and
|
|
6
|
+
// validateToolArguments still treat it as JSON Schema.
|
|
7
|
+
export function toToolParameters(schema: Record<string, unknown>): unknown {
|
|
8
|
+
return typeof (Type as { Unsafe?: (value: never) => unknown }).Unsafe === "function"
|
|
9
|
+
? (Type as { Unsafe: (value: never) => unknown }).Unsafe(schema as never)
|
|
10
|
+
: schema;
|
|
11
|
+
}
|