@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.
- package/.claude/settings.local.json +9 -0
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test$colon$coverage.log +3419 -3072
- package/.turbo/turbo-test.log +1805 -1680
- package/coverage/coverage-final.json +59 -56
- package/dist/config/remote-config.d.ts +51 -0
- package/dist/config/remote-config.d.ts.map +1 -1
- package/dist/config/remote-config.js +74 -0
- package/dist/config/remote-config.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/delegation/did-key-resolver.d.ts +64 -0
- package/dist/delegation/did-key-resolver.d.ts.map +1 -0
- package/dist/delegation/did-key-resolver.js +159 -0
- package/dist/delegation/did-key-resolver.js.map +1 -0
- package/dist/delegation/utils.d.ts +76 -0
- package/dist/delegation/utils.d.ts.map +1 -1
- package/dist/delegation/utils.js +117 -0
- package/dist/delegation/utils.js.map +1 -1
- package/dist/identity/idp-token-resolver.d.ts +17 -1
- package/dist/identity/idp-token-resolver.d.ts.map +1 -1
- package/dist/identity/idp-token-resolver.js +34 -6
- package/dist/identity/idp-token-resolver.js.map +1 -1
- package/dist/identity/idp-token-storage.interface.d.ts +38 -7
- package/dist/identity/idp-token-storage.interface.d.ts.map +1 -1
- package/dist/identity/idp-token-storage.interface.js +2 -0
- package/dist/identity/idp-token-storage.interface.js.map +1 -1
- package/dist/identity/user-did-manager.d.ts +95 -12
- package/dist/identity/user-did-manager.d.ts.map +1 -1
- package/dist/identity/user-did-manager.js +107 -25
- package/dist/identity/user-did-manager.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/base.d.ts +25 -8
- package/dist/runtime/base.d.ts.map +1 -1
- package/dist/runtime/base.js +74 -21
- package/dist/runtime/base.js.map +1 -1
- package/dist/services/session-registration.service.d.ts.map +1 -1
- package/dist/services/session-registration.service.js +10 -90
- package/dist/services/session-registration.service.js.map +1 -1
- package/dist/services/tool-context-builder.d.ts +18 -1
- package/dist/services/tool-context-builder.d.ts.map +1 -1
- package/dist/services/tool-context-builder.js +63 -10
- package/dist/services/tool-context-builder.js.map +1 -1
- package/dist/services/tool-protection.service.d.ts +6 -3
- package/dist/services/tool-protection.service.d.ts.map +1 -1
- package/dist/services/tool-protection.service.js +89 -34
- package/dist/services/tool-protection.service.js.map +1 -1
- package/dist/utils/base58.d.ts +31 -0
- package/dist/utils/base58.d.ts.map +1 -0
- package/dist/utils/base58.js +103 -0
- package/dist/utils/base58.js.map +1 -0
- package/dist/utils/did-helpers.d.ts +33 -0
- package/dist/utils/did-helpers.d.ts.map +1 -1
- package/dist/utils/did-helpers.js +53 -0
- package/dist/utils/did-helpers.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/identity/user-did-manager.test.ts +64 -45
- package/src/__tests__/integration/full-flow.test.ts +23 -10
- package/src/__tests__/runtime/base-extensions.test.ts +23 -21
- package/src/__tests__/runtime/proof-client-did.test.ts +19 -18
- package/src/__tests__/services/agentshield-integration.test.ts +10 -3
- package/src/__tests__/services/tool-protection-merged-config.test.ts +485 -0
- package/src/__tests__/services/tool-protection.service.test.ts +18 -11
- package/src/config/__tests__/merged-config.spec.ts +445 -0
- package/src/config/remote-config.ts +90 -0
- package/src/config.ts +3 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
- package/src/delegation/__tests__/vc-issuer.test.ts +1 -1
- package/src/delegation/did-key-resolver.ts +179 -0
- package/src/delegation/utils.ts +179 -0
- package/src/identity/idp-token-resolver.ts +41 -7
- package/src/identity/idp-token-storage.interface.ts +42 -7
- package/src/identity/user-did-manager.ts +185 -29
- package/src/index.ts +42 -3
- package/src/runtime/base.ts +84 -21
- package/src/services/session-registration.service.ts +26 -121
- package/src/services/tool-context-builder.ts +75 -10
- package/src/services/tool-protection.service.ts +176 -88
- package/src/utils/__tests__/did-helpers.test.ts +55 -0
- package/src/utils/base58.ts +109 -0
- package/src/utils/did-helpers.ts +60 -0
- package/dist/__tests__/utils/mock-providers.d.ts +0 -103
- package/dist/__tests__/utils/mock-providers.d.ts.map +0 -1
- package/dist/__tests__/utils/mock-providers.js +0 -293
- package/dist/__tests__/utils/mock-providers.js.map +0 -1
package/src/runtime/base.ts
CHANGED
|
@@ -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
|
-
//
|
|
159
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
177
|
-
// Continue without user DID -
|
|
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
|
|
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, //
|
|
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:
|
|
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:
|
|
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}: ${
|
|
146
|
+
error: `HTTP ${response.status}: ${errorText}`,
|
|
237
147
|
};
|
|
238
148
|
}
|
|
239
149
|
|
|
240
|
-
// Parse response
|
|
241
|
-
const responseData =
|
|
242
|
-
|
|
243
|
-
|
|
150
|
+
// Parse response
|
|
151
|
+
const responseData = (await response.json()) as {
|
|
152
|
+
data?: RegisterSessionResponse;
|
|
153
|
+
} & RegisterSessionResponse;
|
|
244
154
|
const parseResult = registerSessionResponseSchema.safeParse(
|
|
245
|
-
|
|
246
|
-
responseData
|
|
155
|
+
responseData.data || responseData
|
|
247
156
|
);
|
|
248
157
|
|
|
249
158
|
if (!parseResult.success) {
|
|
250
|
-
this.config.logger(
|
|
251
|
-
|
|
252
|
-
|
|
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 =
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
94
|
-
|
|
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 (!
|
|
101
|
-
// Token not available - throw OAuthRequiredError to trigger
|
|
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
|
-
//
|
|
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
|
|
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: !!
|
|
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
|
*
|