@fitchmultz/pi-mcp-adapter 8.0.6 → 8.0.8

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +142 -8
  2. package/OAUTH.md +13 -16
  3. package/README.md +8 -8
  4. package/cli.js +67 -28
  5. package/dist/abort.js +18 -9
  6. package/dist/client-operations.js +4 -2
  7. package/dist/commands.js +77 -43
  8. package/dist/config.js +194 -105
  9. package/dist/connection-runtime.d.ts +2 -2
  10. package/dist/connection-runtime.js +29 -18
  11. package/dist/consent-manager.d.ts +2 -2
  12. package/dist/consent-manager.js +5 -2
  13. package/dist/direct-tools.d.ts +1 -1
  14. package/dist/direct-tools.js +13 -7
  15. package/dist/elicitation-handler.d.ts +2 -3
  16. package/dist/elicitation-handler.js +113 -58
  17. package/dist/gateway-arguments.d.ts +1 -1
  18. package/dist/gateway-arguments.js +90 -23
  19. package/dist/glimpse-ui.js +10 -5
  20. package/dist/host-html-template.d.ts +3 -1
  21. package/dist/host-html-template.js +9 -12
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +264 -119
  24. package/dist/init.d.ts +1 -3
  25. package/dist/init.js +91 -51
  26. package/dist/legacy-migration.d.ts +1 -1
  27. package/dist/legacy-migration.js +17 -12
  28. package/dist/lifecycle.d.ts +3 -3
  29. package/dist/lifecycle.js +33 -17
  30. package/dist/logger.d.ts +2 -2
  31. package/dist/logger.js +4 -2
  32. package/dist/mcp-auth-flow.js +150 -58
  33. package/dist/mcp-auth.d.ts +7 -7
  34. package/dist/mcp-auth.js +189 -138
  35. package/dist/mcp-callback-server.js +19 -8
  36. package/dist/mcp-code.js +168 -54
  37. package/dist/mcp-keyring-helper.cjs +57 -23
  38. package/dist/mcp-oauth-provider.d.ts +6 -7
  39. package/dist/mcp-oauth-provider.js +146 -72
  40. package/dist/mcp-oauth-runtime.js +29 -16
  41. package/dist/mcp-output-guard.js +193 -65
  42. package/dist/mcp-output-preview.js +41 -20
  43. package/dist/mcp-panel.d.ts +9 -9
  44. package/dist/mcp-panel.js +141 -70
  45. package/dist/mcp-script-worker.mjs +135 -43
  46. package/dist/mcp-setup-panel.d.ts +8 -8
  47. package/dist/mcp-setup-panel.js +130 -44
  48. package/dist/mcp-status.js +14 -7
  49. package/dist/mcp-trace.d.ts +10 -5
  50. package/dist/mcp-trace.js +127 -61
  51. package/dist/metadata-cache.d.ts +3 -4
  52. package/dist/metadata-cache.js +83 -43
  53. package/dist/npx-resolver.js +101 -60
  54. package/dist/onboarding-state.js +4 -2
  55. package/dist/plain-result-reader.js +78 -31
  56. package/dist/prompts.js +36 -20
  57. package/dist/proxy-modes.js +670 -193
  58. package/dist/resource-tools.js +30 -9
  59. package/dist/runtime-owner.js +36 -18
  60. package/dist/sampling-handler.d.ts +1 -2
  61. package/dist/sampling-handler.js +41 -21
  62. package/dist/search-ranking.d.ts +1 -1
  63. package/dist/search-ranking.js +67 -34
  64. package/dist/server-manager.d.ts +11 -11
  65. package/dist/server-manager.js +283 -166
  66. package/dist/session-recovery.js +113 -61
  67. package/dist/tool-approval.js +9 -3
  68. package/dist/tool-loader.js +154 -55
  69. package/dist/tool-metadata.d.ts +1 -1
  70. package/dist/tool-metadata.js +51 -23
  71. package/dist/tool-registrar.js +32 -10
  72. package/dist/tool-result-renderer.js +58 -33
  73. package/dist/types.d.ts +2 -2
  74. package/dist/types.js +39 -18
  75. package/dist/ui-message-history.js +33 -13
  76. package/dist/ui-resource-handler.d.ts +3 -3
  77. package/dist/ui-resource-handler.js +37 -21
  78. package/dist/ui-server.js +101 -43
  79. package/dist/ui-session.js +66 -29
  80. package/dist/ui-stream-types.js +14 -4
  81. package/dist/ui-tool-visibility.js +12 -6
  82. package/dist/unix-socket-transport.d.ts +1 -2
  83. package/dist/unix-socket-transport.js +19 -12
  84. package/dist/utils.d.ts +2 -0
  85. package/dist/utils.js +60 -35
  86. package/package.json +28 -2
  87. package/skills/mcp-scripting/SKILL.md +6 -6
