@jini-ai/mcp 0.3.4 → 0.4.1

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 (99) hide show
  1. package/CHANGELOG.md +156 -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 +18 -16
  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
  96. package/dist/core/oauth.d.ts +0 -274
  97. package/dist/core/oauth.js +0 -667
  98. package/dist/core/secure-write.d.ts +0 -17
  99. package/dist/core/secure-write.js +0 -72
@@ -0,0 +1,115 @@
1
+ import type { RemoteToolDescriptor, RemoteToolResult, McpClientInfo } from "./ports.js";
2
+ /**
3
+ * @file The transport-INDEPENDENT half of host app's minimal MCP client: the JSON-RPC envelope shapes,
4
+ * the handshake constants, and the narrowing of everything a remote server sends back.
5
+ *
6
+ * Why this file exists:
7
+ * `adapter.stdio.ts` and `adapter.http.ts` differ only in how bytes move. Every decision about what
8
+ * a `tools/list` page means, which cursor to follow, how many pages to follow, what a `tools/call`
9
+ * result is allowed to look like, and which fields survive narrowing is identical across both —
10
+ * because it is the PROTOCOL, not the pipe. Duplicating it into the second adapter would have
11
+ * created two copies of this codebase's untrusted-input narrowing that could silently disagree,
12
+ * which is the failure mode the narrowing exists to prevent.
13
+ *
14
+ * Everything here treats its input as HOSTILE. A remote server controls every byte parsed by this
15
+ * file: tool names, descriptions, schemas, annotations, cursors, results. This file's job is to
16
+ * turn that into well-typed values or drop it — never to trust it, and never to throw on it where
17
+ * dropping would do. The trust decisions themselves belong to `trust.ts`; see `ports.ts`'s header
18
+ * for the split.
19
+ *
20
+ * Architectural role:
21
+ * Pure functions and constants. No I/O, no state, no transport knowledge.
22
+ */
23
+ /** The MCP revision this client negotiates. A server replying with a different one is accepted —
24
+ * MCP's own rule is that the server names the version it will actually speak — but the value is
25
+ * carried on the session so a mismatch is visible in logs rather than silent. */
26
+ export declare const CLIENT_PROTOCOL_VERSION = "2025-06-18";
27
+ /** Bound on `tools/list` cursor-following. A server that returns a fresh `nextCursor` forever would
28
+ * otherwise pin the daemon's boot at 100% CPU; `trust.ts`'s `maxTools` caps what is admitted, but
29
+ * that check runs after this loop, so the loop needs its own bound. */
30
+ export declare const MAX_LIST_TOOLS_PAGES = 20;
31
+ /** One JSON-RPC 2.0 frame as received. Every field optional because a remote may omit any of them,
32
+ * and a missing field must be a narrowing failure rather than a crash. */
33
+ export interface JsonRpcResponse {
34
+ jsonrpc?: string | undefined;
35
+ id?: unknown | undefined;
36
+ result?: unknown | undefined;
37
+ error?: {
38
+ code?: number | undefined;
39
+ message?: string | undefined;
40
+ data?: unknown | undefined;
41
+ } | undefined;
42
+ method?: string | undefined;
43
+ params?: unknown | undefined;
44
+ }
45
+ /** Every failure raised by an MCP client adapter, whatever the transport. Distinct from a plain
46
+ * `Error` so `bootstrap.ts`'s fail-open handler can tell "the protocol went wrong" from "the code
47
+ * went wrong" when it logs. */
48
+ export declare class McpProtocolError extends Error {
49
+ constructor({ message }: {
50
+ message: string;
51
+ }, options?: ErrorOptions);
52
+ }
53
+ /**
54
+ * A `McpProtocolError` specifically caused by the remote's token having expired or been revoked
55
+ * (HTTP 401 today — see `adapter.http.ts`'s `assertOkStatus`; a 403 is a scope/permission refusal,
56
+ * not this). Kept as a distinguishable
57
+ * subtype, rather than a generic `McpProtocolError` a caller would have to pattern-match on message
58
+ * text, so a layer that DOES know what "authorization" means for one connection (an OAuth-backed
59
+ * row, at `assistant/external-mcp-oauth.ts`) can react to it — without this module, which is
60
+ * transport-independent protocol plumbing, knowing anything about OAuth.
61
+ */
62
+ export declare class McpAuthFailedError extends McpProtocolError {
63
+ }
64
+ /** The server's half of a completed handshake. Untrusted, like everything else it sends — recorded
65
+ * for diagnostics and audit, never acted on. */
66
+ export interface McpServerIdentity {
67
+ readonly protocolVersion: string;
68
+ readonly info: {
69
+ name?: string | undefined;
70
+ version?: string | undefined;
71
+ };
72
+ }
73
+ export declare function asRecord({ value }: {
74
+ value: unknown;
75
+ }): Record<string, unknown> | null;
76
+ /** Builds the `initialize` params. One function so both transports negotiate identically — a
77
+ * capability advertised on one and not the other would mean a remote could ask one of host app's two
78
+ * clients for something the other refuses. */
79
+ export declare function buildInitializeParams({ clientInfo }: {
80
+ clientInfo: McpClientInfo;
81
+ }): Record<string, unknown>;
82
+ /** Narrows an `initialize` result into the server's self-reported identity. */
83
+ export declare function parseInitializeResult({ result }: {
84
+ result: unknown;
85
+ }): McpServerIdentity;
86
+ /** Narrows a `tools/call` result. A non-object result is carried through as `content` rather than
87
+ * discarded: a server returning a bare string is out of spec but harmless, and the untrusted
88
+ * envelope (`trust.ts` R7) is where it lands either way. */
89
+ export declare function parseCallToolResult({ result }: {
90
+ result: unknown;
91
+ }): RemoteToolResult;
92
+ /** Narrows one `tools/list` response page into its usable descriptors plus the cursor for the next
93
+ * page (`undefined` when there is none). A malformed entry is dropped rather than thrown on — one
94
+ * bad descriptor must not cost the operator every other tool on the same server; `trust.ts`
95
+ * refuses anything that survives this and still fails its own checks. */
96
+ export declare function parseToolsListPage({ result }: {
97
+ result: unknown;
98
+ }): {
99
+ readonly tools: RemoteToolDescriptor[];
100
+ readonly nextCursor: string | undefined;
101
+ };
102
+ /**
103
+ * Drains a paginated `tools/list` for one session, whatever the transport.
104
+ *
105
+ * @param requestPage - Issues one `tools/list` request and returns its raw result.
106
+ * @throws {McpProtocolError} When the remote keeps producing cursors past
107
+ * {@link MAX_LIST_TOOLS_PAGES}.
108
+ * @complexity O(p) in pages, bounded by {@link MAX_LIST_TOOLS_PAGES}.
109
+ */
110
+ export declare function drainToolsList({ requestPage }: {
111
+ requestPage: (required: {
112
+ params: Record<string, unknown>;
113
+ }) => Promise<unknown>;
114
+ }): Promise<RemoteToolDescriptor[]>;
115
+ //# sourceMappingURL=mcp-protocol.d.ts.map
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @file The transport-INDEPENDENT half of host app's minimal MCP client: the JSON-RPC envelope shapes,
3
+ * the handshake constants, and the narrowing of everything a remote server sends back.
4
+ *
5
+ * Why this file exists:
6
+ * `adapter.stdio.ts` and `adapter.http.ts` differ only in how bytes move. Every decision about what
7
+ * a `tools/list` page means, which cursor to follow, how many pages to follow, what a `tools/call`
8
+ * result is allowed to look like, and which fields survive narrowing is identical across both —
9
+ * because it is the PROTOCOL, not the pipe. Duplicating it into the second adapter would have
10
+ * created two copies of this codebase's untrusted-input narrowing that could silently disagree,
11
+ * which is the failure mode the narrowing exists to prevent.
12
+ *
13
+ * Everything here treats its input as HOSTILE. A remote server controls every byte parsed by this
14
+ * file: tool names, descriptions, schemas, annotations, cursors, results. This file's job is to
15
+ * turn that into well-typed values or drop it — never to trust it, and never to throw on it where
16
+ * dropping would do. The trust decisions themselves belong to `trust.ts`; see `ports.ts`'s header
17
+ * for the split.
18
+ *
19
+ * Architectural role:
20
+ * Pure functions and constants. No I/O, no state, no transport knowledge.
21
+ */
22
+ /** The MCP revision this client negotiates. A server replying with a different one is accepted —
23
+ * MCP's own rule is that the server names the version it will actually speak — but the value is
24
+ * carried on the session so a mismatch is visible in logs rather than silent. */
25
+ export const CLIENT_PROTOCOL_VERSION = "2025-06-18";
26
+ /** Bound on `tools/list` cursor-following. A server that returns a fresh `nextCursor` forever would
27
+ * otherwise pin the daemon's boot at 100% CPU; `trust.ts`'s `maxTools` caps what is admitted, but
28
+ * that check runs after this loop, so the loop needs its own bound. */
29
+ export const MAX_LIST_TOOLS_PAGES = 20;
30
+ /** Every failure raised by an MCP client adapter, whatever the transport. Distinct from a plain
31
+ * `Error` so `bootstrap.ts`'s fail-open handler can tell "the protocol went wrong" from "the code
32
+ * went wrong" when it logs. */
33
+ export class McpProtocolError extends Error {
34
+ constructor({ message }, options = {}) {
35
+ super(message, options);
36
+ }
37
+ }
38
+ /**
39
+ * A `McpProtocolError` specifically caused by the remote's token having expired or been revoked
40
+ * (HTTP 401 today — see `adapter.http.ts`'s `assertOkStatus`; a 403 is a scope/permission refusal,
41
+ * not this). Kept as a distinguishable
42
+ * subtype, rather than a generic `McpProtocolError` a caller would have to pattern-match on message
43
+ * text, so a layer that DOES know what "authorization" means for one connection (an OAuth-backed
44
+ * row, at `assistant/external-mcp-oauth.ts`) can react to it — without this module, which is
45
+ * transport-independent protocol plumbing, knowing anything about OAuth.
46
+ */
47
+ export class McpAuthFailedError extends McpProtocolError {
48
+ }
49
+ export function asRecord({ value }) {
50
+ if (typeof value !== "object" || value === null || Array.isArray(value))
51
+ return null;
52
+ return value;
53
+ }
54
+ /** Builds the `initialize` params. One function so both transports negotiate identically — a
55
+ * capability advertised on one and not the other would mean a remote could ask one of host app's two
56
+ * clients for something the other refuses. */
57
+ export function buildInitializeParams({ clientInfo }) {
58
+ return { protocolVersion: CLIENT_PROTOCOL_VERSION, capabilities: {}, clientInfo };
59
+ }
60
+ /** Narrows an `initialize` result into the server's self-reported identity. */
61
+ export function parseInitializeResult({ result }) {
62
+ const record = asRecord({ value: result });
63
+ const info = asRecord({ value: record?.serverInfo });
64
+ return {
65
+ protocolVersion: typeof record?.protocolVersion === "string" ? record.protocolVersion : "",
66
+ info: {
67
+ name: typeof info?.name === "string" ? info.name : undefined,
68
+ version: typeof info?.version === "string" ? info.version : undefined,
69
+ },
70
+ };
71
+ }
72
+ /** Narrows a `tools/call` result. A non-object result is carried through as `content` rather than
73
+ * discarded: a server returning a bare string is out of spec but harmless, and the untrusted
74
+ * envelope (`trust.ts` R7) is where it lands either way. */
75
+ export function parseCallToolResult({ result }) {
76
+ const record = asRecord({ value: result });
77
+ if (!record)
78
+ return { content: result };
79
+ return {
80
+ content: record.content,
81
+ structuredContent: record.structuredContent,
82
+ // The remote's own claim about its own outcome. Surfaced to the model inside the untrusted
83
+ // envelope (`trust.ts` R7); never used to decide anything on this side.
84
+ isError: record.isError === true,
85
+ };
86
+ }
87
+ /** Narrows one `tools/list` response's `annotations` block. Shape only — the trust decisions all
88
+ * live in `trust.ts`. Split out of {@link asRemoteToolDescriptor} purely to keep that function's
89
+ * complexity under the shop ceiling. */
90
+ function asRemoteToolAnnotations(record) {
91
+ if (!record)
92
+ return undefined;
93
+ return {
94
+ title: typeof record.title === "string" ? record.title : undefined,
95
+ readOnlyHint: typeof record.readOnlyHint === "boolean" ? record.readOnlyHint : undefined,
96
+ destructiveHint: typeof record.destructiveHint === "boolean" ? record.destructiveHint : undefined,
97
+ idempotentHint: typeof record.idempotentHint === "boolean" ? record.idempotentHint : undefined,
98
+ openWorldHint: typeof record.openWorldHint === "boolean" ? record.openWorldHint : undefined,
99
+ };
100
+ }
101
+ /** Narrows one `tools/list` entry, discarding anything without a usable `name`. Shape only — the
102
+ * trust decisions all live in `trust.ts`. */
103
+ function asRemoteToolDescriptor(value) {
104
+ const record = asRecord({ value: value });
105
+ if (!record || typeof record.name !== "string" || record.name.length === 0)
106
+ return null;
107
+ return {
108
+ name: record.name,
109
+ description: typeof record.description === "string" ? record.description : undefined,
110
+ inputSchema: record.inputSchema,
111
+ annotations: asRemoteToolAnnotations(asRecord({ value: record.annotations })),
112
+ };
113
+ }
114
+ /** Narrows one `tools/list` response page into its usable descriptors plus the cursor for the next
115
+ * page (`undefined` when there is none). A malformed entry is dropped rather than thrown on — one
116
+ * bad descriptor must not cost the operator every other tool on the same server; `trust.ts`
117
+ * refuses anything that survives this and still fails its own checks. */
118
+ export function parseToolsListPage({ result }) {
119
+ const record = asRecord({ value: result });
120
+ const rawTools = record && Array.isArray(record.tools) ? record.tools : [];
121
+ const tools = [];
122
+ for (const tool of rawTools) {
123
+ const descriptor = asRemoteToolDescriptor(tool);
124
+ if (descriptor)
125
+ tools.push(descriptor);
126
+ }
127
+ const next = record?.nextCursor;
128
+ return { tools, nextCursor: typeof next === "string" && next.length > 0 ? next : undefined };
129
+ }
130
+ /**
131
+ * Drains a paginated `tools/list` for one session, whatever the transport.
132
+ *
133
+ * @param requestPage - Issues one `tools/list` request and returns its raw result.
134
+ * @throws {McpProtocolError} When the remote keeps producing cursors past
135
+ * {@link MAX_LIST_TOOLS_PAGES}.
136
+ * @complexity O(p) in pages, bounded by {@link MAX_LIST_TOOLS_PAGES}.
137
+ */
138
+ export async function drainToolsList({ requestPage }) {
139
+ const collected = [];
140
+ let cursor;
141
+ for (let page = 0; page < MAX_LIST_TOOLS_PAGES; page += 1) {
142
+ const result = await requestPage({ params: cursor === undefined ? {} : { cursor } });
143
+ const { tools, nextCursor } = parseToolsListPage({ result: result });
144
+ collected.push(...tools);
145
+ if (nextCursor === undefined)
146
+ return collected;
147
+ cursor = nextCursor;
148
+ }
149
+ throw new McpProtocolError({ message: `mcp-federation: the remote server kept returning a tools/list nextCursor past ${MAX_LIST_TOOLS_PAGES} pages — refusing to follow it further` });
150
+ }
151
+ //# sourceMappingURL=mcp-protocol.js.map
@@ -0,0 +1,54 @@
1
+ import type { ToolRefusalReason } from "./trust.js";
2
+ import type { FederatedCallConfirmationRequest } from "./ports.js";
3
+ /** Host-owned refusal wording; reason codes and sanitized data remain independent of presentation. */
4
+ export interface FederationRefusalMessages {
5
+ readonly refusalExplanations: Readonly<Record<ToolRefusalReason, string>>;
6
+ readonly unprintableName: string;
7
+ readonly absentExplanation: string;
8
+ readonly inertWriteGrantExplanation: string;
9
+ readonly prefixHeading: string;
10
+ readonly prefixInstruction: string;
11
+ refusalBullet(required: {
12
+ remoteName: string;
13
+ connectionId: string;
14
+ explanation: string;
15
+ }): string;
16
+ omittedRefusals(required: {
17
+ omitted: number;
18
+ total: number;
19
+ }): string;
20
+ }
21
+ /** Shared federation presentation. Hosts replace the complete object, including confirmation and launch copy. */
22
+ export interface FederationMessages extends FederationRefusalMessages {
23
+ authenticationRefused(required: {
24
+ method: string;
25
+ status: number;
26
+ }): string;
27
+ readonly closedByHost: string;
28
+ nativeCollision(required: {
29
+ toolId: string;
30
+ }): string;
31
+ /** Hosts can distinguish uvx/docker/general commands by basename (including Windows suffixes
32
+ * and absolute paths), preserving remediation appropriate to their installed toolchain. */
33
+ launchUnavailable(required: {
34
+ command: string;
35
+ searchedDirs: readonly string[];
36
+ }): string;
37
+ confirmationTitle(required: {
38
+ request: FederatedCallConfirmationRequest;
39
+ }): string;
40
+ confirmationWarning(required: {
41
+ request: FederatedCallConfirmationRequest;
42
+ }): string;
43
+ readonly confirmationDescription: string;
44
+ readonly serviceLabel: string;
45
+ readonly toolLabel: string;
46
+ readonly argumentsLabel: string;
47
+ readonly noArguments: string;
48
+ readonly confirmLabel: string;
49
+ readonly allowChatLabel: string;
50
+ readonly allowAlwaysLabel: string;
51
+ }
52
+ /** Neutral English defaults are explicit host choices, never implicit branding or wire defaults. */
53
+ export declare const defaultFederationMessages: FederationMessages;
54
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1,35 @@
1
+ /** Neutral English defaults are explicit host choices, never implicit branding or wire defaults. */
2
+ export const defaultFederationMessages = {
3
+ refusalExplanations: {
4
+ "not-in-operator-allowlist": "the administrator has not allowed this tool for this connection. Fix: ask the administrator to allow this tool for this connection.",
5
+ "remote-declares-not-read-only": "the server says this tool makes changes, but the connection has no write grant for it. Fix: ask the administrator to allow this tool and its changes for this connection.",
6
+ "remote-declares-destructive": "the server marks this tool as destructive and the admission policy refused it. Fix: ask the administrator to review the connection's destructive-tool policy.",
7
+ "missing-or-invalid-input-schema": "the server published no usable input schema, so arguments would be guesswork. Fix: ask the administrator to report the missing contract to the server vendor.",
8
+ "invalid-remote-tool-name": "the server advertised a name that cannot be registered (letters, digits, '.', '-' or '_', at most 64 characters). Fix: ask the administrator to report the invalid name to the server vendor.",
9
+ "duplicate-remote-tool-name": "the server advertised the same tool name twice; the repeat cannot overwrite the first definition. Fix: ask the administrator to report the duplicate to the server vendor.",
10
+ "connection-tool-cap-reached": "the connection reached its maximum number of tools. Fix: ask the administrator to limit the connection to the tools that are needed, then restart the assistant.",
11
+ },
12
+ unprintableName: "(an invalid tool name sent by this server)",
13
+ absentExplanation: "the administrator allowed this tool, but the server does not offer that name. Ask the administrator to check the name and the server's enabled features.",
14
+ inertWriteGrantExplanation: "this tool has a write grant but no tool grant, so the write grant cannot take effect. Fix: ask the administrator to allow this tool for this connection.",
15
+ prefixHeading: "EXTERNAL TOOL AVAILABILITY — read this before explaining a missing capability.",
16
+ prefixInstruction: "These tools were withheld when the assistant started. They are absent from `search_tools` and `describe_tool` and cannot be called. Explain the listed reason and fix without inventing another cause. This snapshot stays fixed until the assistant restarts.",
17
+ refusalBullet: ({ remoteName, connectionId, explanation }) => `- '${remoteName}' on external server '${connectionId}': ${explanation}`,
18
+ omittedRefusals: ({ omitted, total }) => `- …and ${omitted} more, for ${total} withheld in total. Ask the administrator for the complete admission report.`,
19
+ authenticationRefused: ({ method, status }) => `mcp-federation: the server refused '${method}' with ${status} — its authorization has expired or been revoked; ask the administrator to reconnect this connection`,
20
+ closedByHost: "closed by the host",
21
+ nativeCollision: ({ toolId }) => `mcp-federation: federated tool id '${toolId}' collides with a natively-registered tool — an external server must never be able to shadow the host's own catalog`,
22
+ launchUnavailable: ({ command }) => `This server's command "${command}" is unavailable. Ask the administrator to install or configure the required executable.`,
23
+ confirmationTitle: ({ request }) => `Run ${request.remoteName} on ${request.connectionLabel}?`,
24
+ confirmationWarning: ({ request }) => {
25
+ const base = request.destructive
26
+ ? `${request.connectionLabel} marks this tool as destructive: it can delete or overwrite data, and that may not be undoable.`
27
+ : `This can change things in ${request.connectionLabel}.`;
28
+ return request.writeShapedInputs.length
29
+ ? `${base} Its input ${request.writeShapedInputs.join(", ")} looks like it can change data, so the host asks every time.` : base;
30
+ },
31
+ confirmationDescription: "The assistant wants to run this with exactly the values below. Nothing runs until you allow it.",
32
+ serviceLabel: "Service", toolLabel: "Tool", argumentsLabel: "Arguments", noArguments: "(none)",
33
+ confirmLabel: "Allow", allowChatLabel: "Allow for this chat", allowAlwaysLabel: "Always allow",
34
+ };
35
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1,287 @@
1
+ export interface RemoteToolAnnotations {
2
+ readonly title?: string | undefined;
3
+ readonly readOnlyHint?: boolean | undefined;
4
+ readonly destructiveHint?: boolean | undefined;
5
+ readonly idempotentHint?: boolean | undefined;
6
+ readonly openWorldHint?: boolean | undefined;
7
+ }
8
+ /** One entry of a remote server's `tools/list` response. Every field is attacker-controlled. */
9
+ export interface RemoteToolDescriptor {
10
+ readonly name: string;
11
+ readonly description?: string | undefined;
12
+ /** JSON Schema, as published by the remote. `trust.ts` refuses any tool whose schema is not a
13
+ * JSON-Schema object — a schema-less tool is refused for federated tools for the same reason
14
+ * `buildDomainRegistrations` refuses one for native tools. */
15
+ readonly inputSchema?: unknown | undefined;
16
+ readonly annotations?: RemoteToolAnnotations | undefined;
17
+ }
18
+ /** One `tools/call` response. `isError` is the remote's own claim about its own outcome. */
19
+ export interface RemoteToolResult {
20
+ readonly content?: unknown | undefined;
21
+ readonly structuredContent?: unknown | undefined;
22
+ readonly isError?: boolean | undefined;
23
+ }
24
+ /**
25
+ * An established MCP client session against one remote server.
26
+ *
27
+ * Intentionally three methods and no more: this is the entire surface federation needs. MCP's
28
+ * resources/prompts/sampling/roots are all deliberately out of scope — sampling in particular would
29
+ * let a remote server drive inference on host app's account, which is a far larger grant than "expose
30
+ * some tools" and is not something this pass builds a trust story for.
31
+ */
32
+ export interface McpSessionPort {
33
+ /** The remote's advertised tool surface, already paginated to completion by the adapter. */
34
+ listTools(): Promise<RemoteToolDescriptor[]>;
35
+ callTool(request: McpToolCallRequiredArgs, options?: McpToolCallOptions): Promise<RemoteToolResult>;
36
+ close(required: Record<string, never>): Promise<void>;
37
+ }
38
+ /**
39
+ * The byte-level seam under `adapter.stdio.ts`: a bidirectional stream of newline-delimited
40
+ * JSON-RPC 2.0 messages.
41
+ *
42
+ * Deliberately dumb — it knows about lines, not about MCP. All protocol knowledge (handshake
43
+ * ordering, id correlation, pagination, timeouts) lives in the adapter above it, which is what
44
+ * makes that knowledge testable without a child process.
45
+ */
46
+ export interface McpStdioChannel {
47
+ /** Writes one JSON-RPC message. Implementations append the framing newline themselves. */
48
+ send({ message }: {
49
+ message: string;
50
+ }): void;
51
+ /** Registers the sink for inbound messages, one complete JSON value per call. */
52
+ onMessage({ listener }: {
53
+ listener: (required: {
54
+ message: string;
55
+ }) => void;
56
+ }): void;
57
+ /** Registers the sink for "this channel is gone" — process exit, stream error, explicit close. */
58
+ onClose({ listener }: {
59
+ listener: (required: {
60
+ reason: string;
61
+ }) => void;
62
+ }): void;
63
+ close(required: Record<string, never>): void;
64
+ }
65
+ /**
66
+ * One site owner's configured federated connection.
67
+ *
68
+ * `allowedToolNames` is the load-bearing field and has NO safe default at this layer: an empty
69
+ * allowlist yields zero federated tools, which is the correct behaviour for a misconfigured
70
+ * connection. A DEFAULT is a per-vendor judgement and therefore belongs to a vendor preset, not
71
+ * here — an Agent Plugin's `mcp.json` `defaultTools` is the current example, authored from the
72
+ * server's real tool surface, which is what makes it an independent classification rather than a
73
+ * restatement of the remote's own claims.
74
+ */
75
+ /**
76
+ * Where one connection's config came from — stamped once, at admission, and carried unchanged into
77
+ * every later `tools/call`. `external-mcp-revocation.ts`'s per-call gate is the one consumer: a
78
+ * preset gets only the legacy OAuth `needs_reauth` check (it has no roster row to compare against);
79
+ * a roster connection is re-checked against its CURRENT row every call, keyed by
80
+ * `admissionRevision` — see that file's `rosterRefusalFor`.
81
+ */
82
+ export type FederatedConnectionOrigin = {
83
+ readonly kind: "preset";
84
+ } | {
85
+ readonly kind: "roster";
86
+ readonly admissionRevision: string;
87
+ };
88
+ export interface FederatedMcpConnectionConfig {
89
+ /** Operator-chosen, stable, `[a-z0-9-]`. Becomes part of every federated tool id, so renaming it
90
+ * renames every tool the model sees — pick once. */
91
+ readonly connectionId: string;
92
+ /** Human-readable, shown to the model as provenance on every federated tool description. */
93
+ readonly label: string;
94
+ /** DEFAULT-DENY allowlist of REMOTE tool names (pre-namespacing). */
95
+ readonly allowedToolNames: readonly string[];
96
+ /** Remote names reviewed in a bundled plugin's mcp.json, independent of admission grants.
97
+ * Remote readOnlyHint:false or destructiveHint:true vetoes each declaration. */
98
+ readonly readOnlyRemoteNames?: ReadonlySet<string> | undefined;
99
+ /**
100
+ * SECOND list of REMOTE tool names (pre-namespacing) the operator has separately declared as
101
+ * writes. Kept for configuration compatibility and admission reports, independently of
102
+ * {@link allowedToolNames}. The allowlist alone gates admission; this list does not grant or
103
+ * refuse calls. Descriptor read-only classification and host permissions remain separate gates.
104
+ *
105
+ * Same "no safe default at this layer" rule as {@link allowedToolNames}: every connection that has
106
+ * never been told to allow a write must resolve to an empty list here, and a vendor preset that
107
+ * wants a non-empty default authors it itself, on purpose, the same way
108
+ * an Agent Plugin's `defaultTools.write` is authored rather than inherited from the remote.
109
+ *
110
+ * Destructive tools may be admitted from the allowlist, but protected actions still require
111
+ * per-call confirmation. Ordinary writes and absent annotations do not require a card. See
112
+ * `trust.ts` R3: hints cannot grant admission, and input names alone are not consent evidence.
113
+ */
114
+ readonly writeAllowedToolNames: readonly string[];
115
+ /** How long the initialize+list handshake may take before federation is abandoned for this boot. */
116
+ readonly connectTimeoutMs: number;
117
+ /** Per-`tools/call` ceiling. */
118
+ readonly callTimeoutMs: number;
119
+ /** UTF-8 cap on the serialized text payload before wrapping, and separately on the aggregate
120
+ * serialized image array per result. Wrapper text is outside these two budgets. */
121
+ readonly maxResultBytes: number;
122
+ /** Hard cap on how many tools this connection may contribute, whatever the remote advertises. */
123
+ readonly maxTools: number;
124
+ /** Stamped by `bootstrap.ts` (presets, via `withPresetOrigin`) or `external-mcp-store.ts`'s
125
+ * `toResolvedFederatedConnections` (roster rows) — never by anything else. Optional only so a
126
+ * fixture predating this field still type-checks; `external-mcp-revocation.ts`'s gate treats a
127
+ * non-preset call with no origin as `unverifiable` rather than assuming either source. */
128
+ readonly origin?: FederatedConnectionOrigin | undefined;
129
+ }
130
+ /** One federated tool call's identity, as `mcp-federation/registrations.ts`'s handler hands it to
131
+ * `FederationDeps.assertConnectionUsable` — everything `external-mcp-revocation.ts`'s per-call gate
132
+ * needs to re-check an admitted tool against the connection's CURRENT row, without re-deriving any
133
+ * of it from the registry. Keys are required (may be `undefined`) rather than optional, so the
134
+ * shape is safe to build under `exactOptionalPropertyTypes`. */
135
+ export interface FederatedCallTarget {
136
+ /** The remote's own tool name (pre-namespacing) — what the operator's allowlist and write list
137
+ * name. */
138
+ readonly remoteName: string;
139
+ /** Exactly what was recorded at admission (`AdmittedFederatedTool.declaredAnnotations`) — never
140
+ * re-read from the remote on this path. */
141
+ readonly declaredAnnotations: RemoteToolDescriptor["annotations"] | undefined;
142
+ readonly origin: FederatedConnectionOrigin | undefined;
143
+ }
144
+ /** How a locally-launched federated connection is started.
145
+ *
146
+ * See {@link McpHttpLaunchSpec} for the hosted counterpart, and {@link McpLaunchSpec} for why the
147
+ * two are a union rather than one shape with optional halves. */
148
+ export interface McpStdioLaunchSpec {
149
+ readonly command: string;
150
+ readonly args: readonly string[];
151
+ /** Child-process-only environment. Secrets belong here and NOWHERE else — never in `args`, which
152
+ * are world-readable in `/proc/<pid>/cmdline` on Linux and in `ps` output everywhere. */
153
+ readonly env: Readonly<Record<string, string>>;
154
+ readonly cwd?: string | undefined;
155
+ }
156
+ /**
157
+ * How a HOSTED federated connection is reached: one URL, plus the headers that authenticate to it.
158
+ *
159
+ * `headers` is where an OAuth access token arrives, as `Authorization: Bearer ...`, and it is the
160
+ * hosted analogue of {@link McpStdioLaunchSpec.env}'s "secrets belong here and NOWHERE else" rule —
161
+ * never in `url`, which is logged by proxies, kept in browser history when an operator pastes it,
162
+ * and stored in plaintext on the connection row.
163
+ *
164
+ * Deliberately a plain header bag rather than a token field: this layer should not know that OAuth
165
+ * exists. It is handed headers and sends them. Which credential produced them, whether it can be
166
+ * refreshed, and what to do when it expires are all `assistant/external-mcp-oauth.ts`'s questions,
167
+ * decided before a launch spec is ever built.
168
+ */
169
+ export interface McpHttpLaunchSpec {
170
+ /** Absolute `https:` URL of the server's MCP endpoint (`http:` only for loopback — enforced at
171
+ * save time by `external-mcp-store.ts`, not here). */
172
+ readonly url: string;
173
+ /** Sent on every request to the endpoint. Carries the bearer token, when there is one. */
174
+ readonly headers: Readonly<Record<string, string>>;
175
+ }
176
+ /**
177
+ * How a federated connection is reached, whichever transport it uses.
178
+ *
179
+ * A union rather than one struct with optional `command`/`url`, because the two are genuinely
180
+ * exclusive: a spec carrying both is not a degraded configuration to be tolerated, it is a bug, and
181
+ * a union makes that unrepresentable instead of a runtime check somebody has to remember to write.
182
+ *
183
+ * Narrow with {@link isHttpLaunchSpec}.
184
+ */
185
+ export type McpLaunchSpec = McpStdioLaunchSpec | McpHttpLaunchSpec;
186
+ /** Narrows a {@link McpLaunchSpec} to its hosted arm. */
187
+ export declare function isHttpLaunchSpec(input: {
188
+ spec: McpLaunchSpec;
189
+ }): input is {
190
+ spec: McpHttpLaunchSpec;
191
+ };
192
+ /**
193
+ * The byte-level seam under `adapter.http.ts`, and the hosted counterpart to
194
+ * {@link McpStdioChannel}: one request/response exchange against the server's MCP endpoint.
195
+ *
196
+ * Deliberately narrower than `fetch`: a function that takes a body and returns a status, a content
197
+ * type, a couple of named headers, and text. That is everything the Streamable HTTP transport
198
+ * needs, and shrinking the seam is what lets the adapter's real protocol behaviour — handshake
199
+ * ordering, session-id propagation, SSE framing, pagination, error mapping — be tested against a
200
+ * scripted double instead of a live server, exactly as the stdio adapter's channel seam does.
201
+ */
202
+ export interface McpHttpExchange {
203
+ send(request: McpHttpRequestRequiredArgs, options?: McpHttpRequestOptions): Promise<McpHttpResponse>;
204
+ }
205
+ /** One response from an {@link McpHttpExchange}. */
206
+ export interface McpHttpResponse {
207
+ readonly status: number;
208
+ /** Lowercased `content-type`, value only — parameters such as `; charset=utf-8` may be present
209
+ * and the adapter matches on the prefix. */
210
+ readonly contentType: string;
211
+ /** The server's `Mcp-Session-Id`, when it issued one. */
212
+ readonly sessionId?: string | undefined;
213
+ /** The full body. Bounded by the adapter's own cap before it is parsed. */
214
+ readonly text: string;
215
+ readonly wwwAuthenticate?: string | undefined;
216
+ }
217
+ /**
218
+ * What a per-call confirmation card (G3, `trust.ts` R3) is asked to show, as
219
+ * `mcp-federation/registrations.ts`'s handler hands it to `FederationDeps.confirmCall`.
220
+ *
221
+ * `arguments` is the exact, already-frozen object that will be sent on Confirm — the handler builds it
222
+ * once, before the card is drawn, and sends that same object afterwards, so the card can never show
223
+ * one thing while the remote receives another.
224
+ */
225
+ export interface FederatedCallConfirmationRequest {
226
+ /** The namespaced registry id (`mcp__<connection>__<name>`) — the card's clicks are routed back to it. */
227
+ readonly toolId: string;
228
+ readonly remoteName: string;
229
+ readonly connectionId: string;
230
+ /** The operator's label for the connection, e.g. "Supabase" — the "target" the card names. */
231
+ readonly connectionLabel: string;
232
+ readonly arguments: Readonly<Record<string, unknown>>;
233
+ /** The remote declared `destructiveHint: true` — the card carries the stronger, danger-styled warning. */
234
+ readonly destructive: boolean;
235
+ /**
236
+ * The hints recorded when the tool was admitted, and where its connection came from — the tool
237
+ * identity a remembered approval ("Allow for this chat", "Always allow") is pinned to
238
+ * (`assistant/external-mcp-tool-approvals.ts`), so a changed server, name or hints asks again.
239
+ */
240
+ readonly declaredAnnotations: RemoteToolDescriptor["annotations"] | undefined;
241
+ readonly origin: FederatedConnectionOrigin | undefined;
242
+ /** The description the model reads (`AdmittedFederatedTool.description`) and the input schema, as
243
+ * admitted — also part of that identity, so a drifted description or schema asks again. */
244
+ readonly description: string;
245
+ readonly inputSchema: Readonly<Record<string, unknown>>;
246
+ /**
247
+ * Input names, in the schema or in this call's arguments, that look like writes (`trust.ts`
248
+ * `WRITE_SHAPED_INPUT_WORDS`), sorted. These names describe an already-required protected-action
249
+ * card; names alone do not require one. On such a card, non-empty names disable remembered
250
+ * approval so the person reviews the actual input for that call.
251
+ */
252
+ readonly writeShapedInputs: readonly string[];
253
+ }
254
+ /**
255
+ * The human's answer. Only an explicit Confirm is `{ confirmed: true }`; anything else carries the
256
+ * model-facing result that replaces the call (cancelled, expired, or the run ended).
257
+ */
258
+ export type FederatedCallConfirmationOutcome = {
259
+ readonly confirmed: true;
260
+ } | {
261
+ readonly confirmed: false;
262
+ readonly result: Readonly<Record<string, unknown>>;
263
+ };
264
+ /** Host identity sent during initialization; there is no product default. */
265
+ export interface McpClientInfo {
266
+ readonly name: string;
267
+ readonly version: string;
268
+ }
269
+ export type RemoteToolDescriptorAnnotations = RemoteToolDescriptor["annotations"];
270
+ /** Required wire payload and optional per-call cancellation. */
271
+ export interface McpToolCallRequiredArgs {
272
+ readonly name: string;
273
+ readonly arguments: Record<string, unknown>;
274
+ }
275
+ export interface McpToolCallOptions {
276
+ readonly signal?: AbortSignal | undefined;
277
+ }
278
+ export interface McpHttpRequestRequiredArgs {
279
+ readonly url: string;
280
+ readonly method: "POST" | "DELETE";
281
+ readonly headers: Readonly<Record<string, string>>;
282
+ }
283
+ export interface McpHttpRequestOptions {
284
+ readonly body?: string | undefined;
285
+ readonly signal?: AbortSignal | undefined;
286
+ }
287
+ //# sourceMappingURL=ports.d.ts.map
@@ -0,0 +1,5 @@
1
+ /** Narrows a {@link McpLaunchSpec} to its hosted arm. */
2
+ export function isHttpLaunchSpec(input) {
3
+ return "url" in input.spec;
4
+ }
5
+ //# sourceMappingURL=ports.js.map