@mcp-i/core 1.1.0-canary.2 → 1.1.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 (94) hide show
  1. package/README.md +123 -333
  2. package/dist/auth/handshake.d.ts +19 -4
  3. package/dist/auth/handshake.d.ts.map +1 -1
  4. package/dist/auth/handshake.js +52 -15
  5. package/dist/auth/handshake.js.map +1 -1
  6. package/dist/auth/index.d.ts +1 -1
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js.map +1 -1
  9. package/dist/delegation/did-key-resolver.d.ts.map +1 -1
  10. package/dist/delegation/did-key-resolver.js +9 -6
  11. package/dist/delegation/did-key-resolver.js.map +1 -1
  12. package/dist/delegation/outbound-headers.d.ts +2 -4
  13. package/dist/delegation/outbound-headers.d.ts.map +1 -1
  14. package/dist/delegation/outbound-headers.js +2 -3
  15. package/dist/delegation/outbound-headers.js.map +1 -1
  16. package/dist/delegation/statuslist-manager.d.ts.map +1 -1
  17. package/dist/delegation/statuslist-manager.js +1 -1
  18. package/dist/delegation/statuslist-manager.js.map +1 -1
  19. package/dist/delegation/vc-verifier.d.ts.map +1 -1
  20. package/dist/delegation/vc-verifier.js +2 -2
  21. package/dist/delegation/vc-verifier.js.map +1 -1
  22. package/dist/errors.d.ts +42 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +45 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/index.d.ts +3 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/middleware/index.d.ts +1 -0
  31. package/dist/middleware/index.d.ts.map +1 -1
  32. package/dist/middleware/index.js +1 -0
  33. package/dist/middleware/index.js.map +1 -1
  34. package/dist/middleware/mcpi-transport.d.ts +39 -0
  35. package/dist/middleware/mcpi-transport.d.ts.map +1 -0
  36. package/dist/middleware/mcpi-transport.js +121 -0
  37. package/dist/middleware/mcpi-transport.js.map +1 -0
  38. package/dist/middleware/with-mcpi-server.d.ts +25 -9
  39. package/dist/middleware/with-mcpi-server.d.ts.map +1 -1
  40. package/dist/middleware/with-mcpi-server.js +62 -47
  41. package/dist/middleware/with-mcpi-server.js.map +1 -1
  42. package/dist/middleware/with-mcpi.d.ts +26 -5
  43. package/dist/middleware/with-mcpi.d.ts.map +1 -1
  44. package/dist/middleware/with-mcpi.js +108 -10
  45. package/dist/middleware/with-mcpi.js.map +1 -1
  46. package/dist/providers/memory.js +2 -2
  47. package/dist/providers/memory.js.map +1 -1
  48. package/dist/session/manager.d.ts +7 -1
  49. package/dist/session/manager.d.ts.map +1 -1
  50. package/dist/session/manager.js +20 -4
  51. package/dist/session/manager.js.map +1 -1
  52. package/dist/utils/crypto-service.d.ts.map +1 -1
  53. package/dist/utils/crypto-service.js +11 -10
  54. package/dist/utils/crypto-service.js.map +1 -1
  55. package/dist/utils/did-helpers.d.ts +12 -0
  56. package/dist/utils/did-helpers.d.ts.map +1 -1
  57. package/dist/utils/did-helpers.js +18 -0
  58. package/dist/utils/did-helpers.js.map +1 -1
  59. package/package.json +3 -3
  60. package/src/__tests__/errors.test.ts +56 -0
  61. package/src/__tests__/integration/full-flow.test.ts +1 -1
  62. package/src/__tests__/integration/mcp-enhance-server.test.ts +48 -5
  63. package/src/__tests__/integration/mcp-transport-context7.test.ts +19 -15
  64. package/src/__tests__/integration/mcp-transport.test.ts +13 -10
  65. package/src/__tests__/providers/base.test.ts +1 -1
  66. package/src/__tests__/providers/memory.test.ts +2 -2
  67. package/src/__tests__/utils/mock-providers.ts +2 -2
  68. package/src/auth/__tests__/handshake.test.ts +190 -0
  69. package/src/auth/handshake.ts +88 -21
  70. package/src/auth/index.ts +1 -0
  71. package/src/delegation/__tests__/did-key-resolver.test.ts +2 -2
  72. package/src/delegation/__tests__/outbound-headers.test.ts +16 -20
  73. package/src/delegation/__tests__/statuslist-manager.test.ts +120 -7
  74. package/src/delegation/__tests__/vc-verifier.test.ts +45 -3
  75. package/src/delegation/did-key-resolver.ts +11 -6
  76. package/src/delegation/outbound-headers.ts +1 -4
  77. package/src/delegation/statuslist-manager.ts +3 -1
  78. package/src/delegation/vc-verifier.ts +3 -2
  79. package/src/errors.ts +65 -0
  80. package/src/index.ts +10 -0
  81. package/src/middleware/__tests__/mcpi-transport.test.ts +150 -0
  82. package/src/middleware/__tests__/with-mcpi-server.test.ts +117 -0
  83. package/src/middleware/__tests__/with-mcpi.test.ts +124 -6
  84. package/src/middleware/index.ts +6 -0
  85. package/src/middleware/mcpi-transport.ts +162 -0
  86. package/src/middleware/with-mcpi-server.ts +83 -92
  87. package/src/middleware/with-mcpi.ts +147 -11
  88. package/src/proof/__tests__/errors.test.ts +79 -0
  89. package/src/proof/__tests__/verifier.test.ts +5 -5
  90. package/src/providers/memory.ts +2 -2
  91. package/src/session/__tests__/session-manager.test.ts +3 -3
  92. package/src/session/manager.ts +28 -6
  93. package/src/utils/crypto-service.ts +11 -10
  94. package/src/utils/did-helpers.ts +19 -0
