@kya-os/mcp-i-core 1.3.7-canary.clientinfo.20251126041014 → 1.3.8-canary.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 (32) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test$colon$coverage.log +2913 -2246
  3. package/.turbo/turbo-test.log +1207 -2842
  4. package/coverage/coverage-final.json +57 -56
  5. package/dist/__tests__/utils/mock-providers.d.ts +2 -1
  6. package/dist/__tests__/utils/mock-providers.d.ts.map +1 -1
  7. package/dist/__tests__/utils/mock-providers.js.map +1 -1
  8. package/dist/config/remote-config.d.ts +51 -0
  9. package/dist/config/remote-config.d.ts.map +1 -1
  10. package/dist/config/remote-config.js +74 -0
  11. package/dist/config/remote-config.js.map +1 -1
  12. package/dist/config.d.ts +1 -1
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +4 -1
  15. package/dist/config.js.map +1 -1
  16. package/dist/services/session-registration.service.d.ts.map +1 -1
  17. package/dist/services/session-registration.service.js +10 -66
  18. package/dist/services/session-registration.service.js.map +1 -1
  19. package/dist/services/tool-protection.service.d.ts +4 -1
  20. package/dist/services/tool-protection.service.d.ts.map +1 -1
  21. package/dist/services/tool-protection.service.js +31 -16
  22. package/dist/services/tool-protection.service.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/__tests__/integration/full-flow.test.ts +23 -10
  25. package/src/__tests__/services/agentshield-integration.test.ts +10 -3
  26. package/src/__tests__/services/tool-protection-merged-config.test.ts +485 -0
  27. package/src/__tests__/services/tool-protection.service.test.ts +18 -11
  28. package/src/config/__tests__/merged-config.spec.ts +445 -0
  29. package/src/config/remote-config.ts +90 -0
  30. package/src/config.ts +3 -0
  31. package/src/services/session-registration.service.ts +26 -92
  32. package/src/services/tool-protection.service.ts +76 -48
@@ -112,43 +112,6 @@ export class SessionRegistrationService {
112
112
  url,
113
113
  });
114
114
 
115
- // ✅ EMPIRICAL PROOF: Prepare request headers with correct auth format
116
- const requestHeaders = {
117
- "Content-Type": "application/json",
118
- "X-AgentShield-Key": this.config.apiKey, // Fixed: Use X-AgentShield-Key instead of Authorization: Bearer
119
- };
120
-
121
- // ✅ EMPIRICAL PROOF: Log exact request details (sanitized for security)
122
- const sanitizedHeaders = {
123
- ...requestHeaders,
124
- "X-AgentShield-Key": `${this.config.apiKey.slice(0, 8)}...${this.config.apiKey.slice(-4)}`, // Show first 8 and last 4 chars
125
- };
126
-
127
- const sanitizedBody = {
128
- session_id: request.session_id,
129
- agent_did: request.agent_did,
130
- project_id: request.project_id,
131
- created_at: request.created_at,
132
- client_info: request.client_info,
133
- client_identity: request.client_identity,
134
- server_did: request.server_did,
135
- ttl_minutes: request.ttl_minutes,
136
- };
137
-
138
- this.config.logger(
139
- "[SessionRegistration] 🔍 EMPIRICAL DEBUG - Request details",
140
- {
141
- url,
142
- method: "POST",
143
- headers: sanitizedHeaders,
144
- headerKeys: Object.keys(requestHeaders),
145
- authHeaderPresent: !!requestHeaders["X-AgentShield-Key"],
146
- authHeaderName: "X-AgentShield-Key",
147
- body: sanitizedBody,
148
- bodySize: JSON.stringify(request).length,
149
- }
150
- );
151
-
152
115
  // Make the request with timeout
153
116
  const controller = new AbortController();
