@kya-os/mcp-i-core 1.3.0 → 1.3.2
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test$colon$coverage.log +2579 -2669
- package/.turbo/turbo-test.log +1251 -1245
- package/coverage/coverage-final.json +4 -4
- package/dist/services/oauth-config.service.d.ts.map +1 -1
- package/dist/services/oauth-config.service.js +5 -3
- package/dist/services/oauth-config.service.js.map +1 -1
- package/dist/services/oauth-provider-registry.d.ts +11 -0
- package/dist/services/oauth-provider-registry.d.ts.map +1 -1
- package/dist/services/oauth-provider-registry.js +16 -0
- package/dist/services/oauth-provider-registry.js.map +1 -1
- package/dist/services/provider-resolver.d.ts +1 -1
- package/dist/services/provider-resolver.d.ts.map +1 -1
- package/dist/services/provider-resolver.js +14 -13
- package/dist/services/provider-resolver.js.map +1 -1
- package/dist/services/tool-protection.service.d.ts +13 -10
- package/dist/services/tool-protection.service.d.ts.map +1 -1
- package/dist/services/tool-protection.service.js +24 -113
- package/dist/services/tool-protection.service.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/regression/phase2-regression.test.ts +8 -6
- package/src/__tests__/services/cache-no-warming.test.ts +177 -0
- package/src/__tests__/services/provider-resolver-edge-cases.test.ts +168 -64
- package/src/services/__tests__/provider-resolution.integration.test.ts +9 -5
- package/src/services/__tests__/provider-resolver.test.ts +22 -26
- package/src/services/oauth-config.service.ts +6 -3
- package/src/services/oauth-provider-registry.ts +18 -0
- package/src/services/provider-resolver.ts +15 -13
- package/src/services/tool-protection.service.ts +25 -136
- package/src/cache/oauth-config-cache.js +0 -71
- package/src/providers/base.js +0 -38
- package/src/services/oauth-config.service.js +0 -113
- package/src/services/oauth-provider-registry.js +0 -73
- package/src/services/provider-resolver.js +0 -106
|
@@ -45,6 +45,7 @@ describe("ProviderResolver", () => {
|
|
|
45
45
|
getAllProviders: vi.fn().mockReturnValue([]),
|
|
46
46
|
getProviderNames: vi.fn().mockReturnValue([]),
|
|
47
47
|
loadFromAgentShield: vi.fn().mockResolvedValue(undefined),
|
|
48
|
+
getConfiguredProvider: vi.fn().mockReturnValue(null), // New method for configuredProvider
|
|
48
49
|
} as any;
|
|
49
50
|
|
|
50
51
|
resolver = new ProviderResolver(mockRegistry, mockConfigService);
|
|
@@ -121,42 +122,37 @@ describe("ProviderResolver", () => {
|
|
|
121
122
|
expect(provider).toBe("google");
|
|
122
123
|
});
|
|
123
124
|
|
|
124
|
-
it("should
|
|
125
|
+
it("should fall back to configuredProvider for ambiguous scopes", async () => {
|
|
125
126
|
const toolProtection: ToolProtection = {
|
|
126
127
|
requiresDelegation: true,
|
|
127
128
|
requiredScopes: ["github:read", "google:read"],
|
|
128
129
|
};
|
|
129
130
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
(mockRegistry.
|
|
134
|
-
{ clientId: "github_client_id" },
|
|
135
|
-
]);
|
|
136
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
131
|
+
// Ambiguous scopes - both infer different providers
|
|
132
|
+
// Should fall through to Priority 3 (configuredProvider)
|
|
133
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
134
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
137
135
|
|
|
138
136
|
const provider = await resolver.resolveProvider(
|
|
139
137
|
toolProtection,
|
|
140
138
|
"test-project"
|
|
141
139
|
);
|
|
142
140
|
|
|
143
|
-
// Falls back to
|
|
141
|
+
// Falls back to configuredProvider
|
|
144
142
|
expect(provider).toBe("github");
|
|
145
143
|
});
|
|
146
144
|
});
|
|
147
145
|
|
|
148
|
-
describe("resolveProvider - Priority 3:
|
|
149
|
-
it("should use
|
|
146
|
+
describe("resolveProvider - Priority 3: Project-configured provider", () => {
|
|
147
|
+
it("should use configuredProvider as fallback", async () => {
|
|
150
148
|
const toolProtection: ToolProtection = {
|
|
151
149
|
requiresDelegation: true,
|
|
152
150
|
requiredScopes: ["custom:scope"],
|
|
153
151
|
};
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
(mockRegistry.
|
|
157
|
-
|
|
158
|
-
]);
|
|
159
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
153
|
+
// configuredProvider is github
|
|
154
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
155
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
160
156
|
|
|
161
157
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
162
158
|
|
|
@@ -167,30 +163,28 @@ describe("ProviderResolver", () => {
|
|
|
167
163
|
|
|
168
164
|
expect(provider).toBe("github");
|
|
169
165
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
170
|
-
expect.stringContaining("
|
|
166
|
+
expect.stringContaining("project-configured provider")
|
|
171
167
|
);
|
|
172
168
|
|
|
173
169
|
consoleSpy.mockRestore();
|
|
174
170
|
});
|
|
175
171
|
|
|
176
|
-
it("should log
|
|
172
|
+
it("should log warning when using configuredProvider fallback", async () => {
|
|
177
173
|
const toolProtection: ToolProtection = {
|
|
178
174
|
requiresDelegation: true,
|
|
179
175
|
requiredScopes: [],
|
|
180
176
|
};
|
|
181
177
|
|
|
182
|
-
|
|
183
|
-
(mockRegistry.
|
|
184
|
-
|
|
185
|
-
]);
|
|
186
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
178
|
+
// configuredProvider is github
|
|
179
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
180
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
187
181
|
|
|
188
182
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
189
183
|
|
|
190
184
|
await resolver.resolveProvider(toolProtection, "test-project");
|
|
191
185
|
|
|
192
186
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
193
|
-
expect.stringContaining("
|
|
187
|
+
expect.stringContaining("Consider explicitly setting oauthProvider")
|
|
194
188
|
);
|
|
195
189
|
|
|
196
190
|
consoleSpy.mockRestore();
|
|
@@ -198,19 +192,21 @@ describe("ProviderResolver", () => {
|
|
|
198
192
|
});
|
|
199
193
|
|
|
200
194
|
describe("resolveProvider - Priority 4: Error if no provider", () => {
|
|
201
|
-
it("should throw error if no provider
|
|
195
|
+
it("should throw error if no provider is configured", async () => {
|
|
202
196
|
const toolProtection: ToolProtection = {
|
|
203
197
|
requiresDelegation: true,
|
|
204
198
|
requiredScopes: [],
|
|
205
199
|
};
|
|
206
200
|
|
|
201
|
+
// No configuredProvider set
|
|
207
202
|
(mockRegistry.hasProvider as any).mockReturnValue(false);
|
|
203
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue(null);
|
|
208
204
|
(mockRegistry.getAllProviders as any).mockReturnValue([]);
|
|
209
205
|
(mockRegistry.getProviderNames as any).mockReturnValue([]);
|
|
210
206
|
|
|
211
207
|
await expect(
|
|
212
208
|
resolver.resolveProvider(toolProtection, "test-project")
|
|
213
|
-
).rejects.toThrow(/no provider
|
|
209
|
+
).rejects.toThrow(/no provider is configured/);
|
|
214
210
|
});
|
|
215
211
|
});
|
|
216
212
|
});
|
|
@@ -99,6 +99,7 @@ export class OAuthConfigService {
|
|
|
99
99
|
success: boolean;
|
|
100
100
|
data?: {
|
|
101
101
|
providers?: Record<string, unknown>;
|
|
102
|
+
configuredProvider?: string | null;
|
|
102
103
|
};
|
|
103
104
|
};
|
|
104
105
|
|
|
@@ -116,11 +117,12 @@ export class OAuthConfigService {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
// Build OAuthConfig object
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
120
|
+
// Extract configuredProvider from API response - this indicates which provider
|
|
121
|
+
// the user has actually configured in AgentShield dashboard
|
|
122
|
+
const configuredProvider = result.data.configuredProvider || null;
|
|
122
123
|
const config: OAuthConfig = {
|
|
123
124
|
providers: providers as Record<string, OAuthProvider>,
|
|
125
|
+
configuredProvider,
|
|
124
126
|
};
|
|
125
127
|
|
|
126
128
|
// Cache config
|
|
@@ -130,6 +132,7 @@ export class OAuthConfigService {
|
|
|
130
132
|
projectId,
|
|
131
133
|
providerCount: Object.keys(providers).length,
|
|
132
134
|
providers: Object.keys(providers),
|
|
135
|
+
configuredProvider,
|
|
133
136
|
expiresAt: new Date(
|
|
134
137
|
Date.now() + this.config.cacheTtl
|
|
135
138
|
).toISOString(),
|
|
@@ -19,6 +19,7 @@ import type { ProviderValidator } from "./provider-validator.js";
|
|
|
19
19
|
*/
|
|
20
20
|
export class OAuthProviderRegistry {
|
|
21
21
|
private providers: Map<string, OAuthProvider> = new Map();
|
|
22
|
+
private _configuredProvider: string | null = null;
|
|
22
23
|
|
|
23
24
|
constructor(
|
|
24
25
|
private configService: OAuthConfigService,
|
|
@@ -30,6 +31,7 @@ export class OAuthProviderRegistry {
|
|
|
30
31
|
*
|
|
31
32
|
* Fetches OAuth configuration and caches providers in memory.
|
|
32
33
|
* Clears existing providers before loading new ones.
|
|
34
|
+
* Also stores the configured provider from the API response.
|
|
33
35
|
*
|
|
34
36
|
* @param projectId - Project ID to load providers for
|
|
35
37
|
*/
|
|
@@ -39,12 +41,28 @@ export class OAuthProviderRegistry {
|
|
|
39
41
|
// Clear existing providers
|
|
40
42
|
this.providers.clear();
|
|
41
43
|
|
|
44
|
+
// Store the configured provider from API response
|
|
45
|
+
// This is the provider the user has explicitly configured in AgentShield dashboard
|
|
46
|
+
this._configuredProvider = config.configuredProvider || null;
|
|
47
|
+
|
|
42
48
|
// Register all providers from config
|
|
43
49
|
for (const [name, providerConfig] of Object.entries(config.providers)) {
|
|
44
50
|
this.providers.set(name, providerConfig);
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Get the explicitly configured provider for this project
|
|
56
|
+
*
|
|
57
|
+
* Returns the provider that the user has configured in AgentShield dashboard.
|
|
58
|
+
* Used by ProviderResolver as fallback when tool doesn't specify oauthProvider.
|
|
59
|
+
*
|
|
60
|
+
* @returns Configured provider name, or null if no provider is configured
|
|
61
|
+
*/
|
|
62
|
+
getConfiguredProvider(): string | null {
|
|
63
|
+
return this._configuredProvider;
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
/**
|
|
49
67
|
* Get provider by name
|
|
50
68
|
*
|
|
@@ -17,7 +17,7 @@ import type { OAuthConfigService } from "./oauth-config.service.js";
|
|
|
17
17
|
* Priority order:
|
|
18
18
|
* 1. Tool-specific oauthProvider field (Phase 2+ preferred)
|
|
19
19
|
* 2. Scope prefix inference (fallback)
|
|
20
|
-
* 3.
|
|
20
|
+
* 3. Project-configured provider from AgentShield dashboard
|
|
21
21
|
* 4. Error if no provider can be resolved
|
|
22
22
|
*/
|
|
23
23
|
export class ProviderResolver {
|
|
@@ -70,25 +70,27 @@ export class ProviderResolver {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
// Priority 3:
|
|
74
|
-
//
|
|
73
|
+
// Priority 3: Use explicitly configured provider from AgentShield dashboard
|
|
74
|
+
// This is the provider the user has actually configured, not just any available provider
|
|
75
75
|
await this.registry.loadFromAgentShield(projectId);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const firstProviderName = this.registry.getProviderNames()[0];
|
|
76
|
+
const configuredProvider = this.registry.getConfiguredProvider();
|
|
77
|
+
|
|
78
|
+
if (configuredProvider && this.registry.hasProvider(configuredProvider)) {
|
|
80
79
|
console.warn(
|
|
81
80
|
`[ProviderResolver] Tool does not specify oauthProvider. ` +
|
|
82
|
-
`Using
|
|
83
|
-
`
|
|
81
|
+
`Using project-configured provider "${configuredProvider}" as fallback. ` +
|
|
82
|
+
`Consider explicitly setting oauthProvider in tool protection config.`
|
|
84
83
|
);
|
|
85
|
-
return
|
|
84
|
+
return configuredProvider;
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
// Priority 4: Error if no provider
|
|
87
|
+
// Priority 4: Error if no provider is configured
|
|
88
|
+
// NOTE: We intentionally do NOT fall back to "first available provider" anymore
|
|
89
|
+
// because AgentShield returns ALL providers (even unconfigured ones).
|
|
90
|
+
// Only use providers explicitly configured by the user.
|
|
89
91
|
throw new Error(
|
|
90
|
-
`Tool requires OAuth but no provider
|
|
91
|
-
`
|
|
92
|
+
`Tool requires OAuth but no provider is configured for project "${projectId}". ` +
|
|
93
|
+
`Configure an OAuth provider in AgentShield dashboard.`
|
|
92
94
|
);
|
|
93
95
|
}
|
|
94
96
|
|
|
@@ -797,162 +797,51 @@ export class ToolProtectionService {
|
|
|
797
797
|
}
|
|
798
798
|
|
|
799
799
|
/**
|
|
800
|
-
* Clear cache
|
|
800
|
+
* Clear cache WITHOUT warming
|
|
801
801
|
*
|
|
802
|
-
* This method
|
|
803
|
-
* After clearing the KV entry, it fetches fresh data from the API and writes
|
|
804
|
-
* it back to KV. This ensures:
|
|
805
|
-
* 1. The global KV entry is deleted
|
|
806
|
-
* 2. Fresh data is fetched from API
|
|
807
|
-
* 3. New data is written to KV (updating edge cache)
|
|
802
|
+
* IMPORTANT: This method intentionally does NOT fetch fresh data after clearing.
|
|
808
803
|
*
|
|
809
|
-
*
|
|
804
|
+
* Why no cache warming?
|
|
805
|
+
* - Upstream APIs (AgentShield) may have their own CDN/cache layers
|
|
806
|
+
* - Immediately fetching after clear would get stale CDN data
|
|
807
|
+
* - Storing stale data defeats the purpose of cache invalidation
|
|
808
|
+
* - The next actual tool call will fetch fresh data (with natural delay)
|
|
809
|
+
*
|
|
810
|
+
* This was the behavior in mcp-i-cloudflare@1.6.3 that worked perfectly.
|
|
811
|
+
* Cache warming was added as an "optimization" that backfired when upstream
|
|
812
|
+
* APIs have their own caching.
|
|
810
813
|
*
|
|
811
814
|
* @param agentDid DID of the agent (used for cache key)
|
|
812
|
-
* @returns
|
|
815
|
+
* @returns Cache key that was cleared
|
|
813
816
|
*/
|
|
814
817
|
async clearAndRefresh(agentDid: string): Promise<{
|
|
815
818
|
config: ToolProtectionConfig;
|
|
816
819
|
cacheKey: string;
|
|
817
|
-
source: '
|
|
820
|
+
source: 'cleared';
|
|
818
821
|
}> {
|
|
819
822
|
const cacheKey = this.config.projectId
|
|
820
823
|
? `config:tool-protections:${this.config.projectId}`
|
|
821
824
|
: `agent:${agentDid}`;
|
|
822
825
|
|
|
823
|
-
console.log("[ToolProtectionService]
|
|
826
|
+
console.log("[ToolProtectionService] clearCache (no warming)", {
|
|
824
827
|
cacheKey,
|
|
825
828
|
projectId: this.config.projectId || "none",
|
|
826
829
|
agentDid: agentDid.slice(0, 20) + "...",
|
|
827
830
|
});
|
|
828
831
|
|
|
829
|
-
//
|
|
832
|
+
// Delete the cache entry - that's it, no warming
|
|
830
833
|
await this.cache.delete(cacheKey);
|
|
831
834
|
|
|
832
|
-
console.log("[ToolProtectionService] Cache entry deleted", {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const response = await this.fetchFromApi(agentDid);
|
|
837
|
-
|
|
838
|
-
// Transform API response to internal format (same logic as getToolProtectionConfig)
|
|
839
|
-
const toolProtections: Record<string, ToolProtection> = {};
|
|
840
|
-
|
|
841
|
-
if (response.data.toolProtections) {
|
|
842
|
-
for (const [toolName, toolConfig] of Object.entries(
|
|
843
|
-
response.data.toolProtections
|
|
844
|
-
)) {
|
|
845
|
-
const requiresDelegation =
|
|
846
|
-
(toolConfig as any).requiresDelegation ??
|
|
847
|
-
(toolConfig as any).requires_delegation ??
|
|
848
|
-
false;
|
|
849
|
-
const requiredScopes =
|
|
850
|
-
(toolConfig as any).requiredScopes ??
|
|
851
|
-
(toolConfig as any).required_scopes ??
|
|
852
|
-
(toolConfig as any).scopes ??
|
|
853
|
-
[];
|
|
854
|
-
const oauthProvider =
|
|
855
|
-
(toolConfig as any).oauthProvider ??
|
|
856
|
-
(toolConfig as any).oauth_provider ??
|
|
857
|
-
undefined;
|
|
858
|
-
const riskLevel =
|
|
859
|
-
(toolConfig as any).riskLevel ??
|
|
860
|
-
(toolConfig as any).risk_level ??
|
|
861
|
-
undefined;
|
|
862
|
-
|
|
863
|
-
toolProtections[toolName] = {
|
|
864
|
-
requiresDelegation,
|
|
865
|
-
requiredScopes,
|
|
866
|
-
...(oauthProvider && { oauthProvider }),
|
|
867
|
-
...(riskLevel && { riskLevel }),
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
} else if (response.data.tools) {
|
|
871
|
-
if (Array.isArray(response.data.tools)) {
|
|
872
|
-
for (const tool of response.data.tools) {
|
|
873
|
-
const toolName = (tool as any).name;
|
|
874
|
-
if (!toolName) continue;
|
|
875
|
-
const requiresDelegation =
|
|
876
|
-
(tool as any).requiresDelegation ?? (tool as any).requires_delegation ?? false;
|
|
877
|
-
const requiredScopes =
|
|
878
|
-
(tool as any).requiredScopes ??
|
|
879
|
-
(tool as any).required_scopes ??
|
|
880
|
-
(tool as any).scopes ??
|
|
881
|
-
[];
|
|
882
|
-
const oauthProvider =
|
|
883
|
-
(tool as any).oauthProvider ?? (tool as any).oauth_provider ?? undefined;
|
|
884
|
-
const riskLevel = (tool as any).riskLevel ?? (tool as any).risk_level ?? undefined;
|
|
885
|
-
|
|
886
|
-
toolProtections[toolName] = {
|
|
887
|
-
requiresDelegation,
|
|
888
|
-
requiredScopes,
|
|
889
|
-
...(oauthProvider && { oauthProvider }),
|
|
890
|
-
...(riskLevel && { riskLevel }),
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
} else {
|
|
894
|
-
for (const [toolName, toolConfig] of Object.entries(
|
|
895
|
-
response.data.tools
|
|
896
|
-
)) {
|
|
897
|
-
const requiresDelegation =
|
|
898
|
-
(toolConfig as any).requiresDelegation ??
|
|
899
|
-
(toolConfig as any).requires_delegation ??
|
|
900
|
-
false;
|
|
901
|
-
const requiredScopes =
|
|
902
|
-
(toolConfig as any).requiredScopes ??
|
|
903
|
-
(toolConfig as any).required_scopes ??
|
|
904
|
-
(toolConfig as any).scopes ??
|
|
905
|
-
[];
|
|
906
|
-
const oauthProvider =
|
|
907
|
-
(toolConfig as any).oauthProvider ??
|
|
908
|
-
(toolConfig as any).oauth_provider ??
|
|
909
|
-
undefined;
|
|
910
|
-
const riskLevel =
|
|
911
|
-
(toolConfig as any).riskLevel ??
|
|
912
|
-
(toolConfig as any).risk_level ??
|
|
913
|
-
undefined;
|
|
914
|
-
|
|
915
|
-
toolProtections[toolName] = {
|
|
916
|
-
requiresDelegation,
|
|
917
|
-
requiredScopes,
|
|
918
|
-
...(oauthProvider && { oauthProvider }),
|
|
919
|
-
...(riskLevel && { riskLevel }),
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
// Construct fresh config (ToolProtectionConfig type)
|
|
926
|
-
const freshConfig: ToolProtectionConfig = {
|
|
927
|
-
toolProtections,
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
// 3. Write fresh config to cache
|
|
931
|
-
const ttl = this.config.cacheTtl ?? 300000;
|
|
932
|
-
await this.cache.set(cacheKey, freshConfig, ttl);
|
|
933
|
-
|
|
934
|
-
console.log("[ToolProtectionService] Fresh config fetched and cached", {
|
|
935
|
-
cacheKey,
|
|
936
|
-
toolCount: Object.keys(toolProtections).length,
|
|
937
|
-
protectedTools: Object.entries(toolProtections)
|
|
938
|
-
.filter(([_, cfg]) => cfg.requiresDelegation)
|
|
939
|
-
.map(([name]) => name),
|
|
940
|
-
source: "api",
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
return { config: freshConfig, cacheKey, source: 'api' };
|
|
944
|
-
} catch (error) {
|
|
945
|
-
console.warn("[ToolProtectionService] API fetch failed during refresh, using fallback", {
|
|
946
|
-
error: error instanceof Error ? error.message : String(error),
|
|
947
|
-
cacheKey,
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
// Use fallback config if API fails
|
|
951
|
-
const fallbackConfig: ToolProtectionConfig = this.config.fallbackConfig || {
|
|
952
|
-
toolProtections: {},
|
|
953
|
-
};
|
|
835
|
+
console.log("[ToolProtectionService] Cache entry deleted (next call will fetch fresh)", {
|
|
836
|
+
cacheKey,
|
|
837
|
+
note: "No cache warming - upstream APIs may have CDN caching",
|
|
838
|
+
});
|
|
954
839
|
|
|
955
|
-
|
|
956
|
-
|
|
840
|
+
// Return empty config - next tool call will fetch fresh from API
|
|
841
|
+
return {
|
|
842
|
+
config: { toolProtections: {} },
|
|
843
|
+
cacheKey,
|
|
844
|
+
source: 'cleared'
|
|
845
|
+
};
|
|
957
846
|
}
|
|
958
847
|
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Platform-agnostic cache interface for OAuth provider configurations
|
|
3
|
-
*
|
|
4
|
-
* This interface allows different runtime adapters to provide their own
|
|
5
|
-
* caching implementations (e.g., in-memory for Node.js, KV for Cloudflare)
|
|
6
|
-
*
|
|
7
|
-
* @package @kya-os/mcp-i-core
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* In-memory cache implementation
|
|
11
|
-
*
|
|
12
|
-
* Suitable for:
|
|
13
|
-
* - Node.js runtimes
|
|
14
|
-
* - Development/testing
|
|
15
|
-
* - Single-instance deployments
|
|
16
|
-
*
|
|
17
|
-
* NOT suitable for:
|
|
18
|
-
* - Multi-instance deployments (cache not shared)
|
|
19
|
-
* - Serverless environments (state not persisted)
|
|
20
|
-
*/
|
|
21
|
-
export class InMemoryOAuthConfigCache {
|
|
22
|
-
cache = new Map();
|
|
23
|
-
async get(key) {
|
|
24
|
-
const entry = this.cache.get(key);
|
|
25
|
-
if (!entry) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
// Check if expired
|
|
29
|
-
if (Date.now() > entry.expiresAt) {
|
|
30
|
-
this.cache.delete(key);
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
return entry.value;
|
|
34
|
-
}
|
|
35
|
-
async set(key, value, ttl) {
|
|
36
|
-
// If TTL is <= 0, don't store (entry would be immediately expired)
|
|
37
|
-
if (ttl <= 0) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const expiresAt = Date.now() + ttl;
|
|
41
|
-
this.cache.set(key, { value, expiresAt });
|
|
42
|
-
}
|
|
43
|
-
async clear() {
|
|
44
|
-
this.cache.clear();
|
|
45
|
-
}
|
|
46
|
-
async delete(key) {
|
|
47
|
-
this.cache.delete(key);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* No-op cache implementation (disables caching)
|
|
52
|
-
*
|
|
53
|
-
* Use when:
|
|
54
|
-
* - You want to disable caching entirely
|
|
55
|
-
* - Testing scenarios that require fresh data
|
|
56
|
-
*/
|
|
57
|
-
export class NoOpOAuthConfigCache {
|
|
58
|
-
async get(_key) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
async set(_key, _value, _ttl) {
|
|
62
|
-
// No-op
|
|
63
|
-
}
|
|
64
|
-
async clear() {
|
|
65
|
-
// No-op
|
|
66
|
-
}
|
|
67
|
-
async delete(_key) {
|
|
68
|
-
// No-op
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
//# sourceMappingURL=oauth-config-cache.js.map
|
package/src/providers/base.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base Provider Classes
|
|
3
|
-
*
|
|
4
|
-
* Abstract classes that define the provider interfaces for
|
|
5
|
-
* platform-specific implementations.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Cryptographic operations provider
|
|
9
|
-
*/
|
|
10
|
-
export class CryptoProvider {
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Clock/timing operations provider
|
|
14
|
-
*/
|
|
15
|
-
export class ClockProvider {
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Network fetch operations provider
|
|
19
|
-
*/
|
|
20
|
-
export class FetchProvider {
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Storage operations provider
|
|
24
|
-
*/
|
|
25
|
-
export class StorageProvider {
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Nonce cache provider
|
|
29
|
-
* Handles replay prevention
|
|
30
|
-
*
|
|
31
|
-
* Nonces should be scoped per agent to prevent cross-agent replay attacks.
|
|
32
|
-
* When agentDid is provided, implementations should use agent-scoped keys.
|
|
33
|
-
*/
|
|
34
|
-
export class NonceCacheProvider {
|
|
35
|
-
}
|
|
36
|
-
export class IdentityProvider {
|
|
37
|
-
}
|
|
38
|
-
//# sourceMappingURL=base.js.map
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OAuth Config Service
|
|
3
|
-
*
|
|
4
|
-
* Fetches and caches OAuth provider configurations from AgentShield API.
|
|
5
|
-
* Provides caching with TTL to reduce API calls.
|
|
6
|
-
*
|
|
7
|
-
* @package @kya-os/mcp-i-core
|
|
8
|
-
*/
|
|
9
|
-
import { InMemoryOAuthConfigCache } from "../cache/oauth-config-cache.js";
|
|
10
|
-
/**
|
|
11
|
-
* Service for fetching OAuth provider configurations from AgentShield API
|
|
12
|
-
*/
|
|
13
|
-
export class OAuthConfigService {
|
|
14
|
-
config;
|
|
15
|
-
constructor(config) {
|
|
16
|
-
this.config = {
|
|
17
|
-
baseUrl: config.baseUrl,
|
|
18
|
-
apiKey: config.apiKey,
|
|
19
|
-
fetchProvider: config.fetchProvider,
|
|
20
|
-
cacheTtl: config.cacheTtl ?? 5 * 60 * 1000, // Default 5 minutes
|
|
21
|
-
logger: config.logger || (() => { }),
|
|
22
|
-
cache: config.cache || new InMemoryOAuthConfigCache(),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Get OAuth configuration for a project
|
|
27
|
-
*
|
|
28
|
-
* Fetches from AgentShield API: GET /api/v1/bouncer/projects/{projectId}/providers
|
|
29
|
-
* Caches responses with TTL to reduce API calls.
|
|
30
|
-
*
|
|
31
|
-
* @param projectId - Project ID to fetch config for
|
|
32
|
-
* @returns OAuth configuration with providers object
|
|
33
|
-
*/
|
|
34
|
-
async getOAuthConfig(projectId) {
|
|
35
|
-
// Check cache
|
|
36
|
-
const cached = await this.config.cache.get(projectId);
|
|
37
|
-
if (cached) {
|
|
38
|
-
this.config.logger("[OAuthConfigService] Cache hit", {
|
|
39
|
-
projectId,
|
|
40
|
-
});
|
|
41
|
-
return cached;
|
|
42
|
-
}
|
|
43
|
-
// Fetch from AgentShield API
|
|
44
|
-
const url = `${this.config.baseUrl}/api/v1/bouncer/projects/${encodeURIComponent(projectId)}/providers`;
|
|
45
|
-
this.config.logger("[OAuthConfigService] Fetching config from API", {
|
|
46
|
-
projectId,
|
|
47
|
-
url,
|
|
48
|
-
});
|
|
49
|
-
try {
|
|
50
|
-
const response = await this.config.fetchProvider.fetch(url, {
|
|
51
|
-
method: "GET",
|
|
52
|
-
headers: {
|
|
53
|
-
Authorization: `Bearer ${this.config.apiKey}`,
|
|
54
|
-
"Content-Type": "application/json",
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
if (!response.ok) {
|
|
58
|
-
const errorText = await response.text().catch(() => "Unknown error");
|
|
59
|
-
throw new Error(`Failed to fetch OAuth config: ${response.status} ${response.statusText} - ${errorText}`);
|
|
60
|
-
}
|
|
61
|
-
const result = await response.json();
|
|
62
|
-
// Validate response structure
|
|
63
|
-
if (!result.success || !result.data) {
|
|
64
|
-
throw new Error("Invalid API response: missing success or data field");
|
|
65
|
-
}
|
|
66
|
-
// Parse providers object
|
|
67
|
-
const providers = result.data.providers || {};
|
|
68
|
-
if (typeof providers !== "object" || Array.isArray(providers)) {
|
|
69
|
-
throw new Error("Invalid API response: providers must be an object, not an array");
|
|
70
|
-
}
|
|
71
|
-
// Build OAuthConfig object
|
|
72
|
-
// Note: API does NOT return defaultProvider field (Phase 1 architecture)
|
|
73
|
-
// Phase 1 uses configured provider as temporary fallback
|
|
74
|
-
// Phase 2+ requires tools to explicitly specify oauthProvider
|
|
75
|
-
const config = {
|
|
76
|
-
providers: providers,
|
|
77
|
-
};
|
|
78
|
-
// Cache config
|
|
79
|
-
await this.config.cache.set(projectId, config, this.config.cacheTtl);
|
|
80
|
-
this.config.logger("[OAuthConfigService] Config fetched and cached", {
|
|
81
|
-
projectId,
|
|
82
|
-
providerCount: Object.keys(providers).length,
|
|
83
|
-
providers: Object.keys(providers),
|
|
84
|
-
expiresAt: new Date(Date.now() + this.config.cacheTtl).toISOString(),
|
|
85
|
-
});
|
|
86
|
-
return config;
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
this.config.logger("[OAuthConfigService] API fetch failed", {
|
|
90
|
-
projectId,
|
|
91
|
-
error: error instanceof Error ? error.message : String(error),
|
|
92
|
-
});
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Clear cached config for a project
|
|
98
|
-
*
|
|
99
|
-
* @param projectId - Project ID to clear cache for
|
|
100
|
-
*/
|
|
101
|
-
async clearCache(projectId) {
|
|
102
|
-
await this.config.cache.delete(projectId);
|
|
103
|
-
this.config.logger("[OAuthConfigService] Cache cleared", { projectId });
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Clear all cached configs
|
|
107
|
-
*/
|
|
108
|
-
async clearAllCache() {
|
|
109
|
-
await this.config.cache.clear();
|
|
110
|
-
this.config.logger("[OAuthConfigService] All cache cleared");
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
//# sourceMappingURL=oauth-config.service.js.map
|