@jini-ai/mcp 0.3.4 → 0.4.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/README.md +97 -22
  3. package/dist/agent-install/install.d.ts +20 -4
  4. package/dist/agent-install/install.js +11 -11
  5. package/dist/bin/serve.d.ts +25 -13
  6. package/dist/bin/serve.js +42 -21
  7. package/dist/client/client.d.ts +28 -8
  8. package/dist/client/client.js +25 -21
  9. package/dist/core/config.d.ts +57 -18
  10. package/dist/core/config.js +76 -48
  11. package/dist/core/index.d.ts +1 -2
  12. package/dist/core/index.js +1 -2
  13. package/dist/core/install-info.d.ts +4 -1
  14. package/dist/core/install-info.js +5 -5
  15. package/dist/core/tokens.d.ts +39 -13
  16. package/dist/core/tokens.js +54 -24
  17. package/dist/federation/adapter.http.d.ts +67 -0
  18. package/dist/federation/adapter.http.js +414 -0
  19. package/dist/federation/approvals/call-confirmation.d.ts +23 -0
  20. package/dist/federation/approvals/call-confirmation.js +124 -0
  21. package/dist/federation/approvals/index.d.ts +4 -0
  22. package/dist/federation/approvals/index.js +4 -0
  23. package/dist/federation/approvals/ports.d.ts +107 -0
  24. package/dist/federation/approvals/ports.js +2 -0
  25. package/dist/federation/approvals/revocation.d.ts +61 -0
  26. package/dist/federation/approvals/revocation.js +52 -0
  27. package/dist/federation/bootstrap.d.ts +100 -0
  28. package/dist/federation/bootstrap.js +182 -0
  29. package/dist/federation/config.d.ts +68 -0
  30. package/dist/federation/config.js +40 -0
  31. package/dist/federation/index.d.ts +14 -0
  32. package/dist/federation/index.js +14 -0
  33. package/dist/federation/mcp-protocol.d.ts +115 -0
  34. package/dist/federation/mcp-protocol.js +151 -0
  35. package/dist/federation/messages.d.ts +54 -0
  36. package/dist/federation/messages.js +35 -0
  37. package/dist/federation/ports.d.ts +287 -0
  38. package/dist/federation/ports.js +5 -0
  39. package/dist/federation/presets.d.ts +66 -0
  40. package/dist/federation/presets.js +30 -0
  41. package/dist/federation/refusal-notice.d.ts +198 -0
  42. package/dist/federation/refusal-notice.js +193 -0
  43. package/dist/federation/registrations.d.ts +171 -0
  44. package/dist/federation/registrations.js +181 -0
  45. package/dist/federation/reload.d.ts +112 -0
  46. package/dist/federation/reload.js +110 -0
  47. package/dist/federation/stdio/adapter.stdio.d.ts +157 -0
  48. package/dist/federation/stdio/adapter.stdio.js +526 -0
  49. package/dist/federation/stdio/default-connect.d.ts +29 -0
  50. package/dist/federation/stdio/default-connect.js +92 -0
  51. package/dist/federation/stdio/index.d.ts +4 -0
  52. package/dist/federation/stdio/index.js +4 -0
  53. package/dist/federation/stdio/stdio-launch-resolver.d.ts +139 -0
  54. package/dist/federation/stdio/stdio-launch-resolver.js +227 -0
  55. package/dist/federation/testing/adapter.memory.d.ts +177 -0
  56. package/dist/federation/testing/adapter.memory.js +189 -0
  57. package/dist/federation/testing/index.d.ts +3 -0
  58. package/dist/federation/testing/index.js +3 -0
  59. package/dist/federation/testing/tool-approvals.memory.d.ts +27 -0
  60. package/dist/federation/testing/tool-approvals.memory.js +38 -0
  61. package/dist/federation/tool-approvals.d.ts +97 -0
  62. package/dist/federation/tool-approvals.js +42 -0
  63. package/dist/federation/trust.d.ts +471 -0
  64. package/dist/federation/trust.js +707 -0
  65. package/dist/index.d.ts +175 -2
  66. package/dist/index.js +172 -2
  67. package/dist/server/daemon-client.d.ts +16 -4
  68. package/dist/server/daemon-client.js +41 -19
  69. package/dist/server/ports.d.ts +9 -0
  70. package/dist/server/ports.js +2 -0
  71. package/dist/server/resource-protocol.d.ts +14 -4
  72. package/dist/server/resource-protocol.js +6 -6
  73. package/dist/server/resources/active-resource.js +3 -3
  74. package/dist/server/sdk-transport.d.ts +11 -0
  75. package/dist/server/sdk-transport.js +31 -0
  76. package/dist/server/tool-protocol.d.ts +31 -9
  77. package/dist/server/tool-protocol.js +20 -18
  78. package/dist/server/tool-server.d.ts +60 -43
  79. package/dist/server/tool-server.js +51 -27
  80. package/dist/server/tools/component-catalog-tools.js +7 -7
  81. package/dist/server/tools/delegated-tool.d.ts +7 -4
  82. package/dist/server/tools/delegated-tool.js +14 -15
  83. package/dist/server/tools/run-tools.js +15 -15
  84. package/dist/server/tools/tool-catalog-tools.js +7 -7
  85. package/dist/tools/ask-choice/index.d.ts +123 -0
  86. package/dist/tools/ask-choice/index.js +98 -0
  87. package/dist/tools/ask-choice/pending.d.ts +11 -0
  88. package/dist/tools/ask-choice/pending.js +34 -0
  89. package/dist/tools/ask-choice/question.d.ts +16 -0
  90. package/dist/tools/ask-choice/question.js +60 -0
  91. package/dist/tools/ask-choice/schema.d.ts +91 -0
  92. package/dist/tools/ask-choice/schema.js +27 -0
  93. package/dist/tools/ask-choice/types.d.ts +106 -0
  94. package/dist/tools/ask-choice/types.js +2 -0
  95. package/package.json +43 -5
