@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.
- package/README.md +123 -333
- package/dist/auth/handshake.d.ts +19 -4
- package/dist/auth/handshake.d.ts.map +1 -1
- package/dist/auth/handshake.js +52 -15
- package/dist/auth/handshake.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/delegation/did-key-resolver.d.ts.map +1 -1
- package/dist/delegation/did-key-resolver.js +9 -6
- package/dist/delegation/did-key-resolver.js.map +1 -1
- package/dist/delegation/outbound-headers.d.ts +2 -4
- package/dist/delegation/outbound-headers.d.ts.map +1 -1
- package/dist/delegation/outbound-headers.js +2 -3
- package/dist/delegation/outbound-headers.js.map +1 -1
- package/dist/delegation/statuslist-manager.d.ts.map +1 -1
- package/dist/delegation/statuslist-manager.js +1 -1
- package/dist/delegation/statuslist-manager.js.map +1 -1
- package/dist/delegation/vc-verifier.d.ts.map +1 -1
- package/dist/delegation/vc-verifier.js +2 -2
- package/dist/delegation/vc-verifier.js.map +1 -1
- package/dist/errors.d.ts +42 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +45 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/mcpi-transport.d.ts +39 -0
- package/dist/middleware/mcpi-transport.d.ts.map +1 -0
- package/dist/middleware/mcpi-transport.js +121 -0
- package/dist/middleware/mcpi-transport.js.map +1 -0
- package/dist/middleware/with-mcpi-server.d.ts +25 -9
- package/dist/middleware/with-mcpi-server.d.ts.map +1 -1
- package/dist/middleware/with-mcpi-server.js +62 -47
- package/dist/middleware/with-mcpi-server.js.map +1 -1
- package/dist/middleware/with-mcpi.d.ts +26 -5
- package/dist/middleware/with-mcpi.d.ts.map +1 -1
- package/dist/middleware/with-mcpi.js +108 -10
- package/dist/middleware/with-mcpi.js.map +1 -1
- package/dist/providers/memory.js +2 -2
- package/dist/providers/memory.js.map +1 -1
- package/dist/session/manager.d.ts +7 -1
- package/dist/session/manager.d.ts.map +1 -1
- package/dist/session/manager.js +20 -4
- package/dist/session/manager.js.map +1 -1
- package/dist/utils/crypto-service.d.ts.map +1 -1
- package/dist/utils/crypto-service.js +11 -10
- package/dist/utils/crypto-service.js.map +1 -1
- package/dist/utils/did-helpers.d.ts +12 -0
- package/dist/utils/did-helpers.d.ts.map +1 -1
- package/dist/utils/did-helpers.js +18 -0
- package/dist/utils/did-helpers.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/errors.test.ts +56 -0
- package/src/__tests__/integration/full-flow.test.ts +1 -1
- package/src/__tests__/integration/mcp-enhance-server.test.ts +48 -5
- package/src/__tests__/integration/mcp-transport-context7.test.ts +19 -15
- package/src/__tests__/integration/mcp-transport.test.ts +13 -10
- package/src/__tests__/providers/base.test.ts +1 -1
- package/src/__tests__/providers/memory.test.ts +2 -2
- package/src/__tests__/utils/mock-providers.ts +2 -2
- package/src/auth/__tests__/handshake.test.ts +190 -0
- package/src/auth/handshake.ts +88 -21
- package/src/auth/index.ts +1 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +2 -2
- package/src/delegation/__tests__/outbound-headers.test.ts +16 -20
- package/src/delegation/__tests__/statuslist-manager.test.ts +120 -7
- package/src/delegation/__tests__/vc-verifier.test.ts +45 -3
- package/src/delegation/did-key-resolver.ts +11 -6
- package/src/delegation/outbound-headers.ts +1 -4
- package/src/delegation/statuslist-manager.ts +3 -1
- package/src/delegation/vc-verifier.ts +3 -2
- package/src/errors.ts +65 -0
- package/src/index.ts +10 -0
- package/src/middleware/__tests__/mcpi-transport.test.ts +150 -0
- package/src/middleware/__tests__/with-mcpi-server.test.ts +117 -0
- package/src/middleware/__tests__/with-mcpi.test.ts +124 -6
- package/src/middleware/index.ts +6 -0
- package/src/middleware/mcpi-transport.ts +162 -0
- package/src/middleware/with-mcpi-server.ts +83 -92
- package/src/middleware/with-mcpi.ts +147 -11
- package/src/proof/__tests__/errors.test.ts +79 -0
- package/src/proof/__tests__/verifier.test.ts +5 -5
- package/src/providers/memory.ts +2 -2
- package/src/session/__tests__/session-manager.test.ts +3 -3
- package/src/session/manager.ts +28 -6
- package/src/utils/crypto-service.ts +11 -10
- package/src/utils/did-helpers.ts +19 -0
package/README.md
CHANGED
|
@@ -1,413 +1,203 @@
|
|
|
1
1
|
# @mcp-i/core
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Identity, delegation, and proof for the Model Context Protocol.**
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
11
|
+
## Try It
|
|
32
12
|
|
|
33
|
-
|
|
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/
|
|
19
|
+
npx tsx examples/consent-basic/src/server.ts
|
|
40
20
|
```
|
|
41
21
|
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
+
---
|
|
59
32
|
|
|
60
|
-
|
|
61
|
-
- Credentials with `credentialStatus` require `delegation.statusListResolver`.
|
|
33
|
+
## Add to Your Server
|
|
62
34
|
|
|
63
|
-
|
|
35
|
+
One line to add identity, sessions, and proofs to any MCP server:
|
|
64
36
|
|
|
65
37
|
```typescript
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
41
|
+
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
42
|
+
await withMCPI(server, { crypto: new NodeCryptoProvider() });
|
|
79
43
|
|
|
80
|
-
|
|
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
|
-
|
|
51
|
+
If your runtime has a native connection/auth handshake hook, disable tool exposure and call middleware directly:
|
|
85
52
|
|
|
86
53
|
```typescript
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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 {
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
//
|
|
217
|
-
const
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
98
|
+
```bash
|
|
99
|
+
npm install @mcp-i/core
|
|
100
|
+
```
|
|
278
101
|
|
|
279
|
-
|
|
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
|
-
|
|
284
|
-
const generator = new ProofGenerator(agent, cryptoProvider);
|
|
104
|
+
---
|
|
285
105
|
|
|
286
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
##
|
|
317
|
-
|
|
318
|
-
The recommended way to add MCP-I to any `McpServer`-based server:
|
|
125
|
+
## Modules
|
|
319
126
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
136
|
+
All modules available as subpath exports: `@mcp-i/core/delegation`, `@mcp-i/core/proof`, etc.
|
|
327
137
|
|
|
328
|
-
|
|
138
|
+
---
|
|
329
139
|
|
|
330
|
-
|
|
331
|
-
await withMCPI(server, { crypto: new NodeCryptoProvider() });
|
|
140
|
+
## Examples
|
|
332
141
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
341
|
-
```
|
|
152
|
+
---
|
|
342
153
|
|
|
343
|
-
|
|
344
|
-
<summary>Low-level Server API (advanced)</summary>
|
|
154
|
+
## Extension Points
|
|
345
155
|
|
|
346
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
359
|
-
|
|
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
|
-
|
|
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
|
-
|
|
365
|
-
tools: [mcpi.handshakeTool, { name: 'echo', inputSchema: { type: 'object' } }],
|
|
366
|
-
}));
|
|
175
|
+
---
|
|
367
176
|
|
|
368
|
-
|
|
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
|
-
|
|
375
|
-
```
|
|
179
|
+
Three levels defined in [CONFORMANCE.md](./CONFORMANCE.md):
|
|
376
180
|
|
|
377
|
-
|
|
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
|
-
##
|
|
189
|
+
## Contributing
|
|
382
190
|
|
|
383
|
-
|
|
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
|
-
|
|
193
|
+
## Governance
|
|
387
194
|
|
|
388
|
-
|
|
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
|
-
|
|
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)*
|
package/dist/auth/handshake.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,uBAAuB,EAExB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAE7E,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/auth/handshake.ts"],"names":[],"mappings":"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"}
|