@@ -493,11 +493,11 @@ export declare class ManagedClient extends Client {
493
493
  }>;
494
494
  }
495
495
  export { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
496
- export { SdkHttpError, StreamableHTTPClientTransport, SSEClientTransport, auth, extractWWWAuthenticateParams, UnauthorizedError } from "@modelcontextprotocol/client";
496
+ export { SdkHttpError, StreamableHTTPClientTransport, SSEClientTransport, auth, extractWWWAuthenticateParams, UnauthorizedError, } from "@modelcontextprotocol/client";
497
497
  export { UnixSocketClientTransport } from "./unix-socket-transport.ts";
498
498
  export { trackToolTransport, trackToolTransportFailure } from "./session-recovery.ts";
499
499
  export { McpOAuthProvider } from "./mcp-oauth-provider.ts";
500
- export { extractOAuthConfig, isOAuthChallenge, recordOAuthChallenge, getOAuthRequest, bindOAuthRequestIssuer } from "./mcp-auth-flow.ts";
500
+ export { extractOAuthConfig, isOAuthChallenge, recordOAuthChallenge, getOAuthRequest, bindOAuthRequestIssuer, } from "./mcp-auth-flow.ts";
501
501
  export { registerSamplingHandler } from "./sampling-handler.ts";
502
502
  export { registerElicitationHandler } from "./elicitation-handler.ts";
503
503
  export { serverStreamResultPatchNotificationSchema } from "./ui-stream-types.ts";
@@ -8,10 +8,13 @@ export class ManagedClient extends Client {
8
8
  oauthProvider;
9
9
  own(operation, kind) {
10
10
  let pending = clientOperations.get(this);
11
- if (!pending)
12
- clientOperations.set(this, pending = new Map());
11
+ if (!pending) {
12
+ clientOperations.set(this, (pending = new Map()));
13
+ }
13
14
  pending.set(operation, kind);
14
- const settled = () => { pending.delete(operation); };
15
+ const settled = () => {
16
+ pending.delete(operation);
17
+ };
15
18
  void operation.then(settled, settled);
16
19
  return operation;
17
20
  }
@@ -33,44 +36,52 @@ export class ManagedClient extends Client {
33
36
  ? this.oauthProvider.runWithSignal(controller.signal, () => {
34
37
  // Native legacy cancellation notifications must retain their operation's auth context.
35
38
  const abort = AsyncLocalStorage.bind(() => controller.abort(signal?.reason));
36
- if (signal?.aborted)
39
+ if (signal?.aborted) {
37
40
  abort();
38
- else
41
+ }
42
+ else {
39
43
  signal?.addEventListener("abort", abort, { once: true });
44
+ }
40
45
  removeAbortListener = () => signal?.removeEventListener("abort", abort);
41
46
  return start(controller.signal);
42
47
  })
43
48
  : start(signal);
44
- void operation.then(removeAbortListener, () => { controller?.abort(); removeAbortListener(); });
49
+ void operation.then(removeAbortListener, () => {
50
+ controller?.abort();
51
+ removeAbortListener();
52
+ });
45
53
  return this.own(operation, "request/refresh");
46
54
  }
47
55
  callTool(...args) {
48
- return this.track(signal => super.callTool(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
56
+ return this.track((signal) => super.callTool(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
49
57
  }
50
58
  readResource(...args) {
51
- return this.track(signal => super.readResource(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
59
+ return this.track((signal) => super.readResource(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
52
60
  }
53
61
  getPrompt(...args) {
54
- return this.track(signal => super.getPrompt(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
62
+ return this.track((signal) => super.getPrompt(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
55
63
  }
56
64
  listTools(...args) {
57
- return this.track(signal => super.listTools(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
65
+ return this.track((signal) => super.listTools(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
58
66
  }
59
67
  listResources(...args) {
60
- return this.track(signal => super.listResources(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
68
+ return this.track((signal) => super.listResources(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
61
69
  }
62
70
  listPrompts(...args) {
63
- return this.track(signal => super.listPrompts(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
71
+ return this.track((signal) => super.listPrompts(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
64
72
  }
65
73
  complete(...args) {
66
- return this.track(signal => super.complete(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
74
+ return this.track((signal) => super.complete(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
67
75
  }
68
76
  setLoggingLevel(...args) {
69
- return this.track(signal => super.setLoggingLevel(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
77
+ return this.track((signal) => super.setLoggingLevel(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
70
78
  }
71
79
  listen(...args) {
72
80
  return this.track(async (signal) => {
73
- const subscription = await super.listen(args[0], { ...args[1], ...(signal ? { signal } : {}) });
81
+ const subscription = await super.listen(args[0], {
82
+ ...args[1],
83
+ ...(signal ? { signal } : {}),
84
+ });
74
85
  this.own(subscription.closed, "subscription");
75
86
  return subscription;
76
87
  }, args[1]?.signal);
@@ -82,17 +93,17 @@ export class ManagedClient extends Client {
82
93
  }, args[1]?.signal);
83
94
  }
84
95
  unsubscribeResource(...args) {
85
- return this.track(signal => super.unsubscribeResource(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
96
+ return this.track((signal) => super.unsubscribeResource(args[0], { ...args[1], ...(signal ? { signal } : {}) }), args[1]?.signal);
86
97
  }
87
98
  }
88
99
  // Loaded only by the manager's connection path. Keep native constructors and
89
100
  // synchronous connection setup together, after the one asynchronous import.
90
101
  export { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
91
- export { SdkHttpError, StreamableHTTPClientTransport, SSEClientTransport, auth, extractWWWAuthenticateParams, UnauthorizedError } from "@modelcontextprotocol/client";
102
+ export { SdkHttpError, StreamableHTTPClientTransport, SSEClientTransport, auth, extractWWWAuthenticateParams, UnauthorizedError, } from "@modelcontextprotocol/client";
92
103
  export { UnixSocketClientTransport } from "./unix-socket-transport.js";
93
104
  export { trackToolTransport, trackToolTransportFailure } from "./session-recovery.js";
94
105
  export { McpOAuthProvider } from "./mcp-oauth-provider.js";
95
- export { extractOAuthConfig, isOAuthChallenge, recordOAuthChallenge, getOAuthRequest, bindOAuthRequestIssuer } from "./mcp-auth-flow.js";
106
+ export { extractOAuthConfig, isOAuthChallenge, recordOAuthChallenge, getOAuthRequest, bindOAuthRequestIssuer, } from "./mcp-auth-flow.js";
96
107
  export { registerSamplingHandler } from "./sampling-handler.js";
97
108
  export { registerElicitationHandler } from "./elicitation-handler.js";
98
109
  export { serverStreamResultPatchNotificationSchema } from "./ui-stream-types.js";
@@ -1,6 +1,6 @@
1
1
  export declare class ConsentManager {
2
- private decisions;
3
- private log;
2
+ private readonly decisions;
3
+ private readonly log;
4
4
  hasDecisions(): boolean;
5
5
  requiresPrompt(serverName: string): boolean;
6
6
  registerDecision(serverName: string, approved: boolean): void;
@@ -2,7 +2,9 @@ import { logger } from "./logger.js";
2
2
  export class ConsentManager {
3
3
  decisions = new Map();
4
4
  log = logger.child({ component: "ConsentManager" });
5
- hasDecisions() { return this.decisions.size > 0; }
5
+ hasDecisions() {
6
+ return this.decisions.size > 0;
7
+ }
6
8
  requiresPrompt(serverName) {
7
9
  return this.decisions.get(serverName) !== true;
8
10
  }
@@ -12,8 +14,9 @@ export class ConsentManager {
12
14
  }
13
15
  ensureApproved(serverName) {
14
16
  const decision = this.decisions.get(serverName);
15
- if (decision === true)
17
+ if (decision === true) {
16
18
  return;
19
+ }
17
20
  throw new Error(decision === false
18
21
  ? `Tool calls for "${serverName}" were denied for this session`
19
22
  : `Tool call approval required for "${serverName}"`);
@@ -1,7 +1,7 @@
1
1
  import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { McpExtensionState } from "./state.ts";
3
- import type { DirectToolSpec, McpAdapterOptions, McpConfig } from "./types.ts";
4
3
  export { getMissingConfiguredDirectToolServers } from "./metadata-cache.ts";
4
+ import { type DirectToolSpec, type McpAdapterOptions, type McpConfig } from "./types.ts";
5
5
  export declare function directToolSelection(config: McpConfig, serverName: string, envOverride?: string[]): true | string[] | false;
6
6
  export declare function buildProxyDescription(config: McpConfig): string;
7
7
  type DirectToolExecute = (toolCallId: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Record<string, unknown>>>;
@@ -1,19 +1,23 @@
1
1
  import { abortable, throwIfAborted } from "./abort.js";
2
2
  import { parseDirectToolSelectors } from "./metadata-cache.js";
3
3
  export { getMissingConfiguredDirectToolServers } from "./metadata-cache.js";
4
- import { isServerDisabled } from "./types.js";
4
+ import { isServerDisabled, } from "./types.js";
5
5
  import { nativeMcpResult } from "./tool-registrar.js";
6
6
  export function directToolSelection(config, serverName, envOverride) {
7
7
  if (envOverride) {
8
8
  const selection = parseDirectToolSelectors(envOverride);
9
- if (selection.servers.has(serverName))
9
+ if (selection.servers.has(serverName)) {
10
10
  return true;
11
+ }
11
12
  return selection.tools.has(serverName) ? [...selection.tools.get(serverName)] : false;
12
13
  }
13
14
  return config.mcpServers[serverName]?.directTools ?? config.settings?.directTools ?? false;
14
15
  }
15
16
  export function buildProxyDescription(config) {
16
- const servers = Object.entries(config.mcpServers).filter(([, definition]) => !isServerDisabled(definition)).map(([name]) => name).sort();
17
+ const servers = Object.entries(config.mcpServers)
18
+ .filter(([, definition]) => !isServerDisabled(definition))
19
+ .map(([name]) => name)
20
+ .sort();
17
21
  return [
18
22
  'MCP gateway. Use mcp_search to discover typed tools; enable mcp_script with mcp_search({ enable: ["script"] }) for multi-call composition. Native Pi tools are called directly.',
19
23
  `Configured servers: ${servers.join(", ") || "none"}. Catalog discovery is lazy.`,
@@ -36,18 +40,20 @@ export function createDirectToolExecutor(getState, getInitPromise, spec, beforeE
36
40
  return nativeMcpResult({
37
41
  content: [{ type: "text", text: `MCP initialization failed: ${message}` }],
38
42
  details: { error: "init_failed", message },
39
- }, !!spec.outputSchema);
43
+ }, Boolean(spec.outputSchema));
40
44
  }
41
45
  }
42
46
  if (!state) {
43
47
  return nativeMcpResult({
44
48
  content: [{ type: "text", text: "MCP not initialized" }],
45
49
  details: { error: "not_initialized" },
46
- }, !!spec.outputSchema);
50
+ }, Boolean(spec.outputSchema));
47
51
  }
48
52
  const { executeCall } = await import("./proxy-modes.js");
49
- const result = await executeCall(state, spec.originalName, params, spec.serverName, undefined, signal, { toolCallId }, beforeExecute ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation) : undefined, { exactOriginalName: true, ...(spec.resourceUri ? { resourceUri: spec.resourceUri } : {}) });
53
+ const result = await executeCall(state, spec.originalName, params, spec.serverName, undefined, signal, { toolCallId }, beforeExecute
54
+ ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation)
55
+ : undefined, { exactOriginalName: true, ...(spec.resourceUri ? { resourceUri: spec.resourceUri } : {}) });
50
56
  const { mode: _mode, ...details } = result.details;
51
- return nativeMcpResult({ ...result, details }, !!spec.outputSchema);
57
+ return nativeMcpResult({ ...result, details }, Boolean(spec.outputSchema));
52
58
  };
53
59
  }
@@ -1,8 +1,7 @@
1
1
  import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
- import type { Client } from "@modelcontextprotocol/client";
3
- import { type ElicitRequest, type ElicitRequestFormParams, type ElicitRequestURLParams, type ElicitResult } from "@modelcontextprotocol/client";
2
+ import { type Client, type ElicitRequest, type ElicitRequestFormParams, type ElicitRequestURLParams, type ElicitResult } from "@modelcontextprotocol/client";
4
3
  export type ElicitationValue = string | number | boolean | string[] | undefined;
5
- export type ElicitationUIContext = ExtensionUIContext;
4
+ export type ElicitationUIContext = Pick<ExtensionUIContext, "select" | "input" | "notify">;
6
5
  export interface ElicitationHandlerOptions {
7
6
  serverName: string;
8
7
  ui: ElicitationUIContext;
@@ -1,8 +1,8 @@
1
1
  import { ProtocolErrorCode, ProtocolError, } from "@modelcontextprotocol/client";
2
2
  import { AjvJsonSchemaValidator } from "@modelcontextprotocol/client/validators/ajv";
3
3
  import open from "open";
4
- export function registerElicitationHandler(client, options, own = run => run()) {
5
- client.setRequestHandler("elicitation/create", request => own(() => handleElicitationRequest(options, request)));
4
+ export function registerElicitationHandler(client, options, own = (run) => run()) {
5
+ client.setRequestHandler("elicitation/create", (request) => own(() => handleElicitationRequest(options, request)));
6
6
  }
7
7
  export async function handleElicitationRequest(options, request) {
8
8
  return request.params.mode === "url"
@@ -11,38 +11,47 @@ export async function handleElicitationRequest(options, request) {
11
11
  }
12
12
  export async function handleFormElicitation(options, params) {
13
13
  const decision = await options.ui.select(`MCP Input Request\nServer: ${options.serverName}\n\n${params.message}`, ["Continue", "Decline"]);
14
- if (decision === undefined)
14
+ if (decision === undefined) {
15
15
  return { action: "cancel" };
16
- if (decision === "Decline")
16
+ }
17
+ if (decision === "Decline") {
17
18
  return { action: "decline" };
19
+ }
18
20
  const values = {};
19
21
  const properties = Object.entries(params.requestedSchema.properties);
20
22
  for (const [name, schema] of properties) {
21
23
  const value = await collectValidField(options.ui, params, name, schema);
22
- if (!("value" in value))
24
+ if (!("value" in value)) {
23
25
  return { action: "cancel" };
26
+ }
24
27
  values[name] = value.value;
25
28
  }
26
29
  while (true) {
27
30
  const content = coerceAndValidateFormValues(params, values);
28
31
  const action = await options.ui.select(formatReview(options.serverName, properties, content), properties.length > 0 ? ["Submit", "Edit", "Decline"] : ["Submit", "Decline"]);
29
- if (action === undefined)
32
+ if (action === undefined) {
30
33
  return { action: "cancel" };
31
- if (action === "Decline")
34
+ }
35
+ if (action === "Decline") {
32
36
  return { action: "decline" };
33
- if (action === "Submit")
37
+ }
38
+ if (action === "Submit") {
34
39
  return { action: "accept", content };
40
+ }
35
41
  const labels = properties.map(([name, schema]) => `${schema.title ?? humanizeName(name)} (${name})`);
36
42
  const selected = await options.ui.select("Choose a field to edit", labels);
37
- if (selected === undefined)
43
+ if (selected === undefined) {
38
44
  return { action: "cancel" };
45
+ }
39
46
  const property = properties[labels.indexOf(selected)];
40
- if (!property)
47
+ if (!property) {
41
48
  continue;
49
+ }
42
50
  const [name, schema] = property;
43
51
  const value = await collectValidField(options.ui, params, name, schema, values[name]);
44
- if (!("value" in value))
52
+ if (!("value" in value)) {
45
53
  return { action: "cancel" };
54
+ }
46
55
  values[name] = value.value;
47
56
  }
48
57
  }
@@ -50,8 +59,9 @@ async function collectValidField(ui, params, name, schema, current) {
50
59
  const required = params.requestedSchema.required?.includes(name) === true;
51
60
  while (true) {
52
61
  const result = await collectField(ui, params, name, schema, current);
53
- if (!("value" in result))
62
+ if (!("value" in result)) {
54
63
  return result;
64
+ }
55
65
  try {
56
66
  coerceAndValidateFormValues({
57
67
  ...params,
@@ -71,92 +81,124 @@ async function collectValidField(ui, params, name, schema, current) {
71
81
  }
72
82
  async function collectField(ui, params, name, schema, current) {
73
83
  const required = params.requestedSchema.required?.includes(name) === true;
74
- const title = [schema.title ?? humanizeName(name), required ? "(required)" : "", schema.description]
84
+ const title = [
85
+ schema.title ?? humanizeName(name),
86
+ required ? "(required)" : "",
87
+ schema.description,
88
+ ]
75
89
  .filter(Boolean)
76
90
  .join(" ");
77
91
  if (schema.type === "string" && ("enum" in schema || "oneOf" in schema)) {
78
92
  const choices = "oneOf" in schema
79
- ? schema.oneOf.map(option => ({ value: option.const, display: formatChoice(option.const, option.title) }))
93
+ ? schema.oneOf.map((option) => ({
94
+ value: option.const,
95
+ display: formatChoice(option.const, option.title),
96
+ }))
80
97
  : schema.enum.map((value, index) => ({
81
98
  value,
82
99
  display: formatChoice(value, "enumNames" in schema ? schema.enumNames?.[index] : undefined),
83
100
  }));
84
- const displays = uniqueLabels(choices.map(choice => choice.display));
101
+ const displays = uniqueLabels(choices.map((choice) => choice.display));
85
102
  const actions = [...displays];
86
103
  const useDefault = schema.default === undefined ? undefined : uniqueAction("Use default", actions);
87
- if (useDefault)
104
+ if (useDefault) {
88
105
  actions.push(useDefault);
106
+ }
89
107
  const omit = required ? undefined : uniqueAction("Omit", actions);
90
- if (omit)
108
+ if (omit) {
91
109
  actions.push(omit);
110
+ }
92
111
  const action = await ui.select(title, actions);
93
- if (action === undefined)
112
+ if (action === undefined) {
94
113
  return { cancelled: true };
95
- if (action === useDefault)
114
+ }
115
+ if (action === useDefault) {
96
116
  return { cancelled: false, value: schema.default };
97
- if (action === omit)
117
+ }
118
+ if (action === omit) {
98
119
  return { cancelled: false, value: undefined };
120
+ }
99
121
  return { cancelled: false, value: choices[displays.indexOf(action)]?.value };
100
122
  }
101
123
  if (schema.type === "boolean") {
102
124
  const actions = ["Yes", "No"];
103
- if (schema.default !== undefined)
125
+ if (schema.default !== undefined) {
104
126
  actions.push("Use default");
105
- if (!required)
127
+ }
128
+ if (!required) {
106
129
  actions.push("Omit");
130
+ }
107
131
  const action = await ui.select(title, actions);
108
- if (action === undefined)
132
+ if (action === undefined) {
109
133
  return { cancelled: true };
110
- if (action === "Use default")
134
+ }
135
+ if (action === "Use default") {
111
136
  return { cancelled: false, value: schema.default };
112
- if (action === "Omit")
137
+ }
138
+ if (action === "Omit") {
113
139
  return { cancelled: false, value: undefined };
140
+ }
114
141
  return { cancelled: false, value: action === "Yes" };
115
142
  }
116
143
  if (schema.type === "array") {
117
144
  const actions = ["Choose values"];
118
- if (schema.default !== undefined)
145
+ if (schema.default !== undefined) {
119
146
  actions.push("Use default");
120
- if (!required)
147
+ }
148
+ if (!required) {
121
149
  actions.push("Omit");
150
+ }
122
151
  const action = await ui.select(title, actions);
123
- if (action === undefined)
152
+ if (action === undefined) {
124
153
  return { cancelled: true };
125
- if (action === "Use default")
154
+ }
155
+ if (action === "Use default") {
126
156
  return { cancelled: false, value: schema.default };
127
- if (action === "Omit")
157
+ }
158
+ if (action === "Omit") {
128
159
  return { cancelled: false, value: undefined };
160
+ }
129
161
  const choices = extractMultiSelectOptions(schema);
130
162
  const selected = new Set(Array.isArray(current) ? current : []);
131
163
  while (true) {
132
- const displays = uniqueLabels(choices.map(choice => selected.has(choice.value) ? `✓ ${choice.display}` : choice.display));
164
+ const displays = uniqueLabels(choices.map((choice) => selected.has(choice.value) ? `✓ ${choice.display}` : choice.display));
133
165
  const done = uniqueAction("Done", displays);
134
166
  const picked = await ui.select(title, [...displays, done]);
135
- if (picked === undefined)
167
+ if (picked === undefined) {
136
168
  return { cancelled: true };
137
- if (picked === done)
169
+ }
170
+ if (picked === done) {
138
171
  return { cancelled: false, value: [...selected] };
172
+ }
139
173
  const choice = choices[displays.indexOf(picked)];
140
- if (!choice)
174
+ if (!choice) {
141
175
  continue;
142
- if (selected.has(choice.value))
176
+ }
177
+ if (selected.has(choice.value)) {
143
178
  selected.delete(choice.value);
144
- else
179
+ }
180
+ else {
145
181
  selected.add(choice.value);
182
+ }
146
183
  }
147
184
  }
148
185
  const actions = ["Enter value"];
149
- if (schema.default !== undefined)
186
+ if (schema.default !== undefined) {
150
187
  actions.push("Use default");
151
- if (!required)
188
+ }
189
+ if (!required) {
152
190
  actions.push("Omit");
191
+ }
153
192
  const action = await ui.select(title, actions);
154
- if (action === undefined)
193
+ if (action === undefined) {
155
194
  return { cancelled: true };
156
- if (action === "Use default")
195
+ }
196
+ if (action === "Use default") {
157
197
  return { cancelled: false, value: schema.default };
158
- if (action === "Omit")
198
+ }
199
+ if (action === "Omit") {
159
200
  return { cancelled: false, value: undefined };
201
+ }
160
202
  const entered = await ui.input(title, current === undefined ? undefined : String(current));
161
203
  return entered === undefined ? { cancelled: true } : { cancelled: false, value: entered };
162
204
  }
@@ -166,8 +208,9 @@ export function coerceAndValidateFormValues(params, values) {
166
208
  for (const [name, schema] of Object.entries(params.requestedSchema.properties)) {
167
209
  const value = values[name];
168
210
  if (value === undefined) {
169
- if (required.has(name))
211
+ if (required.has(name)) {
170
212
  throw new Error(`Missing required elicitation field: ${name}`);
213
+ }
171
214
  continue;
172
215
  }
173
216
  if (schema.type === "string") {
@@ -182,7 +225,7 @@ export function coerceAndValidateFormValues(params, values) {
182
225
  if ("enum" in schema && !schema.enum.includes(stringValue)) {
183
226
  throw new Error(`Elicitation field ${name} is not an allowed value`);
184
227
  }
185
- if ("oneOf" in schema && !schema.oneOf.some(option => option.const === stringValue)) {
228
+ if ("oneOf" in schema && !schema.oneOf.some((option) => option.const === stringValue)) {
186
229
  throw new Error(`Elicitation field ${name} is not an allowed value`);
187
230
  }
188
231
  output[name] = stringValue;
@@ -193,8 +236,9 @@ export function coerceAndValidateFormValues(params, values) {
193
236
  throw new Error(`Elicitation field ${name} must be a number`);
194
237
  }
195
238
  const numberValue = typeof value === "number" ? value : Number(value);
196
- if (!Number.isFinite(numberValue))
239
+ if (!Number.isFinite(numberValue)) {
197
240
  throw new Error(`Elicitation field ${name} must be a number`);
241
+ }
198
242
  if (schema.type === "integer" && !Number.isInteger(numberValue)) {
199
243
  throw new Error(`Elicitation field ${name} must be an integer`);
200
244
  }
@@ -212,9 +256,10 @@ export function coerceAndValidateFormValues(params, values) {
212
256
  continue;
213
257
  }
214
258
  if (schema.type === "array") {
215
- if (!Array.isArray(value))
259
+ if (!Array.isArray(value)) {
216
260
  throw new Error(`Elicitation field ${name} must be a list`);
217
- const allowed = new Set(extractMultiSelectOptions(schema).map(option => option.value));
261
+ }
262
+ const allowed = new Set(extractMultiSelectOptions(schema).map((option) => option.value));
218
263
  const arrayValue = value.map(String);
219
264
  if (schema.minItems !== undefined && arrayValue.length < schema.minItems) {
220
265
  throw new Error(`Elicitation field ${name} has fewer than ${schema.minItems} selections`);
@@ -222,14 +267,13 @@ export function coerceAndValidateFormValues(params, values) {
222
267
  if (schema.maxItems !== undefined && arrayValue.length > schema.maxItems) {
223
268
  throw new Error(`Elicitation field ${name} has more than ${schema.maxItems} selections`);
224
269
  }
225
- if (arrayValue.some(item => !allowed.has(item))) {
270
+ if (arrayValue.some((item) => !allowed.has(item))) {
226
271
  throw new Error(`Elicitation field ${name} contains an invalid selection`);
227
272
  }
228
273
  output[name] = arrayValue;
229
274
  }
230
275
  }
231
- const validation = new AjvJsonSchemaValidator()
232
- .getValidator(params.requestedSchema)(output);
276
+ const validation = new AjvJsonSchemaValidator().getValidator(params.requestedSchema)(output);
233
277
  if (!validation.valid) {
234
278
  throw new Error(`Invalid elicitation response: ${validation.errorMessage}`);
235
279
  }
@@ -240,33 +284,39 @@ function formatChoice(value, title) {
240
284
  }
241
285
  function uniqueLabels(labels) {
242
286
  const used = new Set();
243
- return labels.map(label => {
287
+ return labels.map((label) => {
244
288
  let unique = label;
245
- while (used.has(unique))
289
+ while (used.has(unique)) {
246
290
  unique += "…";
291
+ }
247
292
  used.add(unique);
248
293
  return unique;
249
294
  });
250
295
  }
251
296
  function uniqueAction(label, choices) {
252
297
  let unique = label;
253
- while (choices.includes(unique))
298
+ while (choices.includes(unique)) {
254
299
  unique += "…";
300
+ }
255
301
  return unique;
256
302
  }
257
303
  function extractMultiSelectOptions(schema) {
258
304
  const items = schema.items;
259
305
  return items.anyOf
260
- ? items.anyOf.map(option => ({ value: option.const, display: formatChoice(option.const, option.title) }))
261
- : (items.enum ?? []).map(value => ({ value, display: value }));
306
+ ? items.anyOf.map((option) => ({
307
+ value: option.const,
308
+ display: formatChoice(option.const, option.title),
309
+ }))
310
+ : (items.enum ?? []).map((value) => ({ value, display: value }));
262
311
  }
263
312
  function formatReview(serverName, properties, content) {
264
313
  const rows = properties.map(([name, schema]) => `${schema.title ?? humanizeName(name)}: ${content[name] === undefined ? "(omitted)" : String(content[name])}`);
265
314
  return [`Review input for ${serverName}`, "", ...rows].join("\n");
266
315
  }
267
316
  export async function handleUrlElicitation(options, params) {
268
- if (!options.allowUrl)
317
+ if (!options.allowUrl) {
269
318
  throw new ProtocolError(ProtocolErrorCode.InvalidParams, "URL elicitation is not supported");
319
+ }
270
320
  let parsed;
271
321
  try {
272
322
  parsed = new URL(params.url);
@@ -288,10 +338,12 @@ export async function handleUrlElicitation(options, params) {
288
338
  "",
289
339
  "Open this URL in your browser?",
290
340
  ].join("\n"), ["Open", "Decline"]);
291
- if (decision === undefined)
341
+ if (decision === undefined) {
292
342
  return { action: "cancel" };
293
- if (decision === "Decline")
343
+ }
344
+ if (decision === "Decline") {
294
345
  return { action: "decline" };
346
+ }
295
347
  try {
296
348
  await open(params.url);
297
349
  }
@@ -304,5 +356,8 @@ export async function handleUrlElicitation(options, params) {
304
356
  return { action: "accept" };
305
357
  }
306
358
  function humanizeName(name) {
307
- return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, char => char.toUpperCase());
359
+ return name
360
+ .replace(/[_-]+/g, " ")
361
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
362
+ .replace(/^./, (char) => char.toUpperCase());
308
363
  }
@@ -1,7 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  declare const actions: readonly ["status", "search", "list", "describe", "call", "connect", "instructions", "resources", "read-resource", "read-result", "auth-start", "auth-complete", "ui-messages"];
3
3
  export type GatewayArguments = {
4
- action: typeof actions[number];
4
+ action: (typeof actions)[number];
5
5
  server?: string;
6
6
  tool?: string;
7
7
  args?: Record<string, unknown>;