@fitchmultz/pi-mcp-adapter 5.0.0 → 7.0.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 (129) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/OAUTH.md +3 -3
  3. package/README.md +160 -88
  4. package/dist/app-bridge.bundle.js +75 -33
  5. package/dist/commands.js +9 -6
  6. package/dist/config.js +41 -16
  7. package/dist/direct-tools.d.ts +2 -4
  8. package/dist/direct-tools.js +21 -319
  9. package/dist/gateway-arguments.d.ts +33 -0
  10. package/dist/gateway-arguments.js +63 -0
  11. package/dist/index.js +181 -276
  12. package/dist/init.js +6 -20
  13. package/dist/lifecycle.d.ts +2 -2
  14. package/dist/lifecycle.js +2 -2
  15. package/dist/mcp-auth-flow.js +2 -2
  16. package/dist/mcp-auth.d.ts +1 -1
  17. package/dist/mcp-auth.js +3 -3
  18. package/dist/mcp-code.d.ts +7 -6
  19. package/dist/mcp-code.js +98 -51
  20. package/dist/mcp-oauth-provider.d.ts +5 -5
  21. package/dist/mcp-oauth-provider.js +5 -5
  22. package/dist/mcp-output-guard.d.ts +57 -2
  23. package/dist/mcp-output-guard.js +129 -24
  24. package/dist/mcp-panel.d.ts +3 -3
  25. package/dist/mcp-panel.js +36 -99
  26. package/dist/mcp-script-worker.mjs +5 -2
  27. package/dist/mcp-setup-panel.d.ts +5 -4
  28. package/dist/mcp-setup-panel.js +15 -7
  29. package/dist/mcp-status.js +2 -1
  30. package/dist/mcp-trace.d.ts +1 -1
  31. package/dist/mcp-trace.js +1 -1
  32. package/dist/metadata-cache.d.ts +1 -1
  33. package/dist/metadata-cache.js +25 -88
  34. package/dist/npx-resolver.js +8 -4
  35. package/dist/prompts.d.ts +1 -1
  36. package/dist/prompts.js +1 -1
  37. package/dist/proxy-modes.d.ts +41 -7
  38. package/dist/proxy-modes.js +275 -490
  39. package/dist/resource-tools.d.ts +15 -0
  40. package/dist/resource-tools.js +26 -1
  41. package/dist/sampling-handler.js +5 -1
  42. package/dist/search-ranking.d.ts +0 -2
  43. package/dist/search-ranking.js +70 -137
  44. package/dist/server-manager.d.ts +2 -2
  45. package/dist/server-manager.js +44 -27
  46. package/dist/session-recovery.d.ts +7 -4
  47. package/dist/session-recovery.js +76 -10
  48. package/dist/state.d.ts +1 -1
  49. package/dist/tool-loader.d.ts +32 -0
  50. package/dist/tool-loader.js +152 -0
  51. package/dist/tool-metadata.d.ts +67 -0
  52. package/dist/tool-metadata.js +23 -17
  53. package/dist/tool-registrar.d.ts +4 -4
  54. package/dist/tool-registrar.js +18 -13
  55. package/dist/tool-result-renderer.d.ts +6 -3
  56. package/dist/tool-result-renderer.js +11 -6
  57. package/dist/types.d.ts +11 -36
  58. package/dist/ui-resource-handler.d.ts +2 -2
  59. package/dist/ui-resource-handler.js +2 -2
  60. package/dist/ui-server.js +4 -3
  61. package/dist/ui-stream-types.d.ts +8 -8
  62. package/dist/unix-socket-transport.d.ts +1 -1
  63. package/dist/unix-socket-transport.js +1 -1
  64. package/dist/utils.d.ts +1 -0
  65. package/dist/utils.js +16 -4
  66. package/package.json +37 -96
  67. package/skills/mcp-scripting/SKILL.md +67 -20
  68. package/abort.ts +0 -34
  69. package/agent-dir.ts +0 -24
  70. package/app-bridge.bundle.js +0 -67
  71. package/banner.png +0 -0
  72. package/checkpoint.ts +0 -55
  73. package/commands.ts +0 -619
  74. package/config.ts +0 -1204
  75. package/consent-manager.ts +0 -25
  76. package/direct-tools.ts +0 -408
  77. package/dist/ts-shape.d.ts +0 -2
  78. package/dist/ts-shape.js +0 -147
  79. package/elicitation-handler.ts +0 -349
  80. package/error-signal.ts +0 -21
  81. package/glimpse-ui.ts +0 -80
  82. package/host-html-template.ts +0 -456
  83. package/index.ts +0 -900
  84. package/init.ts +0 -627
  85. package/legacy-migration.ts +0 -47
  86. package/lifecycle.ts +0 -165
  87. package/logger.ts +0 -127
  88. package/mcp-auth-flow.ts +0 -975
  89. package/mcp-auth.ts +0 -793
  90. package/mcp-callback-server.ts +0 -450
  91. package/mcp-code.ts +0 -386
  92. package/mcp-keyring-helper.cjs +0 -89
  93. package/mcp-oauth-provider.ts +0 -774
  94. package/mcp-output-guard.ts +0 -412
  95. package/mcp-panel.ts +0 -978
  96. package/mcp-script-worker.mjs +0 -128
  97. package/mcp-setup-panel.ts +0 -666
  98. package/mcp-status.ts +0 -109
  99. package/mcp-trace.ts +0 -295
  100. package/metadata-cache.ts +0 -333
  101. package/npx-resolver.ts +0 -488
  102. package/onboarding-state.ts +0 -58
  103. package/panel-keys.ts +0 -37
  104. package/prompts.ts +0 -337
  105. package/proxy-modes.ts +0 -1361
  106. package/resource-tools.ts +0 -17
  107. package/runtime-owner.ts +0 -123
  108. package/sampling-handler.ts +0 -273
  109. package/scripts/build.mjs +0 -131
  110. package/scripts/prepare.mjs +0 -102
  111. package/search-ranking.ts +0 -196
  112. package/server-manager.ts +0 -1320
  113. package/session-recovery.ts +0 -316
  114. package/state.ts +0 -64
  115. package/tool-approval.ts +0 -78
  116. package/tool-metadata.ts +0 -235
  117. package/tool-registrar.ts +0 -69
  118. package/tool-result-renderer.ts +0 -286
  119. package/ts-shape.ts +0 -145
  120. package/tsconfig.build.json +0 -13
  121. package/tsconfig.json +0 -16
  122. package/types.ts +0 -810
  123. package/ui-resource-handler.ts +0 -249
  124. package/ui-server.ts +0 -808
  125. package/ui-session.ts +0 -568
  126. package/ui-stream-types.ts +0 -89
  127. package/ui-tool-visibility.ts +0 -25
  128. package/unix-socket-transport.ts +0 -85
  129. package/utils.ts +0 -309
