@kya-os/create-mcpi-app 1.7.25 → 1.7.27

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.
@@ -1,81 +1,97 @@
1
1
 
2
2
  
3
- > @kya-os/create-mcpi-app@1.7.18 test:coverage /Users/dylanhobbs/Documents/@kya-os/xmcp-i/packages/create-mcpi-app
3
+ > @kya-os/create-mcpi-app@1.7.26 test:coverage /Users/dylanhobbs/Documents/@kya-os/xmcp-i/packages/create-mcpi-app
4
4
  > vitest --run --coverage
5
5
 
6
- [?25l
6
+ [?25l▲ [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
7
+
8
+ package.json:15:6:
9
+  15 │ "types": "./dist/index.d.ts"
10
+ ╵ ~~~~~~~
11
+
12
+ The "import" condition comes earlier and will be used for all "import" statements:
13
+
14
+ package.json:13:6:
15
+  13 │ "import": "./dist/index.js",
16
+ ╵ ~~~~~~~~
17
+
18
+ The "require" condition comes earlier and will be used for all "require" calls:
19
+
20
+ package.json:14:6:
21
+  14 │ "require": "./dist/index.js",
22
+ ╵ ~~~~~~~~~
23
+
24
+ ▲ [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
25
+
26
+ package.json:20:6:
27
+  20 │ "types": "./dist/helpers/config-builder.d.ts"
28
+ ╵ ~~~~~~~
29
+
30
+ The "import" condition comes earlier and will be used for all "import" statements:
31
+
32
+ package.json:18:6:
33
+  18 │ "import": "./dist/helpers/config-builder.js",
34
+ ╵ ~~~~~~~~
35
+
36
+ The "require" condition comes earlier and will be used for all "require" calls:
37
+
38
+ package.json:19:6:
39
+  19 │ "require": "./dist/helpers/config-builder.js",
40
+ ╵ ~~~~~~~~~
41
+
42
+
7
43
   RUN  v4.0.5 /Users/dylanhobbs/Documents/@kya-os/xmcp-i/packages/create-mcpi-app
8
44
  Coverage enabled with v8
9
45
 
10
46
  [?2026h
11
-  ❯ src/__tests__/helpers/generate-identity.test.ts [queued]
47
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts [queued]
12
48
 
13
49
   Test Files 0 passed (12)
14
50
   Tests 0 passed (0)
15
-  Start at 11:56:38
16
-  Duration 202ms
51
+  Start at 14:20:47
52
+  Duration 201ms
17
53
  [?2026l[?2026h
54
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts [queued]
18
55
   ❯ src/__tests__/helpers/generate-identity.test.ts [queued]
19
-  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
20
-  ❯ test-cloudflare/tests/cache-invalidation.test.ts [queued]
21
-  ❯ test-cloudflare/tests/cors-security.test.ts [queued]
22
-  ❯ test-cloudflare/tests/session-management.test.ts [queued]
56
+  ❯ test-cloudflare/tests/delegation.test.ts 0/12
23
57
 
24
58
   Test Files 0 passed (12)
25
-  Tests 0 passed (0)
26
-  Start at 11:56:38
27
-  Duration 305ms
28
- [?2026l[?2026h
59
+  Tests 0 passed (12)
60
+  Start at 14:20:47
61
+  Duration 509ms
62
+ [?2026l[?2026h
63
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts [queued]
29
64
   ❯ src/__tests__/helpers/generate-identity.test.ts [queued]
30
-  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
31
-  ❯ test-cloudflare/tests/cache-invalidation.test.ts [queued]
32
-  ❯ test-cloudflare/tests/cors-security.test.ts 1/29
33
-  ❯ test-cloudflare/tests/delegation.test.ts [queued]
34
-  ❯ test-cloudflare/tests/do-routing.test.ts [queued]
35
-  ❯ test-cloudflare/tests/session-management.test.ts 0/17
65
+  ❯ test-cloudflare/tests/cors-security.test.ts [queued]
66
+  ❯ test-cloudflare/tests/delegation.test.ts 0/12
36
67
 
37
68
   Test Files 0 passed (12)
38
-  Tests 1 passed (46)
39
-  Start at 11:56:38
40
-  Duration 439ms
41
- [?2026l[?2026h ✓ test-cloudflare/tests/cors-security.test.ts (29 tests) 48ms
42
- stdout | test-cloudflare/tests/session-management.test.ts > Session Management > Session Security > should not expose session data in logs
43
- [Session] Created session: secure-session
44
-
45
- ✓ test-cloudflare/tests/session-management.test.ts (17 tests) 57ms
46
- ✓ test-cloudflare/tests/delegation.test.ts (12 tests) 5ms
47
- ✓ test-cloudflare/tests/cache-invalidation.test.ts (18 tests) 11ms
69
+  Tests 0 passed (12)
70
+  Start at 14:20:47
71
+  Duration 637ms
72
+ [?2026l[?2026h ✓ test-cloudflare/tests/delegation.test.ts (12 tests) 8ms
73
+ ✓ test-cloudflare/tests/cors-security.test.ts (29 tests) 5ms
74
+ ✓ src/__tests__/helpers/generate-identity.test.ts (24 tests) 34ms
48
75
 
49
76
   ❯ src/__tests__/cloudflare-template.test.ts [queued]
50
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts [queued]
77
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
51
78
   ❯ src/__tests__/helpers/generate-config.test.ts [queued]
52
-  ❯ src/__tests__/helpers/generate-identity.test.ts 0/24
53
79
   ❯ src/__tests__/helpers/install.test.ts [queued]
54
80
   ❯ src/__tests__/helpers/validate-project-structure.test.ts [queued]
55
81
   ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
82
+  ❯ test-cloudflare/tests/cache-invalidation.test.ts 1/18
56
83
   ❯ test-cloudflare/tests/do-routing.test.ts 0/14
84
+  ❯ test-cloudflare/tests/session-management.test.ts 0/17
57
85
 
58
-  Test Files 4 passed (12)
59
-  Tests 76 passed (114)
60
-  Start at 11:56:38
61
-  Duration 539ms
62
- [?2026l[?2026hstderr | src/helpers/__tests__/config-builder.spec.ts > buildConfigWithRemote > should fallback to local config when remote fetch fails
63
- [RemoteConfig] Neither projectId nor agentDid provided
64
-
65
-
66
-  ❯ src/__tests__/cloudflare-template.test.ts 0/24
67
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
68
-  ❯ src/__tests__/helpers/generate-config.test.ts 0/25
69
-  ❯ src/__tests__/helpers/install.test.ts 1/20
70
-  ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
71
-  ❯ test-cloudflare/tests/do-routing.test.ts 1/14
86
+  Test Files 3 passed (12)
87
+  Tests 66 passed (133)
88
+  Start at 14:20:47
89
+  Duration 739ms
90
+ [?2026l[?2026h ✓ test-cloudflare/tests/cache-invalidation.test.ts (18 tests) 6ms
91
+ stdout | test-cloudflare/tests/session-management.test.ts > Session Management > Session Security > should not expose session data in logs
92
+ [Session] Created session: secure-session
72
93
 
73
-  Test Files 6 passed (12)
74
-  Tests 114 passed (237)
75
-  Start at 11:56:38
76
-  Duration 645ms
77
- [?2026l[?2026h ✓ src/__tests__/helpers/generate-identity.test.ts (24 tests) 44ms
78
- ✓ src/helpers/__tests__/config-builder.spec.ts (12 tests) 6ms
94
+ ✓ test-cloudflare/tests/session-management.test.ts (17 tests) 54ms
79
95
  stdout | src/__tests__/helpers/install.test.ts > install > Dependency installation > should install dependencies with npm
80
96
  
81
97
  📦 Installing dependencies with npm...
@@ -98,32 +114,34 @@
98
114
 
99
115
 
100
116
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
101
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
117
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
102
118
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
103
-  ❯ src/__tests__/helpers/install.test.ts 1/20
119
+  ❯ src/__tests__/helpers/install.test.ts 0/20
104
120
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
121
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
105
122
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
106
123
 
107
-  Test Files 6 passed (12)
108
-  Tests 114 passed (237)
109
-  Start at 11:56:38
110
-  Duration 645ms
111
- [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Install progress reporting > should report correct package manager in log
124
+  Test Files 5 passed (12)
125
+  Tests 105 passed (225)
126
+  Start at 14:20:47
127
+  Duration 840ms
128
+ [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Install progress reporting > should report correct package manager in log
112
129
  ⚠️ Warning: No lockfile generated (pnpm-lock.yaml)
113
130
 
114
131
 
115
132
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
116
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
133
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
117
134
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
118
-  ❯ src/__tests__/helpers/install.test.ts 1/20
135
+  ❯ src/__tests__/helpers/install.test.ts 0/20
119
136
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
137
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
120
138
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
121
139
 
122
-  Test Files 6 passed (12)
123
-  Tests 114 passed (237)
124
-  Start at 11:56:38
125
-  Duration 645ms
126
- [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Install progress reporting > should check for lockfile after installation
140
+  Test Files 5 passed (12)
141
+  Tests 105 passed (225)
142
+  Start at 14:20:47
143
+  Duration 840ms
144
+ [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Install progress reporting > should check for lockfile after installation
127
145
  
128
146
  📦 Installing dependencies with npm...
129
147
  ✓ Lockfile created (package-lock.json) - remember to commit it
@@ -134,182 +152,1069 @@
134
152
 
135
153
 
136
154
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
137
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
155
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
138
156
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
139
-  ❯ src/__tests__/helpers/install.test.ts 1/20
157
+  ❯ src/__tests__/helpers/install.test.ts 0/20
140
158
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
159
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
141
160
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
142
161
 
143
-  Test Files 6 passed (12)
144
-  Tests 114 passed (237)
145
-  Start at 11:56:38
146
-  Duration 645ms
147
- [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Error handling > should throw error when installation fails
162
+  Test Files 5 passed (12)
163
+  Tests 105 passed (225)
164
+  Start at 14:20:47
165
+  Duration 840ms
166
+ [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Error handling > should throw error when installation fails
148
167
  Failed to install dependencies with npm.
149
168
 
150
169
 
151
170
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
152
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
171
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
153
172
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
154
-  ❯ src/__tests__/helpers/install.test.ts 1/20
173
+  ❯ src/__tests__/helpers/install.test.ts 0/20
155
174
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
175
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
156
176
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
157
177
 
158
-  Test Files 6 passed (12)
159
-  Tests 114 passed (237)
160
-  Start at 11:56:38
161
-  Duration 645ms
162
- [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Error handling > should handle network errors during installation
178
+  Test Files 5 passed (12)
179
+  Tests 105 passed (225)
180
+  Start at 14:20:47
181
+  Duration 840ms
182
+ [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Error handling > should handle network errors during installation
163
183
  
164
184
  📦 Installing dependencies with npm...
165
185
 
166
186
 
167
187
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
168
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
188
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
169
189
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
170
-  ❯ src/__tests__/helpers/install.test.ts 1/20
190
+  ❯ src/__tests__/helpers/install.test.ts 0/20
171
191
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
192
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
172
193
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
173
194
 
174
-  Test Files 6 passed (12)
175
-  Tests 114 passed (237)
176
-  Start at 11:56:38
177
-  Duration 645ms
178
- [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Error handling > should handle network errors during installation
195
+  Test Files 5 passed (12)
196
+  Tests 105 passed (225)
197
+  Start at 14:20:47
198
+  Duration 840ms
199
+ [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Error handling > should handle network errors during installation
179
200
  Failed to install dependencies with npm.
180
201
 
181
202
 
182
203
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
183
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
204
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
184
205
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
185
-  ❯ src/__tests__/helpers/install.test.ts 1/20
206
+  ❯ src/__tests__/helpers/install.test.ts 0/20
186
207
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
208
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
187
209
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
188
210
 
189
-  Test Files 6 passed (12)
190
-  Tests 114 passed (237)
191
-  Start at 11:56:38
192
-  Duration 645ms
193
- [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Error handling > should handle permission errors during installation
211
+  Test Files 5 passed (12)
212
+  Tests 105 passed (225)
213
+  Start at 14:20:47
214
+  Duration 840ms
215
+ [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Error handling > should handle permission errors during installation
194
216
  
195
217
  📦 Installing dependencies with npm...
196
218
 
197
219
 
198
220
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
199
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
221
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
200
222
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
201
-  ❯ src/__tests__/helpers/install.test.ts 1/20
223
+  ❯ src/__tests__/helpers/install.test.ts 0/20
202
224
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
225
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
203
226
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
204
227
 
205
-  Test Files 6 passed (12)
206
-  Tests 114 passed (237)
207
-  Start at 11:56:38
208
-  Duration 645ms
209
- [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Error handling > should handle permission errors during installation
228
+  Test Files 5 passed (12)
229
+  Tests 105 passed (225)
230
+  Start at 14:20:47
231
+  Duration 840ms
232
+ [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Error handling > should handle permission errors during installation
210
233
  Failed to install dependencies with npm.
211
234
 
212
235
 
213
236
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
214
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
237
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
215
238
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
216
-  ❯ src/__tests__/helpers/install.test.ts 1/20
239
+  ❯ src/__tests__/helpers/install.test.ts 0/20
217
240
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
241
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
218
242
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
219
243
 
220
-  Test Files 6 passed (12)
221
-  Tests 114 passed (237)
222
-  Start at 11:56:38
223
-  Duration 645ms
224
- [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Error handling > should log error message when installation fails
244
+  Test Files 5 passed (12)
245
+  Tests 105 passed (225)
246
+  Start at 14:20:47
247
+  Duration 840ms
248
+ [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Error handling > should log error message when installation fails
225
249
  
226
250
  📦 Installing dependencies with npm...
227
251
 
228
- stdout | src/__tests__/helpers/install.test.ts > install > Error handling > should handle invalid package manager gracefully
229
- 
230
- 📦 Installing dependencies with unknown...
231
-
232
252
 
233
253
   ❯ src/__tests__/cloudflare-template.test.ts 0/24
234
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
254
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
235
255
   ❯ src/__tests__/helpers/generate-config.test.ts 0/25
236
-  ❯ src/__tests__/helpers/install.test.ts 1/20
256
+  ❯ src/__tests__/helpers/install.test.ts 0/20
237
257
   ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
258
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
238
259
   ❯ test-cloudflare/tests/do-routing.test.ts 1/14
239
260
 
261
+  Test Files 5 passed (12)
262
+  Tests 105 passed (225)
263
+  Start at 14:20:47
264
+  Duration 840ms
265
+ [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Error handling > should handle invalid package manager gracefully
266
+ 
267
+ 📦 Installing dependencies with unknown...
268
+
269
+
270
+  ❯ src/__tests__/cloudflare-template.test.ts 2/24
271
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
272
+  ❯ src/__tests__/helpers/generate-config.test.ts 18/25
273
+  ❯ src/__tests__/helpers/validate-project-structure.test.ts 18/23
274
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
275
+  ❯ test-cloudflare/tests/do-routing.test.ts 10/14
276
+
240
277
   Test Files 6 passed (12)
241
-  Tests 114 passed (237)
242
-  Start at 11:56:38
243
-  Duration 645ms
278
+  Tests 172 passed (225)
279
+  Start at 14:20:47
280
+  Duration 1.00s
244
281
  [?2026l[?2026hstderr | src/__tests__/helpers/install.test.ts > install > Error handling > should handle invalid package manager gracefully
245
282
  ⚠️ Warning: Unknown package manager "unknown", cannot check lockfile
246
283
 
247
284
 
248
-  ❯ src/__tests__/cloudflare-template.test.ts 0/24
249
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
250
-  ❯ src/__tests__/helpers/generate-config.test.ts 0/25
251
-  ❯ src/__tests__/helpers/install.test.ts 1/20
252
-  ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
253
-  ❯ test-cloudflare/tests/do-routing.test.ts 1/14
285
+  ❯ src/__tests__/cloudflare-template.test.ts 2/24
286
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
287
+  ❯ src/__tests__/helpers/generate-config.test.ts 18/25
288
+  ❯ src/__tests__/helpers/validate-project-structure.test.ts 18/23
289
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
290
+  ❯ test-cloudflare/tests/do-routing.test.ts 10/14
254
291
 
255
292
   Test Files 6 passed (12)
256
-  Tests 114 passed (237)
257
-  Start at 11:56:38
258
-  Duration 645ms
293
+  Tests 172 passed (225)
294
+  Start at 14:20:47
295
+  Duration 1.00s
259
296
  [?2026l[?2026hstdout | src/__tests__/helpers/install.test.ts > install > Lockfile validation > should warn when lockfile not created
260
297
  
261
298
  📦 Installing dependencies with npm...
262
299
 
300
+ ✓ src/__tests__/helpers/install.test.ts (20 tests) 79ms
263
301
 
264
-  ❯ src/__tests__/cloudflare-template.test.ts 0/24
265
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 0/19
266
-  ❯ src/__tests__/helpers/generate-config.test.ts 0/25
267
-  ❯ src/__tests__/helpers/install.test.ts 1/20
268
-  ❯ src/__tests__/helpers/validate-project-structure.test.ts 0/23
269
-  ❯ test-cloudflare/tests/do-routing.test.ts 1/14
302
+  ❯ src/__tests__/cloudflare-template.test.ts 2/24
303
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 4/19
304
+  ❯ src/__tests__/helpers/generate-config.test.ts 18/25
305
+  ❯ src/__tests__/helpers/validate-project-structure.test.ts 18/23
306
+  ❯ src/helpers/__tests__/config-builder.spec.ts [queued]
307
+  ❯ test-cloudflare/tests/do-routing.test.ts 10/14
270
308
 
271
309
   Test Files 6 passed (12)
272
-  Tests 114 passed (237)
273
-  Start at 11:56:38
274
-  Duration 645ms
275
- [?2026l[?2026h ✓ src/__tests__/helpers/install.test.ts (20 tests) 56ms
276
- ✓ test-cloudflare/tests/do-routing.test.ts (14 tests) 200ms
277
- ✓ src/__tests__/helpers/generate-config.test.ts (25 tests) 84ms
278
- ✓ src/__tests__/helpers/validate-project-structure.test.ts (23 tests) 90ms
279
-
280
-
281
-  ❯ src/__tests__/cloudflare-template.test.ts 5/24
282
-  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 2/19
283
-
284
-  Test Files 10 passed (12)
285
-  Tests 201 passed (237)
286
-  Start at 11:56:38
287
-  Duration 852ms
288
- [?2026l[?2026h ✓ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts (19 tests) 261ms
289
- ✓ src/__tests__/cloudflare-template.test.ts (24 tests) 297ms
290
-
291
-
292
-
293
-  Test Files 12 passed (12)
294
-  Tests 237 passed (237)
295
-  Start at 11:56:38
296
-  Duration 953ms
297
- [?2026l
298
-  Test Files  12 passed (12)
299
-  Tests  237 passed (237)
300
-  Start at  11:56:38
301
-  Duration  1.06s (transform 749ms, setup 0ms, collect 1.70s, tests 1.16s, environment 1ms, prepare 693ms)
302
-
303
-  % Coverage report from v8
304
- -----------------------------------|---------|----------|---------|---------|---------------------
305
- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
306
- -----------------------------------|---------|----------|---------|---------|---------------------
307
- All files  |  97.36 |  95.48 |  100 |  97.36 |  
308
-  config-builder.ts  |  100 |  100 |  100 |  100 |  
309
-  fetch-cloudflare-mcpi-template.ts |  94.69 |  90.16 |  100 |  94.69 | 1836,1842,1931-1939
310
-  generate-config.ts  |  100 |  100 |  100 |  100 |  
311
-  generate-identity.ts  |  100 |  100 |  100 |  100 |  
312
-  install.ts  |  100 |  100 |  100 |  100 |  
313
-  validate-project-structure.ts  |  100 |  100 |  100 |  100 |  
314
- -----------------------------------|---------|----------|---------|---------|---------------------
315
- [?25h
310
+  Tests 172 passed (225)
311
+  Start at 14:20:47
312
+  Duration 1.00s
313
+ [?2026l[?2026hstderr | src/helpers/__tests__/config-builder.spec.ts > buildConfigWithRemote > should fallback to local config when remote fetch fails
314
+ [RemoteConfig] Neither projectId nor agentDid provided
315
+
316
+
317
+
318
+
319
+  ❯ src/__tests__/cloudflare-template.test.ts 4/24
320
+  ❯ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 18/19
321
+
322
+  Test Files 1 failed | 9 passed (12)
323
+  Tests 1 failed | 215 passed (237)
324
+  Start at 14:20:47
325
+  Duration 1.10s
326
+ [?2026l[?2026h ✓ src/__tests__/helpers/validate-project-structure.test.ts (23 tests) 128ms
327
+ ✓ src/__tests__/helpers/generate-config.test.ts (25 tests) 197ms
328
+ ✓ src/helpers/__tests__/config-builder.spec.ts (12 tests) 6ms
329
+ ❯ test-cloudflare/tests/do-routing.test.ts (14 tests | 1 failed) 320ms
330
+ ✓ should route to different instances for different sessions 15ms
331
+ ✓ should route to same instance for same session 0ms
332
+ ✓ should handle both mcp-session-id header casings 0ms
333
+ ✓ should generate random session if header missing 0ms
334
+ ✓ should distribute requests across shards 50ms
335
+ ✓ should consistently route same session to same shard 0ms
336
+ ✓ should respect custom shard count 2ms
337
+ ✓ should fall back to default when strategy unknown 0ms
338
+ ✓ should use session strategy when not configured 0ms
339
+ ✓ session routing should have O(1) complexity 85ms
340
+  × shard routing should have O(n) complexity for hash 164ms
341
+ ✓ should handle empty session ID 0ms
342
+ ✓ should handle non-numeric shard count 0ms
343
+ ✓ should handle special characters in session ID 1ms
344
+
345
+  src/__tests__/cloudflare-template.test.ts 4/24
346
+  src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts 18/19
347
+
348
+  Test Files 1 failed | 9 passed (12)
349
+  Tests 1 failed | 215 passed (237)
350
+  Start at 14:20:47
351
+  Duration 1.10s
352
+ [?2026l ✓ src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts (19 tests) 391ms
353
+ ❯ src/__tests__/cloudflare-template.test.ts (24 tests | 1 failed) 381ms
354
+ ✓ should create all required files and directories 40ms
355
+ ✓ should create mcpi-runtime-config.ts with correct structure 30ms
356
+ ✓ should create index.ts with correct structure 95ms
357
+ ✓ should include MCP_SERVER_URL placeholder in wrangler.toml 26ms
358
+  × should log warning when MCP_SERVER_URL not configured 27ms
359
+ ✓ should store MCP_SERVER_URL in class property 22ms
360
+ ✓ should include all required KV namespace bindings in wrangler.toml 11ms
361
+ ✓ should create KV namespace creation scripts in package.json 9ms
362
+ ✓ should create KV namespace listing scripts 8ms
363
+ ✓ should include tool protection configuration in runtime config 9ms
364
+ ✓ should include CloudflareRuntime import for tool protection 7ms
365
+ ✓ should conditionally enable tool protection KV when API key provided 12ms
366
+ ✓ should comment out tool protection KV when no API key provided 9ms
367
+ ✓ should generate identity when skipIdentity is false 7ms
368
+ ✓ should skip identity generation when skipIdentity is true 4ms
369
+ ✓ should include identity variables in wrangler.toml 10ms
370
+ ✓ should create .dev.vars.example template 9ms
371
+ ✓ should handle file system errors gracefully 1ms
372
+ ✓ should handle invalid project names 6ms
373
+ ✓ should use correct package manager in scripts 8ms
374
+ ✓ should create correct package.json structure 11ms
375
+ ✓ should create greet tool with correct structure 8ms
376
+ ✓ should create test files with correct structure 6ms
377
+ ✓ should create README with project name 5ms
378
+
379
+ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
380
+
381
+  FAIL  src/__tests__/cloudflare-template.test.ts > Cloudflare Template Generation > MCP_SERVER_URL configuration > should log warning when MCP_SERVER_URL not configured
382
+ AssertionError: expected 'import { McpAgent } from "agents/mcp"…' to contain 'Warning: MCP_SERVER_URL not configured'
383
+
384
+ - Expected
385
+ + Received
386
+
387
+ - Warning: MCP_SERVER_URL not configured
388
+ + import { McpAgent } from "agents/mcp";
389
+ + import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
390
+ + import { createCloudflareRuntime, type CloudflareEnv, KVProofArchive, type DetachedProof, createOAuthCallbackHandler, CloudflareRuntime } from "@kya-os/mcp-i-cloudflare";
391
+ + import { DelegationRequiredError } from "@kya-os/mcp-i-core";
392
+ + import type { ToolProtectionService } from "@kya-os/mcp-i-core";
393
+ + import { Hono } from "hono";
394
+ + import { cors } from "hono/cors";
395
+ + import { greetTool } from "./tools/greet";
396
+ + import { getRuntimeConfig } from "./mcpi-runtime-config";
397
+ + import type { CloudflareRuntimeConfig } from "@kya-os/mcp-i-cloudflare/config";
398
+ +
399
+ + /**
400
+ + * Extended CloudflareEnv with prefixed KV bindings for multi-agent deployments
401
+ + * This allows multiple agents to share the same Cloudflare account without conflicts
402
+ + */
403
+ + interface PrefixedCloudflareEnv extends CloudflareEnv {
404
+ + // Prefixed KV bindings (e.g., MYAGENT_NONCE_CACHE, MYAGENT_PROOF_ARCHIVE)
405
+ + [key: string]: KVNamespace | string | DurableObjectState | undefined;
406
+ + // Optional routing configuration
407
+ + DO_ROUTING_STRATEGY?: string;
408
+ + DO_SHARD_COUNT?: string;
409
+ + // Prefixed KV namespaces (dynamically accessed)
410
+ + TESTPROJECT_NONCE_CACHE?: KVNamespace;
411
+ + TESTPROJECT_PROOF_ARCHIVE?: KVNamespace;
412
+ + TESTPROJECT_IDENTITY_STORAGE?: KVNamespace;
413
+ + TESTPROJECT_DELEGATION_STORAGE?: KVNamespace;
414
+ + TESTPROJECT_TOOL_PROTECTION_KV?: KVNamespace;
415
+ + }
416
+ +
417
+ + export class TestprojectMCP extends McpAgent {
418
+ + server = new McpServer({
419
+ + name: "test-project",
420
+ + version: "1.0.0"
421
+ + });
422
+ +
423
+ + private mcpiRuntime?: ReturnType<typeof createCloudflareRuntime>;
424
+ + private proofArchive?: KVProofArchive;
425
+ + private agentShieldConfig?: { apiUrl: string; apiKey: string };
426
+ + private env: PrefixedCloudflareEnv;
427
+ + private mcpServerUrl?: string;
428
+ +
429
+ + constructor(state: DurableObjectState, env: PrefixedCloudflareEnv) {
430
+ + super(state, env);
431
+ + this.env = env;
432
+ +
433
+ + // Create CloudflareEnv adapter to map prefixed KV bindings to expected names
434
+ + // This allows multiple agents to be deployed without KV namespace conflicts
435
+ + const mappedEnv: CloudflareEnv = {
436
+ + // Map prefixed bindings to standard names expected by createCloudflareRuntime
437
+ + NONCE_CACHE: env.TESTPROJECT_NONCE_CACHE,
438
+ + PROOF_ARCHIVE: env.TESTPROJECT_PROOF_ARCHIVE,
439
+ + IDENTITY_STORAGE: env.TESTPROJECT_IDENTITY_STORAGE,
440
+ + DELEGATION_STORAGE: env.TESTPROJECT_DELEGATION_STORAGE,
441
+ + TOOL_PROTECTION_KV: env.TESTPROJECT_TOOL_PROTECTION_KV,
442
+ + // Pass through environment variables unchanged
443
+ + MCP_IDENTITY_PRIVATE_KEY: env.MCP_IDENTITY_PRIVATE_KEY,
444
+ + MCP_IDENTITY_PUBLIC_KEY: env.MCP_IDENTITY_PUBLIC_KEY,
445
+ + MCP_IDENTITY_AGENT_DID: env.MCP_IDENTITY_AGENT_DID,
446
+ + // Pass through other env vars for runtime config
447
+ + AGENTSHIELD_API_URL: env.AGENTSHIELD_API_URL,
448
+ + AGENTSHIELD_API_KEY: env.AGENTSHIELD_API_KEY,
449
+ + AGENTSHIELD_PROJECT_ID: env.AGENTSHIELD_PROJECT_ID,
450
+ + MCPI_ENV: env.MCPI_ENV,
451
+ + MCP_SERVER_URL: env.MCP_SERVER_URL,
452
+ + // Pass Durable Object state for identity persistence
453
+ + // NOTE: Without this, identity will be ephemeral (new DID every call)!
454
+ + // Note: createCloudflareRuntime will automatically use KVIdentityProvider if IDENTITY_STORAGE KV is available
455
+ + _durableObjectState: state,
456
+ + };
457
+ +
458
+ + // Store MCP server URL for proof submission context
459
+ + // Auto-detect from request URL if not explicitly set (will be set in fetch handler)
460
+ + this.mcpServerUrl = env.MCP_SERVER_URL;
461
+ + if (this.mcpServerUrl) {
462
+ + // Ensure URL includes /mcp path if not already present
463
+ + if (!this.mcpServerUrl.endsWith('/mcp')) {
464
+ + this.mcpServerUrl = this.mcpServerUrl.replace(//$/, '') + '/mcp';
465
+ + }
466
+ + console.log('[MCP-I] MCP Server URL configured:', this.mcpServerUrl);
467
+ + } else {
468
+ + // Will be auto-detected from request URL in fetch handler
469
+ + console.log('[MCP-I] MCP Server URL will be auto-detected from request');
470
+ + }
471
+ +
472
+ + // Load runtime configuration for AgentShield integration
473
+ + // Pass mappedEnv so it can access KV bindings with standard names
474
+ + const runtimeConfig = getRuntimeConfig(mappedEnv);
475
+ +
476
+ + // ✅ Create tool protection service helper function
477
+ + // Always import CloudflareRuntime but conditionally instantiate
478
+ + function createToolProtectionService(env: CloudflareEnv, runtimeConfig: CloudflareRuntimeConfig): ToolProtectionService | undefined {
479
+ + if (!runtimeConfig.toolProtection) {
480
+ + return undefined;
481
+ + }
482
+ +
483
+ + if (!env.TOOL_PROTECTION_KV || !env.AGENTSHIELD_API_KEY) {
484
+ + console.log('[MCP-I] Tool protection disabled - configure TOOL_PROTECTION_KV and AGENTSHIELD_API_KEY to enable');
485
+ + return undefined;
486
+ + }
487
+ +
488
+ + return CloudflareRuntime.createToolProtectionService(
489
+ + env.TOOL_PROTECTION_KV,
490
+ + {
491
+ + apiUrl: runtimeConfig.toolProtection.agentShield?.apiUrl || env.AGENTSHIELD_API_URL || 'https://kya.vouched.id',
492
+ + apiKey: env.AGENTSHIELD_API_KEY,
493
+ + projectId: runtimeConfig.toolProtection.agentShield?.projectId || env.AGENTSHIELD_PROJECT_ID,
494
+ + cacheTtl: runtimeConfig.toolProtection.agentShield?.cacheTtl || 300000,
495
+ + debug: runtimeConfig.environment === 'development',
496
+ + fallbackConfig: runtimeConfig.toolProtection.fallback
497
+ + }
498
+ + );
499
+ + }
500
+ +
501
+ + // Create tool protection service if configured
502
+ + // Note: createCloudflareRuntime will automatically use KVIdentityProvider if IDENTITY_STORAGE KV is available
503
+ + const toolProtectionService = createToolProtectionService(mappedEnv, runtimeConfig);
504
+ +
505
+ + // Initialize MCP-I runtime for cryptographic proofs and identity
506
+ + this.mcpiRuntime = createCloudflareRuntime({
507
+ + env: mappedEnv,
508
+ + environment: runtimeConfig.environment,
509
+ + audit: {
510
+ + enabled: runtimeConfig.audit?.enabled ?? true,
511
+ + logFunction: runtimeConfig.audit?.logFunction || ((record) => console.log('[MCP-I Audit]', record))
512
+ + },
513
+ + toolProtectionService
514
+ + });
515
+ +
516
+ + // Initialize proof archive if PROOF_ARCHIVE KV is available
517
+ + if (mappedEnv.PROOF_ARCHIVE) {
518
+ + this.proofArchive = new KVProofArchive(mappedEnv.PROOF_ARCHIVE);
519
+ + console.log('[MCP-I] Proof archive enabled');
520
+ + }
521
+ +
522
+ + // Load AgentShield config for proof submission
523
+ + if (runtimeConfig.proofing?.enabled && runtimeConfig.proofing.batchQueue) {
524
+ + const agentShieldDest = runtimeConfig.proofing.batchQueue.destinations?.find(
525
+ + (dest) => dest.type === "agentshield" && dest.apiKey
526
+ + );
527
+ + if (agentShieldDest) {
528
+ + this.agentShieldConfig = {
529
+ + apiUrl: agentShieldDest.apiUrl,
530
+ + apiKey: agentShieldDest.apiKey!
531
+ + };
532
+ + console.log('[MCP-I] AgentShield enabled:', agentShieldDest.apiUrl);
533
+ + }
534
+ + }
535
+ + }
536
+ +
537
+ + /**
538
+ + * Handle incoming requests
539
+ + * Auto-detects MCP Server URL from request if not explicitly configured
540
+ + */
541
+ + async fetch(request: Request): Promise<Response> {
542
+ + // Auto-detect MCP Server URL from request if not already set
543
+ + if (!this.mcpServerUrl && request.url) {
544
+ + try {
545
+ + const requestUrl = new URL(request.url);
546
+ + // Extract origin and ensure /mcp path is included
547
+ + this.mcpServerUrl = requestUrl.origin + '/mcp';
548
+ + console.log('[MCP-I] MCP Server URL auto-detected from request:', this.mcpServerUrl);
549
+ + } catch (error) {
550
+ + console.warn('[MCP-I] Failed to auto-detect MCP Server URL from request:', error);
551
+ + }
552
+ + }
553
+ +
554
+ + // Delegate to McpAgent's fetch handler
555
+ + return super.fetch(request);
556
+ + }
557
+ +
558
+ + /**
559
+ + * Override getInstanceId() to enable multi-instance Durable Object routing
560
+ + *
561
+ + * This method is called internally by McpAgent to determine which DO instance
562
+ + * should handle the request. By overriding it, we can implement custom routing
563
+ + * strategies (session-based, shard-based, etc.) while maintaining full
564
+ + * McpAgent compatibility and preserving PartyServer routing context.
565
+ + *
566
+ + * @returns Instance ID used by McpAgent for DO routing
567
+ + */
568
+ + getInstanceId(): string {
569
+ + try {
570
+ + // Get session ID from McpAgent's built-in extraction
571
+ + const sessionId = this.getSessionId();
572
+ +
573
+ + // Get routing strategy from environment (default: session)
574
+ + const strategy = this.env.DO_ROUTING_STRATEGY || 'session';
575
+ +
576
+ + if (strategy === 'session') {
577
+ + // One DO instance per MCP session (recommended for most use cases)
578
+ + // Sessions are isolated, ensuring data consistency per client
579
+ + return `session:${sessionId}`;
580
+ + } else if (strategy === 'shard') {
581
+ + // Hash-based sharding across N DO instances (for high load)
582
+ + // Distributes load evenly while maintaining session affinity
583
+ + const shardCount = parseInt(this.env.DO_SHARD_COUNT || '10');
584
+ + // Validate shard count - must be a valid positive number
585
+ + const validShardCount = (!isNaN(shardCount) && shardCount > 0) ? shardCount : 10;
586
+ +
587
+ + // Simple hash function for session ID
588
+ + let hash = 0;
589
+ + for (let i = 0; i < sessionId.length; i++) {
590
+ + hash = ((hash << 5) - hash) + sessionId.charCodeAt(i);
591
+ + hash = hash & hash; // Convert to 32bit integer
592
+ + }
593
+ +
594
+ + const shard = Math.abs(hash) % validShardCount;
595
+ + return `shard:${shard}`;
596
+ + }
597
+ +
598
+ + // Fallback to single instance (legacy behavior)
599
+ + return 'default';
600
+ + } catch (error) {
601
+ + // If session extraction fails, fall back to default instance
602
+ + console.error('[DO Routing] Failed to extract session ID:', error);
603
+ + return 'default';
604
+ + }
605
+ + }
606
+ +
607
+ + /**
608
+ + * Retrieve delegation token from KV storage
609
+ + * Uses two-tier lookup: session cache (fast) → agent DID (stable)
610
+ + *
611
+ + * @param sessionId - MCP session ID from Claude Desktop
612
+ + * @returns Delegation token if found, null otherwise
613
+ + */
614
+ + private async getDelegationToken(sessionId?: string): Promise<string | null> {
615
+ + const delegationStorage = this.env.TESTPROJECT_DELEGATION_STORAGE;
616
+ +
617
+ + if (!delegationStorage) {
618
+ + console.log('[Delegation] No delegation storage configured');
619
+ + return null;
620
+ + }
621
+ +
622
+ + try {
623
+ + // Fast path: Try session cache first
624
+ + if (sessionId) {
625
+ + const sessionKey = `session:${sessionId}`;
626
+ + const sessionToken = await delegationStorage.get(sessionKey);
627
+ +
628
+ + if (sessionToken) {
629
+ + // Verify token is still valid before returning
630
+ + const isValid = await this.verifyDelegationWithAgentShield(sessionToken);
631
+ + if (isValid) {
632
+ + console.log('[Delegation] ✅ Token retrieved from session cache and verified');
633
+ + return sessionToken;
634
+ + } else {
635
+ + // Token invalid, remove from cache
636
+ + await this.invalidateDelegationCache(sessionId, sessionToken);
637
+ + console.log('[Delegation] ⚠️ Cached token was invalid, removed from cache');
638
+ + }
639
+ + }
640
+ + }
641
+ +
642
+ + // Fallback: Try agent DID (stable across session changes)
643
+ + if (this.mcpiRuntime) {
644
+ + const identity = await this.mcpiRuntime.getIdentity();
645
+ + if (identity?.did) {
646
+ + const agentKey = `agent:${identity.did}:delegation`;
647
+ + const agentToken = await delegationStorage.get(agentKey);
648
+ +
649
+ + if (agentToken) {
650
+ + // Verify token is still valid before returning
651
+ + const isValid = await this.verifyDelegationWithAgentShield(agentToken);
652
+ + if (isValid) {
653
+ + console.log('[Delegation] ✅ Token retrieved using agent DID and verified');
654
+ +
655
+ + // Re-cache for current session (performance optimization)
656
+ + if (sessionId) {
657
+ + const sessionCacheKey = `session:${sessionId}`;
658
+ + await delegationStorage.put(sessionCacheKey, agentToken, {
659
+ + expirationTtl: 300 // 5 minutes for security (reduced from 30)
660
+ + });
661
+ + console.log('[Delegation] Token cached for session with 5-minute TTL:', sessionId);
662
+ + }
663
+ +
664
+ + return agentToken;
665
+ + } else {
666
+ + // Token invalid, remove from cache
667
+ + await this.invalidateDelegationCache(sessionId, agentToken, identity.did);
668
+ + console.log('[Delegation] ⚠️ Agent token was invalid, removed from cache');
669
+ + }
670
+ + }
671
+ + }
672
+ + }
673
+ +
674
+ + console.log('[Delegation] No delegation token found');
675
+ + return null;
676
+ + } catch (error) {
677
+ + console.error('[Delegation] Failed to retrieve token:', error);
678
+ + return null;
679
+ + }
680
+ + }
681
+ +
682
+ + /**
683
+ + * Verify delegation token with AgentShield API
684
+ + * @param token - Delegation token to verify
685
+ + * @returns True if token is valid, false otherwise
686
+ + */
687
+ + private async verifyDelegationWithAgentShield(token: string): Promise<boolean> {
688
+ + // Check verification cache first (1 minute TTL for verified tokens)
689
+ + const verificationCache = this.env.TOOL_PROTECTION_KV;
690
+ + if (verificationCache) {
691
+ + const cacheKey = `verified:${token.substring(0, 16)}`; // Use prefix to avoid key size issues
692
+ + const cached = await verificationCache.get(cacheKey);
693
+ + if (cached === '1') {
694
+ + console.log('[Delegation] Token verification cached as valid');
695
+ + return true;
696
+ + }
697
+ + }
698
+ +
699
+ + try {
700
+ + const agentShieldUrl = this.env.AGENTSHIELD_API_URL || 'https://kya.vouched.id';
701
+ + const apiKey = this.env.AGENTSHIELD_API_KEY;
702
+ +
703
+ + if (!apiKey) {
704
+ + console.warn('[Delegation] No AgentShield API key configured, skipping verification');
705
+ + return true; // Allow in development without API key
706
+ + }
707
+ +
708
+ + // Verify with AgentShield API
709
+ + const response = await fetch(`${agentShieldUrl}/api/v1/bouncer/delegations/verify`, {
710
+ + method: 'POST',
711
+ + headers: {
712
+ + 'Authorization': `Bearer ${apiKey}`,
713
+ + 'Content-Type': 'application/json'
714
+ + },
715
+ + body: JSON.stringify({ token })
716
+ + });
717
+ +
718
+ + if (response.ok) {
719
+ + // Cache successful verification for 1 minute
720
+ + if (verificationCache) {
721
+ + const cacheKey = `verified:${token.substring(0, 16)}`;
722
+ + await verificationCache.put(cacheKey, '1', {
723
+ + expirationTtl: 60 // 1 minute cache for verified tokens
724
+ + });
725
+ + }
726
+ + console.log('[Delegation] Token verified successfully with AgentShield');
727
+ + return true;
728
+ + }
729
+ +
730
+ + if (response.status === 401 || response.status === 403) {
731
+ + console.log('[Delegation] Token verification failed: unauthorized');
732
+ + return false;
733
+ + }
734
+ +
735
+ + console.warn('[Delegation] Token verification returned unexpected status:', response.status);
736
+ + return false; // Fail closed for security
737
+ +
738
+ + } catch (error) {
739
+ + console.error('[Delegation] Error verifying token with AgentShield:', error);
740
+ + return false; // Fail closed on errors
741
+ + }
742
+ + }
743
+ +
744
+ + /**
745
+ + * Invalidate delegation token in all caches
746
+ + * @param sessionId - Session ID to clear
747
+ + * @param token - Token to invalidate
748
+ + * @param agentDid - Agent DID to clear
749
+ + */
750
+ + private async invalidateDelegationCache(sessionId?: string, token?: string, agentDid?: string): Promise<void> {
751
+ + const delegationStorage = this.env.TESTPROJECT_DELEGATION_STORAGE;
752
+ + const verificationCache = this.env.TOOL_PROTECTION_KV;
753
+ +
754
+ + if (!delegationStorage) return;
755
+ +
756
+ + const deletions: Promise<void>[] = [];
757
+ +
758
+ + // Clear session cache
759
+ + if (sessionId) {
760
+ + const sessionKey = `session:${sessionId}`;
761
+ + deletions.push(delegationStorage.delete(sessionKey));
762
+ + }
763
+ +
764
+ + // Clear agent cache
765
+ + if (agentDid) {
766
+ + const agentKey = `agent:${agentDid}:delegation`;
767
+ + deletions.push(delegationStorage.delete(agentKey));
768
+ + }
769
+ +
770
+ + // Clear verification cache
771
+ + if (token && verificationCache) {
772
+ + const cacheKey = `verified:${token.substring(0, 16)}`;
773
+ + deletions.push(verificationCache.delete(cacheKey));
774
+ + }
775
+ +
776
+ + await Promise.all(deletions);
777
+ + console.log('[Delegation] Cache invalidated for revoked/invalid token');
778
+ + }
779
+ +
780
+ + /**
781
+ + * Submit proof to AgentShield API
782
+ + * Uses the proof.jws directly (full JWS format from CloudflareRuntime)
783
+ + *
784
+ + * Also submits optional context for AgentShield dashboard integration.
785
+ + * Context provides plaintext tool/args data while proof provides cryptographic verification.
786
+ + */
787
+ + private async submitProofToAgentShield(
788
+ + proof: DetachedProof,
789
+ + session: { id: string },
790
+ + toolName: string,
791
+ + args: Record<string, unknown>,
792
+ + result: unknown
793
+ + ): Promise<void> {
794
+ + if (!this.agentShieldConfig || !proof.jws || !proof.meta) return;
795
+ +
796
+ + const { apiUrl, apiKey } = this.agentShieldConfig;
797
+ +
798
+ + // Get tool call context from runtime (if available)
799
+ + const toolCallContext = this.mcpiRuntime?.getLastToolCallContext();
800
+ +
801
+ + // Proof already has correct format from CloudflareRuntime
802
+ + // Adding optional context for AgentShield dashboard (Option A architecture)
803
+ + const requestBody = {
804
+ + session_id: session.id,
805
+ + delegation_id: null,
806
+ + proofs: [{
807
+ + jws: proof.jws, // Already in full JWS format
808
+ + meta: proof.meta // Already has all required fields
809
+ + }],
810
+ + // ✅ NEW: Optional context for dashboard integration
811
+ + context: {
812
+ + toolCalls: toolCallContext ? [toolCallContext] : [{
813
+ + // Fallback if context not available from runtime
814
+ + tool: toolName,
815
+ + args: args,
816
+ + result: result,
817
+ + scopeId: proof.meta.scopeId || `${toolName}:execute`
818
+ + }],
819
+ + // ✅ NEW: MCP server URL for tool discovery (optional, only needed once)
820
+ + mcpServerUrl: this.mcpServerUrl
821
+ + }
822
+ + };
823
+ +
824
+ + console.log('[AgentShield] Submitting proof with context:', {
825
+ + did: proof.meta.did,
826
+ + sessionId: proof.meta.sessionId,
827
+ + jwsFormat: proof.jws.split('.').length === 3 ? 'valid (3 parts)' : 'invalid',
828
+ + contextTool: requestBody.context.toolCalls[0]?.tool,
829
+ + contextScopeId: requestBody.context.toolCalls[0]?.scopeId,
830
+ + mcpServerUrl: requestBody.context.mcpServerUrl || 'not-set'
831
+ + });
832
+ +
833
+ + const response = await fetch(`${apiUrl}/api/v1/bouncer/proofs`, {
834
+ + method: 'POST',
835
+ + headers: {
836
+ + 'Content-Type': 'application/json',
837
+ + 'Authorization': `Bearer ${apiKey}`
838
+ + },
839
+ + body: JSON.stringify(requestBody)
840
+ + });
841
+ +
842
+ + if (!response.ok) {
843
+ + const errorText = await response.text();
844
+ + console.error('[AgentShield] Submission failed:', response.status, errorText);
845
+ + throw new Error(`AgentShield error: ${response.status}`);
846
+ + }
847
+ +
848
+ + const responseData = await response.json() as { success?: boolean; received?: number; processed?: number; accepted?: number; rejected?: number; errors?: Array<{ proofId: string; error: string }> };
849
+ + console.log('[AgentShield] Response:', responseData);
850
+ +
851
+ + if (responseData.accepted) {
852
+ + console.log('[AgentShield] ✅ Proofs accepted:', responseData.accepted);
853
+ + }
854
+ + if (responseData.rejected) {
855
+ + console.log('[AgentShield] ❌ Proofs rejected:', responseData.rejected);
856
+ + }
857
+ + }
858
+ +
859
+ + async init() {
860
+ + // Initialize MCP-I runtime (generates/loads identity, sets up nonce cache)
861
+ + await this.mcpiRuntime?.initialize();
862
+ +
863
+ + const identity = await this.mcpiRuntime?.getIdentity();
864
+ + console.log('[MCP-I] Initialized with DID:', identity?.did);
865
+ +
866
+ + this.server.tool(
867
+ + greetTool.name,
868
+ + greetTool.description,
869
+ + greetTool.inputSchema.shape,
870
+ + async (args: { name: string }) => {
871
+ + // Use MCP-I runtime's processToolCall for automatic proof generation
872
+ + if (this.mcpiRuntime) {
873
+ + try {
874
+ + // Read MCP session ID from Claude Desktop (via agents framework)
875
+ + let mcpSessionId: string | undefined;
876
+ + try {
877
+ + mcpSessionId = this.getSessionId();
878
+ + console.log('[Delegation] Session ID from agents framework:', mcpSessionId);
879
+ + } catch (error) {
880
+ + console.log('[Delegation] Failed to get session ID from framework:', error);
881
+ + mcpSessionId = undefined;
882
+ + }
883
+ +
884
+ + // Retrieve delegation token if available
885
+ + const delegationToken = await this.getDelegationToken(mcpSessionId);
886
+ +
887
+ + // Create session with proper ID (use actual session ID when available)
888
+ + const timestamp = Date.now();
889
+ + const sessionId = mcpSessionId || `ephemeral-${timestamp}-${Math.random().toString(36).substring(2, 10)}`;
890
+ +
891
+ + const session = {
892
+ + id: sessionId, // Use actual session ID from Claude Desktop
893
+ + audience: 'https://kya.vouched.id', // CRITICAL: Must match AgentShield domain
894
+ + agentDid: (await this.mcpiRuntime.getIdentity()).did,
895
+ + createdAt: timestamp,
896
+ + expiresAt: timestamp + (30 * 60 * 1000), // 30 minutes
897
+ + delegationToken // Include delegation token if available
898
+ + };
899
+ +
900
+ + // Execute tool with automatic proof generation
901
+ + const result = await this.mcpiRuntime.processToolCall(
902
+ + greetTool.name,
903
+ + args,
904
+ + greetTool.handler,
905
+ + session
906
+ + );
907
+ +
908
+ + // Get proof in DetachedProof format
909
+ + const proof = this.mcpiRuntime.getLastProof() as DetachedProof;
910
+ +
911
+ + if (proof && proof.jws && proof.meta) {
912
+ + // Log proof details (using DetachedProof format)
913
+ + console.log('[MCP-I Proof]', {
914
+ + tool: greetTool.name,
915
+ + did: proof.meta.did,
916
+ + timestamp: proof.meta.ts,
917
+ + jws: proof.jws.substring(0, 50) + '...',
918
+ + jwsValid: proof.jws.split('.').length === 3
919
+ + });
920
+ +
921
+ + // Parallelize proof operations for better performance
922
+ + const proofOperations: Promise<void>[] = [];
923
+ +
924
+ + // Add proof archive operation
925
+ + if (this.proofArchive) {
926
+ + proofOperations.push(
927
+ + this.proofArchive.store(proof, {
928
+ + toolName: greetTool.name
929
+ + }).then(() => {
930
+ + console.log('[MCP-I] Proof stored in archive');
931
+ + }).catch((archiveError: unknown) => {
932
+ + console.error('[MCP-I] Archive error:', archiveError instanceof Error ? archiveError.message : String(archiveError));
933
+ + })
934
+ + );
935
+ + }
936
+ +
937
+ + // Add AgentShield submission operation
938
+ + if (this.agentShieldConfig) {
939
+ + proofOperations.push(
940
+ + this.submitProofToAgentShield(proof, session, greetTool.name, args, result)
941
+ + .catch((err: unknown) => {
942
+ + console.error('[MCP-I] AgentShield failed:', err instanceof Error ? err.message : String(err));
943
+ + })
944
+ + );
945
+ + }
946
+ +
947
+ + // Execute all proof operations in parallel for better performance
948
+ + if (proofOperations.length > 0) {
949
+ + await Promise.allSettled(proofOperations);
950
+ + }
951
+ +
952
+ + // Attach proof to result for MCP Inspector
953
+ + if (result && typeof result === 'object' && result !== null) {
954
+ + (result as Record<string, unknown>)._meta = {
955
+ + proof: {
956
+ + jws: proof.jws,
957
+ + did: proof.meta.did,
958
+ + kid: proof.meta.kid,
959
+ + timestamp: proof.meta.ts,
960
+ + nonce: proof.meta.nonce,
961
+ + sessionId: proof.meta.sessionId,
962
+ + requestHash: proof.meta.requestHash,
963
+ + responseHash: proof.meta.responseHash
964
+ + }
965
+ + };
966
+ + }
967
+ + }
968
+ +
969
+ + return result;
970
+ + } catch (error: unknown) {
971
+ + // If this is a DelegationRequiredError, re-throw it so the MCP framework can handle it properly
972
+ + // The agents/mcp framework will format it as a proper error response to Claude Desktop
973
+ + if (error instanceof DelegationRequiredError) {
974
+ + console.warn('[MCP-I] Delegation required, propagating error:', {
975
+ + tool: error.toolName,
976
+ + requiredScopes: error.requiredScopes,
977
+ + consentUrl: error.consentUrl
978
+ + });
979
+ + throw error;
980
+ + }
981
+ + // Check for DelegationRequiredError by name (for cases where error is not instanceof)
982
+ + if (error && typeof error === 'object' && 'name' in error && error.name === 'DelegationRequiredError') {
983
+ + const delegationError = error as DelegationRequiredError;
984
+ + console.warn('[MCP-I] Delegation required, propagating error:', {
985
+ + tool: delegationError.toolName,
986
+ + requiredScopes: delegationError.requiredScopes,
987
+ + consentUrl: delegationError.consentUrl
988
+ + });
989
+ + throw error;
990
+ + }
991
+ + 
992
+ + // For other errors, log and fallback to direct execution
993
+ + console.error('[MCP-I] Failed to process tool with runtime:', error);
994
+ + // Fallback to direct execution
995
+ + return await greetTool.handler(args);
996
+ + }
997
+ + }
998
+ +
999
+ + // Fallback if runtime not available
1000
+ + return await greetTool.handler(args);
1001
+ + }
1002
+ + );
1003
+ + }
1004
+ + }
1005
+ +
1006
+ + const app = new Hono();
1007
+ +
1008
+ + // Secure CORS configuration
1009
+ + app.use("/*", (c, next) => {
1010
+ + const allowedOrigins = c.env.ALLOWED_ORIGINS?.split(',').map((o: string) => o.trim()) || [
1011
+ + 'https://claude.ai',
1012
+ + 'https://app.anthropic.com'
1013
+ + ];
1014
+ +
1015
+ + // Add localhost for development if not in production
1016
+ + if (c.env.MCPI_ENV !== 'production' && !allowedOrigins.includes('http://localhost:3000')) {
1017
+ + allowedOrigins.push('http://localhost:3000');
1018
+ + }
1019
+ +
1020
+ + const origin = c.req.header('Origin') || '';
1021
+ + const isAllowed = allowedOrigins.includes(origin);
1022
+ +
1023
+ + return cors({
1024
+ + origin: isAllowed ? origin : allowedOrigins[0], // Default to first allowed origin if not matched
1025
+ + allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
1026
+ + allowHeaders: ["Content-Type", "Authorization", "mcp-session-id", "Mcp-Session-Id", "mcp-protocol-version"],
1027
+ + exposeHeaders: ["mcp-session-id", "Mcp-Session-Id"],
1028
+ + credentials: true,
1029
+ + })(c, next);
1030
+ + });
1031
+ +
1032
+ + app.get("/health", (c) => c.json({
1033
+ + status: 'healthy',
1034
+ + timestamp: new Date().toISOString(),
1035
+ + transport: { sse: '/sse', streamableHttp: '/mcp' }
1036
+ + }));
1037
+ +
1038
+ + /**
1039
+ + * Admin endpoint to clear tool protection cache
1040
+ + *
1041
+ + * This allows AgentShield dashboard to invalidate cached tool protection
1042
+ + * config immediately after changing delegation requirements.
1043
+ + *
1044
+ + * The API key is validated by making a test call to AgentShield API.
1045
+ + *
1046
+ + * Usage:
1047
+ + * POST /admin/clear-cache
1048
+ + * Headers: Authorization: Bearer <AGENTSHIELD_ADMIN_API_KEY>
1049
+ + * Body: { "agent_did": "did:key:z6Mk..." }
1050
+ + *
1051
+ + * Response:
1052
+ + * { "success": true, "message": "Cache cleared", "agent_did": "..." }
1053
+ + */
1054
+ + app.post("/admin/clear-cache", async (c) => {
1055
+ + const env = c.env as PrefixedCloudflareEnv;
1056
+ +
1057
+ + // Parse request body first to get agent_did
1058
+ + const body = await c.req.json().catch(() => ({}));
1059
+ + const agentDid = body.agent_did;
1060
+ +
1061
+ + if (!agentDid || typeof agentDid !== 'string') {
1062
+ + return c.json({
1063
+ + success: false,
1064
+ + error: "Bad Request - agent_did required in body"
1065
+ + }, 400);
1066
+ + }
1067
+ +
1068
+ + // Extract API key from Authorization header
1069
+ + const authHeader = c.req.header("Authorization");
1070
+ + if (!authHeader || !authHeader.startsWith("Bearer ")) {
1071
+ + return c.json({
1072
+ + success: false,
1073
+ + error: "Unauthorized - Missing or invalid Authorization header"
1074
+ + }, 401);
1075
+ + }
1076
+ +
1077
+ + const apiKey = authHeader.slice(7); // Remove "Bearer " prefix
1078
+ +
1079
+ + // Validate API key by making a test call to AgentShield
1080
+ + // Use the bouncer config endpoint as the validation mechanism
1081
+ + const agentShieldUrl = env.AGENTSHIELD_API_URL || "https://kya.vouched.id";
1082
+ + const validationUrl = `${agentShieldUrl}/api/v1/bouncer/config?agent_did=${encodeURIComponent(agentDid)}`;
1083
+ +
1084
+ + try {
1085
+ + const validationResponse = await fetch(validationUrl, {
1086
+ + method: 'GET',
1087
+ + headers: {
1088
+ + 'Content-Type': 'application/json',
1089
+ + 'Authorization': `Bearer ${apiKey}`
1090
+ + }
1091
+ + });
1092
+ +
1093
+ + if (!validationResponse.ok) {
1094
+ + console.warn('[Admin] API key validation failed:', validationResponse.status);
1095
+ + return c.json({
1096
+ + success: false,
1097
+ + error: "Unauthorized - Invalid API key"
1098
+ + }, 401);
1099
+ + }
1100
+ +
1101
+ + // API key is valid, proceed to clear cache
1102
+ + console.log('[Admin] API key validated successfully');
1103
+ + } catch (error) {
1104
+ + console.error('[Admin] API key validation error:', error);
1105
+ + return c.json({
1106
+ + success: false,
1107
+ + error: "Failed to validate API key with AgentShield"
1108
+ + }, 500);
1109
+ + }
1110
+ +
1111
+ + // Clear cache from KV
1112
+ + // Cache key format: KVToolProtectionCache uses 'tool-protection:' prefix + agentDid
1113
+ + // Since we're accessing KV directly (not through cache service), we need the full key
1114
+ + const cacheKey = `tool-protection:${agentDid}`;
1115
+ + const kvNamespace = env.TESTPROJECT_TOOL_PROTECTION_KV;
1116
+ +
1117
+ + if (!kvNamespace) {
1118
+ + return c.json({
1119
+ + success: false,
1120
+ + error: "Tool protection KV namespace not configured"
1121
+ + }, 500);
1122
+ + }
1123
+ +
1124
+ + try {
1125
+ + // Log before and after for debugging
1126
+ + const before = await kvNamespace.get(cacheKey);
1127
+ + await kvNamespace.delete(cacheKey);
1128
+ + const after = await kvNamespace.get(cacheKey);
1129
+ + 
1130
+ + console.log('[Admin] Cache clear operation', {
1131
+ + agentDid: agentDid.slice(0, 20) + '...',
1132
+ + cacheKey,
1133
+ + hadValue: !!before,
1134
+ + cleared: !after,
1135
+ + });
1136
+ + 
1137
+ + return c.json({
1138
+ + success: true,
1139
+ + message: "Cache cleared successfully. Next tool call will fetch fresh config from AgentShield.",
1140
+ + agent_did: agentDid,
1141
+ + cache_key: cacheKey,
1142
+ + had_value: !!before,
1143
+ + });
1144
+ + } catch (error) {
1145
+ + console.error('[Admin] Failed to clear cache:', error);
1146
+ + return c.json({
1147
+ + success: false,
1148
+ + error: "Internal error clearing cache",
1149
+ + details: error instanceof Error ? error.message : String(error)
1150
+ + }, 500);
1151
+ + }
1152
+ + });
1153
+ +
1154
+ + /**
1155
+ + * OAuth Authorization Code Flow callback handler
1156
+ + *
1157
+ + * Handles the redirect from AgentShield after user approves delegation.
1158
+ + * Exchanges authorization code for delegation token and stores in KV.
1159
+ + *
1160
+ + * This endpoint is called by AgentShield after user approves delegation:
1161
+ + * 1. Receives authorization code and state from query params
1162
+ + * 2. Exchanges code for delegation token with AgentShield API
1163
+ + * 3. Stores token in KV with session ID as key
1164
+ + * 4. Returns success page to user
1165
+ + */
1166
+ + app.get('/oauth/callback', (c) => {
1167
+ + const env = c.env as PrefixedCloudflareEnv;
1168
+ + return createOAuthCallbackHandler({
1169
+ + agentShieldApiUrl: env.AGENTSHIELD_API_URL || 'https://kya.vouched.id',
1170
+ + delegationStorage: env.TESTPROJECT_DELEGATION_STORAGE,
1171
+ + autoClose: true,
1172
+ + autoCloseDelay: 5000
1173
+ + })(c);
1174
+ + });
1175
+ +
1176
+ + // Multi-instance DO routing using McpAgent's getInstanceId() override
1177
+ + // The TestprojectMCP class overrides getInstanceId() to enable session-based
1178
+ + // or shard-based routing while preserving PartyServer compatibility.
1179
+ + //
1180
+ + // Routing strategies (configured via DO_ROUTING_STRATEGY env var):
1181
+ + // - 'session' (default): One DO per MCP session - ensures data isolation
1182
+ + // - 'shard': Hash-based distribution across N shards - for high load
1183
+ + //
1184
+ + // McpAgent automatically routes to the correct DO instance using the ID
1185
+ + // returned by getInstanceId(), maintaining full routing context.
1186
+ + app.mount("/sse", TestprojectMCP.serveSSE("/sse").fetch, { replaceRequest: false });
1187
+ + app.mount("/mcp", TestprojectMCP.serve("/mcp").fetch, { replaceRequest: false });
1188
+ +
1189
+ + export default app;
1190
+ +
1191
+
1192
+  ❯ src/__tests__/cloudflare-template.test.ts:149:28
1193
+ 147| 
1194
+ 148|  // Check that warning is logged in index.ts
1195
+ 149|  expect(indexContent).toContain("Warning: MCP_SERVER_URL not configured");
1196
+  |  ^
1197
+ 150|  });
1198
+ 151| 
1199
+
1200
+ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯
1201
+
1202
+  FAIL  test-cloudflare/tests/do-routing.test.ts > Durable Object Multi-Instance Routing > Performance Characteristics > shard routing should have O(n) complexity for hash
1203
+ AssertionError: expected 145.8617079999999 to be less than 100
1204
+  ❯ test-cloudflare/tests/do-routing.test.ts:263:44
1205
+ 261|  expect(shortDuration).toBeGreaterThan(0);
1206
+ 262|  // Both operations should complete in reasonable time (< 100ms total for 1000 iterations)
1207
+ 263|  expect(longDuration + shortDuration).toBeLessThan(100);
1208
+  |  ^
1209
+ 264|  });
1210
+ 265|  });
1211
+
1212
+ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯
1213
+
1214
+
1215
+  Test Files  2 failed | 10 passed (12)
1216
+  Tests  2 failed | 235 passed (237)
1217
+  Start at  14:20:47
1218
+  Duration  1.25s (transform 1.21s, setup 0ms, collect 2.23s, tests 1.61s, environment 1ms, prepare 1.06s)
1219
+
1220
+ [?25h ELIFECYCLE  Command failed with exit code 1.