@kya-os/mcp-i-core 1.3.10-canary.clientinfo.20251126124133 → 1.3.11

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 (90) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-test$colon$coverage.log +3419 -3072
  4. package/.turbo/turbo-test.log +1805 -1680
  5. package/coverage/coverage-final.json +59 -56
  6. package/dist/config/remote-config.d.ts +51 -0
  7. package/dist/config/remote-config.d.ts.map +1 -1
  8. package/dist/config/remote-config.js +74 -0
  9. package/dist/config/remote-config.js.map +1 -1
  10. package/dist/config.d.ts +1 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +4 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/delegation/did-key-resolver.d.ts +64 -0
  15. package/dist/delegation/did-key-resolver.d.ts.map +1 -0
  16. package/dist/delegation/did-key-resolver.js +159 -0
  17. package/dist/delegation/did-key-resolver.js.map +1 -0
  18. package/dist/delegation/utils.d.ts +76 -0
  19. package/dist/delegation/utils.d.ts.map +1 -1
  20. package/dist/delegation/utils.js +117 -0
  21. package/dist/delegation/utils.js.map +1 -1
  22. package/dist/identity/idp-token-resolver.d.ts +17 -1
  23. package/dist/identity/idp-token-resolver.d.ts.map +1 -1
  24. package/dist/identity/idp-token-resolver.js +34 -6
  25. package/dist/identity/idp-token-resolver.js.map +1 -1
  26. package/dist/identity/idp-token-storage.interface.d.ts +38 -7
  27. package/dist/identity/idp-token-storage.interface.d.ts.map +1 -1
  28. package/dist/identity/idp-token-storage.interface.js +2 -0
  29. package/dist/identity/idp-token-storage.interface.js.map +1 -1
  30. package/dist/identity/user-did-manager.d.ts +95 -12
  31. package/dist/identity/user-did-manager.d.ts.map +1 -1
  32. package/dist/identity/user-did-manager.js +107 -25
  33. package/dist/identity/user-did-manager.js.map +1 -1
  34. package/dist/index.d.ts +6 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +24 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/runtime/base.d.ts +25 -8
  39. package/dist/runtime/base.d.ts.map +1 -1
  40. package/dist/runtime/base.js +74 -21
  41. package/dist/runtime/base.js.map +1 -1
  42. package/dist/services/session-registration.service.d.ts.map +1 -1
  43. package/dist/services/session-registration.service.js +10 -90
  44. package/dist/services/session-registration.service.js.map +1 -1
  45. package/dist/services/tool-context-builder.d.ts +18 -1
  46. package/dist/services/tool-context-builder.d.ts.map +1 -1
  47. package/dist/services/tool-context-builder.js +63 -10
  48. package/dist/services/tool-context-builder.js.map +1 -1
  49. package/dist/services/tool-protection.service.d.ts +6 -3
  50. package/dist/services/tool-protection.service.d.ts.map +1 -1
  51. package/dist/services/tool-protection.service.js +89 -34
  52. package/dist/services/tool-protection.service.js.map +1 -1
  53. package/dist/utils/base58.d.ts +31 -0
  54. package/dist/utils/base58.d.ts.map +1 -0
  55. package/dist/utils/base58.js +103 -0
  56. package/dist/utils/base58.js.map +1 -0
  57. package/dist/utils/did-helpers.d.ts +33 -0
  58. package/dist/utils/did-helpers.d.ts.map +1 -1
  59. package/dist/utils/did-helpers.js +53 -0
  60. package/dist/utils/did-helpers.js.map +1 -1
  61. package/package.json +3 -3
  62. package/src/__tests__/identity/user-did-manager.test.ts +64 -45
  63. package/src/__tests__/integration/full-flow.test.ts +23 -10
  64. package/src/__tests__/runtime/base-extensions.test.ts +23 -21
  65. package/src/__tests__/runtime/proof-client-did.test.ts +19 -18
  66. package/src/__tests__/services/agentshield-integration.test.ts +10 -3
  67. package/src/__tests__/services/tool-protection-merged-config.test.ts +485 -0
  68. package/src/__tests__/services/tool-protection.service.test.ts +18 -11
  69. package/src/config/__tests__/merged-config.spec.ts +445 -0
  70. package/src/config/remote-config.ts +90 -0
  71. package/src/config.ts +3 -0
  72. package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
  73. package/src/delegation/__tests__/vc-issuer.test.ts +1 -1
  74. package/src/delegation/did-key-resolver.ts +179 -0
  75. package/src/delegation/utils.ts +179 -0
  76. package/src/identity/idp-token-resolver.ts +41 -7
  77. package/src/identity/idp-token-storage.interface.ts +42 -7
  78. package/src/identity/user-did-manager.ts +185 -29
  79. package/src/index.ts +42 -3
  80. package/src/runtime/base.ts +84 -21
  81. package/src/services/session-registration.service.ts +26 -121
  82. package/src/services/tool-context-builder.ts +75 -10
  83. package/src/services/tool-protection.service.ts +176 -88
  84. package/src/utils/__tests__/did-helpers.test.ts +55 -0
  85. package/src/utils/base58.ts +109 -0
  86. package/src/utils/did-helpers.ts +60 -0
  87. package/dist/__tests__/utils/mock-providers.d.ts +0 -103
  88. package/dist/__tests__/utils/mock-providers.d.ts.map +0 -1
  89. package/dist/__tests__/utils/mock-providers.js +0 -293
  90. package/dist/__tests__/utils/mock-providers.js.map +0 -1