@@ -9,9 +9,9 @@
9
9
  * `ReadResourceRequestSchema` handlers, exactly as `tool-protocol.js` is
10
10
  * wired to `ListToolsRequestSchema`/`CallToolRequestSchema`.
11
11
  */
12
- import { sanitizeUntrustedText } from '@jini-ai/cli';
12
+ import { sanitizeUntrustedText } from '@jini-ai/core/text';
13
13
  /** Projects a resource list into the `Resource[]` shape `resources/list` returns. */
14
- export function resourcesToList(resources) {
14
+ export function resourcesToList({ resources }) {
15
15
  return resources.map((resource) => ({
16
16
  uri: resource.uri,
17
17
  name: resource.name,
@@ -20,7 +20,7 @@ export function resourcesToList(resources) {
20
20
  }));
21
21
  }
22
22
  /** Builds a uri -> def lookup, throwing if two resources in `resources` share a uri (a caller-configuration bug, surfaced eagerly at server-construction time rather than letting the second registration shadow the first). */
23
- export function buildResourceIndex(resources) {
23
+ export function buildResourceIndex({ resources }) {
24
24
  const index = new Map();
25
25
  for (const resource of resources) {
26
26
  if (index.has(resource.uri)) {
@@ -41,18 +41,18 @@ export function buildResourceIndex(resources) {
41
41
  * it leaves this function, the same conservative posture `handleToolCall` uses — a `read`
42
42
  * implementation may re-throw text that traces back to an untrusted daemon response.
43
43
  */
44
- export async function handleResourceRead(uri, resources, ctx) {
44
+ export async function handleResourceRead({ uri, resources, ctx }) {
45
45
  const resource = resources.get(uri);
46
46
  if (resource === undefined) {
47
47
  throw new Error(`unsupported resource URI: ${uri}`);
48
48
  }
49
49
  let result;
50
50
  try {
51
- result = await resource.read(ctx);
51
+ result = await resource.read({ ctx: ctx });
52
52
  }
53
53
  catch (err) {
54
54
  const message = err instanceof Error ? err.message : String(err);
55
- throw new Error(sanitizeUntrustedText(message));
55
+ throw new Error(sanitizeUntrustedText({ text: message }));
56
56
  }
57
57
  const mimeType = result.mimeType ?? resource.mimeType;
58
58
  return {
@@ -13,7 +13,7 @@
13
13
  * tool is invoked by the model mid-conversation, while a resource can be
14
14
  * listed and attached to context by the user/client without any tool call.
15
15
  * This module is that second affordance for the same primitive. See
16
- * `source-map.md`'s 2026-07-21 addition for why the rest of OD's resource
16
+ * `archived provenance ledger`'s 2026-07-21 addition for why the rest of OD's resource
17
17
  * surface (`od://skills/...`, `od://design-systems/...`) was NOT ported the
18
18
  * same way (both require a Skill/DesignSystem noun this kernel doesn't have).
19
19
  *
@@ -37,8 +37,8 @@ export const activeContextResource = {
37
37
  name: 'Active context',
38
38
  description: 'The resource (resourceRef) plus optional detail the caller last recorded as its current focus via POST /api/active — the same generic, product-neutral pointer the get_active_context tool proxies, exposed here as an attachable MCP resource instead of a tool call.',
39
39
  mimeType: 'application/json',
40
- read: async (ctx) => {
41
- const data = await getDaemonJson(ctx.baseUrl, '/api/active', daemonCallOptions(ctx));
40
+ read: async ({ ctx }) => {
41
+ const data = await getDaemonJson({ baseUrl: ctx.baseUrl, route: '/api/active' }, daemonCallOptions({ ctx }));
42
42
  return { text: JSON.stringify(data, null, 2) };
43
43
  },
44
44
  };
@@ -0,0 +1,11 @@
1
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
2
+ import type { McpTransportLike } from './ports.js';
3
+ /** Internal boundary adapter; SDK callbacks keep their native signature behind the port. */
4
+ export declare function adaptSdkTransport({ transport: sdk }: {
5
+ transport: Transport;
6
+ }): McpTransportLike;
7
+ /** Resolves adapters to the underlying SDK transport; host ports stay structurally usable. */
8
+ export declare function resolveSdkTransport({ transport }: {
9
+ transport: McpTransportLike;
10
+ }): Transport;
11
+ //# sourceMappingURL=sdk-transport.d.ts.map
@@ -0,0 +1,31 @@
1
+ const sdkTransports = new WeakMap();
2
+ /** Internal boundary adapter; SDK callbacks keep their native signature behind the port. */
3
+ export function adaptSdkTransport({ transport: sdk }) {
4
+ const transport = {
5
+ get onmessage() {
6
+ const handler = sdk.onmessage;
7
+ return handler ? ({ message }) => handler(message) : undefined;
8
+ },
9
+ set onmessage(handler) {
10
+ if (handler)
11
+ sdk.onmessage = (message) => handler({ message });
12
+ else
13
+ delete sdk.onmessage;
14
+ },
15
+ get onclose() { return sdk.onclose; },
16
+ set onclose(handler) {
17
+ if (handler)
18
+ sdk.onclose = handler;
19
+ else
20
+ delete sdk.onclose;
21
+ },
22
+ close: (_required) => sdk.close(),
23
+ };
24
+ sdkTransports.set(transport, sdk);
25
+ return transport;
26
+ }
27
+ /** Resolves adapters to the underlying SDK transport; host ports stay structurally usable. */
28
+ export function resolveSdkTransport({ transport }) {
29
+ return (sdkTransports.get(transport) ?? transport);
30
+ }
31
+ //# sourceMappingURL=sdk-transport.js.map
@@ -36,7 +36,9 @@ export interface McpToolContext {
36
36
  * @complexity O(1).
37
37
  * @overallScore 100/100
38
38
  */
39
- export declare function daemonCallOptions(ctx: McpToolContext): {
39
+ export declare function daemonCallOptions({ ctx }: {
40
+ ctx: McpToolContext;
41
+ }): {
40
42
  fetchImpl: typeof fetch;
41
43
  headers?: Record<string, string>;
42
44
  signal?: AbortSignal;
@@ -54,7 +56,10 @@ export interface McpToolDef<Args extends Record<string, unknown> = Record<string
54
56
  readonly description: string;
55
57
  readonly inputSchema: Tool['inputSchema'];
56
58
  readonly annotations?: Tool['annotations'];
57
- readonly handler: (args: Args, ctx: McpToolContext) => Promise<unknown> | unknown;
59
+ readonly handler: (requiredArgs: {
60
+ args: Args;
61
+ ctx: McpToolContext;
62
+ }) => Promise<unknown> | unknown;
58
63
  }
59
64
  /**
60
65
  * Wraps a successful tool result as an MCP `CallToolResult`.
@@ -69,15 +74,26 @@ export interface McpToolDef<Args extends Record<string, unknown> = Record<string
69
74
  * @complexity O(n) in the number of content-array entries, only when `payload` already looks like an
70
75
  * envelope; O(1) otherwise.
71
76
  */
72
- export declare function okResult(payload: unknown): CallToolResult;
77
+ export declare function okResult({ payload }: {
78
+ payload: unknown;
79
+ }): CallToolResult;
73
80
  /** Wraps a tool failure as an MCP `isError` result. */
74
- export declare function errorResult(message: string): CallToolResult;
75
- /** Throws a caller-facing validation error unless `value` is a non-empty string. Mirrors the OD origin's `requireString` — a convenience for tool authors, not part of the MCP protocol itself. */
76
- export declare function requireString(value: unknown, name: string): asserts value is string;
81
+ export declare function errorResult({ message }: {
82
+ message: string;
83
+ }): CallToolResult;
84
+ /** Returns a validated non-empty string, or throws the existing caller-facing error. */
85
+ export declare function requireString({ value, name }: {
86
+ value: unknown;
87
+ name: string;
88
+ }): string;
77
89
  /** Projects a tool list into the `Tool[]` shape `tools/list` returns. */
78
- export declare function toolsToList(tools: readonly McpToolDef[]): Tool[];
90
+ export declare function toolsToList({ tools }: {
91
+ tools: readonly McpToolDef[];
92
+ }): Tool[];
79
93
  /** Builds a name -> def lookup, throwing if two tools in `tools` share a name (a caller-configuration bug, surfaced eagerly at server-construction time rather than silently letting the second registration shadow the first). */
80
- export declare function buildToolIndex(tools: readonly McpToolDef[]): Map<string, McpToolDef>;
94
+ export declare function buildToolIndex({ tools }: {
95
+ tools: readonly McpToolDef[];
96
+ }): Map<string, McpToolDef>;
81
97
  /**
82
98
  * `tools/call` dispatch: looks up `name` in `tools`, validates `rawArgs` against the tool's
83
99
  * declared `inputSchema`, runs its handler, and converts the outcome to a `CallToolResult` — an
@@ -90,5 +106,11 @@ export declare function buildToolIndex(tools: readonly McpToolDef[]): Map<string
90
106
  * result: either can end up echoing caller- or daemon-supplied text, and it is cheaper to sanitize
91
107
  * unconditionally than to prove which call site needs it.
92
108
  */
93
- export declare function handleToolCall(name: string, rawArgs: Record<string, unknown> | undefined, tools: ReadonlyMap<string, McpToolDef>, ctx: McpToolContext): Promise<CallToolResult>;
109
+ export declare function handleToolCall({ name, tools, ctx }: {
110
+ name: string;
111
+ tools: ReadonlyMap<string, McpToolDef>;
112
+ ctx: McpToolContext;
113
+ }, { rawArgs }?: {
114
+ rawArgs?: Record<string, unknown> | undefined;
115
+ }): Promise<CallToolResult>;
94
116
  //# sourceMappingURL=tool-protocol.d.ts.map
@@ -6,7 +6,7 @@
6
6
  * projection, and `tools/call` dispatch (look up a tool by name, run its
7
7
  * handler, wrap the result). None of this touches `@modelcontextprotocol/sdk`'s
8
8
  * `Server`/transport classes or the network — a tool `handler` is just
9
- * `(args, ctx) => value | Promise<value>` that either returns a
9
+ * `({ args, ctx }) => value | Promise<value>` that either returns a
10
10
  * JSON-serializable payload (wrapped as a successful MCP result) or throws
11
11
  * (wrapped as an `{isError:true}` MCP result). `./tool-server.js` is the thin
12
12
  * layer that wires this to a real `Server` + `StdioServerTransport`.
@@ -22,7 +22,7 @@
22
22
  * `delegated-tool-bridge.ts` already keeps an image block in its returned `ToolExecutionResult.output`
23
23
  * (it is model-safe per that package's `tool-result-surfaces.ts`), but this module still turned it
24
24
  * back into text on the way out. It is also how a **human-confirmation token reached model-visible
25
- * text** in a separate, traced incident (`ADR-053` Decision 5): a `resource` block carrying the token
25
+ * text** in a separate, traced incident ( Decision 5): a `resource` block carrying the token
26
26
  * fell to the same stringify path because an earlier version of this fix only recognized `text` and
27
27
  * `image`, and `resource` was not yet in that hand-rolled allowlist.
28
28
  *
@@ -41,9 +41,10 @@
41
41
  * allowlist" into "remember to bump the SDK", not into an unconditional guarantee. It also does not
42
42
  * (and cannot) judge intent: a handler that puts a secret inside an otherwise well-formed `text` block
43
43
  * will still have it pass through as designed — withholding a value from the model is the handler's
44
- * responsibility (`ADR-053` Decision 4), not this generic wrapper's.
44
+ * responsibility ( Decision 4), not this generic wrapper's.
45
+ * See docs/decisions/DR-002-protocol-resource-boundary.md.
45
46
  */
46
- import { sanitizeUntrustedText } from '@jini-ai/cli';
47
+ import { sanitizeUntrustedText } from '@jini-ai/core/text';
47
48
  import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/cfworker';
48
49
  import { ContentBlockSchema } from '@modelcontextprotocol/sdk/types.js';
49
50
  /**
@@ -60,7 +61,7 @@ import { ContentBlockSchema } from '@modelcontextprotocol/sdk/types.js';
60
61
  * @complexity O(1).
61
62
  * @overallScore 100/100
62
63
  */
63
- export function daemonCallOptions(ctx) {
64
+ export function daemonCallOptions({ ctx }) {
64
65
  return {
65
66
  fetchImpl: ctx.fetchImpl,
66
67
  ...(ctx.authHeaders !== undefined ? { headers: { ...ctx.authHeaders } } : {}),
@@ -112,7 +113,7 @@ function isMcpContentEnvelope(payload) {
112
113
  * @complexity O(n) in the number of content-array entries, only when `payload` already looks like an
113
114
  * envelope; O(1) otherwise.
114
115
  */
115
- export function okResult(payload) {
116
+ export function okResult({ payload }) {
116
117
  if (isMcpContentEnvelope(payload)) {
117
118
  return { content: payload.content };
118
119
  }
@@ -123,17 +124,18 @@ export function okResult(payload) {
123
124
  return { content: [{ type: 'text', text }] };
124
125
  }
125
126
  /** Wraps a tool failure as an MCP `isError` result. */
126
- export function errorResult(message) {
127
+ export function errorResult({ message }) {
127
128
  return { isError: true, content: [{ type: 'text', text: message }] };
128
129
  }
129
- /** Throws a caller-facing validation error unless `value` is a non-empty string. Mirrors the OD origin's `requireString` — a convenience for tool authors, not part of the MCP protocol itself. */
130
- export function requireString(value, name) {
130
+ /** Returns a validated non-empty string, or throws the existing caller-facing error. */
131
+ export function requireString({ value, name }) {
131
132
  if (typeof value !== 'string' || value.length === 0) {
132
133
  throw new Error(`${name} is required (string).`);
133
134
  }
135
+ return value;
134
136
  }
135
137
  /** Projects a tool list into the `Tool[]` shape `tools/list` returns. */
136
- export function toolsToList(tools) {
138
+ export function toolsToList({ tools }) {
137
139
  return tools.map((tool) => ({
138
140
  name: tool.name,
139
141
  description: tool.description,
@@ -142,7 +144,7 @@ export function toolsToList(tools) {
142
144
  }));
143
145
  }
144
146
  /** Builds a name -> def lookup, throwing if two tools in `tools` share a name (a caller-configuration bug, surfaced eagerly at server-construction time rather than silently letting the second registration shadow the first). */
145
- export function buildToolIndex(tools) {
147
+ export function buildToolIndex({ tools }) {
146
148
  const index = new Map();
147
149
  for (const tool of tools) {
148
150
  if (index.has(tool.name)) {
@@ -187,10 +189,10 @@ function validatorForTool(tool) {
187
189
  * result: either can end up echoing caller- or daemon-supplied text, and it is cheaper to sanitize
188
190
  * unconditionally than to prove which call site needs it.
189
191
  */
190
- export async function handleToolCall(name, rawArgs, tools, ctx) {
192
+ export async function handleToolCall({ name, tools, ctx }, { rawArgs } = {}) {
191
193
  const tool = tools.get(name);
192
194
  if (tool === undefined) {
193
- return errorResult(`unknown tool: ${name}`);
195
+ return errorResult({ message: `unknown tool: ${name}` });
194
196
  }
195
197
  const args = rawArgs ?? {};
196
198
  // Validation gets its own error boundary, separate from the handler's below. The
@@ -205,21 +207,21 @@ export async function handleToolCall(name, rawArgs, tools, ctx) {
205
207
  try {
206
208
  const validation = validatorForTool(tool)(args);
207
209
  if (!validation.valid) {
208
- return errorResult(sanitizeUntrustedText(`invalid arguments for ${name}: ${validation.errorMessage}`));
210
+ return errorResult({ message: sanitizeUntrustedText({ text: `invalid arguments for ${name}: ${validation.errorMessage}` }) });
209
211
  }
210
212
  validatedArgs = validation.data;
211
213
  }
212
214
  catch (err) {
213
215
  const message = err instanceof Error ? err.message : String(err);
214
- return errorResult(sanitizeUntrustedText(`invalid arguments for ${name}: ${message}`));
216
+ return errorResult({ message: sanitizeUntrustedText({ text: `invalid arguments for ${name}: ${message}` }) });
215
217
  }
216
218
  try {
217
- const result = await tool.handler(validatedArgs, ctx);
218
- return okResult(result);
219
+ const result = await tool.handler({ args: validatedArgs, ctx: ctx });
220
+ return okResult({ payload: result });
219
221
  }
220
222
  catch (err) {
221
223
  const message = err instanceof Error ? err.message : String(err);
222
- return errorResult(sanitizeUntrustedText(message));
224
+ return errorResult({ message: sanitizeUntrustedText({ text: message }) });
223
225
  }
224
226
  }
225
227
  //# sourceMappingURL=tool-protocol.js.map
@@ -9,7 +9,7 @@
9
9
  * project/skill/plugin `instructions` prose — replaced by a caller-supplied,
10
10
  * bounded, explicit `tools: readonly McpToolDef[]` list. A caller is the
11
11
  * *first* user of this mechanism, not the only one it will ever support (see
12
- * `source-map.md`'s 2026-07-21 addition for the full design-decision note).
12
+ * `archived provenance ledger`'s 2026-07-21 addition for the full design-decision note).
13
13
  *
14
14
  * `tools` capability is always advertised (`capabilities: {tools: {}}`).
15
15
  * `resources` capability (`ListResourcesRequestSchema`/
@@ -18,61 +18,72 @@
18
18
  * optional `resources` option — a 2026-07-21 addition once this package
19
19
  * shipped its first genuinely portable resource (`../resources/
20
20
  * active-resource.js`'s `jini://active`, see that file's module doc and
21
- * `source-map.md`'s 2026-07-21 addition for why the *rest* of OD's resource
21
+ * `archived provenance ledger`'s 2026-07-21 addition for why the *rest* of OD's resource
22
22
  * surface — `od://skills/...`, `od://design-systems/...` — still has no
23
23
  * kernel equivalent and stays unported). A caller passing no `resources`
24
24
  * gets the exact same `capabilities: {tools: {}}`-only server this module
25
25
  * always produced.
26
26
  */
27
27
  import type { Readable, Writable } from 'node:stream';
28
+ import type { McpTransportLike } from './ports.js';
29
+ export type { McpTransportLike } from './ports.js';
28
30
  import type { ServerOptions } from '@modelcontextprotocol/sdk/server/index.js';
29
31
  import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
30
32
  import type { CallToolResult, Implementation, ListResourcesResult, ListToolsResult, ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';
31
33
  import { type McpResourceDef } from './resource-protocol.js';
32
34
  import { type McpToolDef } from './tool-protocol.js';
33
- /**
34
- * The minimal surface this module needs from an MCP `Server` instance. A real
35
- * `@modelcontextprotocol/sdk` `Server` satisfies this structurally (its own `setRequestHandler`
36
- * is more general — one generic method covering every request schema — and TS accepts the
37
- * narrowing); a test can substitute a lightweight fake without spinning up a real transport.
38
- * Method-shorthand syntax (not arrow-typed properties) is deliberate: it keeps parameter checking
39
- * bivariant, so a handler typed against a *specific* request shape (as the two internal handlers
40
- * below are) is still assignable here even under `strict`.
41
- */
35
+ /** Object-shaped server port. The default adapter translates it to the SDK interface. */
42
36
  export interface McpServerLike {
43
- setRequestHandler(schema: typeof ListToolsRequestSchema, handler: () => Promise<ListToolsResult>): void;
44
- setRequestHandler(schema: typeof CallToolRequestSchema, handler: (request: {
45
- params: {
46
- name: string;
47
- arguments?: Record<string, unknown>;
48
- };
49
- }, extra?: {
50
- signal?: AbortSignal;
51
- }) => Promise<CallToolResult>): void;
52
- setRequestHandler(schema: typeof ListResourcesRequestSchema, handler: () => Promise<ListResourcesResult>): void;
53
- setRequestHandler(schema: typeof ReadResourceRequestSchema, handler: (request: {
54
- params: {
55
- uri: string;
56
- };
57
- }) => Promise<ReadResourceResult>): void;
58
- connect(transport: McpTransportLike): Promise<void>;
37
+ setRequestHandler(args: {
38
+ schema: typeof ListToolsRequestSchema;
39
+ handler: () => Promise<ListToolsResult>;
40
+ }): void;
41
+ setRequestHandler(args: {
42
+ schema: typeof CallToolRequestSchema;
43
+ handler: (requiredArgs: {
44
+ request: {
45
+ params: {
46
+ name: string;
47
+ arguments?: Record<string, unknown>;
48
+ };
49
+ };
50
+ }, optionalArgs?: {
51
+ extra?: {
52
+ signal?: AbortSignal;
53
+ };
54
+ }) => Promise<CallToolResult>;
55
+ }): void;
56
+ setRequestHandler(args: {
57
+ schema: typeof ListResourcesRequestSchema;
58
+ handler: () => Promise<ListResourcesResult>;
59
+ }): void;
60
+ setRequestHandler(args: {
61
+ schema: typeof ReadResourceRequestSchema;
62
+ handler: (requiredArgs: {
63
+ request: {
64
+ params: {
65
+ uri: string;
66
+ };
67
+ };
68
+ }) => Promise<ReadResourceResult>;
69
+ }): void;
70
+ connect(args: {
71
+ transport: McpTransportLike;
72
+ }): Promise<void>;
59
73
  }
60
- /** The minimal surface this module needs from an MCP transport. A real `StdioServerTransport` satisfies this structurally. */
61
- export interface McpTransportLike {
62
- onmessage?: ((message: unknown) => void) | undefined;
63
- onclose?: (() => void) | undefined;
64
- close(): Promise<void>;
65
- }
66
- export interface McpToolServerOptions {
67
- /** Advertised to the MCP client during initialization. */
74
+ /** Required identity, tool surface, and daemon URL resolver. */
75
+ export interface McpToolServerRequiredArgs {
76
+ /** Identity advertised to the MCP client during initialization. */
68
77
  readonly name: string;
69
78
  readonly version: string;
70
- /** The bounded, explicit tool list this server hosts. Duplicate names throw at construction time. */
79
+ /** Duplicate tool names throw at construction. */
71
80
  readonly tools: readonly McpToolDef[];
81
+ /** Resolves once when the returned handle runs. */
82
+ readonly resolveBaseUrl: () => Promise<string> | string;
83
+ }
84
+ export interface McpToolServerOptions {
72
85
  /** The bounded, explicit read-only resource list this server hosts. Duplicate uris throw at construction time. Omit (or pass an empty array) for a tools-only server — `capabilities.resources` is only advertised when this is non-empty. */
73
86
  readonly resources?: readonly McpResourceDef[];
74
- /** Resolves the daemon HTTP base URL once, at the start of {@link McpToolServerHandle.run}. May be sync or async (e.g. wraps `@jini-ai/cli`'s `resolveDaemonUrl`). */
75
- readonly resolveBaseUrl: () => Promise<string> | string;
76
87
  /** Free-text guidance surfaced to the MCP client alongside the tool list. Optional — omit for a caller with nothing to add beyond each tool's own `description`. */
77
88
  readonly instructions?: string;
78
89
  /** Idle-exit window in ms. Defaults to {@link DEFAULT_IDLE_MS}. */
@@ -90,9 +101,15 @@ export interface McpToolServerOptions {
90
101
  /** Defaults to `process.stdout`; inject for tests. */
91
102
  readonly stdout?: Writable;
92
103
  /** Test/embedding seam: builds the underlying `Server`. Defaults to the real `@modelcontextprotocol/sdk` `Server`. */
93
- readonly createServer?: (info: Implementation, options: ServerOptions) => McpServerLike;
104
+ readonly createServer?: (args: {
105
+ info: Implementation;
106
+ options: ServerOptions;
107
+ }) => McpServerLike;
94
108
  /** Test/embedding seam: builds the underlying transport. Defaults to the real `StdioServerTransport`. */
95
- readonly createTransport?: (stdin?: Readable, stdout?: Writable) => McpTransportLike;
109
+ readonly createTransport?: (requiredArgs: Record<string, never>, optionalArgs?: {
110
+ stdin?: Readable;
111
+ stdout?: Writable;
112
+ }) => McpTransportLike;
96
113
  }
97
114
  export interface McpToolServerHandle {
98
115
  /**
@@ -100,14 +117,14 @@ export interface McpToolServerHandle {
100
117
  * open until the client disconnects (stdin EOF) or the idle-exit window elapses. Resolves once
101
118
  * the transport has closed.
102
119
  */
103
- run(): Promise<void>;
120
+ run(required: Record<string, never>): Promise<void>;
104
121
  }
105
122
  /**
106
- * Builds a stdio MCP server hosting `options.tools` (and, optionally, `options.resources`).
123
+ * Builds a stdio MCP server hosting `requiredArgs.tools` (and, optionally, `options.resources`).
107
124
  * Construction is synchronous and cheap (validates tool-name uniqueness via
108
125
  * {@link buildToolIndex} and resource-uri uniqueness via {@link buildResourceIndex}); all I/O —
109
126
  * resolving the daemon URL, connecting the transport, serving requests — happens in the returned
110
127
  * handle's `run()`.
111
128
  */
112
- export declare function createMcpToolServer(options: McpToolServerOptions): McpToolServerHandle;
129
+ export declare function createMcpToolServer(requiredArgs: McpToolServerRequiredArgs, options?: McpToolServerOptions): McpToolServerHandle;
113
130
  //# sourceMappingURL=tool-server.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { adaptSdkTransport, resolveSdkTransport } from './sdk-transport.js';
1
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
4
  import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
@@ -6,28 +7,33 @@ import { buildResourceIndex, handleResourceRead, resourcesToList } from './resou
6
7
  import { buildToolIndex, handleToolCall, toolsToList } from './tool-protocol.js';
7
8
  /** Auto-exit an idle server after this long with no tool activity — same ceiling the OD origin used for its own stdio MCP server. */
8
9
  const DEFAULT_IDLE_MS = 30 * 60 * 1000;
9
- function defaultCreateServer(info, options) {
10
- return new Server(info, options);
10
+ // SDK objects remain behind these adapters; hosts implement the object-shaped ports above.
11
+ function defaultCreateServer({ info, options }) {
12
+ const server = new Server(info, options);
13
+ return {
14
+ setRequestHandler: (({ schema, handler }) => server.setRequestHandler(schema, (request, extra) => handler({ request }, { extra }))),
15
+ connect: ({ transport }) => server.connect(resolveSdkTransport({ transport })),
16
+ };
11
17
  }
12
- function defaultCreateTransport(stdin, stdout) {
13
- return new StdioServerTransport(stdin, stdout);
18
+ function defaultCreateTransport(_requiredArgs, { stdin, stdout } = {}) {
19
+ return adaptSdkTransport({ transport: new StdioServerTransport(stdin, stdout) });
14
20
  }
15
21
  /**
16
- * Builds a stdio MCP server hosting `options.tools` (and, optionally, `options.resources`).
22
+ * Builds a stdio MCP server hosting `requiredArgs.tools` (and, optionally, `options.resources`).
17
23
  * Construction is synchronous and cheap (validates tool-name uniqueness via
18
24
  * {@link buildToolIndex} and resource-uri uniqueness via {@link buildResourceIndex}); all I/O —
19
25
  * resolving the daemon URL, connecting the transport, serving requests — happens in the returned
20
26
  * handle's `run()`.
21
27
  */
22
- export function createMcpToolServer(options) {
23
- const toolIndex = buildToolIndex(options.tools);
28
+ export function createMcpToolServer(requiredArgs, options = {}) {
29
+ const toolIndex = buildToolIndex({ tools: requiredArgs.tools });
24
30
  const resources = options.resources ?? [];
25
- const resourceIndex = buildResourceIndex(resources);
31
+ const resourceIndex = buildResourceIndex({ resources });
26
32
  const createServer = options.createServer ?? defaultCreateServer;
27
33
  const createTransport = options.createTransport ?? defaultCreateTransport;
28
34
  return {
29
- async run() {
30
- const resolvedBaseUrl = await options.resolveBaseUrl();
35
+ async run(_required) {
36
+ const resolvedBaseUrl = await requiredArgs.resolveBaseUrl();
31
37
  const ctx = {
32
38
  baseUrl: String(resolvedBaseUrl).replace(/\/$/, ''),
33
39
  fetchImpl: options.fetchImpl ?? fetch,
@@ -38,30 +44,48 @@ export function createMcpToolServer(options) {
38
44
  idleMs: options.idleMs ?? DEFAULT_IDLE_MS,
39
45
  onIdle: () => closeTransportForIdle?.(),
40
46
  });
41
- const withActivity = (handler) => (...args) => idleExit.trackRequest(() => handler(...args));
42
- const server = createServer({ name: options.name, version: options.version }, {
43
- capabilities: { tools: {}, ...(resources.length > 0 ? { resources: {} } : {}) },
44
- ...(options.instructions !== undefined ? { instructions: options.instructions } : {}),
47
+ const withActivity = (handler) => (...args) => idleExit.trackRequest({ fn: () => handler(...args) });
48
+ const server = createServer({
49
+ info: { name: requiredArgs.name, version: requiredArgs.version },
50
+ options: {
51
+ capabilities: { tools: {}, ...(resources.length > 0 ? { resources: {} } : {}) },
52
+ ...(options.instructions !== undefined ? { instructions: options.instructions } : {}),
53
+ },
54
+ });
55
+ server.setRequestHandler({
56
+ schema: ListToolsRequestSchema,
57
+ handler: withActivity(async () => ({ tools: toolsToList({ tools: requiredArgs.tools }) })),
58
+ });
59
+ server.setRequestHandler({
60
+ schema: CallToolRequestSchema,
61
+ handler: withActivity(async ({ request }, { extra } = {}) => handleToolCall({ name: request.params.name, tools: toolIndex, ctx: extra?.signal !== undefined ? { ...ctx, signal: extra.signal } : ctx }, { rawArgs: request.params.arguments })),
45
62
  });
46
- server.setRequestHandler(ListToolsRequestSchema, withActivity(async () => ({ tools: toolsToList(options.tools) })));
47
- server.setRequestHandler(CallToolRequestSchema, withActivity(async (request, extra) => handleToolCall(request.params.name, request.params.arguments, toolIndex, extra?.signal !== undefined ? { ...ctx, signal: extra.signal } : ctx)));
48
63
  if (resources.length > 0) {
49
- server.setRequestHandler(ListResourcesRequestSchema, withActivity(async () => ({ resources: resourcesToList(resources) })));
50
- server.setRequestHandler(ReadResourceRequestSchema, withActivity(async (request) => handleResourceRead(request.params.uri, resourceIndex, ctx)));
64
+ server.setRequestHandler({
65
+ schema: ListResourcesRequestSchema,
66
+ handler: withActivity(async () => ({ resources: resourcesToList({ resources }) })),
67
+ });
68
+ server.setRequestHandler({
69
+ schema: ReadResourceRequestSchema,
70
+ handler: withActivity(async ({ request }) => handleResourceRead({ uri: request.params.uri, resources: resourceIndex, ctx })),
71
+ });
51
72
  }
52
- const transport = createTransport(options.stdin, options.stdout);
73
+ const transport = createTransport({}, {
74
+ ...(options.stdin === undefined ? {} : { stdin: options.stdin }),
75
+ ...(options.stdout === undefined ? {} : { stdout: options.stdout }),
76
+ });
53
77
  try {
54
78
  closeTransportForIdle = () => {
55
- void transport.close().catch(() => { });
79
+ void transport.close({}).catch(() => { });
56
80
  };
57
- await server.connect(transport);
81
+ await server.connect({ transport });
58
82
  // `connect()` sets `transport.onmessage` to its own protocol-dispatch handler; wrap it so
59
83
  // every inbound message also counts as activity for the idle-exit timer, without losing
60
84
  // the SDK's own routing.
61
85
  const sdkOnMessage = transport.onmessage;
62
- transport.onmessage = (message) => {
63
- idleExit.noteActivity();
64
- sdkOnMessage?.(message);
86
+ transport.onmessage = ({ message }) => {
87
+ idleExit.noteActivity({});
88
+ sdkOnMessage?.({ message });
65
89
  };
66
90
  const stdin = options.stdin ?? process.stdin;
67
91
  await new Promise((resolve) => {
@@ -71,7 +95,7 @@ export function createMcpToolServer(options) {
71
95
  if (finished)
72
96
  return;
73
97
  finished = true;
74
- idleExit.dispose();
98
+ idleExit.dispose({});
75
99
  resolve();
76
100
  };
77
101
  transport.onclose = () => {
@@ -79,7 +103,7 @@ export function createMcpToolServer(options) {
79
103
  done();
80
104
  };
81
105
  const closeTransportForStdin = () => {
82
- void transport.close().catch(() => done());
106
+ void transport.close({}).catch(() => done());
83
107
  };
84
108
  // Hold the process open until the client disconnects (stdin EOF) — `connect()` only
85
109
  // starts the transport, it doesn't wait for the stream to close.
@@ -88,7 +112,7 @@ export function createMcpToolServer(options) {
88
112
  });
89
113
  }
90
114
  finally {
91
- idleExit.dispose();
115
+ idleExit.dispose({});
92
116
  closeTransportForIdle = null;
93
117
  }
94
118
  },
@@ -39,12 +39,12 @@ export const searchComponentsTool = {
39
39
  additionalProperties: false,
40
40
  },
41
41
  annotations: { ...READ_ANNOTATIONS, title: 'Search the interactive-UI component catalog' },
42
- handler: async (args, ctx) => {
43
- requireString(args.query, 'query');
44
- const params = new URLSearchParams({ q: args.query });
42
+ handler: async ({ args, ctx }) => {
43
+ const query = requireString({ value: args.query, name: 'query' });
44
+ const params = new URLSearchParams({ q: query });
45
45
  if (typeof args.limit === 'number')
46
46
  params.set('limit', String(args.limit));
47
- const data = await getDaemonJson(ctx.baseUrl, `/api/components/search?${params.toString()}`, daemonCallOptions(ctx));
47
+ const data = await getDaemonJson({ baseUrl: ctx.baseUrl, route: `/api/components/search?${params.toString()}` }, daemonCallOptions({ ctx }));
48
48
  return data.hits;
49
49
  },
50
50
  };
@@ -61,9 +61,9 @@ export const describeComponentTool = {
61
61
  additionalProperties: false,
62
62
  },
63
63
  annotations: { ...READ_ANNOTATIONS, title: 'Describe an interactive-UI component' },
64
- handler: async (args, ctx) => {
65
- requireString(args.id, 'id');
66
- return getDaemonJson(ctx.baseUrl, `/api/components/${encodeURIComponent(args.id)}`, daemonCallOptions(ctx));
64
+ handler: async ({ args, ctx }) => {
65
+ const id = requireString({ value: args.id, name: 'id' });
66
+ return getDaemonJson({ baseUrl: ctx.baseUrl, route: `/api/components/${encodeURIComponent(id)}` }, daemonCallOptions({ ctx }));
67
67
  },
68
68
  };
69
69
  /** Both component-catalog discovery tools, ready to pass as `createMcpToolServer`'s `tools` option. */