@inspectr/mcplab 0.1.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 (136) hide show
  1. package/README.md +762 -0
  2. package/dist/app/android-chrome-192x192.png +0 -0
  3. package/dist/app/android-chrome-512x512.png +0 -0
  4. package/dist/app/apple-touch-icon.png +0 -0
  5. package/dist/app/assets/index-DT-Z4AVG.js +249 -0
  6. package/dist/app/assets/index-EP4WAY8u.css +1 -0
  7. package/dist/app/favicon-16x16.png +0 -0
  8. package/dist/app/favicon-32x32.png +0 -0
  9. package/dist/app/favicon.svg +13 -0
  10. package/dist/app/index.html +26 -0
  11. package/dist/app/inspectr_logo_color.svg +9 -0
  12. package/dist/app/mcp.png +0 -0
  13. package/dist/app/mcp.svg +1 -0
  14. package/dist/app/robots.txt +14 -0
  15. package/dist/app/site.webmanifest +1 -0
  16. package/dist/app-server/app-context.d.ts +164 -0
  17. package/dist/app-server/app-context.d.ts.map +1 -0
  18. package/dist/app-server/app-context.js +2 -0
  19. package/dist/app-server/app-context.js.map +1 -0
  20. package/dist/app-server/assistant-common.d.ts +41 -0
  21. package/dist/app-server/assistant-common.d.ts.map +1 -0
  22. package/dist/app-server/assistant-common.js +104 -0
  23. package/dist/app-server/assistant-common.js.map +1 -0
  24. package/dist/app-server/config-store.d.ts +15 -0
  25. package/dist/app-server/config-store.d.ts.map +1 -0
  26. package/dist/app-server/config-store.js +67 -0
  27. package/dist/app-server/config-store.js.map +1 -0
  28. package/dist/app-server/dev-mcp.d.ts +6 -0
  29. package/dist/app-server/dev-mcp.d.ts.map +1 -0
  30. package/dist/app-server/dev-mcp.js +71 -0
  31. package/dist/app-server/dev-mcp.js.map +1 -0
  32. package/dist/app-server/evals-routes.d.ts +22 -0
  33. package/dist/app-server/evals-routes.d.ts.map +1 -0
  34. package/dist/app-server/evals-routes.js +135 -0
  35. package/dist/app-server/evals-routes.js.map +1 -0
  36. package/dist/app-server/http.d.ts +5 -0
  37. package/dist/app-server/http.d.ts.map +1 -0
  38. package/dist/app-server/http.js +31 -0
  39. package/dist/app-server/http.js.map +1 -0
  40. package/dist/app-server/index.d.ts +3 -0
  41. package/dist/app-server/index.d.ts.map +1 -0
  42. package/dist/app-server/index.js +2 -0
  43. package/dist/app-server/index.js.map +1 -0
  44. package/dist/app-server/jobs.d.ts +15 -0
  45. package/dist/app-server/jobs.d.ts.map +1 -0
  46. package/dist/app-server/jobs.js +11 -0
  47. package/dist/app-server/jobs.js.map +1 -0
  48. package/dist/app-server/libraries-store.d.ts +15 -0
  49. package/dist/app-server/libraries-store.d.ts.map +1 -0
  50. package/dist/app-server/libraries-store.js +61 -0
  51. package/dist/app-server/libraries-store.js.map +1 -0
  52. package/dist/app-server/markdown-reports.d.ts +12 -0
  53. package/dist/app-server/markdown-reports.d.ts.map +1 -0
  54. package/dist/app-server/markdown-reports.js +145 -0
  55. package/dist/app-server/markdown-reports.js.map +1 -0
  56. package/dist/app-server/oauth-debugger-domain.d.ts +230 -0
  57. package/dist/app-server/oauth-debugger-domain.d.ts.map +1 -0
  58. package/dist/app-server/oauth-debugger-domain.js +1098 -0
  59. package/dist/app-server/oauth-debugger-domain.js.map +1 -0
  60. package/dist/app-server/oauth-debugger.d.ts +20 -0
  61. package/dist/app-server/oauth-debugger.d.ts.map +1 -0
  62. package/dist/app-server/oauth-debugger.js +193 -0
  63. package/dist/app-server/oauth-debugger.js.map +1 -0
  64. package/dist/app-server/provider-models.d.ts +8 -0
  65. package/dist/app-server/provider-models.d.ts.map +1 -0
  66. package/dist/app-server/provider-models.js +60 -0
  67. package/dist/app-server/provider-models.js.map +1 -0
  68. package/dist/app-server/result-assistant-domain.d.ts +87 -0
  69. package/dist/app-server/result-assistant-domain.d.ts.map +1 -0
  70. package/dist/app-server/result-assistant-domain.js +212 -0
  71. package/dist/app-server/result-assistant-domain.js.map +1 -0
  72. package/dist/app-server/result-assistant.d.ts +22 -0
  73. package/dist/app-server/result-assistant.d.ts.map +1 -0
  74. package/dist/app-server/result-assistant.js +328 -0
  75. package/dist/app-server/result-assistant.js.map +1 -0
  76. package/dist/app-server/router.d.ts +4 -0
  77. package/dist/app-server/router.d.ts.map +1 -0
  78. package/dist/app-server/router.js +374 -0
  79. package/dist/app-server/router.js.map +1 -0
  80. package/dist/app-server/runs-routes.d.ts +44 -0
  81. package/dist/app-server/runs-routes.d.ts.map +1 -0
  82. package/dist/app-server/runs-routes.js +555 -0
  83. package/dist/app-server/runs-routes.js.map +1 -0
  84. package/dist/app-server/runs-store.d.ts +23 -0
  85. package/dist/app-server/runs-store.d.ts.map +1 -0
  86. package/dist/app-server/runs-store.js +84 -0
  87. package/dist/app-server/runs-store.js.map +1 -0
  88. package/dist/app-server/scenario-assistant-domain.d.ts +162 -0
  89. package/dist/app-server/scenario-assistant-domain.d.ts.map +1 -0
  90. package/dist/app-server/scenario-assistant-domain.js +269 -0
  91. package/dist/app-server/scenario-assistant-domain.js.map +1 -0
  92. package/dist/app-server/scenario-assistant.d.ts +29 -0
  93. package/dist/app-server/scenario-assistant.d.ts.map +1 -0
  94. package/dist/app-server/scenario-assistant.js +246 -0
  95. package/dist/app-server/scenario-assistant.js.map +1 -0
  96. package/dist/app-server/settings-store.d.ts +4 -0
  97. package/dist/app-server/settings-store.d.ts.map +1 -0
  98. package/dist/app-server/settings-store.js +32 -0
  99. package/dist/app-server/settings-store.js.map +1 -0
  100. package/dist/app-server/snapshots-routes.d.ts +24 -0
  101. package/dist/app-server/snapshots-routes.d.ts.map +1 -0
  102. package/dist/app-server/snapshots-routes.js +82 -0
  103. package/dist/app-server/snapshots-routes.js.map +1 -0
  104. package/dist/app-server/static-serving.d.ts +17 -0
  105. package/dist/app-server/static-serving.d.ts.map +1 -0
  106. package/dist/app-server/static-serving.js +64 -0
  107. package/dist/app-server/static-serving.js.map +1 -0
  108. package/dist/app-server/store-utils.d.ts +5 -0
  109. package/dist/app-server/store-utils.d.ts.map +1 -0
  110. package/dist/app-server/store-utils.js +26 -0
  111. package/dist/app-server/store-utils.js.map +1 -0
  112. package/dist/app-server/tool-analysis-domain.d.ts +146 -0
  113. package/dist/app-server/tool-analysis-domain.d.ts.map +1 -0
  114. package/dist/app-server/tool-analysis-domain.js +556 -0
  115. package/dist/app-server/tool-analysis-domain.js.map +1 -0
  116. package/dist/app-server/tool-analysis-storage.d.ts +41 -0
  117. package/dist/app-server/tool-analysis-storage.d.ts.map +1 -0
  118. package/dist/app-server/tool-analysis-storage.js +110 -0
  119. package/dist/app-server/tool-analysis-storage.js.map +1 -0
  120. package/dist/app-server/tool-analysis.d.ts +22 -0
  121. package/dist/app-server/tool-analysis.d.ts.map +1 -0
  122. package/dist/app-server/tool-analysis.js +271 -0
  123. package/dist/app-server/tool-analysis.js.map +1 -0
  124. package/dist/app-server/types.d.ts +28 -0
  125. package/dist/app-server/types.d.ts.map +1 -0
  126. package/dist/app-server/types.js +2 -0
  127. package/dist/app-server/types.js.map +1 -0
  128. package/dist/cli.d.ts +3 -0
  129. package/dist/cli.d.ts.map +1 -0
  130. package/dist/cli.js +544 -0
  131. package/dist/cli.js.map +1 -0
  132. package/dist/snapshot.d.ts +80 -0
  133. package/dist/snapshot.d.ts.map +1 -0
  134. package/dist/snapshot.js +401 -0
  135. package/dist/snapshot.js.map +1 -0
  136. package/package.json +55 -0
