@kya-os/mcp-i 1.6.1 → 1.6.2-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.
- package/dist/compiler/index.js +12 -2
- package/dist/compiler/parse-xmcp-config.js +5 -0
- package/dist/runtime/adapter-express.js +1 -1
- package/dist/runtime/adapter-nextjs.js +1 -1
- package/dist/runtime/http.js +1 -1
- package/dist/runtime/mcpi-runtime.js +7 -1
- package/dist/runtime/session.d.ts +13 -0
- package/dist/runtime/session.js +43 -0
- package/dist/runtime/stdio.js +1 -1
- package/dist/runtime/utils/tools.js +196 -23
- package/package.json +3 -3
|
@@ -9,12 +9,12 @@ const auth_handshake_1 = require("../auth-handshake");
|
|
|
9
9
|
const session_1 = require("../session");
|
|
10
10
|
const request_context_1 = require("../request-context");
|
|
11
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
12
|
const rawRuntimeConfigPath = typeof RUNTIME_CONFIG_PATH !== "undefined" ? RUNTIME_CONFIG_PATH : undefined;
|
|
15
13
|
// Single-parse to match single-stringify from webpack DefinePlugin
|
|
16
14
|
const runtimeConfigPath = rawRuntimeConfigPath
|
|
17
|
-
?
|
|
15
|
+
? typeof rawRuntimeConfigPath === "string"
|
|
16
|
+
? JSON.parse(rawRuntimeConfigPath)
|
|
17
|
+
: rawRuntimeConfigPath
|
|
18
18
|
: null;
|
|
19
19
|
/** Validates if a value is a valid Zod schema object */
|
|
20
20
|
function isZodRawShape(value) {
|
|
@@ -68,7 +68,8 @@ async function initializeProofBatchQueue(debug) {
|
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
70
|
const proofingConfig = runtimeConfig.proofing;
|
|
71
|
-
if (!proofingConfig.batchQueue?.destinations ||
|
|
71
|
+
if (!proofingConfig.batchQueue?.destinations ||
|
|
72
|
+
proofingConfig.batchQueue.destinations.length === 0) {
|
|
72
73
|
if (debug) {
|
|
73
74
|
console.error("[MCPI] No proof destinations configured");
|
|
74
75
|
}
|
|
@@ -113,14 +114,15 @@ async function initializeProofBatchQueue(debug) {
|
|
|
113
114
|
return null;
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
|
-
if (typeof global !==
|
|
117
|
-
global.__MCPI_HANDLERS_REGISTERED__ =
|
|
117
|
+
if (typeof global !== "undefined") {
|
|
118
|
+
global.__MCPI_HANDLERS_REGISTERED__ =
|
|
119
|
+
global.__MCPI_HANDLERS_REGISTERED__ || false;
|
|
118
120
|
}
|
|
119
121
|
/** Loads tools and injects them into the server */
|
|
120
122
|
async function addToolsToServer(server, toolModules, identityConfig) {
|
|
121
123
|
// Guard against double registration using global scope
|
|
122
124
|
// Set this IMMEDIATELY to prevent race conditions
|
|
123
|
-
if (typeof global !==
|
|
125
|
+
if (typeof global !== "undefined") {
|
|
124
126
|
if (global.__MCPI_HANDLERS_REGISTERED__) {
|
|
125
127
|
if (identityConfig?.debug) {
|
|
126
128
|
console.error("[MCPI] Handlers already registered, skipping duplicate registration");
|
|
@@ -149,7 +151,7 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
149
151
|
const identity = await identityManager.ensureIdentity();
|
|
150
152
|
if (identityConfig.debug) {
|
|
151
153
|
console.error(`[MCPI] Identity enabled - DID: ${identity.did}`);
|
|
152
|
-
console.error(`[MCPI] Identity path: ${identityConfig.devIdentityPath ||
|
|
154
|
+
console.error(`[MCPI] Identity path: ${identityConfig.devIdentityPath || ".mcpi/identity.json"}`);
|
|
153
155
|
}
|
|
154
156
|
}
|
|
155
157
|
catch (error) {
|
|
@@ -157,6 +159,8 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
157
159
|
console.error("[MCPI] Failed to initialize identity:", error);
|
|
158
160
|
}
|
|
159
161
|
// Continue without identity if initialization fails
|
|
162
|
+
// Set identityManager to null to prevent later unhandled calls to ensureIdentity()
|
|
163
|
+
identityManager = null;
|
|
160
164
|
}
|
|
161
165
|
}
|
|
162
166
|
// Initialize ProofBatchQueue for proof submission (if runtime config exists)
|
|
@@ -206,12 +210,180 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
206
210
|
});
|
|
207
211
|
// Debug: log server state before registering handler
|
|
208
212
|
if (identityConfig?.debug) {
|
|
209
|
-
console.error("[MCPI] About to register handlers (tools/list, tools/call)");
|
|
213
|
+
console.error("[MCPI] About to register handlers (tools/list, tools/call, handshake)");
|
|
210
214
|
}
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
// Set server DID on session manager after identity is loaded
|
|
216
|
+
if (identityManager) {
|
|
217
|
+
try {
|
|
218
|
+
const identity = await identityManager.ensureIdentity();
|
|
219
|
+
sessionManager.setServerDid(identity.did);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
// If identity initialization failed earlier, ensureIdentity() may throw again
|
|
223
|
+
// Log but don't crash - server can operate without identity
|
|
224
|
+
if (identityConfig?.debug) {
|
|
225
|
+
console.error("[MCPI] Failed to set server DID on session manager:", error);
|
|
226
|
+
}
|
|
227
|
+
// Set to null to prevent future attempts
|
|
228
|
+
identityManager = null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Add internal handshake tool for MCP-I session establishment
|
|
232
|
+
// This allows MCP clients to perform a handshake and receive a sessionId
|
|
233
|
+
const handshakeTool = {
|
|
234
|
+
name: "_mcpi_handshake",
|
|
235
|
+
description: "Internal MCP-I handshake tool for session establishment. Clients should call this before protected tools.",
|
|
236
|
+
inputSchema: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: {
|
|
239
|
+
nonce: {
|
|
240
|
+
type: "string",
|
|
241
|
+
description: "Unique nonce for replay prevention",
|
|
242
|
+
},
|
|
243
|
+
audience: {
|
|
244
|
+
type: "string",
|
|
245
|
+
description: "Target audience (server DID or URL)",
|
|
246
|
+
},
|
|
247
|
+
timestamp: { type: "number", description: "Unix timestamp in seconds" },
|
|
248
|
+
agentDid: {
|
|
249
|
+
type: "string",
|
|
250
|
+
description: "Agent DID for delegation verification (optional)",
|
|
251
|
+
},
|
|
252
|
+
clientInfo: {
|
|
253
|
+
type: "object",
|
|
254
|
+
description: "MCP client metadata (optional)",
|
|
255
|
+
properties: {
|
|
256
|
+
name: { type: "string" },
|
|
257
|
+
version: { type: "string" },
|
|
258
|
+
platform: { type: "string" },
|
|
259
|
+
vendor: { type: "string" },
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
clientProtocolVersion: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description: "MCP protocol version (optional)",
|
|
265
|
+
},
|
|
266
|
+
clientCapabilities: {
|
|
267
|
+
type: "object",
|
|
268
|
+
description: "MCP client capabilities (optional)",
|
|
269
|
+
additionalProperties: true,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
required: ["nonce", "audience", "timestamp"],
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
tools.push(handshakeTool);
|
|
276
|
+
// Handshake tool handler
|
|
277
|
+
toolHandlers.set("_mcpi_handshake", async (args) => {
|
|
278
|
+
// Validate required fields explicitly before conversion
|
|
279
|
+
const missingFields = [];
|
|
280
|
+
if (!args.nonce ||
|
|
281
|
+
(typeof args.nonce === "string" && args.nonce.trim().length === 0)) {
|
|
282
|
+
missingFields.push("nonce");
|
|
283
|
+
}
|
|
284
|
+
if (!args.audience ||
|
|
285
|
+
(typeof args.audience === "string" && args.audience.trim().length === 0)) {
|
|
286
|
+
missingFields.push("audience");
|
|
287
|
+
}
|
|
288
|
+
// Validate timestamp is present and valid
|
|
289
|
+
if (args.timestamp === undefined ||
|
|
290
|
+
args.timestamp === null ||
|
|
291
|
+
typeof args.timestamp !== "number" ||
|
|
292
|
+
args.timestamp <= 0 ||
|
|
293
|
+
!Number.isInteger(args.timestamp)) {
|
|
294
|
+
if (args.timestamp === undefined || args.timestamp === null) {
|
|
295
|
+
missingFields.push("timestamp");
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Invalid format (not a positive integer)
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
error: {
|
|
302
|
+
code: "XMCP_I_EHANDSHAKE",
|
|
303
|
+
message: "Invalid timestamp: must be a positive integer (Unix timestamp in seconds)",
|
|
304
|
+
details: {
|
|
305
|
+
received: typeof args.timestamp,
|
|
306
|
+
value: args.timestamp,
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (missingFields.length > 0) {
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
error: {
|
|
316
|
+
code: "XMCP_I_EHANDSHAKE",
|
|
317
|
+
message: `Missing required field(s): ${missingFields.join(", ")}`,
|
|
318
|
+
details: {
|
|
319
|
+
missingFields,
|
|
320
|
+
requiredFields: ["nonce", "audience", "timestamp"],
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
// At this point, timestamp is guaranteed to be a valid positive integer
|
|
326
|
+
const timestamp = args.timestamp;
|
|
327
|
+
// Build handshake request from validated tool arguments
|
|
328
|
+
const handshakeRequest = {
|
|
329
|
+
nonce: String(args.nonce).trim(),
|
|
330
|
+
audience: String(args.audience).trim(),
|
|
331
|
+
timestamp,
|
|
332
|
+
agentDid: typeof args.agentDid === "string" && args.agentDid.trim().length > 0
|
|
333
|
+
? args.agentDid.trim()
|
|
334
|
+
: undefined,
|
|
335
|
+
clientInfo: typeof args.clientInfo === "object" && args.clientInfo !== null
|
|
336
|
+
? args.clientInfo
|
|
337
|
+
: undefined,
|
|
338
|
+
clientProtocolVersion: typeof args.clientProtocolVersion === "string" &&
|
|
339
|
+
args.clientProtocolVersion.trim().length > 0
|
|
340
|
+
? args.clientProtocolVersion.trim()
|
|
341
|
+
: undefined,
|
|
342
|
+
clientCapabilities: typeof args.clientCapabilities === "object" &&
|
|
343
|
+
args.clientCapabilities !== null
|
|
344
|
+
? args.clientCapabilities
|
|
345
|
+
: undefined,
|
|
346
|
+
};
|
|
347
|
+
// Double-check format (should always pass at this point, but defensive)
|
|
348
|
+
if (!(0, session_1.validateHandshakeFormat)(handshakeRequest)) {
|
|
349
|
+
return {
|
|
350
|
+
success: false,
|
|
351
|
+
error: {
|
|
352
|
+
code: "XMCP_I_EHANDSHAKE",
|
|
353
|
+
message: "Invalid handshake request format. Required: nonce, audience, timestamp",
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
// Validate handshake and create session
|
|
358
|
+
const result = await sessionManager.validateHandshake(handshakeRequest);
|
|
359
|
+
// Log clientInfo in development mode
|
|
360
|
+
if (identityConfig?.debug &&
|
|
361
|
+
result.success &&
|
|
362
|
+
result.session?.clientInfo) {
|
|
363
|
+
console.error("[MCPI] Client connected:", {
|
|
364
|
+
name: result.session.clientInfo.name,
|
|
365
|
+
version: result.session.clientInfo.version,
|
|
366
|
+
platform: result.session.clientInfo.platform,
|
|
367
|
+
vendor: result.session.clientInfo.vendor,
|
|
368
|
+
clientId: result.session.clientInfo.clientId,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (result.success && result.session) {
|
|
372
|
+
return {
|
|
373
|
+
success: true,
|
|
374
|
+
sessionId: result.session.sessionId,
|
|
375
|
+
serverDid: result.session.serverDid,
|
|
376
|
+
ttlMinutes: result.session.ttlMinutes,
|
|
377
|
+
...(result.session.clientInfo && {
|
|
378
|
+
clientInfo: result.session.clientInfo,
|
|
379
|
+
}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
success: false,
|
|
384
|
+
error: result.error,
|
|
385
|
+
};
|
|
386
|
+
});
|
|
215
387
|
// Register tools/list handler
|
|
216
388
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async (request) => {
|
|
217
389
|
return {
|
|
@@ -297,7 +469,7 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
297
469
|
` "agentDid": "did:key:z6Mk...",\n` +
|
|
298
470
|
` "nonce": "unique-value",\n` +
|
|
299
471
|
` "audience": "server-did",\n` +
|
|
300
|
-
` "timestamp": ${Date.now()}\n` +
|
|
472
|
+
` "timestamp": ${Math.floor(Date.now() / 1000)}\n` +
|
|
301
473
|
` }\n\n` +
|
|
302
474
|
`2. **Handshake missing agentDid**: Ensure the handshake request includes the 'agentDid' field.\n\n` +
|
|
303
475
|
`3. **Session expired**: The session may have expired. Try performing a new handshake.\n\n` +
|
|
@@ -309,7 +481,7 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
309
481
|
};
|
|
310
482
|
}
|
|
311
483
|
if (identityConfig?.debug) {
|
|
312
|
-
console.error(`[MCPI] Tool "${name}" requires delegation - verifying scopes: ${toolProtection.requiredScopes?.join(
|
|
484
|
+
console.error(`[MCPI] Tool "${name}" requires delegation - verifying scopes: ${toolProtection.requiredScopes?.join(", ")}`);
|
|
313
485
|
}
|
|
314
486
|
// Verify delegation
|
|
315
487
|
const verifyResult = await (0, auth_handshake_1.verifyOrHints)(agentDid, toolProtection.requiredScopes || [], authConfig);
|
|
@@ -348,7 +520,9 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
348
520
|
content: [
|
|
349
521
|
{
|
|
350
522
|
type: "text",
|
|
351
|
-
text: typeof result === "string"
|
|
523
|
+
text: typeof result === "string"
|
|
524
|
+
? result
|
|
525
|
+
: JSON.stringify(result),
|
|
352
526
|
},
|
|
353
527
|
],
|
|
354
528
|
};
|
|
@@ -365,11 +539,9 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
365
539
|
const toolResponse = {
|
|
366
540
|
data: result,
|
|
367
541
|
};
|
|
368
|
-
//
|
|
369
|
-
// In a full implementation, this would use proper handshake
|
|
370
|
-
// TODO: Use proper handshake
|
|
542
|
+
// Reuse the active session if available, otherwise synthesize one
|
|
371
543
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
372
|
-
const
|
|
544
|
+
const proofSession = session ?? {
|
|
373
545
|
sessionId: `tool-${Date.now()}`,
|
|
374
546
|
nonce: `${Date.now()}`,
|
|
375
547
|
audience: "client",
|
|
@@ -382,7 +554,8 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
382
554
|
// This enables AgentShield tool auto-discovery
|
|
383
555
|
let scopeId;
|
|
384
556
|
const toolProtection = tool_protection_registry_1.toolProtectionRegistry.get(name);
|
|
385
|
-
if (toolProtection?.requiredScopes &&
|
|
557
|
+
if (toolProtection?.requiredScopes &&
|
|
558
|
+
toolProtection.requiredScopes.length > 0) {
|
|
386
559
|
// Use the first required scope as the scopeId (e.g., "files:read")
|
|
387
560
|
scopeId = toolProtection.requiredScopes[0];
|
|
388
561
|
}
|
|
@@ -395,9 +568,9 @@ async function addToolsToServer(server, toolModules, identityConfig) {
|
|
|
395
568
|
}
|
|
396
569
|
// Generate proof using the proof generator with scopeId
|
|
397
570
|
const proofGen = new proof_1.ProofGenerator(identity);
|
|
398
|
-
const proof = await proofGen.generateProof(toolRequest, toolResponse,
|
|
571
|
+
const proof = await proofGen.generateProof(toolRequest, toolResponse, proofSession, {
|
|
399
572
|
scopeId, // Pass scopeId for tool auto-discovery
|
|
400
|
-
clientDid: session?.clientDid,
|
|
573
|
+
clientDid: session?.clientDid,
|
|
401
574
|
});
|
|
402
575
|
if (identityConfig?.debug) {
|
|
403
576
|
console.error(`[MCPI] Generated proof for tool "${name}" - DID: ${proof.meta.did}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kya-os/mcp-i",
|
|
3
|
-
"version": "1.6.1",
|
|
3
|
+
"version": "1.6.2-canary.1",
|
|
4
4
|
"description": "The TypeScript MCP framework with identity features built-in",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"model-context-protocol"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@kya-os/contracts": "
|
|
67
|
-
"@kya-os/mcp-i-core": "
|
|
66
|
+
"@kya-os/contracts": "1.6.3-canary.0",
|
|
67
|
+
"@kya-os/mcp-i-core": "1.3.8-canary.0",
|
|
68
68
|
"@modelcontextprotocol/sdk": "^1.11.4",
|
|
69
69
|
"@swc/core": "^1.11.24",
|
|
70
70
|
"@types/express": "^5.0.1",
|