@mcp-i/core 0.1.0 → 1.1.0-canary.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 (35) hide show
  1. package/README.md +43 -20
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/middleware/index.d.ts +5 -1
  7. package/dist/middleware/index.d.ts.map +1 -1
  8. package/dist/middleware/index.js +5 -1
  9. package/dist/middleware/index.js.map +1 -1
  10. package/dist/middleware/with-mcpi-server.d.ts +62 -0
  11. package/dist/middleware/with-mcpi-server.d.ts.map +1 -0
  12. package/dist/middleware/with-mcpi-server.js +94 -0
  13. package/dist/middleware/with-mcpi-server.js.map +1 -0
  14. package/dist/middleware/with-mcpi.d.ts +25 -10
  15. package/dist/middleware/with-mcpi.d.ts.map +1 -1
  16. package/dist/middleware/with-mcpi.js +21 -7
  17. package/dist/middleware/with-mcpi.js.map +1 -1
  18. package/dist/providers/index.d.ts +1 -0
  19. package/dist/providers/index.d.ts.map +1 -1
  20. package/dist/providers/index.js +1 -0
  21. package/dist/providers/index.js.map +1 -1
  22. package/dist/providers/node-crypto.d.ts +26 -0
  23. package/dist/providers/node-crypto.d.ts.map +1 -0
  24. package/dist/providers/node-crypto.js +69 -0
  25. package/dist/providers/node-crypto.js.map +1 -0
  26. package/package.json +8 -3
  27. package/src/__tests__/integration/mcp-enhance-server.test.ts +311 -0
  28. package/src/__tests__/integration/mcp-transport-context7.test.ts +413 -0
  29. package/src/__tests__/integration/mcp-transport.test.ts +390 -0
  30. package/src/index.ts +5 -0
  31. package/src/middleware/index.ts +10 -1
  32. package/src/middleware/with-mcpi-server.ts +185 -0
  33. package/src/middleware/with-mcpi.ts +35 -13
  34. package/src/providers/index.ts +2 -0
  35. package/src/providers/node-crypto.ts +107 -0