@@ -1,450 +0,0 @@
1
- /**
2
- * MCP OAuth Callback Server
3
- *
4
- * HTTP server that handles OAuth callbacks from the authorization server.
5
- * Uses Node.js http module for compatibility.
6
- */
7
-
8
- import { createServer, type Server, type IncomingMessage, type ServerResponse } from "http"
9
- import {
10
- DEFAULT_OAUTH_CALLBACK_PATH,
11
- getConfiguredOAuthCallbackPort,
12
- getOAuthCallbackPath,
13
- getOAuthCallbackPort,
14
- setOAuthCallbackPath,
15
- setOAuthCallbackPort,
16
- } from "./mcp-oauth-provider.ts"
17
-
18
- // HTML templates for callback responses
19
- const HTML_SUCCESS = `<!DOCTYPE html>
20
- <html>
21
- <head>
22
- <title>Pi - Authorization Successful</title>
23
- <style>
24
- body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee; }
25
- .container { text-align: center; padding: 2rem; }
26
- h1 { color: #4ade80; margin-bottom: 1rem; }
27
- p { color: #aaa; }
28
- </style>
29
- </head>
30
- <body>
31
- <div class="container">
32
- <h1>Authorization Successful</h1>
33
- <p>You can close this window and return to Pi.</p>
34
- </div>
35
- <script>setTimeout(() => window.close(), 2000);</script>
36
- </body>
37
- </html>`
38
-
39
- const HTML_MANUAL_SUCCESS = `<!DOCTYPE html>
40
- <html>
41
- <head>
42
- <title>Pi - Authorization Received</title>
43
- <style>
44
- body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee; }
45
- .container { text-align: center; padding: 2rem; }
46
- h1 { color: #4ade80; margin-bottom: 1rem; }
47
- p { color: #aaa; }
48
- </style>
49
- </head>
50
- <body>
51
- <div class="container">
52
- <h1>Authorization Received</h1>
53
- <p>Return to Pi and run auth-complete for this server. No callback URL or code is needed.</p>
54
- </div>
55
- </body>
56
- </html>`
57
-
58
- function escapeHtml(value: string): string {
59
- return value
60
- .replace(/&/g, "&amp;")
61
- .replace(/</g, "&lt;")
62
- .replace(/>/g, "&gt;")
63
- .replace(/"/g, "&quot;")
64
- .replace(/'/g, "&#39;")
65
- }
66
-
67
- const HTML_ERROR = (error: string) => `<!DOCTYPE html>
68
- <html>
69
- <head>
70
- <title>Pi - Authorization Failed</title>
71
- <style>
72
- body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee; }
73
- .container { text-align: center; padding: 2rem; }
74
- h1 { color: #f87171; margin-bottom: 1rem; }
75
- p { color: #aaa; }
76
- .error { color: #fca5a5; font-family: monospace; margin-top: 1rem; padding: 1rem; background: rgba(248,113,113,0.1); border-radius: 0.5rem; }
77
- </style>
78
- </head>
79
- <body>
80
- <div class="container">
81
- <h1>Authorization Failed</h1>
82
- <p>An error occurred during authorization.</p>
83
- <div class="error">${escapeHtml(error)}</div>
84
- </div>
85
- </body>
86
- </html>`
87
-
88
- /** Result of a successful OAuth callback */
89
- export interface OAuthCallbackResult {
90
- code: string
91
- /** RFC 9207 `iss` authorization response parameter, when provided */
92
- iss?: string
93
- }
94
-
95
- export type ValidateOAuthCallback = (response: { iss?: string; error?: string }) => void
96
-
97
- /** Pending authorization request */
98
- interface PendingAuth {
99
- resolve: (result: OAuthCallbackResult) => void
100
- reject: (error: Error) => void
101
- timeout: ReturnType<typeof setTimeout>
102
- validate?: ValidateOAuthCallback
103
- }
104
-
105
- /** Server singleton state */
106
- let server: Server | undefined
107
- let bindingPromise: Promise<void> | undefined
108
- let stoppingPromise: Promise<void> | undefined
109
- let callbackGeneration = 0
110
- const pendingAuths = new Map<string, PendingAuth>()
111
- type ReservedAuth = {
112
- validate: ValidateOAuthCallback | undefined
113
- result?: OAuthCallbackResult | { error: string; iss?: string }
114
- }
115
- const reservedAuthStates = new Map<string, ReservedAuth>()
116
-
117
- /** Timeout for callback completion (5 minutes) */
118
- const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000
119
-
120
- interface EnsureCallbackServerOptions {
121
- strictPort?: boolean
122
- port?: number
123
- callbackHost?: string
124
- callbackPath?: string
125
- oauthState?: string
126
- reserveState?: boolean
127
- validate?: ValidateOAuthCallback
128
- }
129
-
130
- const DEFAULT_OAUTH_CALLBACK_HOST = "localhost"
131
- let callbackServerHost = DEFAULT_OAUTH_CALLBACK_HOST
132
-
133
- /**
134
- * Handle incoming HTTP requests to the callback server.
135
- */
136
- function handleRequest(req: IncomingMessage, res: ServerResponse): void {
137
- const url = new URL(req.url || "/", `http://${req.headers.host}`)
138
-
139
- // Only handle the callback path
140
- if (url.pathname !== getOAuthCallbackPath()) {
141
- res.writeHead(404, { "Content-Type": "text/plain" })
142
- res.end("Not found")
143
- return
144
- }
145
-
146
- const code = url.searchParams.get("code")
147
- const iss = url.searchParams.get("iss")
148
- const state = url.searchParams.get("state")
149
- const error = url.searchParams.get("error")
150
- const errorDescription = url.searchParams.get("error_description")
151
-
152
- // Enforce state parameter presence for CSRF protection
153
- if (!state) {
154
- const errorMsg = "Missing required state parameter - potential CSRF attack"
155
- res.writeHead(400, { "Content-Type": "text/html" })
156
- res.end(HTML_ERROR(errorMsg))
157
- return
158
- }
159
-
160
- const pending = pendingAuths.get(state)
161
- const reserved = reservedAuthStates.get(state)
162
-
163
- if (!pending && !reserved) {
164
- res.writeHead(400, { "Content-Type": "text/html" })
165
- res.end(HTML_ERROR("Invalid or expired state parameter - potential CSRF attack"))
166
- return
167
- }
168
-
169
- try {
170
- const validate = pending?.validate ?? reserved?.validate
171
- if (validate) validate({ ...(iss !== null ? { iss } : {}), ...(error ? { error } : {}) })
172
- else if (error) throw new Error("Cannot verify the OAuth error callback issuer")
173
- } catch (failure) {
174
- const message = failure instanceof Error ? failure.message : "Invalid OAuth callback"
175
- res.writeHead(400, { "Content-Type": "text/html" })
176
- res.end(HTML_ERROR(message))
177
- if (pending) {
178
- clearTimeout(pending.timeout)
179
- pendingAuths.delete(state)
180
- reservedAuthStates.delete(state)
181
- pending.reject(new Error(message))
182
- }
183
- return
184
- }
185
-
186
- // Provider error text is only trusted after state and issuer validation.
187
- if (error) {
188
- const errorMsg = errorDescription || error
189
- // Send HTTP response first before rejecting promise
190
- res.writeHead(200, { "Content-Type": "text/html" })
191
- res.end(HTML_ERROR(errorMsg))
192
- // Reject promise after response is sent (defer to allow test to attach handler)
193
- if (pending) {
194
- reservedAuthStates.delete(state)
195
- clearTimeout(pending.timeout)
196
- pendingAuths.delete(state)
197
- setTimeout(() => pending.reject(new Error(errorMsg)), 0)
198
- } else if (reserved) {
199
- // Keep provider-controlled callback payloads out of tool output.
200
- reserved.result = { error: "OAuth authorization was denied or failed. Finish authorization in the browser, then retry auth-complete.", ...(iss !== null ? { iss } : {}) }
201
- }
202
- return
203
- }
204
-
205
- // Require authorization code
206
- if (!code) {
207
- res.writeHead(400, { "Content-Type": "text/html" })
208
- res.end(HTML_ERROR("No authorization code provided"))
209
- return
210
- }
211
-
212
- if (!pending) {
213
- if (reserved) reserved.result = { code, ...(iss !== null ? { iss } : {}) }
214
- res.writeHead(200, { "Content-Type": "text/html" })
215
- res.end(HTML_MANUAL_SUCCESS)
216
- return
217
- }
218
-
219
- // Clear timeout and resolve the pending promise
220
- clearTimeout(pending.timeout)
221
- pendingAuths.delete(state)
222
- pending.resolve({ code, ...(iss !== null ? { iss } : {}) })
223
-
224
- res.writeHead(200, { "Content-Type": "text/html" })
225
- res.end(HTML_SUCCESS)
226
- }
227
-
228
- /**
229
- * Ensure the callback server is running.
230
- * If strictPort is true, requires binding on the configured callback port.
231
- * If strictPort is false, asks the OS for an available local port.
232
- */
233
- export async function ensureCallbackServer(options: EnsureCallbackServerOptions = {}): Promise<void> {
234
- if (stoppingPromise) {
235
- throw new Error("OAuth callback server stopped")
236
- }
237
- const generation = callbackGeneration
238
- while (bindingPromise) {
239
- await bindingPromise
240
- if (generation !== callbackGeneration) {
241
- throw new Error("OAuth callback server stopped")
242
- }
243
- }
244
- if (generation !== callbackGeneration) {
245
- throw new Error("OAuth callback server stopped")
246
- }
247
-
248
- const operation = ensureCallbackServerLocked(options)
249
- bindingPromise = operation
250
- try {
251
- await operation
252
- } finally {
253
- if (bindingPromise === operation) {
254
- bindingPromise = undefined
255
- }
256
- }
257
- }
258
-
259
- async function ensureCallbackServerLocked(options: EnsureCallbackServerOptions = {}): Promise<void> {
260
- const requiredPort = options.port ?? getConfiguredOAuthCallbackPort()
261
- const strictPort = options.strictPort === true
262
- const requestedHost = options.callbackHost ?? DEFAULT_OAUTH_CALLBACK_HOST
263
- const rawRequestedPath = options.callbackPath ?? DEFAULT_OAUTH_CALLBACK_PATH
264
- const requestedPath = rawRequestedPath.startsWith("/") ? rawRequestedPath : `/${rawRequestedPath}`
265
- if (options.reserveState && !options.oauthState) {
266
- throw new Error("OAuth callback reservation requires an oauthState")
267
- }
268
- let reservedState: string | undefined
269
-
270
- const previousServer = server
271
- const needsStrictRebind = Boolean(previousServer && strictPort && getOAuthCallbackPort() !== requiredPort)
272
- const needsHostSwitch = Boolean(previousServer && callbackServerHost !== requestedHost)
273
- const needsPathSwitch = Boolean(previousServer && getOAuthCallbackPath() !== requestedPath)
274
-
275
- if (previousServer) {
276
- if (!needsStrictRebind && !needsHostSwitch) {
277
- if (needsPathSwitch) {
278
- if (pendingAuths.size > 0 || reservedAuthStates.size > 0) {
279
- throw new Error(
280
- `OAuth callback server is using path ${getOAuthCallbackPath()}, but callback path ${requestedPath} is required and cannot be switched while authorizations are pending`
281
- )
282
- }
283
- setOAuthCallbackPath(requestedPath)
284
- }
285
- if (options.reserveState && options.oauthState) {
286
- reservedAuthStates.set(options.oauthState, { validate: options.validate })
287
- reservedState = options.oauthState
288
- }
289
- return
290
- }
291
-
292
- if (pendingAuths.size > 0 || reservedAuthStates.size > 0) {
293
- throw new Error(
294
- `OAuth callback server is running on ${callbackServerHost}:${getOAuthCallbackPort()}, but strict callback endpoint ${requestedHost}:${requiredPort} is required and cannot be switched while authorizations are pending`
295
- )
296
- }
297
- }
298
-
299
- const candidateServer = createServer(handleRequest)
300
- const listenPort = strictPort ? requiredPort : 0
301
-
302
- try {
303
- await new Promise<void>((resolve, reject) => {
304
- candidateServer.once("error", (err) => {
305
- reject(err)
306
- })
307
-
308
- candidateServer.listen(listenPort, requestedHost, () => {
309
- resolve()
310
- })
311
- })
312
-
313
- if (strictPort) {
314
- setOAuthCallbackPort(requiredPort)
315
- } else {
316
- const address = candidateServer.address()
317
- if (!address || typeof address === "string" || typeof address.port !== "number") {
318
- throw new Error("OAuth callback server did not report an assigned port")
319
- }
320
- setOAuthCallbackPort(address.port)
321
- }
322
-
323
- if (previousServer && (needsStrictRebind || needsHostSwitch)) {
324
- await new Promise<void>((resolve) => {
325
- previousServer.close(() => resolve())
326
- })
327
- }
328
-
329
- callbackServerHost = requestedHost
330
- setOAuthCallbackPath(requestedPath)
331
- server = candidateServer
332
- if (options.reserveState && options.oauthState) {
333
- reservedAuthStates.set(options.oauthState, { validate: options.validate })
334
- reservedState = options.oauthState
335
- }
336
- server.unref()
337
- } catch (error) {
338
- if (reservedState) {
339
- reservedAuthStates.delete(reservedState)
340
- }
341
- const nodeError = error as NodeJS.ErrnoException
342
- await new Promise<void>((resolve) => {
343
- candidateServer.close(() => resolve())
344
- })
345
-
346
- if (strictPort && nodeError.code === "EADDRINUSE") {
347
- throw new Error(
348
- `OAuth callback port ${requiredPort} is already in use. Pre-registered OAuth clients require an exact redirect URI; set MCP_OAUTH_CALLBACK_PORT to your registered port or free port ${requiredPort}`,
349
- { cause: error }
350
- )
351
- }
352
-
353
- throw error
354
- }
355
- }
356
-
357
- export function releaseCallbackServer(oauthState: string): void {
358
- reservedAuthStates.delete(oauthState)
359
- }
360
-
361
- /** Consume a validated manual callback without releasing the flow's reservation. */
362
- export function takeCallbackResult(oauthState: string): ReservedAuth["result"] {
363
- const reserved = reservedAuthStates.get(oauthState)
364
- const result = reserved?.result
365
- if (reserved) delete reserved.result
366
- return result
367
- }
368
-
369
- /**
370
- * Wait for a callback with the given OAuth state.
371
- * Returns a promise that resolves with the authorization code and, when the
372
- * authorization server sends one, the RFC 9207 `iss` parameter.
373
- */
374
- export function waitForCallback(oauthState: string): Promise<OAuthCallbackResult> {
375
- const reserved = reservedAuthStates.get(oauthState)
376
- reservedAuthStates.delete(oauthState)
377
- if (reserved?.result) {
378
- return "error" in reserved.result
379
- ? Promise.reject(new Error(reserved.result.error))
380
- : Promise.resolve(reserved.result)
381
- }
382
- const validate = reserved?.validate
383
- return new Promise((resolve, reject) => {
384
- const timeout = setTimeout(() => {
385
- if (pendingAuths.has(oauthState)) {
386
- pendingAuths.delete(oauthState)
387
- reject(new Error("OAuth callback timeout - authorization took too long"))
388
- }
389
- }, CALLBACK_TIMEOUT_MS)
390
-
391
- pendingAuths.set(oauthState, { resolve, reject, timeout, ...(validate ? { validate } : {}) })
392
- })
393
- }
394
-
395
- /**
396
- * Cancel a pending authorization by state.
397
- */
398
- export function cancelPendingCallback(oauthState: string): void {
399
- reservedAuthStates.delete(oauthState)
400
- const pending = pendingAuths.get(oauthState)
401
- if (pending) {
402
- clearTimeout(pending.timeout)
403
- pendingAuths.delete(oauthState)
404
- pending.reject(new Error("Authorization cancelled"))
405
- }
406
- }
407
-
408
- /**
409
- * Stop the callback server and reject all pending authorizations.
410
- */
411
- export function stopCallbackServer(): Promise<void> {
412
- if (stoppingPromise) return stoppingPromise
413
-
414
- callbackGeneration += 1
415
- const cleanup = (async () => {
416
- while (bindingPromise) {
417
- await bindingPromise.catch(() => {})
418
- }
419
-
420
- if (server) {
421
- await new Promise<void>((resolve) => {
422
- server!.close(() => {
423
- resolve()
424
- })
425
- })
426
- server = undefined
427
- }
428
-
429
- setOAuthCallbackPort(getConfiguredOAuthCallbackPort())
430
- callbackServerHost = DEFAULT_OAUTH_CALLBACK_HOST
431
- setOAuthCallbackPath(DEFAULT_OAUTH_CALLBACK_PATH)
432
-
433
- // Reject all pending auths (defer to allow any pending operations to complete)
434
- const pendingList = Array.from(pendingAuths.entries())
435
- pendingAuths.clear()
436
- reservedAuthStates.clear()
437
- setTimeout(() => {
438
- for (const [, pending] of pendingList) {
439
- clearTimeout(pending.timeout)
440
- pending.reject(new Error("OAuth callback server stopped"))
441
- }
442
- }, 0)
443
- })()
444
-
445
- const operation = cleanup.finally(() => {
446
- if (stoppingPromise === operation) stoppingPromise = undefined
447
- })
448
- stoppingPromise = operation
449
- return operation
450
- }