154
117
  const timeoutId = setTimeout(
@@ -159,75 +122,47 @@ export class SessionRegistrationService {
159
122
  try {
160
123
  const response = await this.config.fetchProvider.fetch(url, {
161
124
  method: "POST",
162
- headers: requestHeaders,
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ Authorization: `Bearer ${this.config.apiKey}`,
128
+ },
163
129
  body: JSON.stringify(request),
164
130
  signal: controller.signal,
165
131
  });
166
132
 
167
133
  clearTimeout(timeoutId);
168
134
 
169
- // ✅ EMPIRICAL PROOF: Capture exact response details
170
- const responseHeaders: Record<string, string> = {};
171
- response.headers.forEach((value, key) => {
172
- responseHeaders[key] = value;
173
- });
174
-
175
- const responseText = await response
176
- .text()
177
- .catch(() => "Failed to read response");
178
- let responseBody: unknown;
179
- try {
180
- responseBody = JSON.parse(responseText);
181
- } catch {
182
- responseBody = responseText;
183
- }
184
-
185
- // ✅ EMPIRICAL PROOF: Log exact response details
186
- this.config.logger(
187
- "[SessionRegistration] 🔍 EMPIRICAL DEBUG - Response details",
188
- {
189
- status: response.status,
190
- statusText: response.statusText,
191
- ok: response.ok,
192
- headers: responseHeaders,
193
- body: responseBody,
194
- bodyLength: responseText.length,
195
- clientName: request.client_info.name,
196
- }
197
- );
198
-
199
135
  if (!response.ok) {
200
136
  // Log error but don't throw - this is fire-and-forget
137
+ const errorText = await response.text().catch(() => "Unknown error");
201
138
  this.config.logger("[SessionRegistration] Registration failed", {
202
139
  sessionId,
203
140
  status: response.status,
204
- error: responseText,
205
- // ✅ EMPIRICAL PROOF: Include full response details
206
- responseHeaders,
207
- responseBody,
208
- clientName: request.client_info.name,
141
+ error: errorText,
209
142
  });
210
143
  return {
211
144
  success: false,
212
145
  sessionId,
213
- error: `HTTP ${response.status}: ${responseText}`,
146
+ error: `HTTP ${response.status}: ${errorText}`,
214
147
  };
215
148
  }
216
149
 
217
- // Parse response (using already-captured responseBody)
218
- const responseData = responseBody as
219
- | { data?: RegisterSessionResponse }
220
- | RegisterSessionResponse;
150
+ // Parse response
151
+ const responseData = (await response.json()) as {
152
+ data?: RegisterSessionResponse;
153
+ } & RegisterSessionResponse;
221
154
  const parseResult = registerSessionResponseSchema.safeParse(
222
- (responseData as { data?: RegisterSessionResponse }).data ||
223
- responseData
155
+ responseData.data || responseData
224
156
  );
225
157
 
226
158
  if (!parseResult.success) {
227
- this.config.logger("[SessionRegistration] Invalid response format", {
228
- sessionId,
229
- response: responseData,
230
- });
159
+ this.config.logger(
160
+ "[SessionRegistration] Invalid response format",
161
+ {
162
+ sessionId,
163
+ response: responseData,
164
+ }
165
+ );
231
166
  // Still consider it a success if we got a 200 OK
232
167
  return { success: true, sessionId };
233
168
  }
@@ -252,7 +187,8 @@ export class SessionRegistrationService {
252
187
  }
253
188
 
254
189
  // Log any other error
255
- const errorMsg = error instanceof Error ? error.message : "Unknown error";
190
+ const errorMsg =
191
+ error instanceof Error ? error.message : "Unknown error";
256
192
  this.config.logger("[SessionRegistration] Unexpected error", {
257
193
  sessionId,
258
194
  error: errorMsg,
@@ -274,13 +210,10 @@ export class SessionRegistrationService {
274
210
  this.registerSession(request).catch((error) => {
275
211
  // This should never happen since registerSession catches all errors,
276
212
  // but just in case
277
- this.config.logger(
278
- "[SessionRegistration] Background registration failed",
279
- {
280
- sessionId: request.session_id,
281
- error: error instanceof Error ? error.message : "Unknown error",
282
- }
283
- );
213
+ this.config.logger("[SessionRegistration] Background registration failed", {
214
+ sessionId: request.session_id,
215
+ error: error instanceof Error ? error.message : "Unknown error",
216
+ });
284
217
  });
285
218
  }
286
219
  }
@@ -315,3 +248,4 @@ export function createSessionRegistrationService(options: {
315
248
  logger: options.logger,
316
249
  });
317
250
  }
251
+
@@ -87,34 +87,56 @@ import type {
87
87
  import type { ToolProtectionCache } from "../cache/tool-protection-cache.js";
88
88
  import { InMemoryToolProtectionCache } from "../cache/tool-protection-cache.js";
89
89
 
90
+ /**
91
+ * Tool protection data structure in API responses
92
+ */
93
+ interface ToolProtectionData {
94
+ requiresDelegation?: boolean;
95
+ requires_delegation?: boolean;
96
+ requiredScopes?: string[];
97
+ required_scopes?: string[];
98
+ scopes?: string[];
99
+ riskLevel?: string;
100
+ risk_level?: string;
101
+ oauthProvider?: string;
102
+ oauth_provider?: string;
103
+ authorization?: {
104
+ type: string;
105
+ provider?: string;
106
+ issuer?: string;
107
+ credentialType?: string;
108
+ };
109
+ }
110
+
90
111
  /**
91
112
  * Response from AgentShield API bouncer endpoints
92
113
  *
93
114
  * Supports multiple endpoint formats:
94
- * 1. New endpoint (/projects/{projectId}/tool-protections): { data: { toolProtections: { [toolName]: {...} } } } }
95
- * 2. Old endpoint (/config?agent_did=...): { data: { tools: [{ name: string, ... }] } }
96
- * 3. Legacy format: { data: { tools: { [toolName]: {...} } } }
115
+ * 1. Merged config (/projects/{projectId}/config): { data: { config: { toolProtection: { tools: {...} } } } }
116
+ * 2. Legacy tool-protections endpoint: { data: { toolProtections: { [toolName]: {...} } } }
117
+ * 3. Old config endpoint (/config?agent_did=...): { data: { tools: [{ name: string, ... }] } }
118
+ * 4. Legacy format: { data: { tools: { [toolName]: {...} } } }
97
119
  */
98
120
  interface BouncerConfigApiResponse {
99
121
  success: boolean;
100
122
  data: {
101
123
  agent_did?: string;
102
- // New endpoint format: toolProtections object
103
- toolProtections?: Record<
104
- string,
105
- {
106
- requiresDelegation?: boolean;
107
- requires_delegation?: boolean;
108
- requiredScopes?: string[];
109
- required_scopes?: string[];
110
- scopes?: string[];
111
- riskLevel?: string;
112
- risk_level?: string;
113
- oauthProvider?: string; // Phase 2: Tool-specific OAuth provider
114
- oauth_provider?: string; // Phase 2: snake_case variant
115
- }
116
- >;
117
- // Old endpoint format: tools array or object
124
+
125
+ // NEW: Merged config format (v1.6.0+) - preferred format
126
+ // The entire config is returned with tools embedded at config.toolProtection.tools
127
+ config?: {
128
+ toolProtection?: {
129
+ source?: string;
130
+ tools?: Record<string, ToolProtectionData>;
131
+ };
132
+ // Other config fields we don't need to parse
133
+ [key: string]: unknown;
134
+ };
135
+
136
+ // DEPRECATED: Top-level toolProtections (backward compatibility during transition)
137
+ toolProtections?: Record<string, ToolProtectionData>;
138
+
139
+ // Legacy endpoint formats
118
140
  tools?:
119
141
  | Array<{
120
142
  name: string;
@@ -122,20 +144,10 @@ interface BouncerConfigApiResponse {
122
144
  requires_delegation?: boolean;
123
145
  scopes?: string[];
124
146
  required_scopes?: string[];
125
- oauthProvider?: string; // Phase 2: Tool-specific OAuth provider
126
- oauth_provider?: string; // Phase 2: snake_case variant
147
+ oauthProvider?: string;
148
+ oauth_provider?: string;
127
149
  }>
128
- | Record<
129
- string,
130
- {
131
- requiresDelegation?: boolean;
132
- requires_delegation?: boolean;
133
- scopes?: string[];
134
- required_scopes?: string[];
135
- oauthProvider?: string; // Phase 2: Tool-specific OAuth provider
136
- oauth_provider?: string; // Phase 2: snake_case variant
137
- }
138
- >;
150
+ | Record<string, ToolProtectionData>;
139
151
  reputation_threshold?: number;
140
152
  denied_agents?: string[];
141
153
  };
@@ -271,7 +283,7 @@ export class ToolProtectionService {
271
283
  projectId: this.config.projectId || "none",
272
284
  apiUrl: this.config.apiUrl,
273
285
  endpoint: this.config.projectId
274
- ? `/api/v1/bouncer/projects/${this.config.projectId}/tool-protections`
286
+ ? `/api/v1/bouncer/projects/${this.config.projectId}/config`
275
287
  : `/api/v1/bouncer/config?agent_did=${agentDid}`,
276
288
  });
277
289
  }
@@ -665,7 +677,10 @@ export class ToolProtectionService {
665
677
 
666
678
  /**
667
679
  * Fetch tool protection config from AgentShield API
668
- * Uses projectId endpoint if available (preferred, project-scoped), otherwise falls back to agent_did query param
680
+ *
681
+ * Uses the merged /config endpoint which returns tool protections embedded
682
+ * at config.toolProtection.tools. Falls back to legacy formats for backward
683
+ * compatibility.
669
684
  *
670
685
  * @param agentDid DID of the agent to fetch config for
671
686
  * @param options Optional fetch options
@@ -675,17 +690,17 @@ export class ToolProtectionService {
675
690
  agentDid: string,
676
691
  options?: { bypassCDNCache?: boolean }
677
692
  ): Promise<BouncerConfigApiResponse> {
678
- // Prefer new project-scoped endpoint: /api/v1/bouncer/projects/{projectId}/tool-protections
679
- // Falls back to old endpoint: /api/v1/bouncer/config?agent_did={did} for backward compatibility
693
+ // Use the merged /config endpoint which includes embedded tool protections
694
+ // This endpoint returns config.toolProtection.tools with all tool rules
680
695
  let url: string;
681
- let useNewEndpoint = false;
696
+ let useMergedEndpoint = false;
682
697
 
683
698
  if (this.config.projectId) {
684
- // ✅ NEW ENDPOINT: Project-scoped, returns toolProtections object
685
- url = `${this.config.apiUrl}/api/v1/bouncer/projects/${encodeURIComponent(this.config.projectId)}/tool-protections`;
686
- useNewEndpoint = true;
699
+ // ✅ MERGED CONFIG ENDPOINT: Returns config with embedded toolProtection.tools
700
+ url = `${this.config.apiUrl}/api/v1/bouncer/projects/${encodeURIComponent(this.config.projectId)}/config`;
701
+ useMergedEndpoint = true;
687
702
  } else {
688
- // ⚠️ OLD ENDPOINT: Agent-scoped, returns tools array (backward compatibility)
703
+ // ⚠️ LEGACY ENDPOINT: Agent-scoped, returns tools array (backward compatibility)
689
704
  url = `${this.config.apiUrl}/api/v1/bouncer/config?agent_did=${encodeURIComponent(agentDid)}`;
690
705
  }
691
706
 
@@ -712,9 +727,9 @@ export class ToolProtectionService {
712
727
 
713
728
  if (this.config.debug) {
714
729
  console.log("[ToolProtectionService] Fetching from API:", url, {
715
- method: useNewEndpoint
716
- ? "projects/{projectId}/tool-protections (new)"
717
- : "config?agent_did (old)",
730
+ method: useMergedEndpoint
731
+ ? "projects/{projectId}/config (merged)"
732
+ : "config?agent_did (legacy)",
718
733
  projectId: this.config.projectId || "none",
719
734
  apiKeyPresent: !!this.config.apiKey,
720
735
  apiKeyLength,
@@ -736,19 +751,19 @@ export class ToolProtectionService {
736
751
  );
737
752
  }
738
753
 
739
- // Build headers - new endpoint uses X-API-Key, old endpoint uses Authorization Bearer
754
+ // Build headers - merged endpoint uses X-API-Key, legacy uses Authorization Bearer
740
755
  const headers: Record<string, string> = {
741
756
  "Content-Type": "application/json",
742
757
  };
743
758
 
744
- if (useNewEndpoint) {
745
- // ✅ New endpoint headers
759
+ if (useMergedEndpoint) {
760
+ // ✅ Merged config endpoint headers
746
761
  headers["X-API-Key"] = this.config.apiKey;
747
762
  if (this.config.projectId) {
748
763
  headers["X-Project-Id"] = this.config.projectId;
749
764
  }
750
765
  } else {
751
- // ⚠️ Old endpoint headers (backward compatibility)
766
+ // ⚠️ Legacy endpoint headers (backward compatibility)
752
767
  headers["Authorization"] = `Bearer ${this.config.apiKey}`;
753
768
  }
754
769
 
@@ -786,6 +801,19 @@ export class ToolProtectionService {
786
801
  throw new Error("API returned success: false");
787
802
  }
788
803
 
804
+ // Transform merged config format to normalized format
805
+ // If response contains config.toolProtection.tools, extract them to data.toolProtections
806
+ if (useMergedEndpoint && data.data.config?.toolProtection?.tools) {
807
+ // Extract embedded tools to the standard toolProtections field
808
+ data.data.toolProtections = data.data.config.toolProtection.tools;
809
+ if (this.config.debug) {
810
+ console.log("[ToolProtectionService] Extracted tools from merged config", {
811
+ toolCount: Object.keys(data.data.toolProtections).length,
812
+ tools: Object.keys(data.data.toolProtections),
813
+ });
814
+ }
815
+ }
816
+
789
817
  return data;
790
818
  }
791
819