pi-soly 1.9.3 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/ask/index.ts +12 -11
  2. package/ask/picker.ts +356 -76
  3. package/ask/prompt.ts +6 -2
  4. package/ask/tests/picker.test.ts +273 -82
  5. package/codemap.ts +276 -0
  6. package/hotreload.ts +239 -0
  7. package/init.ts +302 -0
  8. package/mcp/CHANGELOG.md +384 -0
  9. package/mcp/LICENSE +21 -0
  10. package/mcp/OAUTH.md +355 -0
  11. package/mcp/README.md +410 -0
  12. package/mcp/__tests__/agent-dir-paths.upstream-test.ts +80 -0
  13. package/mcp/__tests__/cli.upstream-test.ts +97 -0
  14. package/mcp/__tests__/commands-onboarding.upstream-test.ts +157 -0
  15. package/mcp/__tests__/config.upstream-test.ts +303 -0
  16. package/mcp/__tests__/consent-manager.upstream-test.ts +151 -0
  17. package/mcp/__tests__/direct-tools-auto-auth.upstream-test.ts +218 -0
  18. package/mcp/__tests__/direct-tools.upstream-test.ts +299 -0
  19. package/mcp/__tests__/elicitation-handler.upstream-test.ts +346 -0
  20. package/mcp/__tests__/elicitation-sdk-integration.upstream-test.ts +140 -0
  21. package/mcp/__tests__/errors.upstream-test.ts +218 -0
  22. package/mcp/__tests__/fixtures/elicitation-server.mjs +98 -0
  23. package/mcp/__tests__/host-html-template.upstream-test.ts +278 -0
  24. package/mcp/__tests__/index-lifecycle.upstream-test.ts +484 -0
  25. package/mcp/__tests__/init-elicitation.upstream-test.ts +86 -0
  26. package/mcp/__tests__/interactive-visualizer-server.upstream-test.ts +28 -0
  27. package/mcp/__tests__/logger.upstream-test.ts +175 -0
  28. package/mcp/__tests__/mcp-auth-flow-client-credentials.upstream-test.ts +612 -0
  29. package/mcp/__tests__/mcp-auth-storage.upstream-test.ts +47 -0
  30. package/mcp/__tests__/mcp-callback-server-unref.upstream-test.ts +264 -0
  31. package/mcp/__tests__/mcp-oauth-provider.upstream-test.ts +216 -0
  32. package/mcp/__tests__/mcp-panel-auth.upstream-test.ts +206 -0
  33. package/mcp/__tests__/mcp-panel-exclude-tools.upstream-test.ts +69 -0
  34. package/mcp/__tests__/mcp-panel-keybindings.upstream-test.ts +186 -0
  35. package/mcp/__tests__/npx-resolver.upstream-test.ts +54 -0
  36. package/mcp/__tests__/oauth-handler.upstream-test.ts +77 -0
  37. package/mcp/__tests__/onboarding-state.upstream-test.ts +52 -0
  38. package/mcp/__tests__/package-manifest.upstream-test.ts +22 -0
  39. package/mcp/__tests__/proxy-modes-auto-auth.upstream-test.ts +253 -0
  40. package/mcp/__tests__/proxy-modes-discovery.upstream-test.ts +84 -0
  41. package/mcp/__tests__/proxy-modes-manual-auth.upstream-test.ts +91 -0
  42. package/mcp/__tests__/proxy-modes-ui-messages.upstream-test.ts +72 -0
  43. package/mcp/__tests__/sampling-handler.upstream-test.ts +285 -0
  44. package/mcp/__tests__/server-manager-http-auth.upstream-test.ts +144 -0
  45. package/mcp/__tests__/server-manager-sampling.upstream-test.ts +236 -0
  46. package/mcp/__tests__/tool-metadata.upstream-test.ts +111 -0
  47. package/mcp/__tests__/tool-result-renderer.upstream-test.ts +147 -0
  48. package/mcp/__tests__/ui-integration.upstream-test.ts +551 -0
  49. package/mcp/__tests__/ui-resource-handler.upstream-test.ts +303 -0
  50. package/mcp/__tests__/ui-server.upstream-test.ts +967 -0
  51. package/mcp/__tests__/ui-session-messages.upstream-test.ts +72 -0
  52. package/mcp/__tests__/ui-streaming.upstream-test.ts +543 -0
  53. package/mcp/agent-dir.ts +20 -0
  54. package/mcp/app-bridge.bundle.js +67 -0
  55. package/mcp/cli.js +184 -0
  56. package/mcp/commands.ts +422 -0
  57. package/mcp/config.ts +666 -0
  58. package/mcp/consent-manager.ts +64 -0
  59. package/mcp/direct-tools.ts +439 -0
  60. package/mcp/elicitation-handler.ts +347 -0
  61. package/mcp/errors.ts +219 -0
  62. package/mcp/glimpse-ui.ts +80 -0
  63. package/mcp/host-html-template.ts +427 -0
  64. package/mcp/index.ts +362 -0
  65. package/mcp/init.ts +362 -0
  66. package/mcp/lifecycle.ts +93 -0
  67. package/mcp/logger.ts +169 -0
  68. package/mcp/mcp-auth-flow.ts +559 -0
  69. package/mcp/mcp-auth-flow.upstream-test.ts +259 -0
  70. package/mcp/mcp-auth.ts +302 -0
  71. package/mcp/mcp-auth.upstream-test.ts +373 -0
  72. package/mcp/mcp-callback-server.ts +372 -0
  73. package/mcp/mcp-callback-server.upstream-test.ts +416 -0
  74. package/mcp/mcp-oauth-provider.ts +369 -0
  75. package/mcp/mcp-oauth-provider.upstream-test.ts +518 -0
  76. package/mcp/mcp-panel.ts +829 -0
  77. package/mcp/mcp-setup-panel.ts +580 -0
  78. package/mcp/metadata-cache.ts +201 -0
  79. package/mcp/notify.ts +111 -0
  80. package/mcp/npx-resolver.ts +424 -0
  81. package/mcp/oauth-handler.ts +57 -0
  82. package/mcp/onboarding-state.ts +68 -0
  83. package/mcp/package.json +106 -0
  84. package/mcp/panel-keys.ts +37 -0
  85. package/mcp/proxy-modes.ts +949 -0
  86. package/mcp/resource-tools.ts +17 -0
  87. package/mcp/sampling-handler.ts +268 -0
  88. package/mcp/server-manager.ts +545 -0
  89. package/mcp/state.ts +41 -0
  90. package/mcp/tool-metadata.ts +216 -0
  91. package/mcp/tool-registrar.ts +46 -0
  92. package/mcp/tool-result-renderer.ts +161 -0
  93. package/mcp/types.ts +448 -0
  94. package/mcp/ui-resource-handler.ts +146 -0
  95. package/mcp/ui-server.ts +623 -0
  96. package/mcp/ui-session.ts +386 -0
  97. package/mcp/ui-stream-types.ts +89 -0
  98. package/mcp/utils.ts +129 -0
  99. package/mcp/vitest.config.ts +14 -0
  100. package/migrate.ts +258 -0
  101. package/notification.ts +218 -0
  102. package/notifications-log.ts +83 -0
  103. package/package.json +20 -3
  104. package/status.ts +140 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Tests for mcp-auth.ts - Auth storage module
