@mcp-ts/sdk 2.4.4 → 2.5.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 (89) 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-adapter.js.map +1 -1
  4. package/dist/adapters/agui-adapter.mjs.map +1 -1
  5. package/dist/adapters/agui-middleware.d.mts +3 -3
  6. package/dist/adapters/agui-middleware.d.ts +3 -3
  7. package/dist/adapters/agui-middleware.js.map +1 -1
  8. package/dist/adapters/agui-middleware.mjs.map +1 -1
  9. package/dist/adapters/ai-adapter.d.mts +3 -3
  10. package/dist/adapters/ai-adapter.d.ts +3 -3
  11. package/dist/adapters/ai-adapter.js.map +1 -1
  12. package/dist/adapters/ai-adapter.mjs.map +1 -1
  13. package/dist/adapters/langchain-adapter.d.mts +3 -3
  14. package/dist/adapters/langchain-adapter.d.ts +3 -3
  15. package/dist/adapters/langchain-adapter.js +2 -2
  16. package/dist/adapters/langchain-adapter.js.map +1 -1
  17. package/dist/adapters/langchain-adapter.mjs +2 -2
  18. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  19. package/dist/adapters/mastra-adapter.d.mts +2 -2
  20. package/dist/adapters/mastra-adapter.d.ts +2 -2
  21. package/dist/adapters/mastra-adapter.js +2 -2
  22. package/dist/adapters/mastra-adapter.js.map +1 -1
  23. package/dist/adapters/mastra-adapter.mjs +2 -2
  24. package/dist/adapters/mastra-adapter.mjs.map +1 -1
  25. package/dist/client/index.d.mts +2 -2
  26. package/dist/client/index.d.ts +2 -2
  27. package/dist/client/index.js +6 -0
  28. package/dist/client/index.js.map +1 -1
  29. package/dist/client/index.mjs +6 -0
  30. package/dist/client/index.mjs.map +1 -1
  31. package/dist/client/react.d.mts +15 -4
  32. package/dist/client/react.d.ts +15 -4
  33. package/dist/client/react.js +36 -6
  34. package/dist/client/react.js.map +1 -1
  35. package/dist/client/react.mjs +36 -6
  36. package/dist/client/react.mjs.map +1 -1
  37. package/dist/client/vue.d.mts +4 -4
  38. package/dist/client/vue.d.ts +4 -4
  39. package/dist/client/vue.js +6 -0
  40. package/dist/client/vue.js.map +1 -1
  41. package/dist/client/vue.mjs +6 -0
  42. package/dist/client/vue.mjs.map +1 -1
  43. package/dist/{index-CkM3p0eE.d.mts → index-CTURVnom.d.mts} +5 -1
  44. package/dist/{index-CZk2gu2E.d.ts → index-sVcqrhf7.d.ts} +5 -1
  45. package/dist/index.d.mts +4 -4
  46. package/dist/index.d.ts +4 -4
  47. package/dist/index.js +374 -130
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +370 -130
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/{multi-session-client-ChQrBZhF.d.mts → multi-session-client-CSPSHHla.d.ts} +14 -2
  52. package/dist/{multi-session-client-QOOPdEVp.d.ts → multi-session-client-DMZGVABI.d.mts} +14 -2
  53. package/dist/server/index.d.mts +13 -4
  54. package/dist/server/index.d.ts +13 -4
  55. package/dist/server/index.js +368 -130
  56. package/dist/server/index.js.map +1 -1
  57. package/dist/server/index.mjs +364 -130
  58. package/dist/server/index.mjs.map +1 -1
  59. package/dist/shared/index.d.mts +4 -4
  60. package/dist/shared/index.d.ts +4 -4
  61. package/dist/shared/index.js +2 -6
  62. package/dist/shared/index.js.map +1 -1
  63. package/dist/shared/index.mjs +2 -6
  64. package/dist/shared/index.mjs.map +1 -1
  65. package/dist/{tool-router-DQ-HrD7W.d.ts → tool-router-CS9l0w4a.d.ts} +1 -1
  66. package/dist/{tool-router-CGs3IDOJ.d.mts → tool-router-CVLBaCwH.d.mts} +1 -1
  67. package/dist/{types-Bf-7GOLW.d.mts → types-DK_NGWd4.d.mts} +37 -3
  68. package/dist/{types-Bf-7GOLW.d.ts → types-DK_NGWd4.d.ts} +37 -3
  69. package/migrations/neon/20260513010000_install_mcp_sessions.sql +1 -0
  70. package/migrations/supabase/20260330195700_install_mcp_sessions.sql +1 -0
  71. package/package.json +1 -1
  72. package/src/adapters/agui-adapter.ts +6 -2
  73. package/src/adapters/ai-adapter.ts +3 -1
  74. package/src/adapters/langchain-adapter.ts +5 -3
  75. package/src/adapters/mastra-adapter.ts +5 -3
  76. package/src/client/core/sse-client.ts +17 -0
  77. package/src/client/react/use-mcp.ts +63 -0
  78. package/src/server/handlers/sse-handler.ts +117 -10
  79. package/src/server/mcp/multi-session-client.ts +7 -3
  80. package/src/server/mcp/oauth-client.ts +78 -55
  81. package/src/server/mcp/tool-policy-gateway.ts +90 -0
  82. package/src/server/storage/index.ts +1 -1
  83. package/src/server/storage/neon-backend.ts +65 -60
  84. package/src/server/storage/session-lifecycle.ts +5 -0
  85. package/src/server/storage/supabase-backend.ts +27 -15
  86. package/src/server/storage/tool-policy.ts +89 -0
  87. package/src/server/storage/types.ts +11 -0
  88. package/src/shared/types.ts +48 -1
  89. package/src/shared/utils.ts +16 -14
