@mcp-ts/sdk 2.5.0 → 2.5.2

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 (63) hide show
  1. package/dist/adapters/agui-adapter.d.mts +3 -3
  2. package/dist/adapters/agui-adapter.d.ts +3 -3
  3. package/dist/adapters/agui-middleware.d.mts +3 -3
  4. package/dist/adapters/agui-middleware.d.ts +3 -3
  5. package/dist/adapters/ai-adapter.d.mts +3 -3
  6. package/dist/adapters/ai-adapter.d.ts +3 -3
  7. package/dist/adapters/langchain-adapter.d.mts +3 -3
  8. package/dist/adapters/langchain-adapter.d.ts +3 -3
  9. package/dist/adapters/mastra-adapter.d.mts +2 -2
  10. package/dist/adapters/mastra-adapter.d.ts +2 -2
  11. package/dist/client/index.d.mts +2 -2
  12. package/dist/client/index.d.ts +2 -2
  13. package/dist/client/index.js +3 -0
  14. package/dist/client/index.js.map +1 -1
  15. package/dist/client/index.mjs +3 -0
  16. package/dist/client/index.mjs.map +1 -1
  17. package/dist/client/react.d.mts +4 -4
  18. package/dist/client/react.d.ts +4 -4
  19. package/dist/client/react.js +23 -38
  20. package/dist/client/react.js.map +1 -1
  21. package/dist/client/react.mjs +23 -38
  22. package/dist/client/react.mjs.map +1 -1
  23. package/dist/client/vue.d.mts +5 -4
  24. package/dist/client/vue.d.ts +5 -4
  25. package/dist/client/vue.js +4 -1
  26. package/dist/client/vue.js.map +1 -1
  27. package/dist/client/vue.mjs +4 -1
  28. package/dist/client/vue.mjs.map +1 -1
  29. package/dist/{index-CTURVnom.d.mts → index-B3GPsPsi.d.mts} +2 -1
  30. package/dist/{index-sVcqrhf7.d.ts → index-gNdSQTSz.d.ts} +2 -1
  31. package/dist/index.d.mts +4 -4
  32. package/dist/index.d.ts +4 -4
  33. package/dist/index.js +214 -13
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +214 -13
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/{multi-session-client-CSPSHHla.d.ts → multi-session-client-CB4oDrQX.d.ts} +36 -10
  38. package/dist/{multi-session-client-DMZGVABI.d.mts → multi-session-client-xj3iQIv6.d.mts} +36 -10
  39. package/dist/server/index.d.mts +39 -7
  40. package/dist/server/index.d.ts +39 -7
  41. package/dist/server/index.js +211 -13
  42. package/dist/server/index.js.map +1 -1
  43. package/dist/server/index.mjs +211 -13
  44. package/dist/server/index.mjs.map +1 -1
  45. package/dist/shared/index.d.mts +4 -4
  46. package/dist/shared/index.d.ts +4 -4
  47. package/dist/shared/index.js.map +1 -1
  48. package/dist/shared/index.mjs.map +1 -1
  49. package/dist/{tool-router-CS9l0w4a.d.ts → tool-router-C-Mw1_BQ.d.ts} +1 -1
  50. package/dist/{tool-router-CVLBaCwH.d.mts → tool-router-CsKVXbQB.d.mts} +1 -1
  51. package/dist/{types-DK_NGWd4.d.mts → types-6SmXege4.d.mts} +4 -2
  52. package/dist/{types-DK_NGWd4.d.ts → types-6SmXege4.d.ts} +4 -2
  53. package/package.json +2 -2
  54. package/src/client/core/sse-client.ts +4 -0
  55. package/src/client/react/oauth-popup.tsx +17 -24
  56. package/src/client/react/use-mcp.ts +7 -16
  57. package/src/client/vue/use-mcp.ts +2 -1
  58. package/src/server/handlers/sse-handler.ts +121 -3
  59. package/src/server/mcp/oauth-client.ts +44 -20
  60. package/src/server/mcp/tool-policy-gateway.ts +98 -2
  61. package/src/server/storage/tool-policy.ts +102 -0
  62. package/src/shared/events.ts +1 -0
  63. package/src/shared/types.ts +4 -0
@@ -4,12 +4,27 @@ import { sessions } from '../storage/index.js';
4
4
  import type { Session } from '../storage/types.js';