package/README.md CHANGED
@@ -1,413 +1,203 @@
1
1
  # @mcp-i/core
2
2
 
3
- **MCP-I protocol reference implementation** — delegation, proof, and session for the Model Context Protocol Identity (MCP-I) standard.
3
+ **Identity, delegation, and proof for the Model Context Protocol.**
4
4
 
5
- > This package is a [DIF TAAWG](https://identity.foundation/working-groups/agent-and-authorization.html) protocol reference implementation donated to the Decentralized Identity Foundation.
5
+ MCP-I answers three questions for every AI agent tool call: *who* is calling (DID), *are they allowed* (delegation VC), and *what happened* (signed proof). It uses W3C Verifiable Credentials, Ed25519 signatures, and Decentralized Identifiers — no central authority required.
6
6
 
7
- ---
8
-
9
- ## What is MCP-I?
10
-
11
- MCP-I (Model Context Protocol Identity) is a protocol extension for the Model Context Protocol (MCP) that adds cryptographic identity, delegation chains, and non-repudiation proofs to AI agent interactions. It enables MCP servers to verify *who* is calling (agent DID), *on whose behalf* (user delegation), and *what* was done (signed proof). Delegations are issued as W3C Verifiable Credentials with Ed25519 signatures, revocation is tracked via StatusList2021, and every tool call produces a detached JWS proof for audit trails.
12
-
13
- Spec: [modelcontextprotocol-identity.io](https://modelcontextprotocol-identity.io)
14
-
15
- ---
16
-
17
- ## What this package provides
18
-
19
- | Module | Description |
20
- |--------|-------------|
21
- | **delegation** | W3C VC delegation issuance (`DelegationCredentialIssuer`), verification (`DelegationCredentialVerifier`), graph management, StatusList2021 revocation, cascading revocation, outbound delegation propagation (`buildOutboundDelegationHeaders`), DID:key resolution (`createDidKeyResolver`), and DID:web resolution (`createDidWebResolver`) |
22
- | **proof** | Platform-agnostic proof generation (`ProofGenerator`) and server-side verification (`ProofVerifier`) — JCS canonicalization, SHA-256 hashing, Ed25519 JWS signing/verification |
23
- | **session** | Handshake validation and session management (`SessionManager`) with nonce replay prevention |
24
- | **auth** | Authorization handshake orchestration (`verifyOrHints`) — checks delegation and returns authorization hints |
25
- | **middleware** | MCP SDK integration — `withMCPI(server, { crypto })` adds identity, sessions, and auto-proof to any `McpServer` in one call. Lower-level `createMCPIMiddleware` available for the `Server` API. |
26
- | **providers** | Abstract base classes (`CryptoProvider`, `StorageProvider`, etc.) and in-memory implementations for testing |
27
- | **types** | Pure TypeScript interfaces for all protocol types — zero runtime dependencies |
7
+ > [DIF TAAWG](https://identity.foundation/working-groups/agent-and-authorization.html) protocol reference implementation. Spec: [modelcontextprotocol-identity.io](https://modelcontextprotocol-identity.io)
28
8
 
29
9
  ---
30
10
 
31
- ## Quick Start
11
+ ## Try It
32
12
 
33
- The fastest way to see MCP-I in action is the example server:
13
+ See the consent flow in action an agent calls a protected tool, a human approves, and the agent retries with a signed credential:
34
14
 
35
15
  ```bash
36
16
  git clone https://github.com/modelcontextprotocol-identity/mcp-i-core.git
37
17
  cd mcp-i-core
38
18
  pnpm install
39
- npx tsx examples/node-server/server.ts
19
+ npx tsx examples/consent-basic/src/server.ts
40
20
  ```
41
21
 
42
- This starts an MCP server on stdio with identity handshake and proof generation. See [`examples/node-server/README.md`](./examples/node-server/README.md) for details.
43
-
44
- For outbound delegation propagation (forwarding delegation context to downstream services), see [`examples/outbound-delegation/README.md`](./examples/outbound-delegation/README.md).
45
-
46
- ---
47
-
48
- ## Installation
22
+ Then connect with [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
49
23
 
50
24
  ```bash
51
- npm install @mcp-i/core
25
+ npx @modelcontextprotocol/inspector
26
+ # → Connect to http://localhost:3002/sse
52
27
  ```
53
28
 
54
- ---
55
-
56
- ## Delegation Hardening Compatibility
29
+ Call `checkout` — you'll get a consent link. Open it, approve, then retry the tool. [Full walkthrough →](./examples/consent-basic/README.md)
57
30
 
58
- MCP-I now enforces strict delegation-chain and status-list checks by default.
31
+ ---
59
32
 
60
- - Credentials with `parentId` require `delegation.resolveDelegationChain`.
61
- - Credentials with `credentialStatus` require `delegation.statusListResolver`.
33
+ ## Add to Your Server
62
34
 
63
- For temporary migration support in legacy integrations, you can opt in to compatibility mode:
35
+ One line to add identity, sessions, and proofs to any MCP server:
64
36
 
65
37
  ```typescript
66
- await withMCPI(server, {
67
- crypto: new NodeCryptoProvider(),
68
- delegation: {
69
- allowLegacyUnsafeDelegation: true, // temporary compatibility escape hatch
70
- },
71
- });
72
- ```
73
-
74
- Compatibility mode weakens security guarantees and should only be used during migration.
75
-
76
- ---
38
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
39
+ import { withMCPI, NodeCryptoProvider } from '@mcp-i/core';
77
40
 
78
- ## Examples
41
+ const server = new McpServer({ name: 'my-server', version: '1.0.0' });
42
+ await withMCPI(server, { crypto: new NodeCryptoProvider() });
79
43
 
80
- > Requires Node.js 20+. Save each block as `example.ts` and run with `npx tsx example.ts`.
44
+ // Register tools normally proofs are attached automatically
45
+ ```
81
46
 
82
- ---
47
+ Default behavior is compatibility-first:
48
+ - `withMCPI` auto-registers `_mcpi`, so it appears in MCP Inspector and tool lists.
49
+ - Handshake is not tied to MCP `initialize`; a client/runtime must call it (or use `autoSession`).
83
50
 
84
- ## Example 1 Issue a Delegation VC
51
+ If your runtime has a native connection/auth handshake hook, disable tool exposure and call middleware directly:
85
52
 
86
53
  ```typescript
87
- import {
88
- createHash,
89
- createPrivateKey,
90
- generateKeyPairSync,
91
- randomBytes,
92
- sign as nodeSign,
93
- } from 'node:crypto';
94
- import {
95
- CryptoProvider,
96
- MemoryIdentityProvider,
97
- DelegationCredentialIssuer,
98
- type DelegationIdentityProvider,
99
- type VCSigningFunction,
100
- } from '@mcp-i/core';
101
-
102
- // Minimal Node.js CryptoProvider backed by node:crypto
103
- class NodeCryptoProvider extends CryptoProvider {
104
- async generateKeyPair() {
105
- const { privateKey: pk, publicKey: pub } = generateKeyPairSync('ed25519', {
106
- privateKeyEncoding: { type: 'pkcs8', format: 'der' },
107
- publicKeyEncoding: { type: 'spki', format: 'der' },
108
- });
109
- // Ed25519 PKCS8 DER: 16-byte header + 32-byte seed
110
- // Ed25519 SPKI DER: 12-byte header + 32-byte public key
111
- return {
112
- privateKey: (pk as Buffer).subarray(16).toString('base64'),
113
- publicKey: (pub as Buffer).subarray(12).toString('base64'),
114
- };
115
- }
116
- async hash(data: Uint8Array) {
117
- return 'sha256:' + createHash('sha256').update(data).digest('hex');
118
- }
119
- async randomBytes(n: number) { return new Uint8Array(randomBytes(n)); }
120
- async sign(data: Uint8Array, privateKeyBase64: string) {
121
- const seed = Buffer.from(privateKeyBase64, 'base64').subarray(0, 32);
122
- const hdr = Buffer.from([
123
- 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05,
124
- 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
125
- ]);
126
- const key = createPrivateKey({ key: Buffer.concat([hdr, seed]), format: 'der', type: 'pkcs8' });
127
- return new Uint8Array(nodeSign(null, data, key));
128
- }
129
- async verify(): Promise<boolean> { throw new Error('unused'); }
130
- }
131
-
132
- const cryptoProvider = new NodeCryptoProvider();
133
-
134
- // MemoryIdentityProvider generates a real Ed25519 DID + key pair
135
- const identityProvider = new MemoryIdentityProvider(cryptoProvider);
136
- const agent = await identityProvider.getIdentity();
137
-
138
- // Adapt AgentIdentity to the DelegationIdentityProvider interface
139
- const identity: DelegationIdentityProvider = {
140
- getDid: () => agent.did,
141
- getKeyId: () => agent.kid,
142
- getPrivateKey: () => agent.privateKey,
143
- };
144
-
145
- // Real Ed25519 signing function — delegates to NodeCryptoProvider
146
- const signingFunction: VCSigningFunction = async (canonicalVC, issuerDid) => {
147
- const sig = await cryptoProvider.sign(
148
- new TextEncoder().encode(canonicalVC),
149
- agent.privateKey
150
- );
151
- return {
152
- type: 'Ed25519Signature2020',
153
- created: new Date().toISOString(),
154
- verificationMethod: `${issuerDid}#key-1`,
155
- proofPurpose: 'assertionMethod',
156
- proofValue: Buffer.from(sig).toString('base64url'),
157
- };
158
- };
159
-
160
- const issuer = new DelegationCredentialIssuer(identity, signingFunction);
161
-
162
- const vc = await issuer.createAndIssueDelegation({
163
- id: 'delegation-001',
164
- issuerDid: agent.did,
165
- subjectDid: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuias8sisDArDJF74t',
166
- constraints: {
167
- scopes: ['tool:execute', 'resource:read'],
168
- notAfter: Math.floor(Date.now() / 1000) + 3600,
169
- },
54
+ const mcpi = await withMCPI(server, {
55
+ crypto: new NodeCryptoProvider(),
56
+ handshakeExposure: 'none',
57
+ autoSession: false,
170
58
  });
171
59
 
172
- console.log('VC type:', vc.type);
173
- // ['VerifiableCredential', 'DelegationCredential']
174
- console.log('Scopes:', vc.credentialSubject.delegation.constraints.scopes);
175
- // ['tool:execute', 'resource:read']
176
- console.log('Proof type:', vc.proof?.type);
177
- // 'Ed25519Signature2020'
60
+ // In your runtime's connection handshake hook:
61
+ await mcpi.handleMCPI({
62
+ action: 'handshake',
63
+ nonce: 'client-generated-nonce',
64
+ audience: mcpi.identity.did,
65
+ timestamp: Math.floor(Date.now() / 1000),
66
+ agentDid: 'did:key:...optional...',
67
+ });
178
68
  ```
179
69
 
180
- ---
181
-
182
- ## Example 2 — Session Handshake
70
+ Every tool response now includes a cryptographic proof. For protected tools that require human consent, add `wrapWithDelegation`:
183
71
 
184
72
  ```typescript
185
- import { randomBytes } from 'node:crypto';
186
- import {
187
- CryptoProvider,
188
- SessionManager,
189
- MemoryNonceCacheProvider,
190
- createHandshakeRequest,
191
- } from '@mcp-i/core';
192
-
193
- // SessionManager only needs randomBytes() for session ID generation
194
- class NodeCryptoProvider extends CryptoProvider {
195
- async randomBytes(n: number) { return new Uint8Array(randomBytes(n)); }
196
- async generateKeyPair(): Promise<{ privateKey: string; publicKey: string }> { throw new Error('unused'); }
197
- async hash(_: Uint8Array): Promise<string> { throw new Error('unused'); }
198
- async sign(_: Uint8Array, __: string): Promise<Uint8Array> { throw new Error('unused'); }
199
- async verify(_: Uint8Array, __: Uint8Array, ___: string): Promise<boolean> { throw new Error('unused'); }
200
- }
201
-
202
- const cryptoProvider = new NodeCryptoProvider();
203
- const nonceCache = new MemoryNonceCacheProvider();
73
+ import { createMCPIMiddleware, generateIdentity, NodeCryptoProvider } from '@mcp-i/core';
204
74
 
205
- const sessionManager = new SessionManager(cryptoProvider, {
206
- nonceCache,
207
- sessionTtlMinutes: 30,
208
- timestampSkewSeconds: 120,
209
- serverDid: 'did:web:my-mcp-server.example.com',
210
- });
211
-
212
- // Client: build handshake request (uses globalThis.crypto, built into Node 20+)
213
- const request = createHandshakeRequest('did:web:my-mcp-server.example.com');
214
- request.agentDid = 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK';
75
+ const crypto = new NodeCryptoProvider();
76
+ const identity = await generateIdentity(crypto);
77
+ const mcpi = createMCPIMiddleware({ identity, session: { sessionTtlMinutes: 60 } }, crypto);
215
78
 
216
- // Server: validate it
217
- const result = await sessionManager.validateHandshake(request);
79
+ // Public tool — proof attached automatically
80
+ const search = mcpi.wrapWithProof('search', async (args) => ({
81
+ content: [{ type: 'text', text: `Results for: ${args['query']}` }],
82
+ }));
218
83
 
219
- if (result.success && result.session) {
220
- console.log('Session ID:', result.session.sessionId);
221
- // e.g. 'mcpi_4f3e2a1b-c7d2-4e5f-b6a3-...'
222
- console.log('Audience:', result.session.audience);
223
- // 'did:web:my-mcp-server.example.com'
224
- console.log('Agent DID:', result.session.agentDid);
225
- // 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK'
226
- }
227
- console.log('Stats:', sessionManager.getStats());
228
- // { activeSessions: 1, config: { sessionTtlMinutes: 30, ... } }
84
+ // Protected tool requires delegation with scope 'orders:write'
85
+ const placeOrder = mcpi.wrapWithDelegation(
86
+ 'place_order',
87
+ { scopeId: 'orders:write', consentUrl: 'https://example.com/consent' },
88
+ mcpi.wrapWithProof('place_order', async (args) => ({
89
+ content: [{ type: 'text', text: `Order placed: ${args['item']}` }],
90
+ })),
91
+ );
229
92
  ```
230
93
 
231
94
  ---
232
95
 
233
- ## Example 3 — Generate a Tool Call Proof
234
-
235
- ```typescript
236
- import {
237
- createHash,
238
- createPrivateKey,
239
- generateKeyPairSync,
240
- randomBytes,
241
- sign as nodeSign,
242
- } from 'node:crypto';
243
- import {
244
- CryptoProvider,
245
- MemoryIdentityProvider,
246
- ProofGenerator,
247
- SessionManager,
248
- } from '@mcp-i/core';
249
-
250
- class NodeCryptoProvider extends CryptoProvider {
251
- async generateKeyPair() {
252
- const { privateKey: pk, publicKey: pub } = generateKeyPairSync('ed25519', {
253
- privateKeyEncoding: { type: 'pkcs8', format: 'der' },
254
- publicKeyEncoding: { type: 'spki', format: 'der' },
255
- });
256
- return {
257
- privateKey: (pk as Buffer).subarray(16).toString('base64'),
258
- publicKey: (pub as Buffer).subarray(12).toString('base64'),
259
- };
260
- }
261
- async hash(data: Uint8Array) {
262
- return 'sha256:' + createHash('sha256').update(data).digest('hex');
263
- }
264
- async randomBytes(n: number) { return new Uint8Array(randomBytes(n)); }
265
- async sign(data: Uint8Array, privateKeyBase64: string) {
266
- const seed = Buffer.from(privateKeyBase64, 'base64').subarray(0, 32);
267
- const hdr = Buffer.from([
268
- 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05,
269
- 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
270
- ]);
271
- const key = createPrivateKey({ key: Buffer.concat([hdr, seed]), format: 'der', type: 'pkcs8' });
272
- return new Uint8Array(nodeSign(null, data, key));
273
- }
274
- async verify(): Promise<boolean> { throw new Error('unused'); }
275
- }
96
+ ## Install
276
97
 
277
- const cryptoProvider = new NodeCryptoProvider();
98
+ ```bash
99
+ npm install @mcp-i/core
100
+ ```
278
101
 
279
- // MemoryIdentityProvider generates a fresh DID + Ed25519 key pair
280
- const identityProvider = new MemoryIdentityProvider(cryptoProvider);
281
- const agent = await identityProvider.getIdentity();
102
+ Requires Node.js 20+. Peer dependency on `@modelcontextprotocol/sdk` (optional only needed for `withMCPI`).
282
103
 
283
- // ProofGenerator signs tool call request+response pairs with the agent's key
284
- const generator = new ProofGenerator(agent, cryptoProvider);
104
+ ---
285
105
 
286
- const request = {
287
- method: 'tools/call',
288
- params: { name: 'read_file', arguments: { path: '/etc/hosts' } },
289
- };
290
- const response = { data: { content: '127.0.0.1 localhost' } };
106
+ ## Architecture
291
107
 
292
- // SessionContext — in production this comes from SessionManager.validateHandshake()
293
- const session = {
294
- sessionId: 'mcpi_demo-session',
295
- audience: 'did:web:my-mcp-server.example.com',
296
- nonce: SessionManager.generateNonce(),
297
- timestamp: Math.floor(Date.now() / 1000),
298
- createdAt: Math.floor(Date.now() / 1000),
299
- lastActivity: Math.floor(Date.now() / 1000),
300
- ttlMinutes: 30,
301
- identityState: 'anonymous' as const,
302
- };
303
-
304
- const proof = await generator.generateProof(request, response, session);
305
-
306
- console.log('JWS (first 40 chars):', proof.jws.slice(0, 40) + '...');
307
- // 'eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk...'
308
- console.log('Request hash:', proof.meta.requestHash);
309
- // 'sha256:e3b0...'
310
- console.log('Agent DID:', proof.meta.did);
311
- // 'did:key:z...'
108
+ ```
109
+ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
110
+ │ Agent │────▶│ MCP Server │────▶│ Downstream │
111
+ (did:key) │ │ + MCP-I │ │ Services │
112
+ └─────────────┘ └──────────────┘ └─────────────┘
113
+ │ │ │
114
+ handshake │ verify delegation │ outbound headers
115
+ (nonce+DID) attach proof │ (X-Agent-DID,
116
+ │ │ check scopes │ X-Delegation-Chain)
117
+ ▼ ▼ ▼
118
+ Session established Tool executes Context forwarded
119
+ with replay with signed to downstream
120
+ prevention receipt with delegation
312
121
  ```
313
122
 
314
123
  ---
315
124
 
316
- ## Example 4 — MCP Server with Middleware (`withMCPI`)
317
-
318
- The recommended way to add MCP-I to any `McpServer`-based server:
125
+ ## Modules
319
126
 
320
- ```typescript
321
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
322
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
323
- import { withMCPI, CryptoProvider } from '@mcp-i/core';
324
- import { z } from 'zod';
127
+ | Module | What it does |
128
+ |--------|-------------|
129
+ | **middleware** | `withMCPI(server)` one-call integration. `createMCPIMiddleware` for low-level control. |
130
+ | **delegation** | Issue and verify W3C VCs. DID:key and DID:web resolution. StatusList2021 revocation. Cascading revocation. |
131
+ | **proof** | Generate and verify detached JWS proofs with canonical hashing (JCS + SHA-256). |
132
+ | **session** | Nonce-based handshake. Replay prevention. Session TTL management. |
133
+ | **providers** | Abstract `CryptoProvider`, `IdentityProvider`, `StorageProvider`. Plug in your own KMS, HSM, or vault. |
134
+ | **types** | Pure TypeScript interfaces. Zero runtime dependencies. |
325
135
 
326
- // ... (NodeCryptoProvider as above)
136
+ All modules available as subpath exports: `@mcp-i/core/delegation`, `@mcp-i/core/proof`, etc.
327
137
 
328
- const server = new McpServer({ name: 'my-server', version: '1.0.0' });
138
+ ---
329
139
 
330
- // One call: auto-generates identity, registers handshake, auto-proofs all tools
331
- await withMCPI(server, { crypto: new NodeCryptoProvider() });
140
+ ## Examples
332
141
 
333
- // Register tools normally proofs are attached automatically
334
- server.registerTool(
335
- 'echo',
336
- { description: 'Echo a message', inputSchema: { message: z.string() } },
337
- async ({ message }) => ({ content: [{ type: 'text' as const, text: `Echo: ${message}` }] }),
338
- );
142
+ | Example | What it shows |
143
+ |---------|--------------|
144
+ | [**consent-basic**](./examples/consent-basic/) | Human-in-the-loop consent flow: `needs_authorization` → consent page → delegation VC → tool execution. SSE + Streamable HTTP transports. |
145
+ | [**consent-full**](./examples/consent-full/) | Same consent flow as consent-basic, powered by [`@kya-os/consent`](https://www.npmjs.com/package/@kya-os/consent) multi-mode auth, configurable branding, and production-grade consent UI. |
146
+ | [**node-server**](./examples/node-server/) | Low-level Server API with handshake, proof, and restricted tools. |
147
+ | [**brave-search-mcp-server**](./examples/brave-search-mcp-server/) | Real-world MCP server wrapping Brave Search with MCP-I identity and proofs. |
148
+ | [**outbound-delegation**](./examples/outbound-delegation/) | Forwarding delegation context to downstream services (§7 gateway pattern). |
149
+ | [**verify-proof**](./examples/verify-proof/) | Standalone proof verification with DID:key resolution. |
150
+ | [**context7-with-mcpi**](./examples/context7-with-mcpi/) | Adding MCP-I to an existing MCP server with `withMCPI`. |
339
151
 
340
- await server.connect(new StdioServerTransport());
341
- ```
152
+ ---
342
153
 
343
- <details>
344
- <summary>Low-level Server API (advanced)</summary>
154
+ ## Extension Points
345
155
 
346
- For the low-level `Server` API with manual request handlers, use `createMCPIMiddleware` directly:
156
+ MCP-I is a protocol, not a platform. All cryptographic operations, storage, and identity management are abstracted behind interfaces you implement:
347
157
 
348
158
  ```typescript
349
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
350
- import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
351
- import { createMCPIMiddleware, generateIdentity } from '@mcp-i/core';
352
-
353
- const crypto = new NodeCryptoProvider();
354
- const identity = await generateIdentity(crypto);
355
-
356
- const mcpi = createMCPIMiddleware({ identity, session: { sessionTtlMinutes: 60 } }, crypto);
159
+ // Use AWS KMS instead of local keys
160
+ class KMSCryptoProvider extends CryptoProvider {
161
+ async sign(data: Uint8Array, keyArn: string) {
162
+ return kmsClient.sign({ KeyId: keyArn, Message: data });
163
+ }
164
+ }
357
165
 
358
- const echo = mcpi.wrapWithProof('echo', async (args) => ({
359
- content: [{ type: 'text', text: `Echo: ${args['message']}` }],
360
- }));
166
+ // Use Redis instead of in-memory nonce cache
167
+ class RedisNonceCacheProvider extends NonceCacheProvider {
168
+ async hasNonce(nonce: string) { return redis.exists(`nonce:${nonce}`); }
169
+ async addNonce(nonce: string, ttl: number) { redis.setex(`nonce:${nonce}`, ttl, '1'); }
170
+ }
171
+ ```
361
172
 
362
- const server = new Server({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });
173
+ Supported DID methods: `did:key` (built-in, self-resolving), `did:web` (built-in, HTTP resolution), or any custom method via `DIDResolver`.
363
174
 
364
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
365
- tools: [mcpi.handshakeTool, { name: 'echo', inputSchema: { type: 'object' } }],
366
- }));
175
+ ---
367
176
 
368
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
369
- if (req.params.name === '_mcpi_handshake') return mcpi.handleHandshake(req.params.arguments ?? {});
370
- if (req.params.name === 'echo') return echo(req.params.arguments ?? {});
371
- return { content: [{ type: 'text', text: 'Unknown tool' }], isError: true };
372
- });
177
+ ## Conformance
373
178
 
374
- await server.connect(new StdioServerTransport());
375
- ```
179
+ Three levels defined in [CONFORMANCE.md](./CONFORMANCE.md):
376
180
 
377
- </details>
181
+ | Level | Requirements |
182
+ |-------|-------------|
183
+ | **Level 1** — Core Crypto | Ed25519 signatures, DID:key resolution, JCS canonicalization |
184
+ | **Level 2** — Full Session | Nonce-based handshake, session management, replay prevention |
185
+ | **Level 3** — Full Delegation | W3C VC issuance/verification, scope attenuation, StatusList2021, cascading revocation |
378
186
 
379
187
  ---
380
188
 
381
- ## Example 5 — Verify a Proof with DID:key Resolution
189
+ ## Contributing
382
190
 
383
- ```typescript
384
- import { ProofVerifier, createDidKeyResolver, CryptoProvider } from '@mcp-i/core';
191
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). DCO sign-off required. All PRs must pass CI (type check, lint, test across Node 20/22 on Linux/macOS/Windows).
385
192
 