@@ -4,6 +4,7 @@ import { DORMANT_SESSION_EXPIRATION_MS } from '../../shared/constants.js';
4
4
  import { generateSessionId } from '../../shared/utils.js';
5
5
  import { encryptObject, decryptObject } from './crypto.js';
6
6
  import { resolveSessionExpiresAt } from './session-lifecycle.js';
7
+ import { normalizeToolPolicy } from './tool-policy.js';
7
8
 
8
9
  export interface NeonStorageOptions {
9
10
  schema?: string;
@@ -30,6 +31,7 @@ type NeonSessionRow = {
30
31
  headers?: unknown;
31
32
  auth_url?: string | null;
32
33
  status?: SessionStatus | null;
34
+ tool_policy?: unknown;
33
35
  };
34
36
 
35
37
  type NeonCredentialsRow = {
@@ -103,6 +105,7 @@ export class NeonStorageBackend implements SessionStore {
103
105
  headers: decryptObject(row.headers),
104
106
  authUrl: row.auth_url ?? undefined,
105
107
  status: row.status ?? 'pending',
108
+ toolPolicy: normalizeToolPolicy(row.tool_policy as Parameters<typeof normalizeToolPolicy>[0]),
106
109
  };
107
110
  }
108
111
 
@@ -135,43 +138,35 @@ export class NeonStorageBackend implements SessionStore {
135
138
  const status = session.status ?? 'pending';
136
139
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
137
140
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
138
- const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
141
+ const createdAtMs = new Date(createdAt).getTime();
142
+ const expiresAt = resolveSessionExpiresAt(status, createdAtMs);
143
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy, createdAtMs);
144
+
145
+ const columns: string[] = [
146
+ 'session_id', 'user_id', 'server_id', 'server_name',
147
+ 'server_url', 'transport_type', 'callback_url',
148
+ 'created_at', 'updated_at', 'headers', 'auth_url',
149
+ 'status', 'expires_at',
150
+ ];
151
+ const values: unknown[] = [
152
+ sessionId, userId, session.serverId, session.serverName,
153
+ session.serverUrl, session.transportType, session.callbackUrl,
154
+ createdAt, updatedAt, encryptObject(session.headers),
155
+ session.authUrl ?? null, status,
156
+ expiresAt === null ? null : new Date(expiresAt).toISOString(),
157
+ ];
158
+
159
+ if (toolPolicy) {
160
+ columns.push('tool_policy');
161
+ values.push(toolPolicy);
162
+ }
163
+
164
+ const placeholders = values.map((_, i) => `$${i + 1}`);
139
165
 
140
166
  try {
141
167
  await this.sql.query(
142
- `INSERT INTO ${this.tableName} (
143
- session_id,
144
- user_id,
145
- server_id,
146
- server_name,
147
- server_url,
148
- transport_type,
149
- callback_url,
150
- created_at,
151
- updated_at,
152
- headers,
153
- auth_url,
154
- status,
155
- expires_at
156
- ) VALUES (
157
- $1, $2, $3, $4, $5, $6, $7, $8,
158
- $9, $10, $11, $12, $13
159
- )`,
160
- [
161
- sessionId,
162
- userId,
163
- session.serverId,
164
- session.serverName,
165
- session.serverUrl,
166
- session.transportType,
167
- session.callbackUrl,
168
- createdAt,
169
- updatedAt,
170
- encryptObject(session.headers),
171
- session.authUrl ?? null,
172
- status,
173
- expiresAt === null ? null : new Date(expiresAt).toISOString(),
174
- ]
168
+ `INSERT INTO ${this.tableName} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`,
169
+ values
175
170
  );
176
171
  } catch (error: any) {
177
172
  if (error.code === '23505') {
@@ -200,38 +195,44 @@ export class NeonStorageBackend implements SessionStore {
200
195
  'callbackUrl' in data ||
201
196
  'status' in data ||
202
197
  'headers' in data ||
203
- 'authUrl' in data
198
+ 'authUrl' in data ||
199
+ 'toolPolicy' in data
204
200
  );
205
201
 
206
202
  if (shouldUpdateSession) {
203
+ const setClauses: string[] = [];
204
+ const values: unknown[] = [];
205
+ let paramIndex = 1;
206
+
207
+ const addSet = (column: string, value: unknown) => {
208
+ setClauses.push(`${column} = $${paramIndex++}`);
209
+ values.push(value);
210
+ };
211
+
212
+ addSet('server_id', updatedSession.serverId);
213
+ addSet('server_name', updatedSession.serverName);
214
+ addSet('server_url', updatedSession.serverUrl);
215
+ addSet('transport_type', updatedSession.transportType);
216
+ addSet('callback_url', updatedSession.callbackUrl);
217
+ addSet('status', status);
218
+ addSet('headers', encryptObject(updatedSession.headers));
219
+ addSet('auth_url', updatedSession.authUrl ?? null);
220
+ addSet('expires_at', expiresAt === null ? null : new Date(expiresAt).toISOString());
221
+
222
+ if ('toolPolicy' in data) {
223
+ const policyUpdatedAt = updatedSession.updatedAt ?? Date.now();
224
+ const toolPolicy = normalizeToolPolicy(updatedSession.toolPolicy, policyUpdatedAt) ?? { mode: 'all' as const, toolIds: [], updatedAt: policyUpdatedAt };
225
+ addSet('tool_policy', toolPolicy);
226
+ }
227
+
228
+ setClauses.push('updated_at = now()');
229
+
207
230
  const updatedRows = await this.sql.query(
208
231
  `UPDATE ${this.tableName}
209
- SET
210
- server_id = $1,
211
- server_name = $2,
212
- server_url = $3,
213
- transport_type = $4,
214
- callback_url = $5,
215
- status = $6,
216
- headers = $7,
217
- auth_url = $8,
218
- expires_at = $9,
219
- updated_at = now()
220
- WHERE user_id = $10 AND session_id = $11
232
+ SET ${setClauses.join(', ')}
233
+ WHERE user_id = $${paramIndex++} AND session_id = $${paramIndex++}
221
234
  RETURNING id`,
222
- [
223
- updatedSession.serverId,
224
- updatedSession.serverName,
225
- updatedSession.serverUrl,
226
- updatedSession.transportType,
227
- updatedSession.callbackUrl,
228
- status,
229
- encryptObject(updatedSession.headers),
230
- updatedSession.authUrl ?? null,
231
- expiresAt === null ? null : new Date(expiresAt).toISOString(),
232
- userId,
233
- sessionId,
234
- ]
235
+ [...values, userId, sessionId]
235
236
  ) as Array<{ id: string }>;
236
237
 
237
238
  if (updatedRows.length === 0) {
@@ -412,3 +413,7 @@ export class NeonStorageBackend implements SessionStore {
412
413
  // Neon HTTP queries do not hold a persistent connection.
413
414
  }
414
415
  }
416
+
417
+
418
+
419
+
@@ -4,6 +4,7 @@ import {
4
4
  STATE_EXPIRATION_MS,
5
5
  } from '../../shared/constants.js';
6
6
  import type { Session, SessionStatus } from './types.js';
7
+ import { normalizeToolPolicy } from './tool-policy.js';
7
8
 
8
9
  export function resolveSessionExpiresAt(status: SessionStatus = 'pending', referenceTime = Date.now()): number | null {
9
10
  return status === 'active' ? null : referenceTime + STATE_EXPIRATION_MS;
@@ -26,6 +27,7 @@ export function normalizeNewSession(session: Session, now = Date.now()): Session
26
27
  createdAt,
27
28
  updatedAt,
28
29
  expiresAt: resolveSessionExpiresAt(status, status === 'active' ? updatedAt : createdAt),
30
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt),
29
31
  };
30
32
  }
31
33
 
@@ -46,6 +48,7 @@ export function mergeSessionUpdate(
46
48
  ...updated,
47
49
  status,
48
50
  expiresAt: resolveSessionExpiresAt(status, updatedAt),
51
+ toolPolicy: normalizeToolPolicy(updated.toolPolicy, updatedAt),
49
52
  };
50
53
  }
51
54
 
@@ -63,6 +66,7 @@ export function normalizeStoredSession(session: Session): Session {
63
66
  createdAt,
64
67
  updatedAt,
65
68
  expiresAt,
69
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt),
66
70
  };