@@ -130,19 +130,16 @@ export class MCPIRuntimeBase {
130
130
  return this.cachedIdentity;
131
131
  }
132
132
 
133
- /**
134
- * Handle handshake request
135
- */
136
133
  /**
137
134
  * Handle MCP handshake request
138
135
  *
136
+ * Phase 5: Anonymous Sessions Until OAuth
137
+ * - Sessions start anonymous (no userDid) unless OAuth identity provided
138
+ * - User DID is resolved via AgentShield after OAuth completes
139
+ * - Eliminates DID fragmentation (same user = same DID across sessions)
140
+ *
139
141
  * @param request - Handshake request object (may include oauthIdentity for persistent user DID lookup)
140
142
  * @returns Handshake response with session ID and agent DID
141
- *
142
- * @remarks
143
- * - Accepts optional oauthIdentity via request.oauthIdentity (backward compatible)
144
- * - If OAuth identity provided, uses it to retrieve/create persistent user DID
145
- * - Falls back to ephemeral user DID generation if OAuth unavailable
146
143
  */
147
144
  async handleHandshake(
148
145
  request: any & {
@@ -155,26 +152,36 @@ export class MCPIRuntimeBase {
155
152
  const timestamp = this.clock.now();
156
153
  const sessionId = await this.generateSessionId();
157
154
 
158
- // Generate user DID if user DID generation is enabled
159
- // Use OAuth identity if provided for persistent user DID lookup
155
+ // Phase 5: Try to resolve user DID from existing OAuth mapping
156
+ // Sessions start anonymous - no ephemeral generation
160
157
  let userDid: string | undefined;
161
158
  if (this.userDidManager) {
162
159
  try {
163
160
  const oauthIdentity = request.oauthIdentity;
164
- userDid = await this.userDidManager.getOrCreateUserDid(
161
+ const resolvedDid = await this.userDidManager.getOrCreateUserDid(
165
162
  sessionId,
166
163
  oauthIdentity
167
164
  );
165
+ // Convert null to undefined for session storage
166
+ userDid = resolvedDid ?? undefined;
167
+
168
168
  if (this.config.audit?.enabled) {
169
- console.log("[MCP-I] Generated user DID for session:", {
170
- userDid: userDid.substring(0, 20) + "...",
171
- hasOAuth: !!oauthIdentity,
172
- provider: oauthIdentity?.provider,
173
- });
169
+ if (userDid) {
170
+ console.log("[MCP-I] Resolved existing user DID for session:", {
171
+ userDid: userDid.substring(0, 20) + "...",
172
+ hasOAuth: !!oauthIdentity,
173
+ provider: oauthIdentity?.provider,
174
+ });
175
+ } else {
176
+ console.log("[MCP-I] Session started anonymous (no userDid):", {
177
+ sessionId: sessionId.substring(0, 8) + "...",
178
+ hasOAuth: !!oauthIdentity,
179
+ });
180
+ }
174
181
  }
175
182
  } catch (error) {
176
- console.warn("[MCP-I] Failed to generate user DID:", error);
177
- // Continue without user DID - not critical
183
+ console.warn("[MCP-I] Failed to resolve user DID:", error);
184
+ // Continue without user DID - session is anonymous
178
185
  }
179
186
  }
180
187
 
@@ -211,11 +218,11 @@ export class MCPIRuntimeBase {
211
218
  }
212
219
  : undefined;
213
220
 
214
- // Create session
221
+ // Create session with Phase 5 identity state
215
222
  const session = {
216
223
  id: sessionId,
217
224
  clientDid: request.clientDid || userDid, // Use provided clientDid or generated userDid
218
- userDid: userDid, // Store generated user DID separately
225
+ userDid: userDid, // Store user DID (may be undefined for anonymous sessions)
219
226
  agentDid: request.agentDid, // ✅ FIXED: Only agent DID, no fallback
220
227
  serverDid: identity.did, // ✅ NEW: Server's DID (for clarity)
221
228
  audience: request.audience,
@@ -223,7 +230,10 @@ export class MCPIRuntimeBase {
223
230
  expiresAt: this.clock.calculateExpiry(
224
231
  (this.config.session?.ttlMinutes || 30) * 60
225
232
  ),
226
- clientInfo, // NEW: Store client information
233
+ clientInfo, // Store client information
234
+ // Phase 5: Identity state tracking
235
+ identityState: userDid ? "authenticated" : "anonymous",
236
+ oauthIdentity: request.oauthIdentity ?? undefined,
227
237
  };
228
238
 
229
239
  this.sessions.set(sessionId, session);
@@ -246,6 +256,59 @@ export class MCPIRuntimeBase {
246
256
  };
247
257
  }
248
258
 
259
+ /**
260
+ * Update session identity after OAuth resolution (Phase 5)
261
+ *
262
+ * Called after AgentShield identity/resolve returns a persistent user DID.
263
+ * Updates the session to authenticated state with the resolved DID.
264
+ *
265
+ * @param sessionId - MCP session ID
266
+ * @param userDid - Persistent user DID from AgentShield
267
+ * @param oauthIdentity - OAuth identity information
268
+ * @throws Error if session not found
269
+ */
270
+ async updateSessionIdentity(
271
+ sessionId: string,
272
+ userDid: string,
273
+ oauthIdentity?: { provider: string; subject: string; email?: string }
274
+ ): Promise<void> {
275
+ const session = this.sessions.get(sessionId);
276
+ if (!session) {
277
+ throw new Error(`Session not found: ${sessionId}`);
278
+ }
279
+
280
+ // Update session with resolved identity
281
+ session.userDid = userDid;
282
+ session.identityState = "authenticated";
283
+ if (oauthIdentity) {
284
+ session.oauthIdentity = oauthIdentity;
285
+ }
286
+
287
+ // Update the sessions map
288
+ this.sessions.set(sessionId, session);
289
+
290
+ // Also update UserDidManager cache if available
291
+ if (this.userDidManager) {
292
+ await this.userDidManager.setUserDidForSession(sessionId, userDid, oauthIdentity);
293
+ }
294
+
295
+ if (this.config.audit?.enabled) {
296
+ console.log("[MCP-I] Session identity updated (Phase 5):", {
297
+ sessionId: sessionId.substring(0, 8) + "...",
298
+ userDid: userDid.substring(0, 20) + "...",
299
+ provider: oauthIdentity?.provider,
300
+ identityState: "authenticated",
301
+ });
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Get session by ID
307
+ */
308
+ getSession(sessionId: string): any | undefined {
309
+ return this.sessions.get(sessionId);
310
+ }
311
+
249
312
  /**
250
313
  * Process tool call with automatic proof generation
251
314
  * Returns clean result only - proof is stored for out-of-band retrieval
@@ -109,55 +109,9 @@ export class SessionRegistrationService {
109
109
  sessionId,
110
110
  agentDid: request.agent_did,
111
111
  clientName: request.client_info.name,
112
- clientVersion: request.client_info.version,
113
- hasClientIdentity: !!request.client_identity,
114
112
  url,
115
113
  });
116
114
 
117
- // ✅ EMPIRICAL PROOF: Prepare request headers
118
- // Try Authorization: Bearer first (same as proofs endpoint)
119
- // If that fails, AgentShield may need X-AgentShield-Key instead
120
- const requestHeaders = {
121
- "Content-Type": "application/json",
122
- Authorization: `Bearer ${this.config.apiKey}`, // Use same auth as proofs endpoint
123
- };
124
-
125
- // ✅ EMPIRICAL PROOF: Log exact request details (sanitized for security)
126
- const sanitizedHeaders = {
127
- ...requestHeaders,
128
- Authorization: `Bearer ${this.config.apiKey.slice(0, 8)}...${this.config.apiKey.slice(-4)}`, // Show first 8 and last 4 chars
129
- };
130
-
131
- const sanitizedBody = {
132
- session_id: request.session_id,
133
- agent_did: request.agent_did,
134
- project_id: request.project_id,
135
- created_at: request.created_at,
136
- client_info: request.client_info,
137
- client_identity: request.client_identity,
138
- server_did: request.server_did,
139
- ttl_minutes: request.ttl_minutes,
140
- };
141
-
142
- this.config.logger(
143
- "[SessionRegistration] 🔍 EMPIRICAL DEBUG - Request details",
144
- {
145
- url,
146
- method: "POST",
147
- headers: sanitizedHeaders,
148
- headerKeys: Object.keys(requestHeaders),
149
- authHeaderPresent: !!requestHeaders["Authorization"],
150
- authHeaderName: "Authorization",
151
- authHeaderFormat: requestHeaders["Authorization"]?.startsWith(
152
- "Bearer "
153
- )
154
- ? "Bearer"
155
- : "Other",
156
- body: sanitizedBody,
157
- bodySize: JSON.stringify(request).length,
158
- }
159
- );
160
-
161
115
  // Make the request with timeout
162
116
  const controller = new AbortController();
163
117
  const timeoutId = setTimeout(
@@ -168,89 +122,47 @@ export class SessionRegistrationService {
168
122
  try {
169
123
  const response = await this.config.fetchProvider.fetch(url, {
170
124
  method: "POST",
171
- headers: requestHeaders,
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ Authorization: `Bearer ${this.config.apiKey}`,
128
+ },
172
129
  body: JSON.stringify(request),
173
130
  signal: controller.signal,
174
131
  });
175
132
 
176
133
  clearTimeout(timeoutId);
177
134
 
178
- // ✅ EMPIRICAL PROOF: Capture exact response details
179
- const responseHeaders: Record<string, string> = {};
180
- response.headers.forEach((value, key) => {
181
- responseHeaders[key] = value;
182
- });
183
-
184
- const responseText = await response
185
- .text()
186
- .catch(() => "Failed to read response");
187
- let responseBody: unknown;
188
- try {
189
- responseBody = JSON.parse(responseText);
190
- } catch {
191
- responseBody = responseText;
192
- }
193
-
194
- // ✅ EMPIRICAL PROOF: Log exact response details
195
- this.config.logger(
196
- "[SessionRegistration] 🔍 EMPIRICAL DEBUG - Response details",
197
- {
198
- status: response.status,
199
- statusText: response.statusText,
200
- ok: response.ok,
201
- headers: responseHeaders,
202
- body: responseBody,
203
- bodyLength: responseText.length,
204
- clientName: request.client_info.name,
205
- // ✅ DEBUG: Include request ID from response headers for AgentShield support
206
- requestId:
207
- responseHeaders["x-request-id"] ||
208
- responseHeaders["X-Request-ID"] ||
209
- "not-provided",
210
- // ✅ DEBUG: Full response text for AgentShield team
211
- fullResponseText: responseText,
212
- }
213
- );
214
-
215
135
  if (!response.ok) {
216
136
  // Log error but don't throw - this is fire-and-forget
137
+ const errorText = await response.text().catch(() => "Unknown error");
217
138
  this.config.logger("[SessionRegistration] Registration failed", {
218
139
  sessionId,
219
140
  status: response.status,
220
- error: responseText,
221
- // ✅ DEBUG: EMPIRICAL: Include full response details
222
- responseHeaders,
223
- responseBody,
224
- clientName: request.client_info.name,
225
- // ✅ DEBUG: Request ID for AgentShield support ticket
226
- requestId:
227
- responseHeaders["x-request-id"] ||
228
- responseHeaders["X-Request-ID"] ||
229
- "not-provided",
230
- // ✅ DEBUG: Full request body for AgentShield team
231
- fullRequestBody: JSON.stringify(request, null, 2),
141
+ error: errorText,
232
142
  });
233
143
  return {
234
144
  success: false,
235
145
  sessionId,
236
- error: `HTTP ${response.status}: ${responseText}`,
146
+ error: `HTTP ${response.status}: ${errorText}`,
237
147
  };
238
148
  }
239
149
 
240
- // Parse response (using already-captured responseBody)
241
- const responseData = responseBody as
242
- | { data?: RegisterSessionResponse }
243
- | RegisterSessionResponse;
150
+ // Parse response
151
+ const responseData = (await response.json()) as {
152
+ data?: RegisterSessionResponse;
153
+ } & RegisterSessionResponse;
244
154
  const parseResult = registerSessionResponseSchema.safeParse(
245
- (responseData as { data?: RegisterSessionResponse }).data ||
246
- responseData
155
+ responseData.data || responseData
247
156
  );
248
157
 
249
158
  if (!parseResult.success) {
250
- this.config.logger("[SessionRegistration] Invalid response format", {
251
- sessionId,
252
- response: responseData,
253
- });
159
+ this.config.logger(
160
+ "[SessionRegistration] Invalid response format",
161
+ {
162
+ sessionId,
163
+ response: responseData,
164
+ }
165
+ );
254
166
  // Still consider it a success if we got a 200 OK
255
167
  return { success: true, sessionId };
256
168
  }
@@ -258,12 +170,6 @@ export class SessionRegistrationService {
258
170
  this.config.logger("[SessionRegistration] Session registered", {
259
171
  sessionId,
260
172
  registered: parseResult.data.registered,
261
- // ✅ DEBUG: Include full response for AgentShield debugging
262
- responseData: parseResult.data,
263
- requestId:
264
- responseHeaders["x-request-id"] ||
265
- responseHeaders["X-Request-ID"] ||
266
- "not-provided",
267
173
  });
268
174
 
269
175
  return { success: true, sessionId };
@@ -281,7 +187,8 @@ export class SessionRegistrationService {
281
187
  }
282
188
 
283
189
  // Log any other error
284
- const errorMsg = error instanceof Error ? error.message : "Unknown error";
190
+ const errorMsg =
191
+ error instanceof Error ? error.message : "Unknown error";
285
192
  this.config.logger("[SessionRegistration] Unexpected error", {
286
193
  sessionId,
287
194
  error: errorMsg,
@@ -303,13 +210,10 @@ export class SessionRegistrationService {
303
210
  this.registerSession(request).catch((error) => {
304
211
  // This should never happen since registerSession catches all errors,
305
212
  // but just in case
306
- this.config.logger(
307
- "[SessionRegistration] Background registration failed",
308
- {
309
- sessionId: request.session_id,
310
- error: error instanceof Error ? error.message : "Unknown error",
311
- }
312
- );
213
+ this.config.logger("[SessionRegistration] Background registration failed", {
214
+ sessionId: request.session_id,
215
+ error: error instanceof Error ? error.message : "Unknown error",
216
+ });
313
217
  });
314
218
  }
315
219
  }
@@ -344,3 +248,4 @@ export function createSessionRegistrationService(options: {
344
248
  logger: options.logger,
345
249
  });
346
250
  }
251
+
@@ -4,11 +4,15 @@
4
4
  * Builds ToolExecutionContext for tool handlers by resolving IDP tokens
5
5
  * based on tool protection configuration and user identity.
6
6
  *
7
+ * Updated for CRED-003: Builds idpHeaders based on tokenUsage metadata
8
+ * to support credential providers with custom token usage patterns.
9
+ *
7
10
  * @package @kya-os/mcp-i-core
8
11
  */
9
12
 
10
13
  import type { ToolExecutionContext } from "@kya-os/contracts/config";
11
14
  import type { IdpTokenResolver } from "../identity/idp-token-resolver.js";
15
+ import type { IdpTokensWithMetadata } from "../identity/idp-token-storage.interface.js";
12
16
  import type { ToolProtection } from "../types/tool-protection.js";
13
17
  import type { OAuthConfigService } from "./oauth-config.service.js";
14
18
  import type { ProviderResolver } from "./provider-resolver.js";
@@ -57,7 +61,7 @@ export class ToolContextBuilder {
57
61
  * Build tool execution context
58
62
  *
59
63
  * @param toolName - Name of the tool being executed
60
- * @param userDid - User DID (optional, required for OAuth)
64
+ * @param userDid - User DID (optional, required for OAuth/credentials)
61
65
  * @param sessionId - Session ID (optional)
62
66
  * @param delegationToken - Delegation token (optional)
63
67
  * @param toolProtection - Tool protection configuration (optional)
@@ -70,7 +74,7 @@ export class ToolContextBuilder {
70
74
  delegationToken: string | undefined,
71
75
  toolProtection: ToolProtection | null
72
76
  ): Promise<ToolExecutionContext | undefined> {
73
- // Only build context if tool requires OAuth
77
+ // Only build context if tool requires OAuth/credentials
74
78
  if (!toolProtection?.requiredScopes?.length || !userDid) {
75
79
  return undefined;
76
80
  }
@@ -90,15 +94,16 @@ export class ToolContextBuilder {
90
94
  return undefined;
91
95
  }
92
96
 
93
- // Resolve IDP token
94
- const idpToken = await this.config.tokenResolver.resolveTokenFromDid(
97
+ // CRED-003: Resolve full token data (not just access_token)
98
+ // This includes tokenUsage, cookieFormat, apiHeaders for credential providers
99
+ const tokenData = await this.config.tokenResolver.resolveTokenDataFromDid(
95
100
  userDid,
96
101
  provider,
97
102
  toolProtection.requiredScopes
98
103
  );
99
104
 
100
- if (!idpToken) {
101
- // Token not available - throw OAuthRequiredError to trigger OAuth flow
105
+ if (!tokenData) {
106
+ // Token not available - throw OAuthRequiredError to trigger auth flow
102
107
  this.config.logger("[ToolContextBuilder] Token not available, throwing OAuthRequiredError", {
103
108
  toolName,
104
109
  userDid: userDid.substring(0, 20) + "...",
@@ -107,7 +112,7 @@ export class ToolContextBuilder {
107
112
  });
108
113
 
109
114
  // Throw error with provider and scopes info
110
- // OAuth URL will be built by the Cloudflare layer (agent.ts)
115
+ // Auth URL will be built by the Cloudflare layer (agent.ts)
111
116
  throw new OAuthRequiredError({
112
117
  toolName,
113
118
  requiredScopes: toolProtection.requiredScopes,
@@ -118,9 +123,13 @@ export class ToolContextBuilder {
118
123
  });
119
124
  }
120
125
 
121
- // Build context with token
126
+ // CRED-003: Build headers based on tokenUsage
127
+ const idpHeaders = this.buildAuthHeaders(tokenData);
128
+
129
+ // Build context with token and headers
122
130
  const context: ToolExecutionContext = {
123
- idpToken,
131
+ idpToken: tokenData.access_token,
132
+ idpHeaders,
124
133
  provider,
125
134
  scopes: toolProtection.requiredScopes,
126
135
  userDid,
@@ -132,12 +141,68 @@ export class ToolContextBuilder {
132
141
  toolName,
133
142
  userDid: userDid.substring(0, 20) + "...",
134
143
  provider,
135
- hasToken: !!idpToken,
144
+ hasToken: !!tokenData.access_token,
145
+ tokenUsage: tokenData.tokenUsage,
146
+ headerKeys: Object.keys(idpHeaders),
136
147
  });
137
148
 
138
149
  return context;
139
150
  }
140
151
 
152
+ /**
153
+ * Build authentication headers based on token usage metadata (CRED-003)
154
+ *
155
+ * Supports three modes:
156
+ * - "cookie": Cookie header (with optional cookieFormat template)
157
+ * - "bearer": Authorization: Bearer xxx
158
+ * - "header": Custom header name
159
+ *
160
+ * Also includes any apiHeaders from provider config.
161
+ *
162
+ * @param tokenData - Token data with usage metadata
163
+ * @returns Headers object for API calls
164
+ */
165
+ private buildAuthHeaders(tokenData: IdpTokensWithMetadata): Record<string, string> {
166
+ const authHeaders: Record<string, string> = {};
167
+
168
+ const tokenUsage = tokenData.tokenUsage || "bearer"; // Default to bearer for OAuth
169
+ const token = tokenData.access_token;
170
+
171
+ switch (tokenUsage) {
172
+ case "cookie":
173
+ // Use cookieFormat if specified, otherwise send raw token
174
+ if (tokenData.cookieFormat) {
175
+ authHeaders["Cookie"] = tokenData.cookieFormat.replace(
176
+ /\{\{token\}\}/g,
177
+ token
178
+ );
179
+ } else {
180
+ authHeaders["Cookie"] = token;
181
+ }
182
+ break;
183
+
184
+ case "bearer":
185
+ authHeaders["Authorization"] = `Bearer ${token}`;
186
+ break;
187
+
188
+ case "header":
189
+ const headerName = tokenData.tokenHeader || "X-Session-Token";
190
+ authHeaders[headerName] = token;
191
+ break;
192
+
193
+ default:
194
+ // Unknown usage - default to bearer
195
+ authHeaders["Authorization"] = `Bearer ${token}`;
196
+ }
197
+
198
+ // Add any additional API headers from provider config
199
+ if (tokenData.apiHeaders) {
200
+ Object.assign(authHeaders, tokenData.apiHeaders);
201
+ }
202
+
203
+ return authHeaders;
204
+ }
205
+
141
206
  /**
142
207
  * Resolve OAuth provider for a tool
143
208
  *