@mcp-i/core 1.1.0-canary.2 → 1.1.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/README.md +81 -363
- 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,131 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 |
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://modelcontextprotocol-identity.io">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://modelcontextprotocol-identity.io/images/logo-mark_white.svg">
|
|
5
|
+
<img alt="MCP-I" src="https://modelcontextprotocol-identity.io/images/logo-mark_black.svg" width="64">
|
|
6
|
+
</picture>
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<strong>Identity, delegation, and proof for the Model Context Protocol.</strong>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://www.npmjs.com/package/@mcp-i/core"><img src="https://img.shields.io/npm/v/@mcp-i/core" alt="npm"></a>
|
|
16
|
+
<a href="https://modelcontextprotocol-identity.io"><img src="https://img.shields.io/badge/spec-modelcontextprotocol--identity.io-blue" alt="spec"></a>
|
|
17
|
+
<a href="https://identity.foundation/working-groups/agent-and-authorization.html"><img src="https://img.shields.io/badge/DIF-TAAWG-purple" alt="DIF TAAWG"></a>
|
|
18
|
+
<a href="./LICENSE"><img src="https://img.shields.io/github/license/modelcontextprotocol-identity/mcp-i-core" alt="license"></a>
|
|
19
|
+
</p>
|
|
28
20
|
|
|
29
21
|
---
|
|
30
22
|
|
|
31
|
-
|
|
23
|
+
AI agents call tools on your behalf. But today, there's no way to know *who* called, *whether they were allowed to*, or *what actually happened*. MCP-I fixes that.
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
- **Every server gets a cryptographic identity** (DID) — no accounts, no API keys, no central registry
|
|
26
|
+
- **Every tool call gets a signed proof** — a tamper-evident receipt the agent can't forge or deny
|
|
27
|
+
- **Protected tools require human consent** — per-tool authorization via W3C Delegation Credentials
|
|
28
|
+
- **The AI never knows** — identity, proofs, and consent happen transparently in the protocol layer
|
|
34
29
|
|
|
35
|
-
```bash
|
|
36
|
-
git clone https://github.com/modelcontextprotocol-identity/mcp-i-core.git
|
|
37
|
-
cd mcp-i-core
|
|
38
|
-
pnpm install
|
|
39
|
-
npx tsx examples/node-server/server.ts
|
|
40
30
|
```
|
|
41
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
31
|
npm install @mcp-i/core
|
|
52
32
|
```
|
|
53
33
|
|
|
54
34
|
---
|
|
55
35
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
MCP-I now enforces strict delegation-chain and status-list checks by default.
|
|
36
|
+
## Migrate Any MCP Server in 2 Lines
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
- Credentials with `credentialStatus` require `delegation.statusListResolver`.
|
|
62
|
-
|
|
63
|
-
For temporary migration support in legacy integrations, you can opt in to compatibility mode:
|
|
38
|
+
**Before** — a standard MCP server with no identity or proofs:
|
|
64
39
|
|
|
65
40
|
```typescript
|
|
66
|
-
|
|
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
|
-
---
|
|
77
|
-
|
|
78
|
-
## Examples
|
|
41
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
79
42
|
|
|
80
|
-
|
|
43
|
+
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
81
44
|
|
|
82
|
-
|
|
45
|
+
server.registerTool('greet', { description: 'Say hello' }, async (args) => ({
|
|
46
|
+
content: [{ type: 'text', text: `Hello, ${args.name}!` }],
|
|
47
|
+
}));
|
|
48
|
+
```
|
|
83
49
|
|
|
84
|
-
|
|
50
|
+
**After** — every tool response now carries a signed cryptographic proof:
|
|
85
51
|
|
|
86
52
|
```typescript
|
|
87
|
-
import {
|
|
88
|
-
|
|
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
|
-
},
|
|
170
|
-
});
|
|
171
|
-
|
|
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'
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
---
|
|
53
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
54
|
+
import { withMCPI, NodeCryptoProvider } from '@mcp-i/core'; // +1 line
|
|
181
55
|
|
|
182
|
-
|
|
56
|
+
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
57
|
+
await withMCPI(server, { crypto: new NodeCryptoProvider() }); // +1 line
|
|
183
58
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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();
|
|
204
|
-
|
|
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';
|
|
215
|
-
|
|
216
|
-
// Server: validate it
|
|
217
|
-
const result = await sessionManager.validateHandshake(request);
|
|
218
|
-
|
|
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, ... } }
|
|
59
|
+
server.registerTool('greet', { description: 'Say hello' }, async (args) => ({
|
|
60
|
+
content: [{ type: 'text', text: `Hello, ${args.name}!` }],
|
|
61
|
+
}));
|
|
229
62
|
```
|
|
230
63
|
|
|
231
|
-
|
|
64
|
+
That's it. `withMCPI` auto-generates an Ed25519 identity, registers the `_mcpi` protocol tool, and wraps the transport so every tool response includes a detached JWS proof in `_meta` — invisible to the LLM, verifiable by anyone.
|
|
232
65
|
|
|
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
|
-
}
|
|
276
|
-
|
|
277
|
-
const cryptoProvider = new NodeCryptoProvider();
|
|
278
|
-
|
|
279
|
-
// MemoryIdentityProvider generates a fresh DID + Ed25519 key pair
|
|
280
|
-
const identityProvider = new MemoryIdentityProvider(cryptoProvider);
|
|
281
|
-
const agent = await identityProvider.getIdentity();
|
|
282
|
-
|
|
283
|
-
// ProofGenerator signs tool call request+response pairs with the agent's key
|
|
284
|
-
const generator = new ProofGenerator(agent, cryptoProvider);
|
|
285
|
-
|
|
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' } };
|
|
291
|
-
|
|
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...'
|
|
312
|
-
```
|
|
66
|
+
> See the full working example: [examples/context7-with-mcpi](./examples/context7-with-mcpi/) — a real MCP server (Context7) migrated with exactly 2 lines of code.
|
|
313
67
|
|
|
314
68
|
---
|
|
315
69
|
|
|
316
|
-
##
|
|
70
|
+
## Protect Tools with Human Consent
|
|
317
71
|
|
|
318
|
-
|
|
72
|
+
Some tools shouldn't run without a human saying "yes." MCP-I adds per-tool authorization using W3C Verifiable Credentials:
|
|
319
73
|
|
|
320
74
|
```typescript
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
329
|
-
|
|
330
|
-
// One call: auto-generates identity, registers handshake, auto-proofs all tools
|
|
331
|
-
await withMCPI(server, { crypto: new NodeCryptoProvider() });
|
|
332
|
-
|
|
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}` }] }),
|
|
75
|
+
const checkout = mcpi.wrapWithDelegation(
|
|
76
|
+
'checkout',
|
|
77
|
+
{ scopeId: 'cart:write', consentUrl: 'https://example.com/consent' },
|
|
78
|
+
mcpi.wrapWithProof('checkout', async (args) => ({
|
|
79
|
+
content: [{ type: 'text', text: `Order placed: ${args.item}` }],
|
|
80
|
+
})),
|
|
338
81
|
);
|
|
339
|
-
|
|
340
|
-
await server.connect(new StdioServerTransport());
|
|
341
82
|
```
|
|
342
83
|
|
|
343
|
-
|
|
344
|
-
<summary>Low-level Server API (advanced)</summary>
|
|
345
|
-
|
|
346
|
-
For the low-level `Server` API with manual request handlers, use `createMCPIMiddleware` directly:
|
|
347
|
-
|
|
348
|
-
```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';
|
|
84
|
+
When an agent calls `checkout` without a delegation credential, it gets back a `needs_authorization` response with a consent URL. The human approves, a scoped credential is issued, and the agent retries — now authorized.
|
|
352
85
|
|
|
353
|
-
|
|
354
|
-
const identity = await generateIdentity(crypto);
|
|
86
|
+
> Try it yourself: [examples/consent-basic](./examples/consent-basic/) walks through the full consent flow end-to-end.
|
|
355
87
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const echo = mcpi.wrapWithProof('echo', async (args) => ({
|
|
359
|
-
content: [{ type: 'text', text: `Echo: ${args['message']}` }],
|
|
360
|
-
}));
|
|
361
|
-
|
|
362
|
-
const server = new Server({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
363
|
-
|
|
364
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
365
|
-
tools: [mcpi.handshakeTool, { name: 'echo', inputSchema: { type: 'object' } }],
|
|
366
|
-
}));
|
|
88
|
+
---
|
|
367
89
|
|
|
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
|
-
});
|
|
90
|
+
## See It in Action
|
|
373
91
|
|
|
374
|
-
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/modelcontextprotocol-identity/mcp-i-core.git
|
|
94
|
+
cd mcp-i-core && npm install
|
|
95
|
+
bash scripts/demo.sh
|
|
375
96
|
```
|
|
376
97
|
|
|
377
|
-
|
|
98
|
+
This starts all example servers and opens [MCP Inspector](https://github.com/modelcontextprotocol/inspector). Connect to any server, call a tool, and inspect the proof in `_meta`:
|
|
378
99
|
|
|
379
|
-
|
|
100
|
+
| Port | Example | What it demonstrates |
|
|
101
|
+
|------|---------|---------------------|
|
|
102
|
+
| 3001 | [node-server](./examples/node-server/) | Proofs + restricted tools (low-level API) |
|
|
103
|
+
| 3002 | [consent-basic](./examples/consent-basic/) | Human consent flow with built-in UI |
|
|
104
|
+
| 3003 | [consent-full](./examples/consent-full/) | Production consent UI ([@kya-os/consent](https://www.npmjs.com/package/@kya-os/consent)) |
|
|
105
|
+
| 3004 | [context7-with-mcpi](./examples/context7-with-mcpi/) | 2-line migration of a real MCP server |
|
|
380
106
|
|
|
381
|
-
|
|
107
|
+
Also available: [outbound-delegation](./examples/outbound-delegation/) (gateway pattern), [verify-proof](./examples/verify-proof/) (standalone verification), [statuslist](./examples/statuslist/) (revocation lifecycle).
|
|
382
108
|
|
|
383
|
-
|
|
384
|
-
import { ProofVerifier, createDidKeyResolver, CryptoProvider } from '@mcp-i/core';
|
|
385
|
-
|
|
386
|
-
// ... (NodeCryptoProvider with verify + hash)
|
|
387
|
-
|
|
388
|
-
const crypto = new NodeCryptoProvider();
|
|
389
|
-
const didResolver = createDidKeyResolver();
|
|
109
|
+
---
|
|
390
110
|
|
|
391
|
-
|
|
392
|
-
cryptoProvider: crypto,
|
|
393
|
-
fetchPublicKeyFromDID: async (did) => {
|
|
394
|
-
const result = didResolver(did);
|
|
395
|
-
return result?.publicKeyJwk ?? null;
|
|
396
|
-
},
|
|
397
|
-
timestampSkewSeconds: 120,
|
|
398
|
-
});
|
|
111
|
+
## What's Under the Hood
|
|
399
112
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
113
|
+
| Capability | How it works |
|
|
114
|
+
|-----------|-------------|
|
|
115
|
+
| **Cryptographic identity** | Ed25519 key pairs, `did:key` and `did:web` resolution |
|
|
116
|
+
| **Signed proofs** | Detached JWS over JCS-canonicalized request/response hashes |
|
|
117
|
+
| **Delegation credentials** | W3C Verifiable Credentials with scope constraints |
|
|
118
|
+
| **Revocation** | StatusList2021 bitstring with cascading revocation |
|
|
119
|
+
| **Replay prevention** | Nonce-based handshake with timestamp skew validation |
|
|
120
|
+
| **Extensible** | Bring your own KMS, HSM, nonce cache (Redis, DynamoDB, KV), or DID method |
|
|
403
121
|
|
|
404
122
|
---
|
|
405
123
|
|
|
406
|
-
##
|
|
124
|
+
## Links
|
|
407
125
|
|
|
408
|
-
|
|
126
|
+
- [Spec](https://modelcontextprotocol-identity.io) | [DIF TAAWG](https://identity.foundation/working-groups/agent-and-authorization.html) | [npm](https://www.npmjs.com/package/@mcp-i/core)
|
|
127
|
+
- [CONTRIBUTING.md](./CONTRIBUTING.md) | [CONFORMANCE.md](./CONFORMANCE.md) | [SECURITY.md](./SECURITY.md) | [GOVERNANCE.md](./GOVERNANCE.md)
|
|
409
128
|
|
|
410
|
-
|
|
129
|
+
## License
|
|
411
130
|
|
|
412
|
-
|
|
413
|
-
*Spec: [modelcontextprotocol-identity.io](https://modelcontextprotocol-identity.io)*
|
|
131
|
+
MIT
|
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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"}
|