67
71
  }
68
72
 
@@ -76,3 +80,4 @@ export function isSessionExpired(session: Session, now = Date.now()): boolean {
76
80
 
77
81
  return typeof hydrated.expiresAt === 'number' && hydrated.expiresAt < now;
78
82
  }
83
+
@@ -3,6 +3,7 @@ import type { SessionStore, Session, SessionCredentials } from './types.js';
3
3
  import { generateSessionId } from '../../shared/utils.js';
4
4
  import { encryptObject, decryptObject } from './crypto.js';
5
5
  import { resolveSessionExpiresAt } from './session-lifecycle.js';
6
+ import { normalizeToolPolicy } from './tool-policy.js';
6
7
  import { DORMANT_SESSION_EXPIRATION_MS } from '../../shared/constants.js';
7
8
 
8
9
  export class SupabaseStorageBackend implements SessionStore {
@@ -51,6 +52,7 @@ export class SupabaseStorageBackend implements SessionStore {
51
52
  headers: decryptObject(row.headers),
52
53
  authUrl: row.auth_url,
53
54
  status: row.status ?? 'pending',
55
+ toolPolicy: normalizeToolPolicy(row.tool_policy),
54
56
  };
55
57
  }
56
58
 
@@ -85,23 +87,30 @@ export class SupabaseStorageBackend implements SessionStore {
85
87
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
86
88
  const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
87
89
 
90
+ const insertData: Record<string, unknown> = {
91
+ session_id: sessionId,
92
+ user_id: userId,
93
+ server_id: session.serverId,
94
+ server_name: session.serverName,
95
+ server_url: session.serverUrl,
96
+ transport_type: session.transportType,
97
+ callback_url: session.callbackUrl,
98
+ created_at: createdAt,
99
+ updated_at: updatedAt,
100
+ headers: encryptObject(session.headers),
101
+ auth_url: session.authUrl ?? null,
102
+ status,
103
+ expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString(),
104
+ };
105
+
106
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy);
107
+ if (toolPolicy) {
108
+ insertData.tool_policy = toolPolicy;
109
+ }
110
+
88
111
  const { error } = await this.supabase