3
+ */
4
+
5
+ import { describe, it, before, after } from "node:test"
6
+ import assert from "node:assert"
7
+ import { mkdirSync, rmSync, existsSync } from "fs"
8
+ import { join } from "path"
9
+ import { tmpdir } from "os"
10
+ import { randomBytes } from "crypto"
11
+
12
+ // Set up isolated temp directory for tests
13
+ const TEST_DIR = join(tmpdir(), `mcp-oauth-test-${randomBytes(4).toString('hex')}`)
14
+ process.env.MCP_OAUTH_DIR = TEST_DIR
15
+
16
+ import {
17
+ getAuthEntry,
18
+ getAuthForUrl,
19
+ saveAuthEntry,
20
+ removeAuthEntry,
21
+ updateTokens,
22
+ updateClientInfo,
23
+ updateCodeVerifier,
24
+ clearCodeVerifier,
25
+ updateOAuthState,
26
+ getOAuthState,
27
+ clearOAuthState,
28
+ isTokenExpired,
29
+ hasStoredTokens,
30
+ clearAllCredentials,
31
+ clearClientInfo,
32
+ clearTokens,
33
+ type AuthEntry,
34
+ } from "./mcp-auth.ts"
35
+
36
+ describe("mcp-auth", () => {
37
+ before(() => {
38
+ // Ensure clean state
39
+ try {
40
+ if (existsSync(TEST_DIR)) {
41
+ rmSync(TEST_DIR, { recursive: true, force: true })
42
+ }
43
+ mkdirSync(TEST_DIR, { recursive: true })
44
+ } catch {
45
+ // Ignore cleanup errors
46
+ }
47
+ })
48
+
49
+ after(() => {
50
+ // Clean up temp directory
51
+ try {
52
+ if (existsSync(TEST_DIR)) {
53
+ rmSync(TEST_DIR, { recursive: true, force: true })
54
+ }
55
+ } catch {
56
+ // Ignore cleanup errors
57
+ }
58
+ })
59
+
60
+ describe("getAuthEntry", () => {
61
+ it("should return undefined for non-existent entry", () => {
62
+ const entry = getAuthEntry("non-existent")
63
+ assert.strictEqual(entry, undefined)
64
+ })
65
+ })
66
+
67
+ describe("saveAuthEntry / getAuthEntry", () => {
68
+ it("should save and retrieve an auth entry", () => {
69
+ const entry: AuthEntry = {
70
+ tokens: {
71
+ accessToken: "test-token",
72
+ refreshToken: "refresh-token",
73
+ expiresAt: 1234567890,
74
+ scope: "read write",
75
+ },
76
+ serverUrl: "https://api.example.com",
77
+ }
78
+
79
+ saveAuthEntry("test-server", entry, "https://api.example.com")
80
+ const retrieved = getAuthEntry("test-server")
81
+
82
+ assert.deepStrictEqual(retrieved, entry)
83
+ })
84
+
85
+ it("should update existing entries", () => {
86
+ const entry1: AuthEntry = {
87
+ tokens: { accessToken: "token1" },
88
+ serverUrl: "https://api.example.com",
89
+ }
90
+ const entry2: AuthEntry = {
91
+ tokens: { accessToken: "token2" },
92
+ serverUrl: "https://api.example.com",
93
+ }
94
+
95
+ saveAuthEntry("test-server", entry1, "https://api.example.com")
96
+ saveAuthEntry("test-server", entry2, "https://api.example.com")
97
+ const retrieved = getAuthEntry("test-server")
98
+
99
+ assert.strictEqual(retrieved?.tokens?.accessToken, "token2")
100
+ })
101
+ })
102
+
103
+ describe("getAuthForUrl", () => {
104
+ it("should return entry when URL matches", () => {
105
+ const entry: AuthEntry = {
106
+ tokens: { accessToken: "test-token" },
107
+ serverUrl: "https://api.example.com",
108
+ }
109
+
110
+ saveAuthEntry("test-server", entry, "https://api.example.com")
111
+ const retrieved = getAuthForUrl("test-server", "https://api.example.com")
112
+
113
+ assert.deepStrictEqual(retrieved, entry)
114
+ })
115
+
116
+ it("should return undefined when URL doesn't match", () => {
117
+ const entry: AuthEntry = {
118
+ tokens: { accessToken: "test-token" },
119
+ serverUrl: "https://api.example.com",
120
+ }
121
+
122
+ saveAuthEntry("test-server", entry, "https://api.example.com")
123
+ const retrieved = getAuthForUrl("test-server", "https://different.com")
124
+
125
+ assert.strictEqual(retrieved, undefined)
126
+ })
127
+
128
+ it("should return undefined when serverUrl is not stored", () => {
129
+ const entry: AuthEntry = {
130
+ tokens: { accessToken: "test-token" },
131
+ }
132
+
133
+ saveAuthEntry("test-server", entry)
134
+ const retrieved = getAuthForUrl("test-server", "https://api.example.com")
135
+
136
+ assert.strictEqual(retrieved, undefined)
137
+ })
138
+ })
139
+
140
+ describe("removeAuthEntry", () => {
141
+ it("should remove an entry", () => {
142
+ const entry: AuthEntry = {
143
+ tokens: { accessToken: "test-token" },
144
+ }
145
+
146
+ saveAuthEntry("test-server", entry)
147
+ removeAuthEntry("test-server")
148
+ const retrieved = getAuthEntry("test-server")
149
+
150
+ assert.strictEqual(retrieved, undefined)
151
+ })
152
+ })
153
+
154
+ describe("updateTokens", () => {
155
+ it("should update tokens for a server", () => {
156
+ updateTokens("test-server", {
157
+ accessToken: "new-token",
158
+ refreshToken: "new-refresh",
159
+ expiresAt: 1234567890,
160
+ scope: "read",
161
+ })
162
+
163
+ const entry = getAuthEntry("test-server")
164
+ assert.strictEqual(entry?.tokens?.accessToken, "new-token")
165
+ })
166
+
167
+ it("should preserve existing client info", () => {
168
+ updateClientInfo("test-server", { clientId: "client-123" })
169
+ updateTokens("test-server", { accessToken: "token" })
170
+
171
+ const entry = getAuthEntry("test-server")
172
+ assert.strictEqual(entry?.clientInfo?.clientId, "client-123")
173
+ assert.strictEqual(entry?.tokens?.accessToken, "token")
174
+ })
175
+
176
+ it("should clear URL-bound auth state when tokens move to a different server URL", () => {
177
+ saveAuthEntry("token-url-change", {
178
+ tokens: { accessToken: "old-token", refreshToken: "old-refresh" },
179
+ clientInfo: { clientId: "old-client" },
180
+ codeVerifier: "old-verifier",
181
+ oauthState: "old-state",
182
+ serverUrl: "https://old.example.com/mcp",
183
+ }, "https://old.example.com/mcp")
184
+
185
+ updateTokens("token-url-change", { accessToken: "new-token" }, "https://new.example.com/mcp")
186
+
187
+ assert.strictEqual(getAuthForUrl("token-url-change", "https://old.example.com/mcp"), undefined)
188
+ const newEntry = getAuthForUrl("token-url-change", "https://new.example.com/mcp")
189
+ assert.strictEqual(newEntry?.tokens?.accessToken, "new-token")
190
+ assert.strictEqual(newEntry?.clientInfo, undefined)
191
+ assert.strictEqual(newEntry?.codeVerifier, undefined)
192
+ assert.strictEqual(newEntry?.oauthState, undefined)
193
+ })
194
+
195
+ it("should clear legacy URL-bound auth state when saving tokens with a server URL", () => {
196
+ saveAuthEntry("token-legacy-url-change", {
197
+ tokens: { accessToken: "old-token", refreshToken: "old-refresh" },
198
+ clientInfo: { clientId: "old-client" },
199
+ codeVerifier: "old-verifier",
200
+ oauthState: "old-state",
201
+ })
202
+
203
+ updateTokens("token-legacy-url-change", { accessToken: "new-token" }, "https://new.example.com/mcp")
204
+
205
+ const newEntry = getAuthForUrl("token-legacy-url-change", "https://new.example.com/mcp")
206
+ assert.strictEqual(newEntry?.tokens?.accessToken, "new-token")
207
+ assert.strictEqual(newEntry?.clientInfo, undefined)
208
+ assert.strictEqual(newEntry?.codeVerifier, undefined)
209
+ assert.strictEqual(newEntry?.oauthState, undefined)
210
+ })
211
+ })
212
+
213
+ describe("updateClientInfo", () => {
214
+ it("should update client info for a server", () => {
215
+ updateClientInfo("test-server", {
216
+ clientId: "client-123",
217
+ clientSecret: "secret",
218
+ clientIdIssuedAt: 1234567890,
219
+ clientSecretExpiresAt: 1234567999,
220
+ })
221
+
222
+ const entry = getAuthEntry("test-server")
223
+ assert.strictEqual(entry?.clientInfo?.clientId, "client-123")
224
+ assert.strictEqual(entry?.clientInfo?.clientSecret, "secret")
225
+ })
226
+
227
+ it("should clear URL-bound credentials when client info moves to a different server URL", () => {
228
+ saveAuthEntry("url-change", {
229
+ tokens: { accessToken: "old-token", refreshToken: "old-refresh" },
230
+ clientInfo: { clientId: "old-client" },
231
+ codeVerifier: "old-verifier",
232
+ oauthState: "old-state",
233
+ serverUrl: "https://old.example.com/mcp",
234
+ }, "https://old.example.com/mcp")
235
+
236
+ updateClientInfo("url-change", { clientId: "new-client" }, "https://new.example.com/mcp")
237
+
238
+ assert.strictEqual(getAuthForUrl("url-change", "https://old.example.com/mcp"), undefined)
239
+ const newEntry = getAuthForUrl("url-change", "https://new.example.com/mcp")
240
+ assert.strictEqual(newEntry?.clientInfo?.clientId, "new-client")
241
+ assert.strictEqual(newEntry?.tokens, undefined)
242
+ assert.strictEqual(newEntry?.codeVerifier, undefined)
243
+ assert.strictEqual(newEntry?.oauthState, undefined)
244
+ })
245
+
246
+ it("should clear stale verifier and state when legacy client info gains a server URL", () => {
247
+ saveAuthEntry("legacy-url-change", {
248
+ tokens: { accessToken: "old-token", refreshToken: "old-refresh" },
249
+ clientInfo: { clientId: "old-client" },
250
+ codeVerifier: "old-verifier",
251
+ oauthState: "old-state",
252
+ })
253
+
254
+ updateClientInfo("legacy-url-change", { clientId: "new-client" }, "https://new.example.com/mcp")
255
+
256
+ const newEntry = getAuthForUrl("legacy-url-change", "https://new.example.com/mcp")
257
+ assert.strictEqual(newEntry?.clientInfo?.clientId, "new-client")
258
+ assert.strictEqual(newEntry?.tokens, undefined)
259
+ assert.strictEqual(newEntry?.codeVerifier, undefined)
260
+ assert.strictEqual(newEntry?.oauthState, undefined)
261
+ })
262
+ })
263
+
264
+ describe("updateCodeVerifier / clearCodeVerifier", () => {
265
+ it("should save and retrieve code verifier", () => {
266
+ updateCodeVerifier("test-server", "verifier-123")
267
+ const entry = getAuthEntry("test-server")
268
+ assert.strictEqual(entry?.codeVerifier, "verifier-123")
269
+ })
270
+
271
+ it("should clear code verifier", () => {
272
+ updateCodeVerifier("test-server", "verifier-123")
273
+ clearCodeVerifier("test-server")
274
+ const entry = getAuthEntry("test-server")
275
+ assert.strictEqual(entry?.codeVerifier, undefined)
276
+ })
277
+ })
278
+
279
+ describe("updateOAuthState / getOAuthState / clearOAuthState", () => {
280
+ it("should save and retrieve OAuth state", () => {
281
+ updateOAuthState("test-server", "state-abc-123")
282
+ const state = getOAuthState("test-server")
283
+ assert.strictEqual(state, "state-abc-123")
284
+ })
285
+
286
+ it("should clear OAuth state", () => {
287
+ updateOAuthState("test-server", "state-abc-123")
288
+ clearOAuthState("test-server")
289
+ const state = getOAuthState("test-server")
290
+ assert.strictEqual(state, undefined)
291
+ })
292
+ })
293
+
294
+ describe("isTokenExpired", () => {
295
+ it("should return null if no tokens", () => {
296
+ const expired = isTokenExpired("expiry-test-null")
297
+ assert.strictEqual(expired, null)
298
+ })
299
+
300
+ it("should return false if no expiry", () => {
301
+ updateTokens("expiry-test-no-expiry", { accessToken: "token" })
302
+ const expired = isTokenExpired("expiry-test-no-expiry")
303
+ assert.strictEqual(expired, false)
304
+ })
305
+
306
+ it("should return true if expired", () => {
307
+ updateTokens("expiry-test-expired", {
308
+ accessToken: "token",
309
+ expiresAt: 1, // Way in the past
310
+ })
311
+ const expired = isTokenExpired("expiry-test-expired")
312
+ assert.strictEqual(expired, true)
313
+ })
314
+
315
+ it("should return false if not expired", () => {
316
+ updateTokens("expiry-test-future", {
317
+ accessToken: "token",
318
+ expiresAt: Date.now() / 1000 + 3600, // 1 hour from now
319
+ })
320
+ const expired = isTokenExpired("expiry-test-future")
321
+ assert.strictEqual(expired, false)
322
+ })
323
+ })
324
+
325
+ describe("hasStoredTokens", () => {
326
+ it("should return false if no tokens", () => {
327
+ assert.strictEqual(hasStoredTokens("has-tokens-test-false"), false)
328
+ })
329
+
330
+ it("should return true if tokens exist", () => {
331
+ updateTokens("has-tokens-test-true", { accessToken: "token" })
332
+ assert.strictEqual(hasStoredTokens("has-tokens-test-true"), true)
333
+ })
334
+ })
335
+
336
+ describe("clearAllCredentials", () => {
337
+ it("should remove all credentials", () => {
338
+ updateTokens("test-server", { accessToken: "token" })
339
+ updateClientInfo("test-server", { clientId: "client" })
340
+ updateCodeVerifier("test-server", "verifier")
341
+
342
+ clearAllCredentials("test-server")
343
+
344
+ assert.strictEqual(getAuthEntry("test-server"), undefined)
345
+ })
346
+ })
347
+
348
+ describe("clearClientInfo", () => {
349
+ it("should only remove client info", () => {
350
+ updateTokens("test-server", { accessToken: "token" })
351
+ updateClientInfo("test-server", { clientId: "client" })
352
+
353
+ clearClientInfo("test-server")
354
+
355
+ const entry = getAuthEntry("test-server")
356
+ assert.strictEqual(entry?.clientInfo, undefined)
357
+ assert.strictEqual(entry?.tokens?.accessToken, "token")
358
+ })
359
+ })
360
+
361
+ describe("clearTokens", () => {
362
+ it("should only remove tokens", () => {
363
+ updateTokens("test-server", { accessToken: "token" })
364
+ updateClientInfo("test-server", { clientId: "client" })
365
+
366
+ clearTokens("test-server")
367
+
368
+ const entry = getAuthEntry("test-server")
369
+ assert.strictEqual(entry?.tokens, undefined)
370
+ assert.strictEqual(entry?.clientInfo?.clientId, "client")
371
+ })
372
+ })
373
+ })