@kya-os/create-mcpi-app 1.7.17 → 1.7.20
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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test$colon$coverage.log +315 -0
- package/.turbo/turbo-test.log +95 -0
- package/CHANGELOG.md +372 -0
- package/IMPLEMENTATION_SUMMARY.md +108 -0
- package/REMEDIATION_PLAN.md +99 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +252 -0
- package/coverage/config-builder.ts.html +580 -0
- package/coverage/coverage-final.json +7 -0
- package/coverage/favicon.png +0 -0
- package/coverage/fetch-cloudflare-mcpi-template.ts.html +7006 -0
- package/coverage/generate-config.ts.html +436 -0
- package/coverage/generate-identity.ts.html +574 -0
- package/coverage/index.html +191 -0
- package/coverage/install.ts.html +322 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/validate-project-structure.ts.html +466 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/helpers/__tests__/config-builder.spec.d.ts +8 -0
- package/dist/helpers/__tests__/config-builder.spec.d.ts.map +1 -0
- package/dist/helpers/__tests__/config-builder.spec.js +182 -0
- package/dist/helpers/__tests__/config-builder.spec.js.map +1 -0
- package/dist/helpers/config-builder.d.ts +58 -0
- package/dist/helpers/config-builder.d.ts.map +1 -0
- package/dist/helpers/config-builder.js +102 -0
- package/dist/helpers/config-builder.js.map +1 -0
- package/dist/helpers/create.d.ts +1 -0
- package/dist/helpers/create.d.ts.map +1 -1
- package/dist/helpers/create.js +2 -1
- package/dist/helpers/create.js.map +1 -1
- package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts +1 -0
- package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts.map +1 -1
- package/dist/helpers/fetch-cloudflare-mcpi-template.js +209 -174
- package/dist/helpers/fetch-cloudflare-mcpi-template.js.map +1 -1
- package/dist/helpers/fetch-mcpi-template.d.ts.map +1 -1
- package/dist/helpers/fetch-mcpi-template.js +18 -3
- package/dist/helpers/fetch-mcpi-template.js.map +1 -1
- package/dist/helpers/generate-config.d.ts.map +1 -1
- package/dist/helpers/generate-config.js +27 -40
- package/dist/helpers/generate-config.js.map +1 -1
- package/dist/helpers/install.js +5 -0
- package/dist/helpers/install.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +18 -9
- package/scripts/prepare-pack.js +47 -0
- package/scripts/validate-no-workspace.js +79 -0
- package/src/__tests__/cloudflare-template.test.ts +488 -0
- package/src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts +337 -0
- package/src/__tests__/helpers/generate-config.test.ts +312 -0
- package/src/__tests__/helpers/generate-identity.test.ts +271 -0
- package/src/__tests__/helpers/install.test.ts +362 -0
- package/src/__tests__/helpers/validate-project-structure.test.ts +467 -0
- package/src/__tests__.bak/regression.test.ts +434 -0
- package/src/effects/index.ts +80 -0
- package/src/helpers/__tests__/config-builder.spec.ts +231 -0
- package/src/helpers/apply-identity-preset.ts +209 -0
- package/src/helpers/config-builder.ts +165 -0
- package/src/helpers/copy-template.ts +11 -0
- package/src/helpers/create.ts +239 -0
- package/src/helpers/fetch-cloudflare-mcpi-template.ts +2311 -0
- package/src/helpers/fetch-cloudflare-template.ts +361 -0
- package/src/helpers/fetch-mcpi-template.ts +236 -0
- package/src/helpers/fetch-xmcp-template.ts +153 -0
- package/src/helpers/generate-config.ts +117 -0
- package/src/helpers/generate-identity.ts +163 -0
- package/src/helpers/identity-manager.ts +186 -0
- package/src/helpers/install.ts +79 -0
- package/src/helpers/rename.ts +17 -0
- package/src/helpers/validate-project-structure.ts +127 -0
- package/src/index.ts +480 -0
- package/src/utils/check-node.ts +17 -0
- package/src/utils/is-folder-empty.ts +60 -0
- package/src/utils/validate-project-name.ts +132 -0
- package/test-cloudflare/README.md +164 -0
- package/test-cloudflare/package.json +28 -0
- package/test-cloudflare/src/index.ts +340 -0
- package/test-cloudflare/src/tools/greet.ts +19 -0
- package/test-cloudflare/tests/cache-invalidation.test.ts +410 -0
- package/test-cloudflare/tests/cors-security.test.ts +349 -0
- package/test-cloudflare/tests/delegation.test.ts +335 -0
- package/test-cloudflare/tests/do-routing.test.ts +314 -0
- package/test-cloudflare/tests/integration.test.ts +205 -0
- package/test-cloudflare/tests/session-management.test.ts +359 -0
- package/test-cloudflare/tsconfig.json +22 -0
- package/test-cloudflare/vitest.config.ts +9 -0
- package/test-cloudflare/wrangler.toml +37 -0
- package/test-node/README.md +44 -0
- package/test-node/package.json +23 -0
- package/test-node/src/tools/greet.ts +25 -0
- package/test-node/xmcp.config.ts +20 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Session Management Tests
|
|
5
|
+
* Tests proper session tracking, TTL, and MCP session ID handling
|
|
6
|
+
*/
|
|
7
|
+
describe('Session Management', () => {
|
|
8
|
+
|
|
9
|
+
// Mock KV namespace for session storage
|
|
10
|
+
const mockSessionStorage = {
|
|
11
|
+
get: vi.fn(),
|
|
12
|
+
put: vi.fn(),
|
|
13
|
+
delete: vi.fn()
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('Session ID Reading', () => {
|
|
21
|
+
test('should read MCP session ID from Claude Desktop headers', () => {
|
|
22
|
+
const headers = new Headers({
|
|
23
|
+
'mcp-session-id': 'claude-desktop-session-123'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const sessionId = headers.get('mcp-session-id') ||
|
|
27
|
+
headers.get('Mcp-Session-Id');
|
|
28
|
+
|
|
29
|
+
expect(sessionId).toBe('claude-desktop-session-123');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should handle both header casings', () => {
|
|
33
|
+
const headers1 = new Headers({
|
|
34
|
+
'mcp-session-id': 'session-lower'
|
|
35
|
+
});
|
|
36
|
+
const headers2 = new Headers({
|
|
37
|
+
'Mcp-Session-Id': 'session-pascal'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const id1 = headers1.get('mcp-session-id') || headers1.get('Mcp-Session-Id');
|
|
41
|
+
const id2 = headers2.get('mcp-session-id') || headers2.get('Mcp-Session-Id');
|
|
42
|
+
|
|
43
|
+
expect(id1).toBe('session-lower');
|
|
44
|
+
expect(id2).toBe('session-pascal');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should NOT create ephemeral session for every tool call', () => {
|
|
48
|
+
// Mock multiple tool calls with same session
|
|
49
|
+
const sessionId = 'persistent-session-456';
|
|
50
|
+
const timestamps: string[] = [];
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < 5; i++) {
|
|
53
|
+
// Simulate tool calls with same session
|
|
54
|
+
const headers = new Headers({
|
|
55
|
+
'mcp-session-id': sessionId
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const retrievedId = headers.get('mcp-session-id') ||
|
|
59
|
+
`ephemeral-${Date.now()}-${Math.random()}`;
|
|
60
|
+
|
|
61
|
+
timestamps.push(retrievedId);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// All should be the same session ID
|
|
65
|
+
expect(new Set(timestamps).size).toBe(1);
|
|
66
|
+
expect(timestamps[0]).toBe(sessionId);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('should create ephemeral session only when header missing', () => {
|
|
70
|
+
const headers = new Headers({}); // No session ID
|
|
71
|
+
|
|
72
|
+
const sessionId = headers.get('mcp-session-id') ||
|
|
73
|
+
headers.get('Mcp-Session-Id') ||
|
|
74
|
+
`ephemeral-${Date.now()}-${Math.random().toString(36).substring(2, 10)}`;
|
|
75
|
+
|
|
76
|
+
expect(sessionId).toMatch(/^ephemeral-\d+-[a-z0-9]+$/);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('Session Storage', () => {
|
|
81
|
+
test('should store session with 30-minute TTL per MCP spec', async () => {
|
|
82
|
+
const sessionId = 'test-session-789';
|
|
83
|
+
const sessionData = {
|
|
84
|
+
id: sessionId,
|
|
85
|
+
audience: 'https://kya.vouched.id',
|
|
86
|
+
agentDid: 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK',
|
|
87
|
+
createdAt: Date.now(),
|
|
88
|
+
expiresAt: Date.now() + (30 * 60 * 1000) // 30 minutes
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
await mockSessionStorage.put(
|
|
92
|
+
`session:${sessionId}`,
|
|
93
|
+
JSON.stringify(sessionData),
|
|
94
|
+
{ expirationTtl: 1800 } // 30 minutes in seconds
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(mockSessionStorage.put).toHaveBeenCalledWith(
|
|
98
|
+
`session:${sessionId}`,
|
|
99
|
+
expect.any(String),
|
|
100
|
+
{ expirationTtl: 1800 }
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('should validate session TTL on retrieval', async () => {
|
|
105
|
+
const sessionId = 'expired-session';
|
|
106
|
+
const expiredSession = {
|
|
107
|
+
id: sessionId,
|
|
108
|
+
expiresAt: Date.now() - 1000 // Expired 1 second ago
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
mockSessionStorage.get.mockResolvedValueOnce(JSON.stringify(expiredSession));
|
|
112
|
+
|
|
113
|
+
const data = await mockSessionStorage.get(`session:${sessionId}`);
|
|
114
|
+
const session = JSON.parse(data);
|
|
115
|
+
|
|
116
|
+
// Should detect expiration
|
|
117
|
+
const isExpired = session.expiresAt < Date.now();
|
|
118
|
+
expect(isExpired).toBe(true);
|
|
119
|
+
|
|
120
|
+
// Should delete expired session
|
|
121
|
+
if (isExpired) {
|
|
122
|
+
await mockSessionStorage.delete(`session:${sessionId}`);
|
|
123
|
+
expect(mockSessionStorage.delete).toHaveBeenCalled();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('should include delegation token in session when available', async () => {
|
|
128
|
+
const sessionId = 'session-with-delegation';
|
|
129
|
+
const delegationToken = 'jwt-delegation-token-xyz';
|
|
130
|
+
|
|
131
|
+
const sessionData = {
|
|
132
|
+
id: sessionId,
|
|
133
|
+
audience: 'https://kya.vouched.id',
|
|
134
|
+
agentDid: 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK',
|
|
135
|
+
createdAt: Date.now(),
|
|
136
|
+
expiresAt: Date.now() + (30 * 60 * 1000),
|
|
137
|
+
delegationToken // Include delegation token
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
await mockSessionStorage.put(
|
|
141
|
+
`session:${sessionId}`,
|
|
142
|
+
JSON.stringify(sessionData),
|
|
143
|
+
{ expirationTtl: 1800 }
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const stored = mockSessionStorage.put.mock.calls[0][1];
|
|
147
|
+
const parsedSession = JSON.parse(stored);
|
|
148
|
+
|
|
149
|
+
expect(parsedSession.delegationToken).toBe(delegationToken);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('Session Consistency', () => {
|
|
154
|
+
test('should maintain session across multiple tool calls', async () => {
|
|
155
|
+
const sessionId = 'consistent-session-abc';
|
|
156
|
+
const toolCalls = [
|
|
157
|
+
{ tool: 'greet', timestamp: Date.now() },
|
|
158
|
+
{ tool: 'search', timestamp: Date.now() + 1000 },
|
|
159
|
+
{ tool: 'analyze', timestamp: Date.now() + 2000 }
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
// All calls should use same session
|
|
163
|
+
for (const call of toolCalls) {
|
|
164
|
+
const headers = new Headers({
|
|
165
|
+
'mcp-session-id': sessionId
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const retrievedId = headers.get('mcp-session-id');
|
|
169
|
+
expect(retrievedId).toBe(sessionId);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('should handle session rotation gracefully', async () => {
|
|
174
|
+
const oldSessionId = 'old-session-123';
|
|
175
|
+
const newSessionId = 'new-session-456';
|
|
176
|
+
|
|
177
|
+
// Store data with old session
|
|
178
|
+
const oldSessionData = {
|
|
179
|
+
id: oldSessionId,
|
|
180
|
+
data: 'user-specific-data'
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
await mockSessionStorage.put(
|
|
184
|
+
`session:${oldSessionId}`,
|
|
185
|
+
JSON.stringify(oldSessionData)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Rotate to new session
|
|
189
|
+
const newSessionData = {
|
|
190
|
+
id: newSessionId,
|
|
191
|
+
data: 'user-specific-data',
|
|
192
|
+
previousSession: oldSessionId
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await mockSessionStorage.put(
|
|
196
|
+
`session:${newSessionId}`,
|
|
197
|
+
JSON.stringify(newSessionData)
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Verify rotation tracked
|
|
201
|
+
const newStored = mockSessionStorage.put.mock.calls[1][1];
|
|
202
|
+
const parsed = JSON.parse(newStored);
|
|
203
|
+
expect(parsed.previousSession).toBe(oldSessionId);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('should isolate sessions between users', async () => {
|
|
207
|
+
const user1SessionId = 'user1-session';
|
|
208
|
+
const user2SessionId = 'user2-session';
|
|
209
|
+
|
|
210
|
+
const user1Data = {
|
|
211
|
+
id: user1SessionId,
|
|
212
|
+
userId: 'user-1',
|
|
213
|
+
delegationToken: 'user1-delegation'
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const user2Data = {
|
|
217
|
+
id: user2SessionId,
|
|
218
|
+
userId: 'user-2',
|
|
219
|
+
delegationToken: 'user2-delegation'
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
await mockSessionStorage.put(
|
|
223
|
+
`session:${user1SessionId}`,
|
|
224
|
+
JSON.stringify(user1Data)
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
await mockSessionStorage.put(
|
|
228
|
+
`session:${user2SessionId}`,
|
|
229
|
+
JSON.stringify(user2Data)
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Sessions should be completely isolated
|
|
233
|
+
mockSessionStorage.get.mockResolvedValueOnce(JSON.stringify(user1Data));
|
|
234
|
+
const retrieved1 = await mockSessionStorage.get(`session:${user1SessionId}`);
|
|
235
|
+
const session1 = JSON.parse(retrieved1);
|
|
236
|
+
|
|
237
|
+
mockSessionStorage.get.mockResolvedValueOnce(JSON.stringify(user2Data));
|
|
238
|
+
const retrieved2 = await mockSessionStorage.get(`session:${user2SessionId}`);
|
|
239
|
+
const session2 = JSON.parse(retrieved2);
|
|
240
|
+
|
|
241
|
+
expect(session1.delegationToken).not.toBe(session2.delegationToken);
|
|
242
|
+
expect(session1.userId).not.toBe(session2.userId);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('Session Security', () => {
|
|
247
|
+
test('should not expose session data in logs', () => {
|
|
248
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
249
|
+
|
|
250
|
+
const sessionId = 'secure-session';
|
|
251
|
+
const sensitiveData = {
|
|
252
|
+
delegationToken: 'secret-token-xyz',
|
|
253
|
+
privateKey: 'private-key-abc'
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Simulate logging
|
|
257
|
+
console.log('[Session] Created session:', sessionId);
|
|
258
|
+
// Should NOT log sensitive data
|
|
259
|
+
|
|
260
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
261
|
+
'[Session] Created session:',
|
|
262
|
+
sessionId
|
|
263
|
+
);
|
|
264
|
+
expect(consoleLogSpy).not.toHaveBeenCalledWith(
|
|
265
|
+
expect.stringContaining('secret-token')
|
|
266
|
+
);
|
|
267
|
+
expect(consoleLogSpy).not.toHaveBeenCalledWith(
|
|
268
|
+
expect.stringContaining('private-key')
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
consoleLogSpy.mockRestore();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('should validate session audience', async () => {
|
|
275
|
+
const sessionId = 'audience-test-session';
|
|
276
|
+
const sessionData = {
|
|
277
|
+
id: sessionId,
|
|
278
|
+
audience: 'https://kya.vouched.id',
|
|
279
|
+
createdAt: Date.now()
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Validate audience matches expected
|
|
283
|
+
expect(sessionData.audience).toBe('https://kya.vouched.id');
|
|
284
|
+
|
|
285
|
+
// Should reject mismatched audience
|
|
286
|
+
const invalidAudience = 'https://evil.com';
|
|
287
|
+
expect(invalidAudience).not.toBe(sessionData.audience);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('should enforce session TTL limits', () => {
|
|
291
|
+
const maxTTL = 30 * 60 * 1000; // 30 minutes
|
|
292
|
+
const timestamp = Date.now();
|
|
293
|
+
|
|
294
|
+
const sessionData = {
|
|
295
|
+
createdAt: timestamp,
|
|
296
|
+
expiresAt: timestamp + maxTTL
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const ttl = sessionData.expiresAt - sessionData.createdAt;
|
|
300
|
+
expect(ttl).toBeLessThanOrEqual(maxTTL);
|
|
301
|
+
expect(ttl).toBe(30 * 60 * 1000);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('Edge Cases', () => {
|
|
306
|
+
test('should handle missing session storage gracefully', async () => {
|
|
307
|
+
const env = {}; // No session storage configured
|
|
308
|
+
|
|
309
|
+
// Should not throw, just return null
|
|
310
|
+
const sessionId = 'test-session';
|
|
311
|
+
const result = await Promise.resolve(null); // Would be getDelegationToken result
|
|
312
|
+
|
|
313
|
+
expect(result).toBeNull();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('should handle corrupt session data', async () => {
|
|
317
|
+
const sessionId = 'corrupt-session';
|
|
318
|
+
|
|
319
|
+
// Return invalid JSON
|
|
320
|
+
mockSessionStorage.get.mockResolvedValueOnce('{ invalid json }');
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const data = await mockSessionStorage.get(`session:${sessionId}`);
|
|
324
|
+
JSON.parse(data);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
expect(error).toBeInstanceOf(SyntaxError);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('should handle very long session IDs', () => {
|
|
331
|
+
const longSessionId = 'a'.repeat(1000);
|
|
332
|
+
const headers = new Headers({
|
|
333
|
+
'mcp-session-id': longSessionId
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const retrievedId = headers.get('mcp-session-id');
|
|
337
|
+
expect(retrievedId).toBe(longSessionId);
|
|
338
|
+
expect(retrievedId.length).toBe(1000);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('should handle concurrent session operations', async () => {
|
|
342
|
+
const sessionId = 'concurrent-session';
|
|
343
|
+
|
|
344
|
+
// Simulate concurrent read/write
|
|
345
|
+
const operations = Array(10).fill(null).map((_, i) =>
|
|
346
|
+
mockSessionStorage.put(
|
|
347
|
+
`session:${sessionId}`,
|
|
348
|
+
JSON.stringify({ counter: i }),
|
|
349
|
+
{ expirationTtl: 1800 }
|
|
350
|
+
)
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
await Promise.all(operations);
|
|
354
|
+
|
|
355
|
+
// All operations should complete
|
|
356
|
+
expect(mockSessionStorage.put).toHaveBeenCalledTimes(10);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2022"
|
|
7
|
+
],
|
|
8
|
+
"types": [
|
|
9
|
+
"@cloudflare/workers-types"
|
|
10
|
+
],
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"forceConsistentCasingInFileNames": true
|
|
18
|
+
},
|
|
19
|
+
"include": [
|
|
20
|
+
"src/**/*"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#:schema node_modules/wrangler/config-schema.json
|
|
2
|
+
name = "test-cloudflare"
|
|
3
|
+
main = "src/index.ts"
|
|
4
|
+
compatibility_date = "2025-06-18"
|
|
5
|
+
compatibility_flags = ["nodejs_compat"]
|
|
6
|
+
|
|
7
|
+
[[durable_objects.bindings]]
|
|
8
|
+
name = "MCP_OBJECT"
|
|
9
|
+
class_name = "CloudflareMCP"
|
|
10
|
+
|
|
11
|
+
[[migrations]]
|
|
12
|
+
tag = "v1"
|
|
13
|
+
new_sqlite_classes = ["CloudflareMCP"]
|
|
14
|
+
|
|
15
|
+
[[kv_namespaces]]
|
|
16
|
+
binding = "NONCE_CACHE"
|
|
17
|
+
id = "your-kv-namespace-id"
|
|
18
|
+
|
|
19
|
+
[[kv_namespaces]]
|
|
20
|
+
binding = "PROOF_ARCHIVE"
|
|
21
|
+
id = "your-proof-archive-kv-id"
|
|
22
|
+
|
|
23
|
+
[vars]
|
|
24
|
+
XMCP_I_TS_SKEW_SEC = "120"
|
|
25
|
+
XMCP_I_SESSION_TTL = "1800"
|
|
26
|
+
|
|
27
|
+
[env.production]
|
|
28
|
+
name = "test-cloudflare-production"
|
|
29
|
+
vars = { XMCP_I_TS_SKEW_SEC = "60" }
|
|
30
|
+
|
|
31
|
+
[[env.production.kv_namespaces]]
|
|
32
|
+
binding = "NONCE_CACHE"
|
|
33
|
+
id = "your-production-nonce-kv-id"
|
|
34
|
+
|
|
35
|
+
[[env.production.kv_namespaces]]
|
|
36
|
+
binding = "PROOF_ARCHIVE"
|
|
37
|
+
id = "your-production-proof-kv-id"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# test-node
|
|
2
|
+
|
|
3
|
+
MCP-I server with cryptographic identity built-in.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
1. Install dependencies:
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. Initialize identity (generates cryptographic keys):
|
|
13
|
+
```bash
|
|
14
|
+
npm run init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
3. Start development server:
|
|
18
|
+
```bash
|
|
19
|
+
npm run dev
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Scripts
|
|
23
|
+
|
|
24
|
+
- `npm run dev` - Start development server with hot reload
|
|
25
|
+
- `npm run build` - Build production bundle
|
|
26
|
+
- `npm run start` - Start production server
|
|
27
|
+
- `npm run init` - Initialize agent identity
|
|
28
|
+
- `npm run status` - Check identity and server status
|
|
29
|
+
- `npm run keys:rotate` - Rotate cryptographic keys
|
|
30
|
+
|
|
31
|
+
## Project Structure
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
├── src/
|
|
35
|
+
│ └── tools/ # MCP tools
|
|
36
|
+
│ └── greet.ts # Example tool
|
|
37
|
+
├── xmcp.config.ts # MCP-I configuration
|
|
38
|
+
└── .mcpi/ # Identity files (git-ignored)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Learn More
|
|
42
|
+
|
|
43
|
+
- [MCP-I Documentation](https://github.com/modelcontextprotocol-identity/mcp-i)
|
|
44
|
+
- [Model Context Protocol](https://modelcontextprotocol.io)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test-node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"@kya-os/cli": "^1.2.7",
|
|
6
|
+
"@kya-os/mcp-i": "^1.2.7",
|
|
7
|
+
"zod": "^3.24.4"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "mcpi dev",
|
|
11
|
+
"build": "mcpi build",
|
|
12
|
+
"start": "mcpi start",
|
|
13
|
+
"init": "mcpi init",
|
|
14
|
+
"register": "mcpi register",
|
|
15
|
+
"keys:rotate": "mcpi rotate",
|
|
16
|
+
"identity:clean": "rm -rf .mcpi",
|
|
17
|
+
"status": "mcpi status"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@modelcontextprotocol/inspector": "^0.16.6",
|
|
21
|
+
"swc-loader": "^0.2.6"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type InferSchema, type ToolMetadata } from "@kya-os/mcp-i";
|
|
3
|
+
|
|
4
|
+
// Define the schema for tool parameters
|
|
5
|
+
export const schema = {
|
|
6
|
+
name: z.string().describe("The name of the user to greet"),
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Define tool metadata
|
|
10
|
+
export const metadata: ToolMetadata = {
|
|
11
|
+
name: "greet",
|
|
12
|
+
description: "Greet the user",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Tool implementation
|
|
16
|
+
export default async function greet({ name }: InferSchema<typeof schema>) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text" as const,
|
|
21
|
+
text: `Hello, ${name}!`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { XmcpConfig } from "@kya-os/mcp-i";
|
|
2
|
+
|
|
3
|
+
const config: XmcpConfig = {
|
|
4
|
+
// Point to the tools directory
|
|
5
|
+
paths: {
|
|
6
|
+
tools: "./src/tools",
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
// Enable MCP-I identity features
|
|
10
|
+
identity: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
environment: "development",
|
|
13
|
+
debug: true,
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
// Enable STDIO transport for Claude Desktop
|
|
17
|
+
stdio: true,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default config;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"allowSyntheticDefaultImports": true,
|
|
18
|
+
"allowJs": false,
|
|
19
|
+
"noEmit": false,
|
|
20
|
+
"incremental": true,
|
|
21
|
+
"tsBuildInfoFile": "./dist/.tsbuildinfo"
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*"],
|
|
24
|
+
"exclude": ["node_modules", "dist", "templates", "src/**/*.test.ts", "src/**/__tests__/**", "src/__tests__", "src/__tests__.bak"],
|
|
25
|
+
"references": [{ "path": "../contracts" }]
|
|
26
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
exclude: [
|
|
6
|
+
'**/node_modules/**',
|
|
7
|
+
'**/dist/**',
|
|
8
|
+
'**/.{idea,git,cache,output,temp}/**',
|
|
9
|
+
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
|
|
10
|
+
'**/__tests__.bak/**', // Exclude backup tests
|
|
11
|
+
'**/integration.test.ts', // Exclude Wrangler integration tests (requires wrangler dependency)
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
});
|