89
112
  .from('mcp_sessions')
90
- .insert({
91
- session_id: sessionId,
92
- user_id: userId,
93
- server_id: session.serverId,
94
- server_name: session.serverName,
95
- server_url: session.serverUrl,
96
- transport_type: session.transportType,
97
- callback_url: session.callbackUrl,
98
- created_at: createdAt,
99
- updated_at: updatedAt,
100
- headers: encryptObject(session.headers),
101
- auth_url: session.authUrl ?? null,
102
- status,
103
- expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString(),
104
- });
113
+ .insert(insertData);
105
114
 
106
115
  if (error) {
107
116
  if (error.code === '23505') {
@@ -130,6 +139,7 @@ export class SupabaseStorageBackend implements SessionStore {
130
139
  }
131
140
  if ('headers' in data) updateData.headers = encryptObject(data.headers);
132
141
  if ('authUrl' in data) updateData.auth_url = data.authUrl ?? null;
142
+ if ('toolPolicy' in data) updateData.tool_policy = normalizeToolPolicy(data.toolPolicy);
133
143
 
134
144
  const shouldUpdateSession = Object.keys(updateData).some((key) => key !== 'updated_at');
135
145
 
@@ -351,3 +361,5 @@ export class SupabaseStorageBackend implements SessionStore {
351
361
  // Supabase client handles its own connection pooling over HTTP.
352
362
  }
353
363
  }
364
+
365
+
@@ -0,0 +1,89 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { ToolPolicy } from './types.js';
3
+
4
+ export type ToolPolicyInput = {
5
+ mode?: unknown;
6
+ toolIds?: unknown;
7
+ updatedAt?: unknown;
8
+ } | null | undefined;
9
+
10
+ export function createToolId(serverId: string, toolName: string): string {
11
+ return `${serverId}::${toolName}`;
12
+ }
13
+
14
+ function normalizeToolIds(input?: unknown): string[] {
15
+ if (!Array.isArray(input)) return [];
16
+
17
+ return Array.from(new Set(
18
+ input
19
+ .filter((id): id is string => typeof id === 'string')
20
+ .map((id) => id.trim())
21
+ .filter(Boolean)
22
+ ));
23
+ }
24
+
25
+ export function normalizeToolPolicy(input?: ToolPolicyInput, now = Date.now()): ToolPolicy | undefined {
26
+ if (!input || typeof input !== 'object') {
27
+ return undefined;
28
+ }
29
+
30
+ const mode = input.mode === 'allowlist' || input.mode === 'denylist'
31
+ ? input.mode
32
+ : 'all';
33
+ const updatedAt = typeof input.updatedAt === 'number' && Number.isFinite(input.updatedAt)
34
+ ? input.updatedAt
35
+ : now;
36
+
37
+ if (mode === 'all') {
38
+ return { mode: 'all', toolIds: [], updatedAt };
39
+ }
40
+
41
+ return { mode, toolIds: normalizeToolIds(input.toolIds), updatedAt };
42
+ }
43
+
44
+ export function normalizeToolPolicyForUpdate(input: ToolPolicyInput, now = Date.now()): ToolPolicy {
45
+ return normalizeToolPolicy(input, now) ?? { mode: 'all', toolIds: [], updatedAt: now };
46
+ }
47
+
48
+ export function isToolAllowed(policy: ToolPolicy | undefined, toolName: string, serverId?: string): boolean {
49
+ if (!policy || policy.mode === 'all') return true;
50
+ if (!serverId) return false;
51
+
52
+ const toolId = createToolId(serverId, toolName);
53
+ if (policy.mode === 'allowlist') {
54
+ return policy.toolIds.includes(toolId);
55
+ }
56
+
57
+ return !policy.toolIds.includes(toolId);
58
+ }
59
+
60
+ export function assertToolAllowed(policy: ToolPolicy | undefined, toolName: string, serverId?: string): void {
61
+ if (isToolAllowed(policy, toolName, serverId)) return;
62
+ throw new Error(`Tool "${toolName}" is not allowed for this MCP session`);
63
+ }
64
+
65
+ export function filterToolsByPolicy<T extends Pick<Tool, 'name'>>(
66
+ tools: T[],
67
+ policy: ToolPolicy | undefined,
68
+ serverId?: string
69
+ ): T[] {
70
+ if (!policy || policy.mode === 'all') return tools;
71
+ return tools.filter((tool) => isToolAllowed(policy, tool.name, serverId));
72
+ }
73
+
74
+ export function validateToolPolicyAgainstTools(
75
+ policy: ToolPolicy,
76
+ tools: Array<Pick<Tool, 'name'>>,
77
+ serverId?: string
78
+ ): void {
79
+ if (policy.mode === 'all') return;
80
+ if (!serverId) {
81
+ throw new Error('Cannot validate MCP tool policy without a serverId');
82
+ }
83
+
84
+ const availableIds = new Set(tools.map((tool) => createToolId(serverId, tool.name)));
85
+ const unknownIds = policy.toolIds.filter((id) => !availableIds.has(id));
86
+ if (unknownIds.length > 0) {
87
+ throw new Error(`Unknown tool id(s) for this MCP session: ${unknownIds.join(', ')}`);
88
+ }
89
+ }
@@ -14,6 +14,14 @@ export interface OAuthState {
14
14
 
15
15
  export type SessionStatus = 'pending' | 'active';
16
16
 
17
+ export type ToolPolicyMode = 'all' | 'allowlist' | 'denylist';
18
+
19
+ export interface ToolPolicy {
20
+ mode: ToolPolicyMode;
21
+ toolIds: string[];
22
+ updatedAt: number;
23
+ }
24
+
17
25
  export interface Session {
18
26
  sessionId: string;
19
27
  serverId?: string; // Database server ID for mapping
@@ -37,6 +45,7 @@ export interface Session {
37
45
  * - active: restorable session after successful connection/auth completion
38
46
  */
39
47
  status?: SessionStatus;
48
+ toolPolicy?: ToolPolicy;
40
49
  }
41
50
 
42
51
  export interface SessionCredentials {
@@ -158,3 +167,5 @@ export interface SessionStore {
158
167
  */
159
168
  disconnect(): Promise<void>;
160
169
  }
170
+
171
+
@@ -21,6 +21,7 @@ export interface ToolClient {
21
21
  callTool(name: string, args: Record<string, unknown>): Promise<any>;
22
22
  getServerId?(): string | undefined;
23
23
  getServerName?(): string | undefined;
24
+ getServerUrl?(): string | undefined;
24
25
  getSessionId?(): string;
25
26
  }
26
27
 
@@ -169,6 +170,13 @@ export type ToolInfo = {
169
170
  // Transport type
170
171
  export type TransportType = 'sse' | 'streamable-http';
171
172
  export type SessionStatus = 'pending' | 'active';
173
+ export type ToolPolicyMode = 'all' | 'allowlist' | 'denylist';
174
+
175
+ export interface ToolPolicy {
176
+ mode: ToolPolicyMode;
177
+ toolIds: string[];
178
+ updatedAt: number;
179
+ }
172
180
 
173
181
  // SSE/RPC types
174
182
  export type McpRpcMethod =
@@ -182,7 +190,9 @@ export type McpRpcMethod =
182
190
  | 'listPrompts'
183
191
  | 'getPrompt'
184
192
  | 'listResources'
185
- | 'readResource';
193
+ | 'readResource'
194
+ | 'setToolPolicy'
195
+ | 'getToolPolicy';
186
196
 
187
197
  export interface McpRpcRequest {
188
198
  id: string;
@@ -239,6 +249,18 @@ export interface FinishAuthParams {
239
249
  code: string;
240
250
  }
241
251
 
252
+ export interface SetToolPolicyParams {
253
+ sessionId: string;
254
+ toolPolicy: {
255
+ mode: ToolPolicyMode;
256
+ toolIds?: string[];
257
+ };
258
+ }
259
+
260
+ export interface GetToolPolicyParams {
261
+ sessionId: string;
262
+ }
263
+
242
264
  export type McpRpcParams =
243
265
  | ConnectParams
244
266
  | DisconnectParams
@@ -247,6 +269,8 @@ export type McpRpcParams =
247
269
  | GetPromptParams
248
270
  | ReadResourceParams
249
271
  | FinishAuthParams
272
+ | SetToolPolicyParams
273
+ | GetToolPolicyParams
250
274
  | undefined;
251
275
 
252
276
  // RPC Result Types
@@ -263,6 +287,7 @@ export interface SessionInfo {
263
287
  * `pending` means auth is in progress and should be resumed explicitly by user action.
264
288
  */
265
289
  status: SessionStatus;
290
+ toolPolicy?: ToolPolicy;
266
291
  }
267
292
 
268
293
  export interface SessionListResult {
@@ -292,6 +317,25 @@ export interface ListToolsRpcResult {
292
317
  tools: Tool[];
293
318
  }
294
319
 
320
+ export interface SetToolPolicyResult {
321
+ success: boolean;
322
+ toolPolicy: ToolPolicy;
323
+ tools: Tool[];
324
+ toolCount: number;
325
+ }
326
+
327
+ export type ToolAccessInfo = Tool & {
328
+ toolId: string;
329
+ allowed: boolean;
330
+ };
331
+
332
+ export interface GetToolPolicyResult {
333
+ toolPolicy: ToolPolicy;
334
+ tools: ToolAccessInfo[];
335
+ toolCount: number;
336
+ allowedToolCount: number;
337
+ }
338
+
295
339
  export interface ListPromptsResult {
296
340
  prompts: Array<{
297
341
  name: string;
@@ -314,3 +358,6 @@ export interface ListResourcesResult {
314
358
  }
315
359
 
316
360
  export type { CallToolResult };
361
+
362
+
363
+
@@ -1,17 +1,10 @@
1
- import { customAlphabet } from 'nanoid';
1
+ import { customAlphabet, nanoid } from 'nanoid';
2
2
 
3
3
  const OAUTH_STATE_SEPARATOR = '.';
4
4
 
5
- /** first char: letters only (required by OpenAI) */
6
- const firstChar = customAlphabet(
7
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
8
- 1
9
- );
10
-
11
- /** remaining chars: alphanumeric */
12
- const rest = customAlphabet(
13
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
14
- 11
5
+ const serverIdAlphabet = customAlphabet(
6
+ 'abcdefghijklmnopqrstuvwxyz0123456789',
7
+ 12
15
8
  );
16
9
 
17
10
  /**
@@ -32,11 +25,20 @@ export function sanitizeServerLabel(name: string): string {
32
25
  }
33
26
 
34
27
  /**
35
- * Generates a standard 12-character session ID compliant with external tool restrictions.
36
- * First character is always a letter.
28
+ * Generates a session ID with a human-readable prefix for easy identification.
29
+ * Format: sess_<21-char nanoid> (26 chars total).
30
+ * Not used in tool names, so length is not constrained.
37
31
  */
38
32
  export function generateSessionId(): string {
39
- return firstChar() + rest();
33
+ return 'sess_' + nanoid(21);
34
+ }
35
+
36
+ /**
37
+ * Generates a short server ID suitable for use in tool names.
38
+ * 12-char alphanumeric string that keeps tool_<serverId>_<toolName> under 64 chars.
39
+ */
40
+ export function generateServerId(): string {
41
+ return serverIdAlphabet();
40
42
  }
41
43
 
42
44
  export interface ParsedOAuthState {