@@ -0,0 +1,390 @@
1
+ /**
2
+ * End-to-End MCP Transport Integration Test
3
+ *
4
+ * Exercises the MCP-I middleware through a real MCP SDK Client→Server
5
+ * transport (InMemoryTransport). Unlike unit tests that call handlers
6
+ * directly, these tests go through full JSON-RPC serialization and
7
+ * MCP protocol framing.
8
+ */
9
+
10
+ import { describe, it, expect, afterEach } from 'vitest';
11
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
13
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
14
+ import {
15
+ CallToolRequestSchema,
16
+ ListToolsRequestSchema,
17
+ } from '@modelcontextprotocol/sdk/types.js';
18
+ import { createMCPIMiddleware } from '../../middleware/with-mcpi.js';
19
+ import { NodeCryptoProvider } from '../utils/node-crypto-provider.js';
20
+ import { generateDidKeyFromBase64 } from '../../utils/did-helpers.js';
21
+ import { DelegationCredentialIssuer } from '../../delegation/vc-issuer.js';
22
+ import { ProofVerifier } from '../../proof/verifier.js';
23
+ import { MemoryNonceCacheProvider } from '../../providers/memory.js';
24
+ import { ClockProvider, FetchProvider } from '../../providers/base.js';
25
+ import {
26
+ createDidKeyResolver,
27
+ extractPublicKeyFromDidKey,
28
+ publicKeyToJwk,
29
+ } from '../../delegation/did-key-resolver.js';
30
+ import { base64urlEncodeFromBytes } from '../../utils/base64.js';
31
+ import type {
32
+ DelegationCredential,
33
+ DIDDocument,
34
+ StatusList2021Credential,
35
+ DelegationRecord,
36
+ Proof,
37
+ } from '../../types/protocol.js';
38
+
39
+ // ── Test providers for ProofVerifier ──────────────────────────────
40
+
41
+ class TestClockProvider extends ClockProvider {
42
+ now(): number { return Date.now(); }
43
+ isWithinSkew(timestampMs: number, skewSeconds: number): boolean {
44
+ return Math.abs(Date.now() - timestampMs) <= skewSeconds * 1000;
45
+ }
46
+ hasExpired(expiresAt: number): boolean { return Date.now() > expiresAt; }
47
+ calculateExpiry(ttlSeconds: number): number { return Date.now() + ttlSeconds * 1000; }
48
+ format(timestamp: number): string { return new Date(timestamp).toISOString(); }
49
+ }
50
+
51
+ class TestFetchProvider extends FetchProvider {
52
+ private didResolver = createDidKeyResolver();
53
+ async resolveDID(did: string): Promise<DIDDocument | null> {
54
+ return this.didResolver.resolve(did);
55
+ }
56
+ async fetchStatusList(): Promise<StatusList2021Credential | null> { return null; }
57
+ async fetchDelegationChain(): Promise<DelegationRecord[]> { return []; }
58
+ async fetch(): Promise<Response> { throw new Error('Not implemented'); }
59
+ }
60
+
61
+ // ── Helpers ──────────────────────────────────────────────────────
62
+
63
+ async function setupMcpPair(options?: { autoSession?: boolean }) {
64
+ const crypto = new NodeCryptoProvider();
65
+ const keyPair = await crypto.generateKeyPair();
66
+ const did = generateDidKeyFromBase64(keyPair.publicKey);
67
+ const kid = `${did}#keys-1`;
68
+
69
+ const mcpi = createMCPIMiddleware(
70
+ {
71
+ identity: { did, kid, privateKey: keyPair.privateKey, publicKey: keyPair.publicKey },
72
+ session: { sessionTtlMinutes: 60 },
73
+ autoSession: options?.autoSession,
74
+ },
75
+ crypto,
76
+ );
77
+
78
+ // ── Tool handlers (mirrors examples/node-server/server.ts) ──
79
+
80
+ const greetHandler = mcpi.wrapWithProof('greet', async (args) => ({
81
+ content: [{ type: 'text', text: `Hello, ${args['name'] ?? 'world'}!` }],
82
+ }));
83
+
84
+ const restrictedGreetHandler = mcpi.wrapWithDelegation(
85
+ 'restricted_greet',
86
+ {
87
+ scopeId: 'greeting:restricted',
88
+ consentUrl: 'https://example.com/consent?scope=greeting:restricted',
89
+ },
90
+ mcpi.wrapWithProof('restricted_greet', async (args) => ({
91
+ content: [{ type: 'text', text: `Hello, ${args['name'] ?? 'world'}! (delegation verified)` }],
92
+ })),
93
+ );
94
+
95
+ // ── MCP Server ──
96
+
97
+ const server = new Server(
98
+ { name: 'mcpi-transport-test', version: '1.0.0' },
99
+ { capabilities: { tools: {} } },
100
+ );
101
+
102
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
103
+ tools: [
104
+ mcpi.handshakeTool,
105
+ {
106
+ name: 'greet',
107
+ description: 'Returns a greeting with proof',
108
+ inputSchema: {
109
+ type: 'object' as const,
110
+ properties: { name: { type: 'string' } },
111
+ },
112
+ },
113
+ {
114
+ name: 'restricted_greet',
115
+ description: 'Protected greeting requiring delegation',
116
+ inputSchema: {
117
+ type: 'object' as const,
118
+ properties: {
119
+ name: { type: 'string' },
120
+ _mcpi_delegation: { type: 'object' },
121
+ },
122
+ },
123
+ },
124
+ ],
125
+ }));
126
+
127
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
128
+ const { name, arguments: args = {} } = request.params;
129
+
130
+ if (name === '_mcpi_handshake') {
131
+ return mcpi.handleHandshake(args as Record<string, unknown>);
132
+ }
133
+ if (name === 'greet') {
134
+ return greetHandler(args as Record<string, unknown>);
135
+ }
136
+ if (name === 'restricted_greet') {
137
+ return restrictedGreetHandler(args as Record<string, unknown>);
138
+ }
139
+
140
+ return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
141
+ });
142
+
143
+ // ── Client + transport ──
144
+
145
+ const client = new Client(
146
+ { name: 'mcpi-transport-test-client', version: '1.0.0' },
147
+ );
148
+
149
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
150
+ await server.connect(serverTransport);
151
+ await client.connect(clientTransport);
152
+
153
+ return { client, server, did, kid, keyPair, crypto };
154
+ }
155
+
156
+ async function issueDelegationVC(scopes: string[]): Promise<DelegationCredential> {
157
+ const crypto = new NodeCryptoProvider();
158
+ const keyPair = await crypto.generateKeyPair();
159
+ const did = generateDidKeyFromBase64(keyPair.publicKey);
160
+ const kid = `${did}#keys-1`;
161
+
162
+ const signingFn = async (
163
+ canonicalVC: string,
164
+ _issuerDid: string,
165
+ kidArg: string,
166
+ ): Promise<Proof> => {
167
+ const data = new TextEncoder().encode(canonicalVC);
168
+ const sigBytes = await crypto.sign(data, keyPair.privateKey);
169
+ const proofValue = base64urlEncodeFromBytes(sigBytes);
170
+ return {
171
+ type: 'Ed25519Signature2020',
172
+ created: new Date().toISOString(),
173
+ verificationMethod: kidArg,
174
+ proofPurpose: 'assertionMethod',
175
+ proofValue,
176
+ };
177
+ };
178
+
179
+ const issuer = new DelegationCredentialIssuer(
180
+ { getDid: () => did, getKeyId: () => kid, getPrivateKey: () => keyPair.privateKey },
181
+ signingFn,
182
+ );
183
+
184
+ return issuer.createAndIssueDelegation({
185
+ id: `test-delegation-${Date.now()}-${Math.random().toString(16).slice(2)}`,
186
+ issuerDid: did,
187
+ subjectDid: did,
188
+ constraints: {
189
+ scopes,
190
+ notAfter: Math.floor(Date.now() / 1000) + 3600,
191
+ },
192
+ });
193
+ }
194
+
195
+ // ── Tests ──────────────────────────────────────────────────────
196
+
197
+ describe('MCP Transport Integration', () => {
198
+ const pairs: Array<{ client: Client; server: Server }> = [];
199
+
200
+ afterEach(async () => {
201
+ for (const pair of pairs) {
202
+ await pair.client.close();
203
+ await pair.server.close();
204
+ }
205
+ pairs.length = 0;
206
+ });
207
+
208
+ async function createPair(options?: { autoSession?: boolean }) {
209
+ const pair = await setupMcpPair(options);
210
+ pairs.push(pair);
211
+ return pair;
212
+ }
213
+
214
+ it('listTools returns handshake + app tools', async () => {
215
+ const { client } = await createPair();
216
+
217
+ const result = await client.listTools();
218
+ const toolNames = result.tools.map((t) => t.name);
219
+
220
+ expect(toolNames).toHaveLength(3);
221
+ expect(toolNames).toContain('_mcpi_handshake');
222
+ expect(toolNames).toContain('greet');
223
+ expect(toolNames).toContain('restricted_greet');
224
+ });
225
+
226
+ it('handshake establishes session via MCP transport', async () => {
227
+ const { client, did } = await createPair();
228
+
229
+ const result = await client.callTool({
230
+ name: '_mcpi_handshake',
231
+ arguments: {
232
+ nonce: `transport-test-${Date.now()}`,
233
+ audience: did,
234
+ timestamp: Math.floor(Date.now() / 1000),
235
+ },
236
+ });
237
+
238
+ expect(result.content).toHaveLength(1);
239
+ const first = result.content[0] as { type: string; text: string };
240
+ expect(first.type).toBe('text');
241
+
242
+ const parsed = JSON.parse(first.text);
243
+ expect(parsed.success).toBe(true);
244
+ expect(parsed.sessionId).toMatch(/^mcpi_/);
245
+ expect(parsed.serverDid).toBe(did);
246
+ });
247
+
248
+ it('greet returns proof in _meta after handshake', async () => {
249
+ const { client, did } = await createPair();
250
+
251
+ // Handshake first
252
+ await client.callTool({
253
+ name: '_mcpi_handshake',
254
+ arguments: {
255
+ nonce: `transport-test-${Date.now()}`,
256
+ audience: did,
257
+ timestamp: Math.floor(Date.now() / 1000),
258
+ },
259
+ });
260
+
261
+ // Call greet
262
+ const result = await client.callTool({
263
+ name: 'greet',
264
+ arguments: { name: 'MCP-I' },
265
+ });
266
+
267
+ // Verify tool output
268
+ const first = result.content[0] as { type: string; text: string };
269
+ expect(first.text).toBe('Hello, MCP-I!');
270
+
271
+ // Verify proof in _meta (top-level on the result)
272
+ expect(result._meta).toBeDefined();
273
+ const proof = (result._meta as Record<string, unknown>).proof as {
274
+ jws: string;
275
+ meta: Record<string, unknown>;
276
+ };
277
+ expect(proof).toBeDefined();
278
+ expect(proof.jws).toBeDefined();
279
+ expect(proof.meta.did).toMatch(/^did:key:/);
280
+ expect(proof.meta.sessionId).toMatch(/^mcpi_/);
281
+ expect(proof.meta.requestHash).toMatch(/^sha256:[a-f0-9]{64}$/);
282
+ expect(proof.meta.responseHash).toMatch(/^sha256:[a-f0-9]{64}$/);
283
+ });
284
+
285
+ it('proof from greet verifies cryptographically', async () => {
286
+ const { client, did, kid } = await createPair();
287
+
288
+ // Handshake
289
+ await client.callTool({
290
+ name: '_mcpi_handshake',
291
+ arguments: {
292
+ nonce: `transport-test-${Date.now()}`,
293
+ audience: did,
294
+ timestamp: Math.floor(Date.now() / 1000),
295
+ },
296
+ });
297
+
298
+ // Call greet
299
+ const result = await client.callTool({
300
+ name: 'greet',
301
+ arguments: { name: 'Verifier' },
302
+ });
303
+
304
+ const proof = (result._meta as Record<string, unknown>).proof as {
305
+ jws: string;
306
+ meta: Record<string, unknown>;
307
+ };
308
+
309
+ // Resolve server's public key from did:key
310
+ const publicKeyBytes = extractPublicKeyFromDidKey(did);
311
+ expect(publicKeyBytes).not.toBeNull();
312
+ const publicKeyJwk = publicKeyToJwk(publicKeyBytes!);
313
+
314
+ // Create verifier and verify
315
+ const crypto = new NodeCryptoProvider();
316
+ const verifier = new ProofVerifier({
317
+ cryptoProvider: crypto,
318
+ clockProvider: new TestClockProvider(),
319
+ nonceCacheProvider: new MemoryNonceCacheProvider(),
320
+ fetchProvider: new TestFetchProvider(),
321
+ timestampSkewSeconds: 300,
322
+ });
323
+
324
+ const jwkWithKid = { ...publicKeyJwk, kid };
325
+ const verificationResult = await verifier.verifyProof(proof as any, jwkWithKid as any);
326
+ expect(verificationResult.valid).toBe(true);
327
+ });
328
+
329
+ it('autoSession attaches proof without handshake', async () => {
330
+ const { client } = await createPair({ autoSession: true });
331
+
332
+ // Call greet directly — no handshake
333
+ const result = await client.callTool({
334
+ name: 'greet',
335
+ arguments: { name: 'Auto' },
336
+ });
337
+
338
+ const first = result.content[0] as { type: string; text: string };
339
+ expect(first.text).toBe('Hello, Auto!');
340
+
341
+ // Proof should be present via auto-session
342
+ expect(result._meta).toBeDefined();
343
+ const proof = (result._meta as Record<string, unknown>).proof as {
344
+ jws: string;
345
+ meta: Record<string, unknown>;
346
+ };
347
+ expect(proof).toBeDefined();
348
+ expect(proof.jws).toBeDefined();
349
+ expect(proof.meta.sessionId).toMatch(/^mcpi_/);
350
+ });
351
+
352
+ it('restricted_greet without delegation returns needs_authorization', async () => {
353
+ const { client } = await createPair();
354
+
355
+ const result = await client.callTool({
356
+ name: 'restricted_greet',
357
+ arguments: { name: 'Unauthorized' },
358
+ });
359
+
360
+ const first = result.content[0] as { type: string; text: string };
361
+ const parsed = JSON.parse(first.text);
362
+ expect(parsed.error).toBe('needs_authorization');
363
+ expect(parsed.authorizationUrl).toContain('consent');
364
+ expect(parsed.scopes).toContain('greeting:restricted');
365
+ expect(parsed.resumeToken).toBeDefined();
366
+ });
367
+
368
+ it('restricted_greet with valid delegation VC returns greeting with proof', async () => {
369
+ const { client, did } = await createPair({ autoSession: true });
370
+
371
+ const vc = await issueDelegationVC(['greeting:restricted']);
372
+
373
+ const result = await client.callTool({
374
+ name: 'restricted_greet',
375
+ arguments: { name: 'DIF', _mcpi_delegation: vc },
376
+ });
377
+
378
+ const first = result.content[0] as { type: string; text: string };
379
+ expect(first.text).toBe('Hello, DIF! (delegation verified)');
380
+
381
+ // Proof from inner wrapWithProof
382
+ expect(result._meta).toBeDefined();
383
+ const proof = (result._meta as Record<string, unknown>).proof as {
384
+ jws: string;
385
+ meta: Record<string, unknown>;
386
+ };
387
+ expect(proof).toBeDefined();
388
+ expect(proof.jws).toBeDefined();
389
+ });
390
+ });
package/src/index.ts CHANGED
@@ -236,6 +236,8 @@ export {
236
236
  MemoryIdentityProvider,
237
237
  } from './providers/memory.js';