386
- // ... (NodeCryptoProvider with verify + hash)
193
+ ## Governance
387
194
 
388
- const crypto = new NodeCryptoProvider();
389
- const didResolver = createDidKeyResolver();
390
-
391
- const verifier = new ProofVerifier({
392
- cryptoProvider: crypto,
393
- fetchPublicKeyFromDID: async (did) => {
394
- const result = didResolver(did);
395
- return result?.publicKeyJwk ?? null;
396
- },
397
- timestampSkewSeconds: 120,
398
- });
195
+ See [GOVERNANCE.md](./GOVERNANCE.md). Lazy consensus for non-breaking changes. Explicit vote for breaking changes.
399
196
 
400
- const result = await verifier.verifyProofDetached(proof);
401
- console.log('Valid:', result.valid);
402
- ```
197
+ ## Security
403
198
 
404
- ---
199
+ See [SECURITY.md](./SECURITY.md). 48-hour acknowledgement. 90-day coordinated disclosure.
405
200
 
406
201
  ## License
407
202
 
408
203
  MIT — see [LICENSE](./LICENSE)
409
-
410
- ---
411
-
412
- *This package is a DIF TAAWG protocol reference implementation.*
413
- *Spec: [modelcontextprotocol-identity.io](https://modelcontextprotocol-identity.io)*
@@ -15,12 +15,20 @@ import type { DelegationVerifier, VerifyDelegationResult } from './types.js';
15
15
  export type { DelegationVerifier, VerifyDelegationResult };
16
16
  export interface AgentReputation {
17
17
  agentDid: string;
18
- score: number;
18
+ score: number | null;
19
19
  totalInteractions: number;
20
20
  successRate: number;
21
21
  riskLevel: 'low' | 'medium' | 'high' | 'unknown';
22
22
  updatedAt: number;
23
23
  }
24
+ /**
25
+ * Policy for handling agents with no reputation history.
26
+ *
27
+ * - 'deny' — reject unknown agents outright (strict environments)
28
+ * - 'require-consent' — route to the consent/authorization flow (default)
29
+ * - 'allow' — let unknown agents through (reputation is advisory only)
30
+ */
31
+ export type UnknownAgentPolicy = 'deny' | 'require-consent' | 'allow';
24
32
  export interface AuthHandshakeConfig {
25
33
  delegationVerifier: DelegationVerifier;
26
34
  resumeTokenStore: ResumeTokenStore;
@@ -32,7 +40,15 @@ export interface AuthHandshakeConfig {
32
40
  authorization: {
33
41
  authorizationUrl: string;
34
42
  resumeTokenTtl?: number;
35
- requireAuthForUnknown?: boolean;
43
+ /**
44
+ * How to handle agents with no reputation history (404 from reputation
45
+ * service, network error, or first-time agent).
46
+ *
47
+ * - 'deny' — reject outright
48
+ * - 'require-consent' — route to consent flow (default)
49
+ * - 'allow' — skip reputation gate for unknowns
50
+ */
51
+ unknownAgentPolicy?: UnknownAgentPolicy;
36
52
  minReputationScore?: number;
37
53
  };
38
54
  debug?: boolean;
@@ -96,9 +112,8 @@ export declare class MemoryResumeTokenStore implements ResumeTokenStore {
96
112
  * @param agentDid - The agent's DID to verify
97
113
  * @param scopes - Required scopes for the operation
98
114
  * @param config - Authorization configuration including verifier, token store, etc.
99
- * @param _resumeToken - Optional resume token from previous authorization attempt
100
115
  * @returns Result indicating authorization status, delegation, or auth hints
101
116
  */
102
- export declare function verifyOrHints(agentDid: string, scopes: string[], config: AuthHandshakeConfig, _resumeToken?: string): Promise<VerifyOrHintsResult>;
117
+ export declare function verifyOrHints(agentDid: string, scopes: string[], config: AuthHandshakeConfig): Promise<VerifyOrHintsResult>;
103
118
  export declare function hasSensitiveScopes(scopes: string[]): boolean;
104
119
  //# sourceMappingURL=handshake.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/auth/handshake.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/auth/handshake.ts"],"names":[],"mappings":"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"}