@@ -0,0 +1,1098 @@
1
+ import { randomBytes, createHash } from 'node:crypto';
2
+ import { URL } from 'node:url';
3
+ import { addJobEvent } from './jobs.js';
4
+ const SESSION_TTL_MS = 30 * 60 * 1000;
5
+ const SPEC_BASE = 'https://modelcontextprotocol.io/specification/draft/basic/authorization';
6
+ function nowIso() {
7
+ return new Date().toISOString();
8
+ }
9
+ function makeId(prefix) {
10
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
11
+ }
12
+ function toBase64Url(buffer) {
13
+ return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
14
+ }
15
+ function pkcePair() {
16
+ const verifier = toBase64Url(randomBytes(32));
17
+ const challenge = createHash('sha256').update(verifier).digest('base64url');
18
+ return { verifier, challenge, method: 'S256' };
19
+ }
20
+ function normalizeRuntime(runtime) {
21
+ return {
22
+ redirectMode: runtime?.redirectMode ?? 'local_callback',
23
+ usePkce: runtime?.usePkce !== false,
24
+ codeChallengeMethod: 'S256',
25
+ scopes: runtime?.scopes ?? [],
26
+ resource: runtime?.resource,
27
+ state: runtime?.state,
28
+ nonce: runtime?.nonce,
29
+ extraAuthParams: runtime?.extraAuthParams ?? {}
30
+ };
31
+ }
32
+ function baseSteps(method) {
33
+ const steps = [
34
+ [
35
+ 'resolve_target_metadata',
36
+ 'Resolve target metadata',
37
+ 'Resolve resource and authorization server metadata and endpoints.'
38
+ ]
39
+ ];
40
+ if (method === 'cimd') {
41
+ steps.push([
42
+ 'fetch_cimd',
43
+ 'Fetch + validate Client ID Metadata Document',
44
+ 'Fetch the client metadata document and validate required fields.'
45
+ ]);
46
+ }
47
+ else {
48
+ steps.push([
49
+ 'resolve_registration_source',
50
+ 'Resolve/validate client registration source',
51
+ 'Validate client information for the selected registration method.'
52
+ ]);
53
+ }
54
+ if (method === 'dcr') {
55
+ steps.push([
56
+ 'dynamic_client_registration',
57
+ 'Dynamic Client Registration',
58
+ 'Register a client dynamically and validate the registration response.'
59
+ ]);
60
+ }
61
+ steps.push([
62
+ 'build_authorization_request',
63
+ 'Build authorization request',
64
+ 'Construct the authorization request URL and validate parameters.'
65
+ ], [
66
+ 'browser_authorization',
67
+ 'Browser authorization step',
68
+ 'Open the authorization URL and authenticate/authorize the client.'
69
+ ], [
70
+ 'receive_authorization_response',
71
+ 'Receive authorization response',
72
+ 'Capture the redirect callback via local callback or manual paste.'
73
+ ], [
74
+ 'validate_callback',
75
+ 'Validate state and callback semantics',
76
+ 'Validate state, code, and authorization response semantics.'
77
+ ], [
78
+ 'token_exchange',
79
+ 'Token exchange',
80
+ 'Exchange authorization code for tokens and inspect the token response.'
81
+ ], [
82
+ 'token_validation',
83
+ 'Token response validation',
84
+ 'Validate token response fields and protocol expectations.'
85
+ ], [
86
+ 'resource_probe',
87
+ 'Protected resource / MCP probe',
88
+ 'Optionally call a protected endpoint with the access token.'
89
+ ], [
90
+ 'summary',
91
+ 'Summary + compliance checks',
92
+ 'Summarize validations, failures, and remediation tips.'
93
+ ]);
94
+ return steps.map(([id, title, description]) => ({
95
+ id,
96
+ title,
97
+ description,
98
+ status: 'pending',
99
+ networkExchangeIds: [],
100
+ validationIds: []
101
+ }));
102
+ }
103
+ function step(session, stepId) {
104
+ const found = session.steps.find((s) => s.id === stepId);
105
+ if (!found)
106
+ throw new Error(`Unknown OAuth debugger step: ${stepId}`);
107
+ return found;
108
+ }
109
+ function emitEvent(session, type, payload) {
110
+ addJobEvent(session, {
111
+ type,
112
+ ts: nowIso(),
113
+ payload
114
+ });
115
+ }
116
+ function markStepStarted(session, stepId) {
117
+ const s = step(session, stepId);
118
+ if (s.status === 'completed' || s.status === 'failed')
119
+ return;
120
+ s.status = 'active';
121
+ s.startedAt = s.startedAt ?? nowIso();
122
+ session.updatedAt = Date.now();
123
+ emitEvent(session, 'step_started', { stepId, title: s.title });
124
+ }
125
+ function markStepCompleted(session, stepId, outcomeSummary) {
126
+ const s = step(session, stepId);
127
+ s.status = 'completed';
128
+ s.finishedAt = nowIso();
129
+ if (outcomeSummary)
130
+ s.outcomeSummary = outcomeSummary;
131
+ session.updatedAt = Date.now();
132
+ emitEvent(session, 'step_completed', {
133
+ stepId,
134
+ title: s.title,
135
+ outcomeSummary: outcomeSummary ?? null
136
+ });
137
+ }
138
+ function markStepFailed(session, stepId, message) {
139
+ const s = step(session, stepId);
140
+ s.status = 'failed';
141
+ s.finishedAt = nowIso();
142
+ s.outcomeSummary = message;
143
+ session.updatedAt = Date.now();
144
+ emitEvent(session, 'step_failed', { stepId, title: s.title, message });
145
+ }
146
+ function markStepSkipped(session, stepId, reason) {
147
+ const s = step(session, stepId);
148
+ s.status = 'skipped';
149
+ s.finishedAt = nowIso();
150
+ s.outcomeSummary = reason;
151
+ }
152
+ function addValidation(session, finding) {
153
+ const id = makeId('ov');
154
+ const full = { id, ...finding };
155
+ session.validations.push(full);
156
+ const s = session.steps.find((stepItem) => stepItem.id === finding.stepId);
157
+ if (s)
158
+ s.validationIds.push(id);
159
+ emitEvent(session, 'validation', {
160
+ id,
161
+ stepId: finding.stepId,
162
+ severity: finding.severity,
163
+ code: finding.code,
164
+ title: finding.title
165
+ });
166
+ return full;
167
+ }
168
+ function addSequence(session, event) {
169
+ session.sequence.push({ id: makeId('seq'), ts: nowIso(), ...event });
170
+ }
171
+ function headersToObject(headers) {
172
+ const out = {};
173
+ headers.forEach((value, key) => {
174
+ out[key] = value;
175
+ });
176
+ return out;
177
+ }
178
+ function recordHttp(session, exchange) {
179
+ const id = makeId('http');
180
+ const full = {
181
+ id,
182
+ kind: 'http',
183
+ timestamp: nowIso(),
184
+ ...exchange
185
+ };
186
+ session.network.push(full);
187
+ const s = session.steps.find((stepItem) => stepItem.id === exchange.stepId);
188
+ if (s)
189
+ s.networkExchangeIds.push(id);
190
+ emitEvent(session, full.phase === 'request' ? 'http_request' : 'http_response', {
191
+ id: full.id,
192
+ stepId: full.stepId,
193
+ label: full.label,
194
+ method: full.method ?? null,
195
+ url: full.url,
196
+ status: full.status ?? null
197
+ });
198
+ return full;
199
+ }
200
+ async function fetchWithTrace(params) {
201
+ const { session, stepId, label, url, method = 'GET', headers = {}, bodyText, timeoutMs = 15_000 } = params;
202
+ recordHttp(session, {
203
+ stepId,
204
+ phase: 'request',
205
+ label,
206
+ method,
207
+ url,
208
+ headers,
209
+ bodyText,
210
+ sensitiveFields: []
211
+ });
212
+ addSequence(session, {
213
+ from: 'Debugger',
214
+ to: label.toLowerCase().includes('token')
215
+ ? 'Token Endpoint'
216
+ : label.toLowerCase().includes('probe')
217
+ ? 'MCP/Resource'
218
+ : 'Auth Server',
219
+ label,
220
+ stepId
221
+ });
222
+ const controller = new AbortController();
223
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
224
+ const startedAt = Date.now();
225
+ try {
226
+ const response = await fetch(url, {
227
+ method,
228
+ headers,
229
+ body: bodyText,
230
+ signal: controller.signal
231
+ });
232
+ const responseText = await response.text();
233
+ let responseJson;
234
+ try {
235
+ responseJson = responseText ? JSON.parse(responseText) : undefined;
236
+ }
237
+ catch {
238
+ // non-json response
239
+ }
240
+ recordHttp(session, {
241
+ stepId,
242
+ phase: 'response',
243
+ label,
244
+ url,
245
+ headers: headersToObject(response.headers),
246
+ status: response.status,
247
+ bodyText: responseText,
248
+ durationMs: Date.now() - startedAt,
249
+ sensitiveFields: []
250
+ });
251
+ return { response, responseText, responseJson };
252
+ }
253
+ finally {
254
+ clearTimeout(timer);
255
+ }
256
+ }
257
+ function requiredString(value, error) {
258
+ const text = typeof value === 'string' ? value.trim() : '';
259
+ if (!text)
260
+ throw new Error(error);
261
+ return text;
262
+ }
263
+ function inferResourceMetadataUrl(baseUrl) {
264
+ const u = new URL(baseUrl);
265
+ u.pathname = '/.well-known/oauth-protected-resource';
266
+ u.search = '';
267
+ return u.toString();
268
+ }
269
+ function inferAuthServerMetadataUrl(issuerOrBase) {
270
+ const u = new URL(issuerOrBase);
271
+ u.pathname = '/.well-known/oauth-authorization-server';
272
+ u.search = '';
273
+ return u.toString();
274
+ }
275
+ function localCallbackUrl(session, appBaseUrl) {
276
+ return `${appBaseUrl.replace(/\/$/, '')}/api/oauth-debugger/sessions/${session.id}/callback`;
277
+ }
278
+ function buildAuthorizationUrl(session) {
279
+ const authEndpoint = session.config.target.overrides?.authorizationEndpoint ||
280
+ session.context.authServerMetadata?.authorization_endpoint;
281
+ if (!authEndpoint)
282
+ throw new Error('Authorization endpoint not resolved');
283
+ const resolvedClient = session.context.resolvedClient;
284
+ if (!resolvedClient?.clientId)
285
+ throw new Error('Client not resolved');
286
+ const callbackUrl = requiredString(session.context.callbackUrl, 'Callback URL not set');
287
+ const state = session.config.runtime.state || toBase64Url(randomBytes(16));
288
+ session.config.runtime.state = state;
289
+ const params = new URLSearchParams({
290
+ response_type: 'code',
291
+ client_id: resolvedClient.clientId,
292
+ redirect_uri: callbackUrl,
293
+ state
294
+ });
295
+ if ((session.config.runtime.scopes ?? []).length > 0) {
296
+ params.set('scope', (session.config.runtime.scopes ?? []).join(' '));
297
+ }
298
+ if (session.config.runtime.resource) {
299
+ params.set('resource', session.config.runtime.resource);
300
+ }
301
+ if (session.config.runtime.usePkce) {
302
+ session.context.pkce = session.context.pkce ?? pkcePair();
303
+ params.set('code_challenge', session.context.pkce.challenge);
304
+ params.set('code_challenge_method', session.context.pkce.method);
305
+ }
306
+ if (session.config.runtime.nonce)
307
+ params.set('nonce', session.config.runtime.nonce);
308
+ for (const [key, value] of Object.entries(session.config.runtime.extraAuthParams ?? {})) {
309
+ if (value != null && `${value}` !== '')
310
+ params.set(key, String(value));
311
+ }
312
+ const url = new URL(authEndpoint);
313
+ url.search = params.toString();
314
+ session.context.authorizationRequestUrl = url.toString();
315
+ return url.toString();
316
+ }
317
+ function parseCallbackInput(input) {
318
+ if (input.redirectUrl) {
319
+ const parsed = new URL(input.redirectUrl);
320
+ return {
321
+ rawUrl: input.redirectUrl,
322
+ code: parsed.searchParams.get('code') ?? undefined,
323
+ state: parsed.searchParams.get('state') ?? undefined,
324
+ error: parsed.searchParams.get('error') ?? undefined,
325
+ errorDescription: parsed.searchParams.get('error_description') ?? undefined
326
+ };
327
+ }
328
+ return {
329
+ code: input.code,
330
+ state: input.state
331
+ };
332
+ }
333
+ function resolvedClientFromConfig(session) {
334
+ if (session.config.registrationMethod === 'pre_registered') {
335
+ const c = session.config.clientConfig.preRegistered;
336
+ if (!c?.clientId)
337
+ throw new Error('pre-registered client_id is required');
338
+ session.context.resolvedClient = {
339
+ clientId: c.clientId,
340
+ clientSecret: c.clientSecret,
341
+ tokenEndpointAuthMethod: c.tokenEndpointAuthMethod
342
+ };
343
+ return;
344
+ }
345
+ if (session.config.registrationMethod === 'cimd') {
346
+ const reg = session.context.registration;
347
+ const clientId = reg?.client_id ?? session.config.clientConfig.cimd?.expectedClientId;
348
+ if (!clientId)
349
+ throw new Error('CIMD did not provide client_id and no expectedClientId was set');
350
+ session.context.resolvedClient = {
351
+ clientId,
352
+ tokenEndpointAuthMethod: reg?.token_endpoint_auth_method ?? session.config.clientConfig.cimd?.expectedClientId
353
+ };
354
+ return;
355
+ }
356
+ // dcr handled after registration call
357
+ }
358
+ async function stepResolveTargetMetadata(session) {
359
+ const stepId = 'resolve_target_metadata';
360
+ markStepStarted(session, stepId);
361
+ const server = session.serverConfig;
362
+ if (!server)
363
+ throw new Error(`MCP server '${session.config.target.serverName}' not found`);
364
+ const resourceMetadataUrl = session.config.target.overrides?.authorizationServerMetadataUrl
365
+ ? undefined
366
+ : inferResourceMetadataUrl(session.config.target.overrides?.resourceBaseUrl || server.url);
367
+ if (resourceMetadataUrl) {
368
+ try {
369
+ const { response, responseJson, responseText } = await fetchWithTrace({
370
+ session,
371
+ stepId,
372
+ label: 'Protected Resource Metadata',
373
+ url: resourceMetadataUrl
374
+ });
375
+ if (!response.ok) {
376
+ addValidation(session, {
377
+ stepId,
378
+ severity: 'warning',
379
+ code: 'resource_metadata_fetch_failed',
380
+ title: 'Resource metadata fetch failed',
381
+ detail: `Protected resource metadata returned HTTP ${response.status}.`,
382
+ recommendation: 'Provide manual endpoint overrides or verify the protected resource metadata URL.'
383
+ });
384
+ }
385
+ else {
386
+ session.context.resourceMetadata = responseJson ?? { raw: responseText };
387
+ }
388
+ }
389
+ catch (error) {
390
+ addValidation(session, {
391
+ stepId,
392
+ severity: 'warning',
393
+ code: 'resource_metadata_unreachable',
394
+ title: 'Protected resource metadata unreachable',
395
+ detail: error instanceof Error ? error.message : String(error),
396
+ recommendation: 'Check the MCP server URL and network connectivity, or use manual endpoint overrides.'
397
+ });
398
+ }
399
+ }
400
+ const authMetadataUrl = session.config.target.overrides?.authorizationServerMetadataUrl ||
401
+ (session.context.resourceMetadata?.authorization_servers?.[0]
402
+ ? inferAuthServerMetadataUrl(String(session.context.resourceMetadata.authorization_servers[0]))
403
+ : session.context.resourceMetadata?.authorization_server
404
+ ? inferAuthServerMetadataUrl(String(session.context.resourceMetadata.authorization_server))
405
+ : undefined);
406
+ if (authMetadataUrl) {
407
+ const { response, responseJson, responseText } = await fetchWithTrace({
408
+ session,
409
+ stepId,
410
+ label: 'Authorization Server Metadata',
411
+ url: authMetadataUrl
412
+ });
413
+ if (!response.ok) {
414
+ throw new Error(`Authorization server metadata request failed (${response.status})`);
415
+ }
416
+ session.context.authServerMetadata = responseJson ?? { raw: responseText };
417
+ }
418
+ else {
419
+ session.context.authServerMetadata = {};
420
+ addValidation(session, {
421
+ stepId,
422
+ severity: 'warning',
423
+ code: 'auth_metadata_missing',
424
+ title: 'Authorization metadata URL not discovered',
425
+ detail: 'Could not derive authorization server metadata URL automatically from the selected MCP server.',
426
+ recommendation: 'Use Advanced overrides to set authorization/token/registration endpoints.'
427
+ });
428
+ }
429
+ if (session.config.target.overrides?.authorizationEndpoint) {
430
+ session.context.authServerMetadata = {
431
+ ...(session.context.authServerMetadata ?? {}),
432
+ authorization_endpoint: session.config.target.overrides.authorizationEndpoint
433
+ };
434
+ }
435
+ if (session.config.target.overrides?.tokenEndpoint) {
436
+ session.context.authServerMetadata = {
437
+ ...(session.context.authServerMetadata ?? {}),
438
+ token_endpoint: session.config.target.overrides.tokenEndpoint
439
+ };
440
+ }
441
+ if (session.config.target.overrides?.registrationEndpoint) {
442
+ session.context.authServerMetadata = {
443
+ ...(session.context.authServerMetadata ?? {}),
444
+ registration_endpoint: session.config.target.overrides.registrationEndpoint
445
+ };
446
+ }
447
+ markStepCompleted(session, stepId, 'Metadata resolution finished');
448
+ }
449
+ async function stepResolveRegistrationSource(session) {
450
+ const stepId = 'resolve_registration_source';
451
+ if (!session.steps.some((s) => s.id === stepId))
452
+ return;
453
+ markStepStarted(session, stepId);
454
+ resolvedClientFromConfig(session);
455
+ if (session.context.resolvedClient?.clientId) {
456
+ markStepCompleted(session, stepId, `Client ${session.context.resolvedClient.clientId} resolved`);
457
+ return;
458
+ }
459
+ markStepCompleted(session, stepId, 'Registration source deferred');
460
+ }
461
+ async function stepFetchCimd(session) {
462
+ const stepId = 'fetch_cimd';
463
+ if (!session.steps.some((s) => s.id === stepId))
464
+ return;
465
+ markStepStarted(session, stepId);
466
+ const cimdUrl = session.config.clientConfig.cimd?.cimdUrl || session.config.target.overrides?.cimdUrl;
467
+ if (!cimdUrl)
468
+ throw new Error('CIMD URL is required for CIMD registration method');
469
+ const { response, responseJson, responseText } = await fetchWithTrace({
470
+ session,
471
+ stepId,
472
+ label: 'Client ID Metadata Document',
473
+ url: cimdUrl
474
+ });
475
+ if (!response.ok) {
476
+ throw new Error(`CIMD request failed (${response.status})`);
477
+ }
478
+ session.context.registration = responseJson ?? { raw: responseText };
479
+ const clientId = session.context.registration?.client_id;
480
+ if (!clientId) {
481
+ addValidation(session, {
482
+ stepId,
483
+ severity: 'error',
484
+ code: 'cimd_missing_client_id',
485
+ title: 'CIMD missing client_id',
486
+ detail: 'The Client ID Metadata Document does not contain a client_id.',
487
+ specReference: SPEC_BASE
488
+ });
489
+ }
490
+ resolvedClientFromConfig(session);
491
+ markStepCompleted(session, stepId, `Fetched CIMD${clientId ? ` for ${clientId}` : ''}`);
492
+ }
493
+ async function stepDcr(session) {
494
+ const stepId = 'dynamic_client_registration';
495
+ if (!session.steps.some((s) => s.id === stepId))
496
+ return;
497
+ markStepStarted(session, stepId);
498
+ const registrationEndpoint = session.context.authServerMetadata?.registration_endpoint;
499
+ if (!registrationEndpoint) {
500
+ throw new Error('Registration endpoint not available for DCR');
501
+ }
502
+ const redirectUri = requiredString(session.context.callbackUrl, 'Callback URL not set');
503
+ const bodyObj = {
504
+ redirect_uris: [redirectUri],
505
+ token_endpoint_auth_method: session.config.clientConfig.dcr?.tokenEndpointAuthMethod ?? 'none',
506
+ client_name: 'MCP Lab OAuth Debugger',
507
+ grant_types: ['authorization_code'],
508
+ response_types: ['code'],
509
+ ...(session.config.clientConfig.dcr?.metadata ?? {})
510
+ };
511
+ const bodyText = JSON.stringify(bodyObj);
512
+ const { response, responseJson, responseText } = await fetchWithTrace({
513
+ session,
514
+ stepId,
515
+ label: 'Dynamic Client Registration',
516
+ url: String(registrationEndpoint),
517
+ method: 'POST',
518
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
519
+ bodyText
520
+ });
521
+ if (!response.ok) {
522
+ throw new Error(`DCR failed (${response.status})`);
523
+ }
524
+ session.context.registration = responseJson ?? { raw: responseText };
525
+ const clientId = session.context.registration?.client_id;
526
+ if (!clientId) {
527
+ throw new Error('DCR response missing client_id');
528
+ }
529
+ session.context.resolvedClient = {
530
+ clientId: String(clientId),
531
+ clientSecret: typeof session.context.registration?.client_secret === 'string'
532
+ ? session.context.registration.client_secret
533
+ : undefined,
534
+ tokenEndpointAuthMethod: session.context.registration?.token_endpoint_auth_method ??
535
+ session.config.clientConfig.dcr?.tokenEndpointAuthMethod
536
+ };
537
+ markStepCompleted(session, stepId, `DCR created client ${clientId}`);
538
+ }
539
+ async function stepBuildAuthorizationRequest(session) {
540
+ const stepId = 'build_authorization_request';
541
+ markStepStarted(session, stepId);
542
+ const authUrl = buildAuthorizationUrl(session);
543
+ addValidation(session, {
544
+ stepId,
545
+ severity: 'info',
546
+ code: 'auth_url_built',
547
+ title: 'Authorization request constructed',
548
+ detail: 'Authorization request URL was built successfully.',
549
+ specReference: SPEC_BASE
550
+ });
551
+ markStepCompleted(session, stepId, authUrl);
552
+ }
553
+ async function stepBrowserAuthorizationPause(session) {
554
+ const stepId = 'browser_authorization';
555
+ markStepStarted(session, stepId);
556
+ const authUrl = session.context.authorizationRequestUrl;
557
+ if (!authUrl)
558
+ throw new Error('Authorization URL not built');
559
+ addSequence(session, {
560
+ from: 'User',
561
+ to: 'Auth Server',
562
+ label: 'Open authorization URL',
563
+ stepId
564
+ });
565
+ if (session.config.runtime.redirectMode === 'manual') {
566
+ session.status = 'waiting_for_user';
567
+ emitEvent(session, 'waiting_for_user', {
568
+ stepId,
569
+ nextAction: 'paste_callback_url',
570
+ authorizationUrl: authUrl
571
+ });
572
+ markStepCompleted(session, stepId, 'Waiting for manual callback paste');
573
+ }
574
+ else {
575
+ session.status = 'waiting_for_browser_callback';
576
+ emitEvent(session, 'waiting_for_browser_callback', {
577
+ stepId,
578
+ nextAction: 'open_authorize_url',
579
+ authorizationUrl: authUrl,
580
+ callbackUrl: session.context.callbackUrl ?? null
581
+ });
582
+ markStepCompleted(session, stepId, 'Waiting for browser callback');
583
+ }
584
+ }
585
+ function stepReceiveAuthorizationResponse(session) {
586
+ const stepId = 'receive_authorization_response';
587
+ markStepStarted(session, stepId);
588
+ if (!session.context.callbackResult) {
589
+ throw new Error('No authorization response callback captured');
590
+ }
591
+ markStepCompleted(session, stepId, 'Authorization response captured');
592
+ }
593
+ function stepValidateCallback(session) {
594
+ const stepId = 'validate_callback';
595
+ markStepStarted(session, stepId);
596
+ const cb = session.context.callbackResult;
597
+ if (!cb)
598
+ throw new Error('No callback result');
599
+ if (cb.error) {
600
+ addValidation(session, {
601
+ stepId,
602
+ severity: 'error',
603
+ code: 'authorization_error',
604
+ title: 'Authorization server returned an error',
605
+ detail: `${cb.error}${cb.errorDescription ? `: ${cb.errorDescription}` : ''}`,
606
+ recommendation: 'Inspect the authorization request parameters and client registration details.'
607
+ });
608
+ throw new Error(`Authorization error: ${cb.error}`);
609
+ }
610
+ if (!cb.code) {
611
+ addValidation(session, {
612
+ stepId,
613
+ severity: 'error',
614
+ code: 'missing_code',
615
+ title: 'Missing authorization code',
616
+ detail: 'The callback did not include an authorization code.',
617
+ specReference: SPEC_BASE
618
+ });
619
+ throw new Error('Authorization code missing from callback');
620
+ }
621
+ if (session.config.runtime.state && cb.state !== session.config.runtime.state) {
622
+ addValidation(session, {
623
+ stepId,
624
+ severity: 'error',
625
+ code: 'state_mismatch',
626
+ title: 'State mismatch',
627
+ detail: `Expected state '${session.config.runtime.state}' but received '${cb.state ?? ''}'.`,
628
+ recommendation: 'Verify redirect handling and ensure the authorization response belongs to this session.'
629
+ });
630
+ throw new Error('State mismatch');
631
+ }
632
+ addValidation(session, {
633
+ stepId,
634
+ severity: 'info',
635
+ code: 'callback_validated',
636
+ title: 'Authorization callback validated',
637
+ detail: 'Authorization code and state semantics look valid.',
638
+ specReference: SPEC_BASE
639
+ });
640
+ markStepCompleted(session, stepId, 'Callback validation passed');
641
+ }
642
+ async function stepTokenExchange(session) {
643
+ const stepId = 'token_exchange';
644
+ markStepStarted(session, stepId);
645
+ const tokenEndpoint = session.context.authServerMetadata?.token_endpoint;
646
+ if (!tokenEndpoint)
647
+ throw new Error('Token endpoint not resolved');
648
+ const client = session.context.resolvedClient;
649
+ if (!client?.clientId)
650
+ throw new Error('Client not resolved');
651
+ const cb = session.context.callbackResult;
652
+ if (!cb?.code)
653
+ throw new Error('Authorization code missing');
654
+ const redirectUri = requiredString(session.context.callbackUrl, 'Callback URL not set');
655
+ const form = new URLSearchParams({
656
+ grant_type: 'authorization_code',
657
+ code: cb.code,
658
+ redirect_uri: redirectUri,
659
+ client_id: client.clientId
660
+ });
661
+ if (session.config.runtime.usePkce && session.context.pkce?.verifier) {
662
+ form.set('code_verifier', session.context.pkce.verifier);
663
+ }
664
+ if (session.config.runtime.resource)
665
+ form.set('resource', session.config.runtime.resource);
666
+ const headers = {
667
+ 'content-type': 'application/x-www-form-urlencoded',
668
+ accept: 'application/json'
669
+ };
670
+ if (client.clientSecret) {
671
+ const authMethod = client.tokenEndpointAuthMethod ?? 'client_secret_basic';
672
+ if (authMethod === 'client_secret_post') {
673
+ form.set('client_secret', client.clientSecret);
674
+ }
675
+ else {
676
+ headers.authorization = `Basic ${Buffer.from(`${client.clientId}:${client.clientSecret}`, 'utf8').toString('base64')}`;
677
+ }
678
+ }
679
+ const { response, responseJson, responseText } = await fetchWithTrace({
680
+ session,
681
+ stepId,
682
+ label: 'Token request',
683
+ url: String(tokenEndpoint),
684
+ method: 'POST',
685
+ headers,
686
+ bodyText: form.toString()
687
+ });
688
+ session.context.tokenResponse = responseJson ?? { raw: responseText, status: response.status };
689
+ if (!response.ok) {
690
+ throw new Error(`Token exchange failed (${response.status})`);
691
+ }
692
+ markStepCompleted(session, stepId, `Token response HTTP ${response.status}`);
693
+ }
694
+ function stepTokenValidation(session) {
695
+ const stepId = 'token_validation';
696
+ markStepStarted(session, stepId);
697
+ const token = session.context.tokenResponse;
698
+ if (!token || typeof token !== 'object')
699
+ throw new Error('Token response missing');
700
+ if (!('access_token' in token)) {
701
+ addValidation(session, {
702
+ stepId,
703
+ severity: 'error',
704
+ code: 'token_missing_access_token',
705
+ title: 'Token response missing access_token',
706
+ detail: 'Token endpoint response did not include access_token.',
707
+ recommendation: 'Inspect token endpoint response and OAuth server configuration.'
708
+ });
709
+ throw new Error('Token response missing access_token');
710
+ }
711
+ if (!('token_type' in token)) {
712
+ addValidation(session, {
713
+ stepId,
714
+ severity: 'warning',
715
+ code: 'token_missing_token_type',
716
+ title: 'Token response missing token_type',
717
+ detail: 'Token response did not include token_type.',
718
+ recommendation: 'Most clients expect token_type (typically Bearer).'
719
+ });
720
+ }
721
+ addValidation(session, {
722
+ stepId,
723
+ severity: 'info',
724
+ code: 'token_response_validated',
725
+ title: 'Token response validated',
726
+ detail: 'Token response includes access_token and basic fields.',
727
+ specReference: SPEC_BASE
728
+ });
729
+ markStepCompleted(session, stepId, 'Token validation complete');
730
+ }
731
+ async function stepResourceProbe(session) {
732
+ const stepId = 'resource_probe';
733
+ markStepStarted(session, stepId);
734
+ const accessToken = typeof session.context.tokenResponse?.access_token === 'string'
735
+ ? session.context.tokenResponse.access_token
736
+ : undefined;
737
+ const probeUrl = session.config.target.overrides?.resourceBaseUrl || session.serverConfig?.url;
738
+ if (!accessToken || !probeUrl) {
739
+ markStepSkipped(session, stepId, 'No access token or probe URL available');
740
+ emitEvent(session, 'log', {
741
+ message: 'Skipping protected probe (missing access token or probe URL)'
742
+ });
743
+ return;
744
+ }
745
+ try {
746
+ const { response, responseText } = await fetchWithTrace({
747
+ session,
748
+ stepId,
749
+ label: 'Protected resource probe',
750
+ url: probeUrl,
751
+ headers: {
752
+ authorization: `Bearer ${accessToken}`,
753
+ accept: 'application/json'
754
+ }
755
+ });
756
+ session.context.probeResponse = {
757
+ status: response.status,
758
+ bodyText: responseText,
759
+ url: probeUrl
760
+ };
761
+ if (!response.ok) {
762
+ addValidation(session, {
763
+ stepId,
764
+ severity: 'warning',
765
+ code: 'probe_not_ok',
766
+ title: 'Protected probe returned non-success',
767
+ detail: `Protected probe returned HTTP ${response.status}.`,
768
+ recommendation: 'Verify audience/resource, scopes, and token issuer expectations on the MCP server.'
769
+ });
770
+ }
771
+ else {
772
+ addValidation(session, {
773
+ stepId,
774
+ severity: 'info',
775
+ code: 'probe_ok',
776
+ title: 'Protected probe succeeded',
777
+ detail: 'The bearer token was accepted by the probe endpoint.'
778
+ });
779
+ }
780
+ markStepCompleted(session, stepId, `Probe HTTP ${response.status}`);
781
+ }
782
+ catch (error) {
783
+ addValidation(session, {
784
+ stepId,
785
+ severity: 'warning',
786
+ code: 'probe_failed',
787
+ title: 'Protected probe failed',
788
+ detail: error instanceof Error ? error.message : String(error),
789
+ recommendation: 'If this endpoint is not a protected HTTP resource, ignore this warning or override resourceBaseUrl.'
790
+ });
791
+ markStepCompleted(session, stepId, 'Probe failed (captured as warning)');
792
+ }
793
+ }
794
+ function stepSummary(session) {
795
+ const stepId = 'summary';
796
+ markStepStarted(session, stepId);
797
+ const hasErrors = session.validations.some((v) => v.severity === 'error');
798
+ if (hasErrors) {
799
+ addValidation(session, {
800
+ stepId,
801
+ severity: 'info',
802
+ code: 'summary_failed',
803
+ title: 'OAuth debugger summary',
804
+ detail: 'One or more blocking validation errors were found.',
805
+ recommendation: 'Review failed steps and network exchanges to identify the protocol mismatch.'
806
+ });
807
+ }
808
+ else {
809
+ addValidation(session, {
810
+ stepId,
811
+ severity: 'info',
812
+ code: 'summary_ok',
813
+ title: 'OAuth debugger summary',
814
+ detail: 'Flow completed without blocking validation errors.'
815
+ });
816
+ }
817
+ markStepCompleted(session, stepId, hasErrors ? 'Completed with validation errors' : 'Completed successfully');
818
+ }
819
+ function resetPendingStepStatesForResume(session) {
820
+ // no-op for now; step runner checks status
821
+ }
822
+ function nextPendingStep(session) {
823
+ return session.steps.find((s) => s.status === 'pending');
824
+ }
825
+ export function cleanupOAuthDebuggerSessions(sessions, now = Date.now()) {
826
+ for (const [id, session] of sessions) {
827
+ if (now - session.updatedAt > SESSION_TTL_MS) {
828
+ sessions.delete(id);
829
+ }
830
+ }
831
+ }
832
+ export function createOAuthDebuggerSession(params) {
833
+ const runtime = normalizeRuntime(params.config.runtime);
834
+ const serverOauth = params.serverConfig?.auth?.type === 'oauth_authorization_code'
835
+ ? params.serverConfig.auth
836
+ : undefined;
837
+ const clientConfig = params.config.registrationMethod === 'pre_registered'
838
+ ? {
839
+ ...params.config.clientConfig,
840
+ preRegistered: {
841
+ clientId: params.config.clientConfig.preRegistered?.clientId || serverOauth?.client_id || '',
842
+ clientSecret: params.config.clientConfig.preRegistered?.clientSecret ?? serverOauth?.client_secret,
843
+ tokenEndpointAuthMethod: params.config.clientConfig.preRegistered?.tokenEndpointAuthMethod
844
+ }
845
+ }
846
+ : params.config.clientConfig;
847
+ const session = {
848
+ id: makeId('oauthdbg'),
849
+ createdAt: Date.now(),
850
+ updatedAt: Date.now(),
851
+ status: 'configuring',
852
+ config: {
853
+ profile: 'latest',
854
+ target: params.config.target,
855
+ registrationMethod: params.config.registrationMethod,
856
+ clientConfig,
857
+ runtime: {
858
+ ...runtime,
859
+ scopes: runtime.scopes && runtime.scopes.length > 0
860
+ ? runtime.scopes
861
+ : serverOauth?.scope
862
+ ? serverOauth.scope.split(/\s+/).filter(Boolean)
863
+ : []
864
+ },
865
+ display: {
866
+ showSensitiveValues: params.config.display?.showSensitiveValues !== false
867
+ }
868
+ },
869
+ steps: baseSteps(params.config.registrationMethod),
870
+ validations: [],
871
+ network: [],
872
+ sequence: [],
873
+ events: [],
874
+ clients: new Set(),
875
+ abortController: new AbortController(),
876
+ serverConfig: params.serverConfig,
877
+ context: {}
878
+ };
879
+ return session;
880
+ }
881
+ export function oauthDebuggerSessionView(session) {
882
+ const errorCount = session.network.filter((n) => n.phase === 'response' && typeof n.status === 'number' && n.status >= 400).length;
883
+ const token = session.context.tokenResponse && typeof session.context.tokenResponse === 'object'
884
+ ? session.context.tokenResponse
885
+ : undefined;
886
+ return {
887
+ id: session.id,
888
+ status: session.status,
889
+ createdAt: new Date(session.createdAt).toISOString(),
890
+ updatedAt: new Date(session.updatedAt).toISOString(),
891
+ profile: session.config.profile,
892
+ registrationMethod: session.config.registrationMethod,
893
+ stepStates: session.steps,
894
+ validations: session.validations,
895
+ network: session.network,
896
+ networkSummary: {
897
+ requestCount: session.network.filter((n) => n.phase === 'request').length,
898
+ errorCount
899
+ },
900
+ sequence: session.sequence,
901
+ uiHints: {
902
+ nextAction: session.status === 'configuring'
903
+ ? 'start'
904
+ : session.status === 'waiting_for_user'
905
+ ? 'paste_callback_url'
906
+ : session.status === 'waiting_for_browser_callback'
907
+ ? 'open_authorize_url'
908
+ : 'none',
909
+ authorizationUrl: session.context.authorizationRequestUrl,
910
+ callbackMode: session.config.runtime.redirectMode,
911
+ callbackUrl: session.context.callbackUrl
912
+ },
913
+ summary: {
914
+ issuer: session.context.authServerMetadata?.issuer ?? session.context.resourceMetadata?.issuer,
915
+ clientId: session.context.resolvedClient?.clientId,
916
+ redirectUri: session.context.callbackUrl,
917
+ tokenEndpointStatus: session.network
918
+ .filter((n) => n.label === 'Token request' && n.phase === 'response')
919
+ .slice(-1)[0]?.status,
920
+ tokenType: typeof token?.token_type === 'string' ? token.token_type : undefined,
921
+ grantedScopes: typeof token?.scope === 'string'
922
+ ? String(token.scope).split(/\s+/).filter(Boolean)
923
+ : undefined
924
+ }
925
+ };
926
+ }
927
+ export async function startOrResumeOAuthDebuggerSession(params) {
928
+ const { session, appBaseUrl } = params;
929
+ if (session.status === 'stopped')
930
+ throw new Error('Session already stopped');
931
+ session.updatedAt = Date.now();
932
+ session.context.callbackUrl = localCallbackUrl(session, appBaseUrl);
933
+ if (session.status === 'configuring') {
934
+ emitEvent(session, 'started', {
935
+ sessionId: session.id,
936
+ registrationMethod: session.config.registrationMethod,
937
+ profile: session.config.profile
938
+ });
939
+ }
940
+ session.status = 'running';
941
+ resetPendingStepStatesForResume(session);
942
+ while (true) {
943
+ if (session.abortController.signal.aborted) {
944
+ session.status = 'stopped';
945
+ emitEvent(session, 'stopped', { message: 'OAuth debug session stopped by user' });
946
+ return;
947
+ }
948
+ const pending = nextPendingStep(session);
949
+ if (!pending) {
950
+ session.status = 'completed';
951
+ emitEvent(session, 'completed', {
952
+ summary: oauthDebuggerSessionView(session).summary ?? null
953
+ });
954
+ return;
955
+ }
956
+ try {
957
+ switch (pending.id) {
958
+ case 'resolve_target_metadata':
959
+ await stepResolveTargetMetadata(session);
960
+ break;
961
+ case 'resolve_registration_source':
962
+ await stepResolveRegistrationSource(session);
963
+ break;
964
+ case 'fetch_cimd':
965
+ await stepFetchCimd(session);
966
+ break;
967
+ case 'dynamic_client_registration':
968
+ await stepDcr(session);
969
+ break;
970
+ case 'build_authorization_request':
971
+ await stepBuildAuthorizationRequest(session);
972
+ break;
973
+ case 'browser_authorization':
974
+ await stepBrowserAuthorizationPause(session);
975
+ return;
976
+ case 'receive_authorization_response':
977
+ if (!session.context.callbackResult) {
978
+ session.status =
979
+ session.config.runtime.redirectMode === 'manual'
980
+ ? 'waiting_for_user'
981
+ : 'waiting_for_browser_callback';
982
+ return;
983
+ }
984
+ stepReceiveAuthorizationResponse(session);
985
+ break;
986
+ case 'validate_callback':
987
+ stepValidateCallback(session);
988
+ break;
989
+ case 'token_exchange':
990
+ await stepTokenExchange(session);
991
+ break;
992
+ case 'token_validation':
993
+ stepTokenValidation(session);
994
+ break;
995
+ case 'resource_probe':
996
+ await stepResourceProbe(session);
997
+ break;
998
+ case 'summary':
999
+ stepSummary(session);
1000
+ break;
1001
+ default:
1002
+ markStepSkipped(session, pending.id, 'Unsupported step in v1');
1003
+ }
1004
+ }
1005
+ catch (error) {
1006
+ const message = error instanceof Error ? error.message : String(error);
1007
+ markStepFailed(session, pending.id, message);
1008
+ session.status = 'error';
1009
+ emitEvent(session, 'error', { message });
1010
+ return;
1011
+ }
1012
+ }
1013
+ }
1014
+ export function submitManualCallbackToSession(params) {
1015
+ const parsed = parseCallbackInput({
1016
+ redirectUrl: params.redirectUrl,
1017
+ code: params.code,
1018
+ state: params.state
1019
+ });
1020
+ params.session.context.callbackResult = parsed;
1021
+ params.session.updatedAt = Date.now();
1022
+ emitEvent(params.session, 'log', {
1023
+ message: 'Manual callback input received.'
1024
+ });
1025
+ }
1026
+ export function submitBrowserCallbackToSession(params) {
1027
+ params.session.context.callbackResult = parseCallbackInput({ redirectUrl: params.rawUrl });
1028
+ params.session.updatedAt = Date.now();
1029
+ emitEvent(params.session, 'log', { message: 'Browser callback captured.' });
1030
+ }
1031
+ export function stopOAuthDebuggerSession(session) {
1032
+ if (session.status === 'running' ||
1033
+ session.status === 'waiting_for_user' ||
1034
+ session.status === 'waiting_for_browser_callback') {
1035
+ session.abortController.abort();
1036
+ session.status = 'stopped';
1037
+ session.updatedAt = Date.now();
1038
+ emitEvent(session, 'stopped', { message: 'Stop requested by user' });
1039
+ }
1040
+ }
1041
+ export function oauthDebuggerExportMarkdown(session) {
1042
+ const view = oauthDebuggerSessionView(session);
1043
+ const lines = [];
1044
+ lines.push('# OAuth Debugger Report');
1045
+ lines.push('');
1046
+ lines.push(`- Session ID: ${view.id}`);
1047
+ lines.push(`- Status: ${view.status}`);
1048
+ lines.push(`- Profile: ${view.profile}`);
1049
+ lines.push(`- Registration method: ${view.registrationMethod}`);
1050
+ lines.push(`- Target MCP server: ${session.config.target.serverName}`);
1051
+ lines.push('');
1052
+ lines.push('## Steps');
1053
+ for (const s of view.stepStates) {
1054
+ lines.push(`- ${s.title}: ${s.status}${s.outcomeSummary ? ` — ${s.outcomeSummary}` : ''}`);
1055
+ }
1056
+ lines.push('');
1057
+ lines.push('## Findings');
1058
+ if (view.validations.length === 0) {
1059
+ lines.push('- No validation findings recorded.');
1060
+ }
1061
+ else {
1062
+ for (const v of view.validations) {
1063
+ lines.push(`- [${v.severity}] (${v.stepId}) ${v.title}: ${v.detail}`);
1064
+ if (v.recommendation)
1065
+ lines.push(` - Recommendation: ${v.recommendation}`);
1066
+ }
1067
+ }
1068
+ lines.push('');
1069
+ lines.push('## Network');
1070
+ for (const n of view.network.filter((e) => e.phase === 'response')) {
1071
+ lines.push(`- ${n.label}: ${n.status ?? '-'} ${n.url}`);
1072
+ }
1073
+ return `${lines.join('\n')}\n`;
1074
+ }
1075
+ export function oauthDebuggerExportRawTrace(session) {
1076
+ const lines = [];
1077
+ for (const ex of session.network) {
1078
+ if (ex.phase === 'request') {
1079
+ lines.push(`> ${ex.label}`);
1080
+ lines.push(`> ${ex.method ?? 'GET'} ${ex.url}`);
1081
+ for (const [k, v] of Object.entries(ex.headers))
1082
+ lines.push(`> ${k}: ${v}`);
1083
+ if (ex.bodyText)
1084
+ lines.push(`>\n${ex.bodyText}`);
1085
+ }
1086
+ else {
1087
+ lines.push(`< ${ex.label}`);
1088
+ lines.push(`< ${ex.status ?? '-'} ${ex.url}`);
1089
+ for (const [k, v] of Object.entries(ex.headers))
1090
+ lines.push(`< ${k}: ${v}`);
1091
+ if (ex.bodyText)
1092
+ lines.push(`<\n${ex.bodyText}`);
1093
+ }
1094
+ lines.push('');
1095
+ }
1096
+ return `${lines.join('\n')}\n`;
1097
+ }
1098
+ //# sourceMappingURL=oauth-debugger-domain.js.map