@kya-os/mcp-i 1.5.2 → 1.5.3-canary.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.
@@ -4,6 +4,15 @@ exports.addToolsToServer = addToolsToServer;
4
4
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
5
5
  const proof_1 = require("../proof");
6
6
  const identity_1 = require("../identity");
7
+ const tool_protection_registry_1 = require("../tool-protection-registry");
8
+ const auth_handshake_1 = require("../auth-handshake");
9
+ const session_1 = require("../session");
10
+ const request_context_1 = require("../request-context");
11
+ const proof_batch_queue_1 = require("../proof-batch-queue");
12
+ // Parse runtime config path from injected variable
13
+ // @ts-expect-error: injected by compiler
14
+ const rawRuntimeConfigPath = typeof RUNTIME_CONFIG_PATH !== "undefined" ? RUNTIME_CONFIG_PATH : undefined;
15
+ const runtimeConfigPath = rawRuntimeConfigPath ? JSON.parse(rawRuntimeConfigPath) : null;
7
16
  /** Validates if a value is a valid Zod schema object */
8
17
  function isZodRawShape(value) {
9
18
  if (typeof value !== "object" || value === null) {
@@ -24,6 +33,83 @@ function pathToName(path) {
24
33
  const fileName = path.split("/").pop() || path;
25
34
  return fileName.replace(/\.[^/.]+$/, "");
26
35
  }
36
+ // Global ProofBatchQueue instance (singleton)
37
+ let globalProofQueue = null;
38
+ /** Load runtime config and initialize ProofBatchQueue if configured */
39
+ async function initializeProofBatchQueue(debug) {
40
+ // Return existing instance if already initialized
41
+ if (globalProofQueue) {
42
+ return globalProofQueue;
43
+ }
44
+ // Check if runtime config path is available
45
+ if (!runtimeConfigPath) {
46
+ if (debug) {
47
+ console.error("[MCPI] No runtime config found - proof submission disabled");
48
+ }
49
+ return null;
50
+ }
51
+ try {
52
+ if (debug) {
53
+ console.error("[MCPI] Loading runtime config from:", runtimeConfigPath);
54
+ }
55
+ // Dynamically import the runtime config
56
+ const configModule = await import(runtimeConfigPath);
57
+ const runtimeConfig = typeof configModule.getRuntimeConfig === "function"
58
+ ? configModule.getRuntimeConfig()
59
+ : configModule.default;
60
+ // Check if proofing is enabled
61
+ if (!runtimeConfig?.proofing?.enabled) {
62
+ if (debug) {
63
+ console.error("[MCPI] Proofing is disabled in runtime config");
64
+ }
65
+ return null;
66
+ }
67
+ const proofingConfig = runtimeConfig.proofing;
68
+ if (!proofingConfig.batchQueue?.destinations || proofingConfig.batchQueue.destinations.length === 0) {
69
+ if (debug) {
70
+ console.error("[MCPI] No proof destinations configured");
71
+ }
72
+ return null;
73
+ }
74
+ // Create proof destinations
75
+ const destinations = [];
76
+ for (const destConfig of proofingConfig.batchQueue.destinations) {
77
+ if (destConfig.type === "agentshield") {
78
+ destinations.push(new proof_batch_queue_1.AgentShieldProofDestination(destConfig.apiUrl, destConfig.apiKey));
79
+ if (debug) {
80
+ console.error(`[MCPI] Added AgentShield destination: ${destConfig.apiUrl}`);
81
+ }
82
+ }
83
+ // Add other destination types here (KTA, etc.)
84
+ }
85
+ if (destinations.length === 0) {
86
+ if (debug) {
87
+ console.error("[MCPI] No valid proof destinations configured");
88
+ }
89
+ return null;
90
+ }
91
+ // Initialize ProofBatchQueue
92
+ globalProofQueue = new proof_batch_queue_1.ProofBatchQueue({
93
+ destinations,
94
+ maxBatchSize: proofingConfig.batchQueue.maxBatchSize || 10,
95
+ flushIntervalMs: proofingConfig.batchQueue.flushIntervalMs || 5000,
96
+ maxRetries: proofingConfig.batchQueue.maxRetries || 3,
97
+ debug: proofingConfig.batchQueue.debug || debug,
98
+ });
99
+ if (debug) {
100
+ console.error(`[MCPI] ProofBatchQueue initialized (batch: ${proofingConfig.batchQueue.maxBatchSize}, ` +
101
+ `flush: ${proofingConfig.batchQueue.flushIntervalMs}ms, ` +
102
+ `destinations: ${destinations.length})`);
103
+ }
104
+ return globalProofQueue;
105
+ }
106
+ catch (error) {
107
+ if (debug) {
108
+ console.error("[MCPI] Failed to initialize ProofBatchQueue:", error);
109
+ }
110
+ return null;
111
+ }
112
+ }
27
113
  if (typeof global !== 'undefined') {
28
114
  global.__MCPI_HANDLERS_REGISTERED__ = global.__MCPI_HANDLERS_REGISTERED__ || false;
29
115
  }
@@ -44,6 +130,8 @@ async function addToolsToServer(server, toolModules, identityConfig) {
44
130
  if (identityConfig?.debug) {
45
131
  console.error("[MCPI] Registering handlers for the first time");
46
132
  }
133
+ // Initialize session manager for handshake support
134
+ const sessionManager = new session_1.SessionManager();
47
135
  // Initialize identity manager if identity is enabled
48
136
  let identityManager = null;
49
137
  if (identityConfig?.enabled) {
@@ -68,6 +156,8 @@ async function addToolsToServer(server, toolModules, identityConfig) {
68
156
  // Continue without identity if initialization fails
69
157
  }
70
158
  }
159
+ // Initialize ProofBatchQueue for proof submission (if runtime config exists)
160
+ await initializeProofBatchQueue(identityConfig?.debug);
71
161
  // Collect all tools and their handlers
72
162
  const tools = [];
73
163
  const toolHandlers = new Map();
@@ -113,8 +203,12 @@ async function addToolsToServer(server, toolModules, identityConfig) {
113
203
  });
114
204
  // Debug: log server state before registering handler
115
205
  if (identityConfig?.debug) {
116
- console.error("[MCPI] About to register tools/list handler");
206
+ console.error("[MCPI] About to register handlers (tools/list, tools/call)");
117
207
  }
208
+ // NOTE: Custom handshake handler removed - it was causing registration errors
209
+ // Handshake/session management should be handled via HTTP headers or custom tools
210
+ // The setRequestHandler expects a Zod schema, not a request object
211
+ // TODO: Implement handshake as a custom tool if needed
118
212
  // Register tools/list handler
119
213
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async (request) => {
120
214
  return {
@@ -123,92 +217,215 @@ async function addToolsToServer(server, toolModules, identityConfig) {
123
217
  });
124
218
  // Register tools/call handler
125
219
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
126
- const { name, arguments: args } = request.params;
127
- const handler = toolHandlers.get(name);
128
- if (!handler) {
129
- return {
130
- content: [
131
- {
132
- type: "text",
133
- text: `Tool "${name}" not found`,
134
- },
135
- ],
136
- isError: true,
137
- };
220
+ // Extract sessionId from request metadata (XMCP-I extension)
221
+ const sessionId = request._meta?.sessionId;
222
+ let session = null;
223
+ if (sessionId) {
224
+ session = await sessionManager.getSession(sessionId);
225
+ if (!session && identityConfig?.debug) {
226
+ console.error(`[MCPI] Session not found or expired: ${sessionId}`);
227
+ }
138
228
  }
139
- try {
140
- // Execute the tool handler
141
- const result = await handler(args || {});
142
- // Build base response
143
- const baseResponse = {
144
- content: [
145
- {
146
- type: "text",
147
- text: typeof result === "string" ? result : JSON.stringify(result),
148
- },
149
- ],
150
- };
151
- // If identity is enabled, generate cryptographic proof
152
- if (identityManager) {
153
- try {
154
- // Ensure identity exists
155
- const identity = await identityManager.ensureIdentity();
156
- // Create a session context for this request
157
- const toolRequest = {
158
- method: name,
159
- params: args || {},
160
- };
161
- const toolResponse = {
162
- data: result,
163
- };
164
- // Create a session context for standalone tool calls
165
- // In a full implementation, this would use proper handshake
166
- // TODO: Use proper handshake
167
- const timestamp = Math.floor(Date.now() / 1000);
168
- const session = {
169
- sessionId: `tool-${Date.now()}`,
170
- nonce: `${Date.now()}`,
171
- audience: "client",
172
- createdAt: timestamp,
173
- timestamp,
174
- lastActivity: timestamp,
175
- ttlMinutes: 30,
229
+ // Wrap tool execution with request context for AsyncLocalStorage
230
+ return (0, request_context_1.runWithContext)({
231
+ session: session || undefined,
232
+ requestId: request._meta?.requestId,
233
+ startTime: Date.now(),
234
+ }, async () => {
235
+ const { name, arguments: args } = request.params;
236
+ const handler = toolHandlers.get(name);
237
+ if (!handler) {
238
+ return {
239
+ content: [
240
+ {
241
+ type: "text",
242
+ text: `Tool "${name}" not found`,
243
+ },
244
+ ],
245
+ isError: true,
246
+ };
247
+ }
248
+ // PHASE 1.5: Check if tool is protected BEFORE executing
249
+ const toolProtection = tool_protection_registry_1.toolProtectionRegistry.get(name);
250
+ if (toolProtection?.requiresDelegation) {
251
+ const authConfig = tool_protection_registry_1.toolProtectionRegistry.getAuthConfig();
252
+ const delegationVerifier = tool_protection_registry_1.toolProtectionRegistry.getDelegationVerifier();
253
+ if (!delegationVerifier || !authConfig) {
254
+ if (identityConfig?.debug) {
255
+ console.error(`[MCPI] Tool "${name}" requires delegation but verifier not configured`);
256
+ }
257
+ return {
258
+ content: [
259
+ {
260
+ type: "text",
261
+ text: `❌ Tool "${name}" requires delegation verification, but the delegation system is not configured.\n\n` +
262
+ `To fix this:\n` +
263
+ `1. Configure a delegation verifier:\n` +
264
+ ` - MemoryDelegationVerifier (development/testing)\n` +
265
+ ` - KVDelegationVerifier (production with KV store)\n` +
266
+ ` - AgentShieldVerifier (production with AgentShield service)\n\n` +
267
+ `2. Enable delegation in your runtime config:\n` +
268
+ ` const runtime = new MCPIRuntime({\n` +
269
+ ` delegation: { enabled: true },\n` +
270
+ ` // ... other config\n` +
271
+ ` });\n\n` +
272
+ `3. Ensure the verifier is initialized before tool execution.\n\n` +
273
+ `📚 Documentation: https://docs.mcp-i.dev/delegation/setup`,
274
+ },
275
+ ],
276
+ isError: true,
176
277
  };
177
- // Generate proof using the proof generator
178
- const proofGen = new proof_1.ProofGenerator(identity);
179
- const proof = await proofGen.generateProof(toolRequest, toolResponse, session);
278
+ }
279
+ // Extract agent DID from current request context (AsyncLocalStorage)
280
+ const agentDid = (0, request_context_1.getCurrentAgentDid)();
281
+ if (!agentDid) {
180
282
  if (identityConfig?.debug) {
181
- console.error(`[MCPI] Generated proof for tool "${name}" - DID: ${proof.meta.did}`);
283
+ console.error(`[MCPI] Tool "${name}" requires delegation but no agent DID in session`);
182
284
  }
183
- // Return response with proof metadata
184
285
  return {
185
- ...baseResponse,
186
- _meta: {
187
- proof,
188
- },
286
+ content: [
287
+ {
288
+ type: "text",
289
+ text: `❌ Tool "${name}" requires delegation, but no agent identity was found in the current session.\n\n` +
290
+ `This usually means one of the following:\n\n` +
291
+ `1. **No handshake was performed**: The MCP client must send a handshake request before calling protected tools.\n` +
292
+ ` Example handshake:\n` +
293
+ ` {\n` +
294
+ ` "agentDid": "did:key:z6Mk...",\n` +
295
+ ` "nonce": "unique-value",\n` +
296
+ ` "audience": "server-did",\n` +
297
+ ` "timestamp": ${Date.now()}\n` +
298
+ ` }\n\n` +
299
+ `2. **Handshake missing agentDid**: Ensure the handshake request includes the 'agentDid' field.\n\n` +
300
+ `3. **Session expired**: The session may have expired. Try performing a new handshake.\n\n` +
301
+ `4. **Request missing sessionId**: Ensure tool call requests include _meta.sessionId from the handshake response.\n\n` +
302
+ `📚 Learn more: https://docs.mcp-i.dev/delegation/handshake`,
303
+ },
304
+ ],
305
+ isError: true,
189
306
  };
190
307
  }
191
- catch (proofError) {
308
+ if (identityConfig?.debug) {
309
+ console.error(`[MCPI] Tool "${name}" requires delegation - verifying scopes: ${toolProtection.requiredScopes?.join(', ')}`);
310
+ }
311
+ // Verify delegation
312
+ const verifyResult = await (0, auth_handshake_1.verifyOrHints)(agentDid, toolProtection.requiredScopes || [], authConfig);
313
+ // If not authorized, return needs_authorization error
314
+ if (!verifyResult.authorized) {
192
315
  if (identityConfig?.debug) {
193
- console.error(`[MCPI] Failed to generate proof for tool "${name}":`, proofError);
316
+ console.error(`[MCPI] Tool "${name}" blocked - authorization required`);
194
317
  }
195
- // Return base response without proof if generation fails
196
- return baseResponse;
318
+ // Return MCP error format with authorization hints
319
+ return {
320
+ content: [
321
+ {
322
+ type: "text",
323
+ text: JSON.stringify({
324
+ error: "needs_authorization",
325
+ message: `Tool "${name}" requires user authorization`,
326
+ authorizationUrl: verifyResult.authError?.authorizationUrl,
327
+ resumeToken: verifyResult.authError?.resumeToken,
328
+ scopes: verifyResult.authError?.scopes,
329
+ display: verifyResult.authError?.display,
330
+ }),
331
+ },
332
+ ],
333
+ isError: true,
334
+ };
335
+ }
336
+ if (identityConfig?.debug) {
337
+ console.error(`[MCPI] Tool "${name}" authorized - executing handler`);
197
338
  }
198
339
  }
199
- return baseResponse;
200
- }
201
- catch (error) {
202
- return {
203
- content: [
204
- {
205
- type: "text",
206
- text: `Error executing tool "${name}": ${error instanceof Error ? error.message : String(error)}`,
207
- },
208
- ],
209
- isError: true,
210
- };
211
- }
340
+ try {
341
+ // Execute the tool handler
342
+ const result = await handler(args || {});
343
+ // Build base response
344
+ const baseResponse = {
345
+ content: [
346
+ {
347
+ type: "text",
348
+ text: typeof result === "string" ? result : JSON.stringify(result),
349
+ },
350
+ ],
351
+ };
352
+ // If identity is enabled, generate cryptographic proof
353
+ if (identityManager) {
354
+ try {
355
+ // Ensure identity exists
356
+ const identity = await identityManager.ensureIdentity();
357
+ // Create a session context for this request
358
+ const toolRequest = {
359
+ method: name,
360
+ params: args || {},
361
+ };
362
+ const toolResponse = {
363
+ data: result,
364
+ };
365
+ // Create a session context for standalone tool calls
366
+ // In a full implementation, this would use proper handshake
367
+ // TODO: Use proper handshake
368
+ const timestamp = Math.floor(Date.now() / 1000);
369
+ const session = {
370
+ sessionId: `tool-${Date.now()}`,
371
+ nonce: `${Date.now()}`,
372
+ audience: "client",
373
+ createdAt: timestamp,
374
+ timestamp,
375
+ lastActivity: timestamp,
376
+ ttlMinutes: 30,
377
+ };
378
+ // Generate proof using the proof generator
379
+ const proofGen = new proof_1.ProofGenerator(identity);
380
+ const proof = await proofGen.generateProof(toolRequest, toolResponse, session);
381
+ if (identityConfig?.debug) {
382
+ console.error(`[MCPI] Generated proof for tool "${name}" - DID: ${proof.meta.did}`);
383
+ }
384
+ // Submit proof to ProofBatchQueue (if initialized)
385
+ if (globalProofQueue) {
386
+ try {
387
+ globalProofQueue.enqueue(proof);
388
+ if (identityConfig?.debug) {
389
+ console.error(`[MCPI] Proof enqueued for submission to AgentShield`);
390
+ }
391
+ }
392
+ catch (queueError) {
393
+ if (identityConfig?.debug) {
394
+ console.error(`[MCPI] Failed to enqueue proof:`, queueError);
395
+ }
396
+ // Continue even if queueing fails - proof is still in response
397
+ }
398
+ }
399
+ // Return response with proof metadata
400
+ return {
401
+ ...baseResponse,
402
+ _meta: {
403
+ proof,
404
+ },
405
+ };
406
+ }
407
+ catch (proofError) {
408
+ if (identityConfig?.debug) {
409
+ console.error(`[MCPI] Failed to generate proof for tool "${name}":`, proofError);
410
+ }
411
+ // Return base response without proof if generation fails
412
+ return baseResponse;
413
+ }
414
+ }
415
+ return baseResponse;
416
+ }
417
+ catch (error) {
418
+ return {
419
+ content: [
420
+ {
421
+ type: "text",
422
+ text: `Error executing tool "${name}": ${error instanceof Error ? error.message : String(error)}`,
423
+ },
424
+ ],
425
+ isError: true,
426
+ };
427
+ }
428
+ });
212
429
  });
213
430
  if (identityConfig?.debug) {
214
431
  console.error("[MCPI] Handlers registered successfully");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-os/mcp-i",
3
- "version": "1.5.2",
3
+ "version": "1.5.3-canary.1",
4
4
  "description": "The TypeScript MCP framework with identity features built-in",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",