5
5
  import { assertToolAllowed, filterToolsByPolicy } from '../storage/tool-policy.js';
6
6
 
7
+ /**
8
+ * Internal shape expected from the underlying MCP client.
9
+ * Extends `ToolClient` with the raw `fetchTools` / `listTools` / `callTool`
10
+ * methods so the gateway can fetch unfiltered results and apply policy on top.
11
+ */
7
12
  type RawToolClient = ToolClient & {
8
13
  fetchTools(): Promise<{ tools: Tool[] }>;
9
14
  listTools(): Promise<{ tools: Tool[] }>;
10
15
  callTool(name: string, args: Record<string, unknown>): Promise<CallToolResult>;
11
16
  };
12
17
 
18
+ /**
19
+ * A thin policy-enforcement layer that wraps a raw `MCPClient`.
20
+ *
21
+ * Every public method on this class loads the current session's `toolPolicy`
22
+ * from the session store and uses it to filter or guard tool access before
23
+ * delegating to the underlying client.
24
+ *
25
+ * This keeps security logic in one place and prevents agents from calling
26
+ * tools that a user has explicitly blocked via the management UI.
27
+ */
13
28
  export class ToolPolicyGateway implements ToolClient {
14
29
  constructor(
15
30
  private readonly userId: string,
@@ -17,26 +32,52 @@ export class ToolPolicyGateway implements ToolClient {
17
32
  private readonly client: RawToolClient
18
33
  ) {}
19
34
 
35
+ /**
36
+ * Returns whether the underlying MCP client transport is currently connected.
37
+ */
20
38
  isConnected(): boolean {
21
39
  return this.client.isConnected();
22
40
  }
23
41
 
42
+ /**
43
+ * Returns the server ID from the underlying client, if available.
44
+ */
24
45
  getServerId(): string | undefined {
25
46
  return this.client.getServerId?.();
26
47
  }
27
48
 
49
+ /**
50
+ * Returns the human-readable server name from the underlying client, if available.
51
+ */
28
52
  getServerName(): string | undefined {
29
53
  return this.client.getServerName?.();
30
54
  }
31
55
 
56
+ /**
57
+ * Returns the server URL from the underlying client, if available.
58
+ */
32
59
  getServerUrl(): string | undefined {
33
60
  return this.client.getServerUrl?.();
34
61
  }
35
62
 
63
+ /**
64
+ * Returns the session ID — prefers the value reported by the underlying
65
+ * client, falling back to the one injected at construction time.
66
+ */
36
67
  getSessionId(): string {
37
68
  return this.client.getSessionId?.() ?? this.sessionId;
38
69
  }
39
70
 
71
+ /**
72
+ * Returns the **policy-filtered** list of tools that the current session
73
+ * is allowed to call.
74
+ *
75
+ * Internally calls `client.fetchTools()` (which is cache-backed) so no
76
+ * extra network round-trip is incurred when called after `fetchTools()`.
77
+ *
78
+ * @returns A `ListToolsResult` containing only the permitted tools.
79
+ * @throws {Error} When the session does not exist in the store.
80
+ */
40
81
  async listTools(): Promise<ListToolsResult> {
41
82
  const session = await this.getSession();
42
83
  const result = await this.client.fetchTools();
@@ -48,24 +89,65 @@ export class ToolPolicyGateway implements ToolClient {
48
89
  } as ListToolsResult;
49
90
  }
50
91
 
92
+ /**
93
+ * Returns the **complete, unfiltered** list of tools from the remote server,
94
+ * bypassing any tool-access policy.
95
+ *
96
+ * Used by the management UI to show all available tools (including blocked
97
+ * ones) so users can toggle individual tool access in the dialog.
98
+ *
99
+ * @returns The raw `ListToolsResult` from the remote server.
100
+ */
51
101
  async listAllTools(): Promise<ListToolsResult> {
52
102
  return await this.client.fetchTools() as ListToolsResult;
53
103
  }
54
104
 
105
+ /**
106
+ * Executes a tool call on the remote server after verifying that the tool
107
+ * is permitted by the current session's policy.
108
+ *
109
+ * @param name - The exact tool name to invoke.
110
+ * @param args - Key/value arguments to pass to the tool.
111
+ * @returns The tool's `CallToolResult`.
112
+ * @throws {Error} When the tool is blocked by the session's policy.
113
+ * @throws {Error} When the session does not exist in the store.
114
+ */
55
115
  async callTool(name: string, args: Record<string, unknown>): Promise<CallToolResult> {
56
116
  const session = await this.getSession();
57
117
  this.assertAllowed(session, name);
58
118
  return await this.client.callTool(name, args);
59
119
  }
60
120
 
121
+ /**
122
+ * Filters a raw tools array down to only those permitted by the session's
123
+ * `toolPolicy`.
124
+ *
125
+ * @param session - The session whose policy should be applied.
126
+ * @param tools - The unfiltered list of tools from the remote server.
127
+ * @returns A subset of `tools` that the policy allows.
128
+ */
61
129
  filterTools(session: Session, tools: Tool[]): Tool[] {
62
130
  return filterToolsByPolicy(tools, session.toolPolicy, this.getPolicyServerId(session));
63
131
  }
64
132
 
133
+ /**
134
+ * Throws if `toolName` is blocked by the session's policy.
135
+ * Call this before proxying a `callTool` request to the remote server.
136
+ *
137
+ * @param session - The session whose policy should be enforced.
138
+ * @param toolName - The tool being invoked.
139
+ * @throws {Error} When the tool is not permitted.
140
+ */
65
141
  assertAllowed(session: Session, toolName: string): void {
66
142
  assertToolAllowed(session.toolPolicy, toolName, this.getPolicyServerId(session));
67
143
  }
68
144
 
145
+ /**
146
+ * Loads the session from the store and throws if it does not exist.
147
+ *
148
+ * @returns The fully-hydrated `Session` record.
149
+ * @throws {Error} When the session cannot be found.
150
+ */
69
151
  private async getSession(): Promise<Session> {
70
152
  const session = await sessions.get(this.userId, this.sessionId);
71
153
  if (!session) {
@@ -74,11 +156,27 @@ export class ToolPolicyGateway implements ToolClient {
74
156
  return session;
75
157
  }
76
158
 
159
+ /**
160
+ * Resolves the server ID to use when evaluating tool policy.
161
+ * Prefers the value from the live client (most accurate) and falls back
162
+ * to the server ID stored on the session record.
163
+ */
77
164
  private getPolicyServerId(session: Session): string | undefined {
78
165
  return this.client.getServerId?.() ?? session.serverId;
79
166
  }
80
167
  }
81
168
 
169
+ /**
170
+ * Factory function that creates a `ToolPolicyGateway` for a specific session.
171
+ *
172
+ * Prefer this over constructing `ToolPolicyGateway` directly — it keeps call
173
+ * sites concise and makes it easier to swap the implementation in tests.
174
+ *
175
+ * @param userId - The owner of the session (used for storage lookups).
176
+ * @param sessionId - The session to enforce policy for.
177
+ * @param client - The raw MCP client that the gateway wraps.
178
+ * @returns A fully configured `ToolPolicyGateway` instance.
179
+ */
82
180
  export function createToolPolicyGateway(
83
181
  userId: string,
84
182
  sessionId: string,
@@ -86,5 +184,3 @@ export function createToolPolicyGateway(
86
184
  ): ToolPolicyGateway {
87
185
  return new ToolPolicyGateway(userId, sessionId, client);
88
186
  }
89
-
90
-
@@ -1,16 +1,42 @@
1
1
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
2
2
  import type { ToolPolicy } from './types.js';
3
3
 
4
+ /**
5
+ * Raw, unvalidated shape of a tool policy as it may arrive from an external
6
+ * source (HTTP request body, database read, etc.). All fields are typed as
7
+ * `unknown` so callers are forced to pass through one of the `normalize*`
8
+ * helpers before using the value as a `ToolPolicy`.
9
+ */
4
10
  export type ToolPolicyInput = {
5
11
  mode?: unknown;
6
12
  toolIds?: unknown;
7
13
  updatedAt?: unknown;
8
14
  } | null | undefined;
9
15
 
16
+ /**
17
+ * Constructs the canonical tool ID string used throughout the policy system.
18
+ *
19
+ * Tool IDs combine the server ID and the tool name with a `::` separator so
20
+ * they are globally unique across all connected MCP servers:
21
+ * ```
22
+ * "mn0v3pfwwbxv::delete_mcp_server"
23
+ * ```
24
+ *
25
+ * @param serverId - The short server identifier (e.g. `"mn0v3pfwwbxv"`).
26
+ * @param toolName - The exact tool name as reported by the remote server.
27
+ * @returns A namespaced tool ID string.
28
+ */
10
29
  export function createToolId(serverId: string, toolName: string): string {
11
30
  return `${serverId}::${toolName}`;
12
31
  }
13
32
 
33
+ /**
34
+ * Coerces an arbitrary value into a deduplicated array of trimmed, non-empty
35
+ * strings. Returns an empty array for any non-array or invalid input.
36
+ *
37
+ * @param input - Raw value to normalise (typically from user input or a DB read).
38
+ * @returns A clean `string[]` with duplicates and blank entries removed.
39
+ */
14
40
  function normalizeToolIds(input?: unknown): string[] {
15
41
  if (!Array.isArray(input)) return [];
16
42
 
@@ -22,6 +48,18 @@ function normalizeToolIds(input?: unknown): string[] {
22
48
  ));
23
49
  }
24
50
 
51
+ /**
52
+ * Converts a raw, potentially untrusted `ToolPolicyInput` into a validated
53
+ * `ToolPolicy` object, or `undefined` if the input is absent / malformed.
54
+ *
55
+ * - Invalid `mode` values fall back to `"all"` (no restrictions).
56
+ * - Invalid `updatedAt` values fall back to `now`.
57
+ * - `"all"` mode always produces an empty `toolIds` array regardless of input.
58
+ *
59
+ * @param input - Raw policy data to normalise (from request body, DB, etc.).
60
+ * @param now - Unix timestamp used as the fallback for `updatedAt`. Defaults to `Date.now()`.
61
+ * @returns A well-formed `ToolPolicy`, or `undefined` if `input` is absent.
62
+ */
25
63
  export function normalizeToolPolicy(input?: ToolPolicyInput, now = Date.now()): ToolPolicy | undefined {
26
64
  if (!input || typeof input !== 'object') {
27
65
  return undefined;
@@ -41,10 +79,39 @@ export function normalizeToolPolicy(input?: ToolPolicyInput, now = Date.now()):
41
79
  return { mode, toolIds: normalizeToolIds(input.toolIds), updatedAt };
42
80
  }
43
81
 
82
+ /**
83
+ * Same as `normalizeToolPolicy` but guaranteed to return a `ToolPolicy`.
84
+ * Falls back to an unrestricted `{ mode: "all" }` policy when the input is
85
+ * absent or invalid.
86
+ *
87
+ * Use this variant when a policy is required (e.g. when persisting an update)
88
+ * rather than when reading an optional policy from the session store.
89
+ *
90
+ * @param input - Raw policy data to normalise.
91
+ * @param now - Unix timestamp used as the fallback for `updatedAt`. Defaults to `Date.now()`.
92
+ * @returns A well-formed `ToolPolicy`, never `undefined`.
93
+ */
44
94
  export function normalizeToolPolicyForUpdate(input: ToolPolicyInput, now = Date.now()): ToolPolicy {
45
95
  return normalizeToolPolicy(input, now) ?? { mode: 'all', toolIds: [], updatedAt: now };
46
96
  }
47
97
 
98
+ /**
99
+ * Determines whether a specific tool is permitted under the given policy.
100
+ *
101
+ * | Policy mode | Allowed when… |
102
+ * |--------------|--------------------------------------------------|
103
+ * | `"all"` | Always (no restrictions). |
104
+ * | `"allowlist"`| The tool's ID is present in `policy.toolIds`. |
105
+ * | `"denylist"` | The tool's ID is **not** in `policy.toolIds`. |
106
+ *
107
+ * Returns `false` when the policy requires a `serverId` for ID construction
108
+ * but none is provided (fail-safe / deny by default).
109
+ *
110
+ * @param policy - The active tool policy, or `undefined` for unrestricted access.
111
+ * @param toolName - The tool name to check (as reported by the remote server).
112
+ * @param serverId - The server that owns the tool (required for `allowlist`/`denylist`).
113
+ * @returns `true` if the tool is allowed, `false` otherwise.
114
+ */
48
115
  export function isToolAllowed(policy: ToolPolicy | undefined, toolName: string, serverId?: string): boolean {
49
116
  if (!policy || policy.mode === 'all') return true;
50
117
  if (!serverId) return false;
@@ -57,11 +124,33 @@ export function isToolAllowed(policy: ToolPolicy | undefined, toolName: string,
57
124
  return !policy.toolIds.includes(toolId);
58
125
  }
59
126
 
127
+ /**
128
+ * Asserts that a tool call is permitted under the current session policy.
129
+ * Throws a descriptive error if the tool is blocked — call this inside
130
+ * `ToolPolicyGateway.callTool` before proxying the request to the remote server.
131
+ *
132
+ * @param policy - The active tool policy, or `undefined` for unrestricted access.
133
+ * @param toolName - The tool being invoked.
134
+ * @param serverId - The server that owns the tool.
135
+ * @throws {Error} When the tool is not permitted by the policy.
136
+ */
60
137
  export function assertToolAllowed(policy: ToolPolicy | undefined, toolName: string, serverId?: string): void {
61
138
  if (isToolAllowed(policy, toolName, serverId)) return;
62
139
  throw new Error(`Tool "${toolName}" is not allowed for this MCP session`);
63
140
  }
64
141
 
142
+ /**
143
+ * Returns the subset of `tools` that are permitted under the given policy.
144
+ *
145
+ * When no policy is set (or mode is `"all"`), the original array is returned
146
+ * unchanged (no copy is made). Otherwise each tool is tested with
147
+ * `isToolAllowed` and only the passing tools are included.
148
+ *
149
+ * @param tools - The full, unfiltered list of tools from the remote server.
150
+ * @param policy - The active tool policy, or `undefined` for unrestricted access.
151
+ * @param serverId - The server that owns the tools (required for `allowlist`/`denylist`).
152
+ * @returns A filtered array containing only the permitted tools.
153
+ */
65
154
  export function filterToolsByPolicy<T extends Pick<Tool, 'name'>>(
66
155
  tools: T[],
67
156
  policy: ToolPolicy | undefined,
@@ -71,6 +160,19 @@ export function filterToolsByPolicy<T extends Pick<Tool, 'name'>>(
71
160
  return tools.filter((tool) => isToolAllowed(policy, tool.name, serverId));
72
161
  }
73
162
 
163
+ /**
164
+ * Validates that every tool ID referenced in a policy actually exists on the
165
+ * connected MCP server. Throws if any ID is unknown.
166
+ *
167
+ * This prevents silent misconfigurations where a user accidentally saves a
168
+ * policy referencing a tool that no longer exists (e.g. after a server update).
169
+ *
170
+ * @param policy - The policy to validate (skipped for `"all"` mode).
171
+ * @param tools - The authoritative list of tools from the remote server.
172
+ * @param serverId - Required to construct tool IDs for comparison.
173
+ * @throws {Error} When `serverId` is missing and the policy is not `"all"`.
174
+ * @throws {Error} When the policy contains tool IDs not present in `tools`.
175
+ */
74
176
  export function validateToolPolicyAgainstTools(
75
177
  policy: ToolPolicy,
76
178
  tools: Array<Pick<Tool, 'name'>>,
@@ -98,6 +98,7 @@ export type McpConnectionEvent =
98
98
  serverId: string;
99
99
  toolCount: number;
100
100
  tools: any[];
101
+ allTools?: any[];
101
102
  timestamp: number;
102
103
  }
103
104
  | {
@@ -182,6 +182,7 @@ export interface ToolPolicy {
182
182
  export type McpRpcMethod =
183
183
  | 'connect'
184
184
  | 'disconnect'
185
+ | 'reconnect'
185
186
  | 'listTools'
186
187
  | 'callTool'
187
188
  | 'listSessions'
@@ -223,6 +224,8 @@ export interface DisconnectParams {
223
224
  sessionId: string;
224
225
  }
225
226
 
227
+ export type ReconnectParams = ConnectParams;
228
+
226
229
  export interface SessionParams {
227
230
  sessionId: string;
228
231
  }
@@ -264,6 +267,7 @@ export interface GetToolPolicyParams {
264
267
  export type McpRpcParams =
265
268
  | ConnectParams
266
269
  | DisconnectParams
270
+ | ReconnectParams
267
271
  | SessionParams
268
272
  | CallToolParams
269
273
  | GetPromptParams