238
238
 
239
+ export { NodeCryptoProvider } from './providers/node-crypto.js';
240
+
239
241
  // Middleware
240
242
  export {
241
243
  createMCPIMiddleware,
@@ -246,6 +248,9 @@ export {
246
248
  type MCPIToolDefinition,
247
249
  type MCPIToolHandler,
248
250
  type MCPIServer,
251
+ withMCPI,
252
+ generateIdentity,
253
+ type WithMCPIOptions,
249
254
  } from './middleware/index.js';
250
255
 
251
256
  // Logging
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * MCP-I Middleware
3
3
  *
4
- * Provides identity-aware middleware for @modelcontextprotocol/sdk Server.
4
+ * Primary entry point: `withMCPI(server, { crypto })` — adds identity,
5
+ * handshake, and auto-proofs to any McpServer instance in one call.
6
+ *
7
+ * For the low-level `Server` API or custom patterns, use `createMCPIMiddleware` directly.
5
8
  */
6
9
 
7
10
  export {
@@ -14,3 +17,9 @@ export {
14
17
  type MCPIToolHandler,
15
18
  type MCPIServer,
16
19
  } from './with-mcpi.js';
20
+
21
+ export {
22
+ withMCPI,
23
+ generateIdentity,
24
+ type WithMCPIOptions,
25
+ } from './with-mcpi-server.js';
@@ -0,0 +1,185 @@
1
+ /**
2
+ * McpServer Adapter for MCP-I
3
+ *
4
+ * Adds MCP-I identity, session management, and proof generation to a
5
+ * standard McpServer instance with a single function call.
6
+ *
7
+ * Usage:
8
+ * import { withMCPI } from '@mcp-i/core/middleware';
9
+ * const mcpi = await withMCPI(server, { crypto: new NodeCryptoProvider() });
10
+ * // All tools registered on `server` now get proofs automatically.
11
+ */
12
+
13
+ import type { CryptoProvider } from "../providers/base.js";
14
+ import { generateDidKeyFromBase64 } from "../utils/did-helpers.js";
15
+ import {
16
+ createMCPIMiddleware,
17
+ type MCPIIdentityConfig,
18
+ type MCPIDelegationConfig,
19
+ type MCPIMiddleware,
20
+ } from "./with-mcpi.js";
21
+ import { z } from "zod";
22
+
23
+ export interface WithMCPIOptions {
24
+ /** Platform-specific crypto implementation (required) */
25
+ crypto: CryptoProvider;
26
+ /** Identity config — auto-generated if omitted */
27
+ identity?: MCPIIdentityConfig;
28
+ /** Session configuration */
29
+ session?: { sessionTtlMinutes?: number };
30
+ /** Auto-create sessions for non-MCP-I clients (default: true) */
31
+ autoSession?: boolean;
32
+ /** Attach proofs to all tool responses (default: true) */
33
+ proofAllTools?: boolean;
34
+ /** Tools to skip proof generation for */
35
+ excludeTools?: string[];
36
+ /** Delegation verification config */
37
+ delegation?: MCPIDelegationConfig;
38
+ }
39
+
40
+ /**
41
+ * Generate a fresh Ed25519 identity for MCP-I.
42
+ *
43
+ * @param crypto - Platform-specific crypto provider
44
+ * @returns Identity config with DID, kid, and key material
45
+ */
46
+ export async function generateIdentity(
47
+ crypto: CryptoProvider,
48
+ ): Promise<MCPIIdentityConfig> {
49
+ const keyPair = await crypto.generateKeyPair();
50
+ const did = generateDidKeyFromBase64(keyPair.publicKey);
51
+ return {
52
+ did,
53
+ kid: `${did}#keys-1`,
54
+ privateKey: keyPair.privateKey,
55
+ publicKey: keyPair.publicKey,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * McpServer type — minimal interface to avoid hard dependency on the SDK.
61
+ * Matches the public API of @modelcontextprotocol/sdk McpServer.
62
+ */
63
+ interface McpServerLike {
64
+ server: {
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ [key: string]: any;
67
+ };
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ registerTool(...args: any[]): void;
70
+ }
71
+
72
+ /**
73
+ * Add MCP-I to a McpServer instance.
74
+ *
75
+ * 1. Auto-generates Ed25519 identity (or uses provided one)
76
+ * 2. Registers `_mcpi_handshake` tool
77
+ * 3. Intercepts the `tools/call` request handler to auto-attach proofs
78
+ *
79
+ * @param server - McpServer instance
80
+ * @param options - Configuration
81
+ * @returns The MCPIMiddleware instance for advanced usage (wrapWithDelegation, etc.)
82
+ */
83
+ export async function withMCPI(
84
+ server: McpServerLike,
85
+ options: WithMCPIOptions,
86
+ ): Promise<MCPIMiddleware> {
87
+ const identity =
88
+ options.identity ?? (await generateIdentity(options.crypto));
89
+
90
+ const mcpi = createMCPIMiddleware(
91
+ {
92
+ identity,
93
+ session: options.session,
94
+ delegation: options.delegation,
95
+ autoSession: options.autoSession ?? true,
96
+ },
97
+ options.crypto,
98
+ );
99
+
100
+ // Register _mcpi_handshake tool
101
+ server.registerTool(
102
+ "_mcpi_handshake",
103
+ {
104
+ description:
105
+ "MCP-I identity handshake — establishes a cryptographic session",
106
+ inputSchema: {
107
+ nonce: z.string().describe("Client-generated unique nonce"),
108
+ audience: z
109
+ .string()
110
+ .describe("Intended audience (server DID or URL)"),
111
+ timestamp: z.number().describe("Unix epoch seconds"),
112
+ },
113
+ },
114
+ async (args: unknown) => {
115
+ const result = await mcpi.handleHandshake(args as Record<string, unknown>);
116
+ return {
117
+ ...result,
118
+ content: result.content.map((c) => ({ ...c, type: "text" as const })),
119
+ };
120
+ },
121
+ );
122
+
123
+ // Auto-proof interception: wrap the tools/call handler
124
+ const proofAllTools = options.proofAllTools ?? true;
125
+
126
+ if (proofAllTools) {
127
+ const lowLevel = server.server;
128
+ const handlers: Map<string, Function> =
129
+ lowLevel._requestHandlers;
130
+ const original = handlers.get("tools/call");
131
+
132
+ if (original) {
133
+ handlers.set(
134
+ "tools/call",
135
+ async (request: Record<string, unknown>, extra: unknown) => {
136
+ const result = (await (
137
+ original as (
138
+ req: Record<string, unknown>,
139
+ ext: unknown,
140
+ ) => Promise<Record<string, unknown>>
141
+ )(request, extra)) as {
142
+ content?: Array<{
143
+ type: string;
144
+ text: string;
145
+ [key: string]: unknown;
146
+ }>;
147
+ isError?: boolean;
148
+ _meta?: Record<string, unknown>;
149
+ [key: string]: unknown;
150
+ };
151
+
152
+ const params = request.params as
153
+ | { name?: string; arguments?: Record<string, unknown> }
154
+ | undefined;
155
+ const toolName = params?.name;
156
+
157
+ if (
158
+ !toolName ||
159
+ toolName === "_mcpi_handshake" ||
160
+ result.isError
161
+ ) {
162
+ return result;
163
+ }
164
+
165
+ if (options.excludeTools?.includes(toolName)) {
166
+ return result;
167
+ }
168
+
169
+ // Use wrapWithProof to add proof — it handles session management
170
+ const addProof = mcpi.wrapWithProof(
171
+ toolName,
172
+ async () => result as {
173
+ content: Array<{ type: string; text: string; [key: string]: unknown }>;
174
+ isError?: boolean;
175
+ [key: string]: unknown;
176
+ },
177
+ );
178
+ return addProof(params?.arguments ?? {});
179
+ },
180
+ );
181
+ }
182
+ }
183
+
184
+ return mcpi;
185
+ }