@kya-os/mcp-i-core 1.4.14 → 1.4.17
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/dist/compliance/schema-registry.d.ts +3 -3
- package/dist/compliance/schema-registry.js +4 -4
- package/dist/compliance/schema-verifier.js +1 -1
- package/dist/config/remote-config.d.ts +53 -0
- package/dist/config/remote-config.js +75 -0
- package/dist/delegation/delegation-graph.js +15 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +14 -2
- package/dist/services/policy.service.d.ts +130 -0
- package/dist/services/policy.service.js +399 -0
- package/dist/services/tool-context-builder.js +22 -1
- package/dist/types/oauth-required-error.d.ts +6 -0
- package/dist/types/oauth-required-error.js +2 -0
- package/package.json +11 -10
- package/scripts/audit-compliance.ts +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema Registry
|
|
3
3
|
*
|
|
4
|
-
* Canonical list of all schemas from
|
|
4
|
+
* Canonical list of all schemas from schema.modelcontextprotocol-identity.io
|
|
5
5
|
* Used for automated compliance verification.
|
|
6
6
|
*
|
|
7
|
-
* Auto-generated from https://
|
|
7
|
+
* Auto-generated from https://schema.modelcontextprotocol-identity.io/schema-index.json
|
|
8
8
|
* Last updated: 2025-10-17
|
|
9
9
|
*/
|
|
10
10
|
import type { SchemaMetadata } from './schema-verifier';
|
|
11
11
|
/**
|
|
12
|
-
* Complete registry of schemas from
|
|
12
|
+
* Complete registry of schemas from schema.modelcontextprotocol-identity.io
|
|
13
13
|
*
|
|
14
14
|
* As of 2025-10-17, there are 38 schemas covering:
|
|
15
15
|
* - W3C Verifiable Credentials
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Schema Registry
|
|
4
4
|
*
|
|
5
|
-
* Canonical list of all schemas from
|
|
5
|
+
* Canonical list of all schemas from schema.modelcontextprotocol-identity.io
|
|
6
6
|
* Used for automated compliance verification.
|
|
7
7
|
*
|
|
8
|
-
* Auto-generated from https://
|
|
8
|
+
* Auto-generated from https://schema.modelcontextprotocol-identity.io/schema-index.json
|
|
9
9
|
* Last updated: 2025-10-17
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -15,9 +15,9 @@ exports.getSchemasByCategory = getSchemasByCategory;
|
|
|
15
15
|
exports.getSchemaById = getSchemaById;
|
|
16
16
|
exports.getCriticalSchemas = getCriticalSchemas;
|
|
17
17
|
exports.getSchemaStats = getSchemaStats;
|
|
18
|
-
const SCHEMAS_BASE_URL = 'https://
|
|
18
|
+
const SCHEMAS_BASE_URL = 'https://schema.modelcontextprotocol-identity.io/xmcp-i';
|
|
19
19
|
/**
|
|
20
|
-
* Complete registry of schemas from
|
|
20
|
+
* Complete registry of schemas from schema.modelcontextprotocol-identity.io
|
|
21
21
|
*
|
|
22
22
|
* As of 2025-10-17, there are 38 schemas covering:
|
|
23
23
|
* - W3C Verifiable Credentials
|
|
@@ -17,7 +17,7 @@ exports.createSchemaVerifier = createSchemaVerifier;
|
|
|
17
17
|
* Schema Verifier with JSON Schema draft-07 support
|
|
18
18
|
*/
|
|
19
19
|
class SchemaVerifier {
|
|
20
|
-
schemasBaseUrl = 'https://
|
|
20
|
+
schemasBaseUrl = 'https://schema.modelcontextprotocol-identity.io';
|
|
21
21
|
schemaCache = new Map();
|
|
22
22
|
constructor(options) {
|
|
23
23
|
if (options?.schemasBaseUrl) {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { MCPIConfig } from '@kya-os/contracts/config';
|
|
10
10
|
import type { ToolProtection, ToolProtectionMap } from '@kya-os/contracts/tool-protection';
|
|
11
|
+
import type { PolicyConfig } from '@kya-os/contracts';
|
|
11
12
|
/**
|
|
12
13
|
* Options for fetching remote configuration
|
|
13
14
|
*/
|
|
@@ -117,4 +118,56 @@ export declare function hasMergedToolProtections(config: unknown): config is {
|
|
|
117
118
|
tools: ToolProtectionMap;
|
|
118
119
|
};
|
|
119
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* Get policy configuration from a merged config
|
|
123
|
+
*
|
|
124
|
+
* This helper function extracts policy configuration from a merged config response.
|
|
125
|
+
* Returns null if no policy is present in the config.
|
|
126
|
+
*
|
|
127
|
+
* @param config - Config object that may contain policy
|
|
128
|
+
* @returns Policy configuration or null if not present
|
|
129
|
+
*
|
|
130
|
+
* @since 1.7.0
|
|
131
|
+
*/
|
|
132
|
+
export declare function getPolicy(config: {
|
|
133
|
+
policy?: PolicyConfig;
|
|
134
|
+
} | null | undefined): PolicyConfig | null;
|
|
135
|
+
/**
|
|
136
|
+
* Extract policy configuration from merged config with defaults
|
|
137
|
+
*
|
|
138
|
+
* This helper function extracts the policy from a merged config.
|
|
139
|
+
* Returns the default policy config if no policy is found.
|
|
140
|
+
*
|
|
141
|
+
* @param config - Config object that may contain policy
|
|
142
|
+
* @returns Policy configuration (never null, returns defaults if missing)
|
|
143
|
+
*
|
|
144
|
+
* @since 1.7.0
|
|
145
|
+
*/
|
|
146
|
+
export declare function extractPolicy(config: {
|
|
147
|
+
policy?: PolicyConfig;
|
|
148
|
+
} | null | undefined): PolicyConfig;
|
|
149
|
+
/**
|
|
150
|
+
* Check if config has embedded policy configuration
|
|
151
|
+
*
|
|
152
|
+
* Utility to check if a config response includes policy configuration.
|
|
153
|
+
*
|
|
154
|
+
* @param config - Config object to check
|
|
155
|
+
* @returns True if config has policy, false otherwise
|
|
156
|
+
*
|
|
157
|
+
* @since 1.7.0
|
|
158
|
+
*/
|
|
159
|
+
export declare function hasMergedPolicy(config: unknown): config is {
|
|
160
|
+
policy: PolicyConfig;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Check if policy enforcement is enabled for a config
|
|
164
|
+
*
|
|
165
|
+
* @param config - Config object that may contain policy
|
|
166
|
+
* @returns True if policy is present and enabled
|
|
167
|
+
*
|
|
168
|
+
* @since 1.7.0
|
|
169
|
+
*/
|
|
170
|
+
export declare function isPolicyEnabled(config: {
|
|
171
|
+
policy?: PolicyConfig;
|
|
172
|
+
} | null | undefined): boolean;
|
|
120
173
|
//# sourceMappingURL=remote-config.d.ts.map
|
|
@@ -12,7 +12,12 @@ exports.fetchRemoteConfig = fetchRemoteConfig;
|
|
|
12
12
|
exports.getToolProtection = getToolProtection;
|
|
13
13
|
exports.extractToolProtections = extractToolProtections;
|
|
14
14
|
exports.hasMergedToolProtections = hasMergedToolProtections;
|
|
15
|
+
exports.getPolicy = getPolicy;
|
|
16
|
+
exports.extractPolicy = extractPolicy;
|
|
17
|
+
exports.hasMergedPolicy = hasMergedPolicy;
|
|
18
|
+
exports.isPolicyEnabled = isPolicyEnabled;
|
|
15
19
|
const agentshield_api_1 = require("@kya-os/contracts/agentshield-api");
|
|
20
|
+
const contracts_1 = require("@kya-os/contracts");
|
|
16
21
|
/**
|
|
17
22
|
* Fetch configuration from remote API (AgentShield dashboard)
|
|
18
23
|
*
|
|
@@ -175,4 +180,74 @@ function hasMergedToolProtections(config) {
|
|
|
175
180
|
c.toolProtection.tools !== null // typeof null === 'object' in JS
|
|
176
181
|
);
|
|
177
182
|
}
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Policy Configuration Helpers
|
|
185
|
+
// ============================================================================
|
|
186
|
+
/**
|
|
187
|
+
* Get policy configuration from a merged config
|
|
188
|
+
*
|
|
189
|
+
* This helper function extracts policy configuration from a merged config response.
|
|
190
|
+
* Returns null if no policy is present in the config.
|
|
191
|
+
*
|
|
192
|
+
* @param config - Config object that may contain policy
|
|
193
|
+
* @returns Policy configuration or null if not present
|
|
194
|
+
*
|
|
195
|
+
* @since 1.7.0
|
|
196
|
+
*/
|
|
197
|
+
function getPolicy(config) {
|
|
198
|
+
if (!config?.policy) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return config.policy;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Extract policy configuration from merged config with defaults
|
|
205
|
+
*
|
|
206
|
+
* This helper function extracts the policy from a merged config.
|
|
207
|
+
* Returns the default policy config if no policy is found.
|
|
208
|
+
*
|
|
209
|
+
* @param config - Config object that may contain policy
|
|
210
|
+
* @returns Policy configuration (never null, returns defaults if missing)
|
|
211
|
+
*
|
|
212
|
+
* @since 1.7.0
|
|
213
|
+
*/
|
|
214
|
+
function extractPolicy(config) {
|
|
215
|
+
if (!config?.policy) {
|
|
216
|
+
// Return a deep clone to prevent mutation of the shared default
|
|
217
|
+
return structuredClone(contracts_1.DEFAULT_POLICY_CONFIG);
|
|
218
|
+
}
|
|
219
|
+
return config.policy;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Check if config has embedded policy configuration
|
|
223
|
+
*
|
|
224
|
+
* Utility to check if a config response includes policy configuration.
|
|
225
|
+
*
|
|
226
|
+
* @param config - Config object to check
|
|
227
|
+
* @returns True if config has policy, false otherwise
|
|
228
|
+
*
|
|
229
|
+
* @since 1.7.0
|
|
230
|
+
*/
|
|
231
|
+
function hasMergedPolicy(config) {
|
|
232
|
+
if (!config || typeof config !== 'object') {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const c = config;
|
|
236
|
+
return (c.policy !== undefined &&
|
|
237
|
+
typeof c.policy === 'object' &&
|
|
238
|
+
c.policy !== null);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Check if policy enforcement is enabled for a config
|
|
242
|
+
*
|
|
243
|
+
* @param config - Config object that may contain policy
|
|
244
|
+
* @returns True if policy is present and enabled
|
|
245
|
+
*
|
|
246
|
+
* @since 1.7.0
|
|
247
|
+
*/
|
|
248
|
+
function isPolicyEnabled(config) {
|
|
249
|
+
const policy = getPolicy(config);
|
|
250
|
+
// Default to true to match DEFAULT_POLICY_CONFIG.enabled and PolicyService.isEnabled()
|
|
251
|
+
return policy?.enabled ?? true;
|
|
252
|
+
}
|
|
178
253
|
//# sourceMappingURL=remote-config.js.map
|
|
@@ -168,6 +168,21 @@ class DelegationGraphManager {
|
|
|
168
168
|
reason: `Invalid chain: ${child.id} parentId=${child.parentId} but actual parent is ${parent.id}`,
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
|
+
// TODO(security): Add constraint attenuation validation for multi-level chains
|
|
172
|
+
//
|
|
173
|
+
// When multi-agent delegation is supported (Agent → Sub-Agent), this should call:
|
|
174
|
+
// areChildConstraintsValid(parentConstraints, childConstraints)
|
|
175
|
+
// from @kya-os/contracts/delegation to enforce constraint monotonicity:
|
|
176
|
+
// - Child time bounds must be within parent bounds
|
|
177
|
+
// - Child budget must be ≤ parent budget
|
|
178
|
+
// - Child scopes must be subset of parent scopes
|
|
179
|
+
//
|
|
180
|
+
// Currently only single-level delegations (User → Agent) are used in production,
|
|
181
|
+
// so constraint attenuation is not enforced. The DelegationNode interface would
|
|
182
|
+
// need a `constraints?: DelegationConstraints` field to enable this.
|
|
183
|
+
//
|
|
184
|
+
// See: packages/contracts/src/delegation/constraints.ts:163 (areChildConstraintsValid)
|
|
185
|
+
// Spec: MCP-I §4.2 (CRISP Constraints)
|
|
171
186
|
}
|
|
172
187
|
return { valid: true };
|
|
173
188
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type { RuntimeWithAccessControl } from "./runtime/base";
|
|
|
11
11
|
export type { IAuditLogger } from "./runtime/audit-logger";
|
|
12
12
|
export * from "./utils";
|
|
13
13
|
export { ToolProtectionService } from "./services/tool-protection.service";
|
|
14
|
+
export { PolicyService } from "./services/policy.service";
|
|
15
|
+
export type { PolicyEvaluationContext, PolicyEvaluationResult, PolicyServiceConfig, } from "./services/policy.service";
|
|
14
16
|
export { CryptoService } from "./services/crypto.service";
|
|
15
17
|
export type { Ed25519JWK, ParsedJWS } from "./services/crypto.service";
|
|
16
18
|
export { ProofVerifier } from "./services/proof-verifier";
|
|
@@ -62,7 +64,7 @@ export { base64urlEncodeFromBytes, base64urlEncodeFromString, base64urlDecodeToB
|
|
|
62
64
|
import type { HandshakeRequest, SessionContext, NonceCache, NonceCacheEntry, NonceCacheConfig, ProofMeta, DetachedProof, CanonicalHashes, AuditRecord } from "@kya-os/contracts";
|
|
63
65
|
export type { HandshakeRequest, SessionContext, NonceCache, NonceCacheEntry, NonceCacheConfig, ProofMeta, DetachedProof, CanonicalHashes, AuditRecord, };
|
|
64
66
|
export * from "./config";
|
|
65
|
-
export { fetchRemoteConfig, type RemoteConfigCache, type RemoteConfigOptions, } from "./config/remote-config";
|
|
67
|
+
export { fetchRemoteConfig, type RemoteConfigCache, type RemoteConfigOptions, getToolProtection, extractToolProtections, hasMergedToolProtections, getPolicy, extractPolicy, hasMergedPolicy, isPolicyEnabled, } from "./config/remote-config";
|
|
66
68
|
export { UserDidManager } from "./identity/user-did-manager";
|
|
67
69
|
export type { UserDidStorage, UserDidManagerConfig, UserKeyPair, OAuthIdentity, } from "./identity/user-did-manager";
|
|
68
70
|
export { IdpTokenResolver } from "./identity/idp-token-resolver";
|
package/dist/index.js
CHANGED
|
@@ -20,8 +20,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
20
20
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
21
|
};
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.
|
|
24
|
-
exports.createDefaultConsoleLogger = exports.logger = exports.IdpTokenResolver = exports.UserDidManager = exports.fetchRemoteConfig = exports.bytesToBase64 = exports.base64urlDecodeToString = exports.base64urlDecodeToBytes = exports.base64urlEncodeFromString = exports.base64urlEncodeFromBytes = exports.parseVCJWT = exports.completeVCJWT = exports.createUnsignedVCJWT = exports.canonicalizeJSON = exports.getSchemaStats = exports.getCriticalSchemas = exports.getSchemaById = exports.getSchemasByCategory = exports.getAllSchemas = exports.SCHEMA_REGISTRY = exports.createSchemaVerifier = exports.SchemaVerifier = exports.isValidBase58 = exports.base58Decode = exports.base58Encode = exports.resolveDidKeySync = exports.publicKeyToJwk = exports.extractPublicKeyFromDidKey = exports.isEd25519DidKey = exports.createDidKeyResolver = exports.MemoryDelegationGraphStorage = exports.MemoryStatusListStorage = exports.createCascadingRevocationManager = exports.CascadingRevocationManager = exports.createDelegationGraph = exports.DelegationGraphManager = exports.isIndexSet = void 0;
|
|
23
|
+
exports.createStatusListManager = exports.StatusList2021Manager = exports.createDelegationVerifier = exports.DelegationCredentialVerifier = exports.createDelegationIssuer = exports.DelegationCredentialIssuer = exports.createDelegationErrorFormatter = exports.DelegationErrorFormatter = exports.OAuthRequiredError = exports.DelegationRequiredError = exports.NoOpToolProtectionCache = exports.InMemoryToolProtectionCache = exports.createProofVerificationError = exports.PROOF_VERIFICATION_ERROR_CODES = exports.ProofVerificationError = exports.migrateLegacyKeys = exports.StorageKeyHelpers = exports.createStorageProviders = exports.NoOpOAuthConfigCache = exports.InMemoryOAuthConfigCache = exports.BatchDelegationService = exports.OAuthTokenRetrievalService = exports.ProviderValidationError = exports.ProviderValidator = exports.CredentialAuthModeError = exports.ConsentOnlyModeError = exports.ProviderResolver = exports.OAuthProviderRegistry = exports.ToolContextBuilder = exports.defaultSecretResolver = exports.OAuthService = exports.OAuthConfigService = exports.createSessionRegistrationService = exports.SessionRegistrationService = exports.authorizationMatches = exports.AccessControlApiService = exports.ProofVerifier = exports.CryptoService = exports.PolicyService = exports.ToolProtectionService = exports.MCPIRuntimeBase = exports.MemoryIdentityProvider = exports.MemoryNonceCacheProvider = exports.MemoryStorageProvider = exports.IdentityProvider = exports.NonceCacheProvider = exports.StorageProvider = exports.FetchProvider = exports.ClockProvider = exports.CryptoProvider = void 0;
|
|
24
|
+
exports.createDefaultConsoleLogger = exports.logger = exports.IdpTokenResolver = exports.UserDidManager = exports.isPolicyEnabled = exports.hasMergedPolicy = exports.extractPolicy = exports.getPolicy = exports.hasMergedToolProtections = exports.extractToolProtections = exports.getToolProtection = exports.fetchRemoteConfig = exports.bytesToBase64 = exports.base64urlDecodeToString = exports.base64urlDecodeToBytes = exports.base64urlEncodeFromString = exports.base64urlEncodeFromBytes = exports.parseVCJWT = exports.completeVCJWT = exports.createUnsignedVCJWT = exports.canonicalizeJSON = exports.getSchemaStats = exports.getCriticalSchemas = exports.getSchemaById = exports.getSchemasByCategory = exports.getAllSchemas = exports.SCHEMA_REGISTRY = exports.createSchemaVerifier = exports.SchemaVerifier = exports.isValidBase58 = exports.base58Decode = exports.base58Encode = exports.resolveDidKeySync = exports.publicKeyToJwk = exports.extractPublicKeyFromDidKey = exports.isEd25519DidKey = exports.createDidKeyResolver = exports.MemoryDelegationGraphStorage = exports.MemoryStatusListStorage = exports.createCascadingRevocationManager = exports.CascadingRevocationManager = exports.createDelegationGraph = exports.DelegationGraphManager = exports.isIndexSet = exports.BitstringManager = void 0;
|
|
25
25
|
// Base providers
|
|
26
26
|
var base_1 = require("./providers/base");
|
|
27
27
|
Object.defineProperty(exports, "CryptoProvider", { enumerable: true, get: function () { return base_1.CryptoProvider; } });
|
|
@@ -43,6 +43,9 @@ __exportStar(require("./utils"), exports);
|
|
|
43
43
|
// Tool Protection
|
|
44
44
|
var tool_protection_service_1 = require("./services/tool-protection.service");
|
|
45
45
|
Object.defineProperty(exports, "ToolProtectionService", { enumerable: true, get: function () { return tool_protection_service_1.ToolProtectionService; } });
|
|
46
|
+
// Policy Service (v1.7.0)
|
|
47
|
+
var policy_service_1 = require("./services/policy.service");
|
|
48
|
+
Object.defineProperty(exports, "PolicyService", { enumerable: true, get: function () { return policy_service_1.PolicyService; } });
|
|
46
49
|
// Crypto Service
|
|
47
50
|
var crypto_service_1 = require("./services/crypto.service");
|
|
48
51
|
Object.defineProperty(exports, "CryptoService", { enumerable: true, get: function () { return crypto_service_1.CryptoService; } });
|
|
@@ -176,6 +179,15 @@ __exportStar(require("./config"), exports);
|
|
|
176
179
|
// Remote configuration fetching
|
|
177
180
|
var remote_config_1 = require("./config/remote-config");
|
|
178
181
|
Object.defineProperty(exports, "fetchRemoteConfig", { enumerable: true, get: function () { return remote_config_1.fetchRemoteConfig; } });
|
|
182
|
+
// Tool protection helpers
|
|
183
|
+
Object.defineProperty(exports, "getToolProtection", { enumerable: true, get: function () { return remote_config_1.getToolProtection; } });
|
|
184
|
+
Object.defineProperty(exports, "extractToolProtections", { enumerable: true, get: function () { return remote_config_1.extractToolProtections; } });
|
|
185
|
+
Object.defineProperty(exports, "hasMergedToolProtections", { enumerable: true, get: function () { return remote_config_1.hasMergedToolProtections; } });
|
|
186
|
+
// Policy helpers (v1.7.0)
|
|
187
|
+
Object.defineProperty(exports, "getPolicy", { enumerable: true, get: function () { return remote_config_1.getPolicy; } });
|
|
188
|
+
Object.defineProperty(exports, "extractPolicy", { enumerable: true, get: function () { return remote_config_1.extractPolicy; } });
|
|
189
|
+
Object.defineProperty(exports, "hasMergedPolicy", { enumerable: true, get: function () { return remote_config_1.hasMergedPolicy; } });
|
|
190
|
+
Object.defineProperty(exports, "isPolicyEnabled", { enumerable: true, get: function () { return remote_config_1.isPolicyEnabled; } });
|
|
179
191
|
// User DID Manager (Phase 4)
|
|
180
192
|
var user_did_manager_1 = require("./identity/user-did-manager");
|
|
181
193
|
Object.defineProperty(exports, "UserDidManager", { enumerable: true, get: function () { return user_did_manager_1.UserDidManager; } });
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PolicyService - Evaluates agent access policies
|
|
3
|
+
*
|
|
4
|
+
* This service evaluates incoming requests against configured policy rules
|
|
5
|
+
* to determine the appropriate enforcement action (allow, block, challenge, redirect).
|
|
6
|
+
*
|
|
7
|
+
* Policy evaluation order:
|
|
8
|
+
* 1. Allow list (exempt agents, always allowed)
|
|
9
|
+
* 2. Deny list (blocked agents, always blocked)
|
|
10
|
+
* 3. Threshold checks (confidence, reputation)
|
|
11
|
+
* 4. Custom rules (evaluated by priority, first match wins)
|
|
12
|
+
* 5. Default action (if no rules match)
|
|
13
|
+
*
|
|
14
|
+
* @package @kya-os/mcp-i-core
|
|
15
|
+
* @since 1.7.0
|
|
16
|
+
*/
|
|
17
|
+
import type { PolicyConfig, EnforcementAction } from '@kya-os/contracts';
|
|
18
|
+
/**
|
|
19
|
+
* Context for policy evaluation
|
|
20
|
+
* Contains all attributes about the request/agent that can be matched against
|
|
21
|
+
*/
|
|
22
|
+
export interface PolicyEvaluationContext {
|
|
23
|
+
agentType?: string;
|
|
24
|
+
agentName?: string;
|
|
25
|
+
agentVendor?: string;
|
|
26
|
+
clientDid?: string;
|
|
27
|
+
agentDid?: string;
|
|
28
|
+
confidence?: number;
|
|
29
|
+
reputationScore?: number;
|
|
30
|
+
riskLevel?: 'low' | 'medium' | 'high' | 'critical';
|
|
31
|
+
path?: string;
|
|
32
|
+
method?: string;
|
|
33
|
+
origin?: string;
|
|
34
|
+
userAgent?: string;
|
|
35
|
+
signatureVerified?: boolean;
|
|
36
|
+
isAuthenticated?: boolean;
|
|
37
|
+
hasValidDelegation?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Result of policy evaluation
|
|
41
|
+
*/
|
|
42
|
+
export interface PolicyEvaluationResult {
|
|
43
|
+
/** Enforcement action to take */
|
|
44
|
+
action: EnforcementAction;
|
|
45
|
+
/** Reason for the action */
|
|
46
|
+
reason: string;
|
|
47
|
+
/** Rule ID that matched (if any) */
|
|
48
|
+
ruleId?: string;
|
|
49
|
+
/** Rule name that matched (if any) */
|
|
50
|
+
ruleName?: string;
|
|
51
|
+
/** Redirect URL (if action is 'redirect') */
|
|
52
|
+
redirectUrl?: string;
|
|
53
|
+
/** Custom message from the matched rule */
|
|
54
|
+
message?: string;
|
|
55
|
+
/** Whether this was matched by allow/deny list */
|
|
56
|
+
matchType: 'allow-list' | 'deny-list' | 'threshold' | 'rule' | 'default';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Service configuration
|
|
60
|
+
*/
|
|
61
|
+
export interface PolicyServiceConfig {
|
|
62
|
+
/** Enable debug logging */
|
|
63
|
+
debug?: boolean;
|
|
64
|
+
}
|
|
65
|
+
export declare class PolicyService {
|
|
66
|
+
private config;
|
|
67
|
+
private policy;
|
|
68
|
+
constructor(policy?: PolicyConfig, config?: PolicyServiceConfig);
|
|
69
|
+
/**
|
|
70
|
+
* Update the policy configuration
|
|
71
|
+
*/
|
|
72
|
+
setPolicy(policy: PolicyConfig): void;
|
|
73
|
+
/**
|
|
74
|
+
* Get the current policy configuration
|
|
75
|
+
*/
|
|
76
|
+
getPolicy(): PolicyConfig;
|
|
77
|
+
/**
|
|
78
|
+
* Check if policy enforcement is enabled
|
|
79
|
+
*/
|
|
80
|
+
isEnabled(): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Evaluate a request against the policy
|
|
83
|
+
*
|
|
84
|
+
* @param context - Request/agent context to evaluate
|
|
85
|
+
* @returns Evaluation result with action and reason
|
|
86
|
+
*/
|
|
87
|
+
evaluate(context: PolicyEvaluationContext): PolicyEvaluationResult;
|
|
88
|
+
/**
|
|
89
|
+
* Check if context matches any allow list entry
|
|
90
|
+
*/
|
|
91
|
+
private checkAllowList;
|
|
92
|
+
/**
|
|
93
|
+
* Check if context matches any deny list entry
|
|
94
|
+
*/
|
|
95
|
+
private checkDenyList;
|
|
96
|
+
/**
|
|
97
|
+
* Check if context matches a list entry (allow or deny)
|
|
98
|
+
*/
|
|
99
|
+
private matchesListEntry;
|
|
100
|
+
/**
|
|
101
|
+
* Check threshold-based enforcement
|
|
102
|
+
*/
|
|
103
|
+
private checkThresholds;
|
|
104
|
+
/**
|
|
105
|
+
* Evaluate custom policy rules
|
|
106
|
+
*/
|
|
107
|
+
private evaluateRules;
|
|
108
|
+
/**
|
|
109
|
+
* Check if context matches a rule (all conditions must match)
|
|
110
|
+
*/
|
|
111
|
+
private matchesRule;
|
|
112
|
+
/**
|
|
113
|
+
* Check if context matches a single condition
|
|
114
|
+
*/
|
|
115
|
+
private matchesCondition;
|
|
116
|
+
/**
|
|
117
|
+
* Get a value from the context by field name
|
|
118
|
+
*/
|
|
119
|
+
private getContextValue;
|
|
120
|
+
/**
|
|
121
|
+
* Compare two values for equality
|
|
122
|
+
*/
|
|
123
|
+
private compareEqual;
|
|
124
|
+
/**
|
|
125
|
+
* Sanitize context for logging (mask sensitive values)
|
|
126
|
+
*/
|
|
127
|
+
private sanitizeContext;
|
|
128
|
+
}
|
|
129
|
+
export default PolicyService;
|
|
130
|
+
//# sourceMappingURL=policy.service.d.ts.map
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PolicyService - Evaluates agent access policies
|
|
4
|
+
*
|
|
5
|
+
* This service evaluates incoming requests against configured policy rules
|
|
6
|
+
* to determine the appropriate enforcement action (allow, block, challenge, redirect).
|
|
7
|
+
*
|
|
8
|
+
* Policy evaluation order:
|
|
9
|
+
* 1. Allow list (exempt agents, always allowed)
|
|
10
|
+
* 2. Deny list (blocked agents, always blocked)
|
|
11
|
+
* 3. Threshold checks (confidence, reputation)
|
|
12
|
+
* 4. Custom rules (evaluated by priority, first match wins)
|
|
13
|
+
* 5. Default action (if no rules match)
|
|
14
|
+
*
|
|
15
|
+
* @package @kya-os/mcp-i-core
|
|
16
|
+
* @since 1.7.0
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.PolicyService = void 0;
|
|
20
|
+
const contracts_1 = require("@kya-os/contracts");
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// PolicyService Implementation
|
|
23
|
+
// ============================================================================
|
|
24
|
+
class PolicyService {
|
|
25
|
+
config;
|
|
26
|
+
policy;
|
|
27
|
+
constructor(policy, config) {
|
|
28
|
+
// Clone the default config to prevent mutation of the shared default
|
|
29
|
+
this.policy = policy ?? structuredClone(contracts_1.DEFAULT_POLICY_CONFIG);
|
|
30
|
+
this.config = config ?? {};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Update the policy configuration
|
|
34
|
+
*/
|
|
35
|
+
setPolicy(policy) {
|
|
36
|
+
this.policy = policy;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the current policy configuration
|
|
40
|
+
*/
|
|
41
|
+
getPolicy() {
|
|
42
|
+
return this.policy;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if policy enforcement is enabled
|
|
46
|
+
*/
|
|
47
|
+
isEnabled() {
|
|
48
|
+
return this.policy.enabled ?? true;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Evaluate a request against the policy
|
|
52
|
+
*
|
|
53
|
+
* @param context - Request/agent context to evaluate
|
|
54
|
+
* @returns Evaluation result with action and reason
|
|
55
|
+
*/
|
|
56
|
+
evaluate(context) {
|
|
57
|
+
// If policy is disabled, always allow
|
|
58
|
+
if (!this.isEnabled()) {
|
|
59
|
+
return {
|
|
60
|
+
action: 'allow',
|
|
61
|
+
reason: 'Policy enforcement disabled',
|
|
62
|
+
matchType: 'default',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// 1. Check allow list first (exempt agents)
|
|
66
|
+
const allowListMatch = this.checkAllowList(context);
|
|
67
|
+
if (allowListMatch) {
|
|
68
|
+
if (this.config.debug) {
|
|
69
|
+
console.error('[PolicyService] Allow list match', {
|
|
70
|
+
context: this.sanitizeContext(context),
|
|
71
|
+
match: allowListMatch,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
action: 'allow',
|
|
76
|
+
reason: `Allowed: ${allowListMatch.reason ?? 'On allow list'}`,
|
|
77
|
+
matchType: 'allow-list',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// 2. Check deny list (blocked agents)
|
|
81
|
+
const denyListMatch = this.checkDenyList(context);
|
|
82
|
+
if (denyListMatch) {
|
|
83
|
+
if (this.config.debug) {
|
|
84
|
+
console.error('[PolicyService] Deny list match', {
|
|
85
|
+
context: this.sanitizeContext(context),
|
|
86
|
+
match: denyListMatch,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
action: 'block',
|
|
91
|
+
reason: denyListMatch.reason ?? 'On deny list',
|
|
92
|
+
matchType: 'deny-list',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// 3. Check threshold-based enforcement
|
|
96
|
+
const thresholdResult = this.checkThresholds(context);
|
|
97
|
+
if (thresholdResult) {
|
|
98
|
+
if (this.config.debug) {
|
|
99
|
+
console.error('[PolicyService] Threshold match', {
|
|
100
|
+
context: this.sanitizeContext(context),
|
|
101
|
+
result: thresholdResult,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return thresholdResult;
|
|
105
|
+
}
|
|
106
|
+
// 4. Evaluate custom rules (by priority)
|
|
107
|
+
const ruleResult = this.evaluateRules(context);
|
|
108
|
+
if (ruleResult) {
|
|
109
|
+
if (this.config.debug) {
|
|
110
|
+
console.error('[PolicyService] Rule match', {
|
|
111
|
+
context: this.sanitizeContext(context),
|
|
112
|
+
result: ruleResult,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return ruleResult;
|
|
116
|
+
}
|
|
117
|
+
// 5. Default action
|
|
118
|
+
return {
|
|
119
|
+
action: this.policy.defaultAction ?? 'allow',
|
|
120
|
+
reason: 'No matching rules, using default action',
|
|
121
|
+
matchType: 'default',
|
|
122
|
+
redirectUrl: this.policy.defaultAction === 'redirect' ? this.policy.redirectUrl : undefined,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Private Methods
|
|
127
|
+
// ============================================================================
|
|
128
|
+
/**
|
|
129
|
+
* Check if context matches any allow list entry
|
|
130
|
+
*/
|
|
131
|
+
checkAllowList(context) {
|
|
132
|
+
if (!this.policy.allowList?.length) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
for (const entry of this.policy.allowList) {
|
|
136
|
+
if (this.matchesListEntry(context, entry)) {
|
|
137
|
+
return entry;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if context matches any deny list entry
|
|
144
|
+
*/
|
|
145
|
+
checkDenyList(context) {
|
|
146
|
+
if (!this.policy.denyList?.length) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
for (const entry of this.policy.denyList) {
|
|
150
|
+
// Check if entry is active and not expired
|
|
151
|
+
if (entry.active === false)
|
|
152
|
+
continue;
|
|
153
|
+
if (entry.expiresAt && new Date(entry.expiresAt) < new Date())
|
|
154
|
+
continue;
|
|
155
|
+
if (this.matchesListEntry(context, entry)) {
|
|
156
|
+
return entry;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Check if context matches a list entry (allow or deny)
|
|
163
|
+
*/
|
|
164
|
+
matchesListEntry(context, entry) {
|
|
165
|
+
// At least one identifier must match
|
|
166
|
+
if (entry.clientDid && context.clientDid === entry.clientDid)
|
|
167
|
+
return true;
|
|
168
|
+
if (entry.agentDid && context.agentDid === entry.agentDid)
|
|
169
|
+
return true;
|
|
170
|
+
if (entry.clientName && context.agentName?.toLowerCase() === entry.clientName.toLowerCase())
|
|
171
|
+
return true;
|
|
172
|
+
if (entry.agentType && context.agentType === entry.agentType)
|
|
173
|
+
return true;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Check threshold-based enforcement
|
|
178
|
+
*/
|
|
179
|
+
checkThresholds(context) {
|
|
180
|
+
const thresholds = this.policy.thresholds;
|
|
181
|
+
if (!thresholds)
|
|
182
|
+
return null;
|
|
183
|
+
// If signed requests bypass thresholds and request is signed, skip threshold checks
|
|
184
|
+
if (thresholds.trustSignedRequests && context.signatureVerified) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
// Check trusted reputation threshold (bypass other checks)
|
|
188
|
+
if (context.reputationScore !== undefined &&
|
|
189
|
+
context.reputationScore >= thresholds.trustedReputationThreshold) {
|
|
190
|
+
return null; // Trusted, continue to rules
|
|
191
|
+
}
|
|
192
|
+
// Check confidence threshold
|
|
193
|
+
if (context.confidence !== undefined &&
|
|
194
|
+
context.confidence >= thresholds.confidenceThreshold) {
|
|
195
|
+
return {
|
|
196
|
+
action: thresholds.confidenceAction,
|
|
197
|
+
reason: `Detection confidence (${context.confidence}) exceeds threshold (${thresholds.confidenceThreshold})`,
|
|
198
|
+
matchType: 'threshold',
|
|
199
|
+
redirectUrl: thresholds.confidenceAction === 'redirect' ? this.policy.redirectUrl : undefined,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// Check minimum reputation score
|
|
203
|
+
if (thresholds.minReputationScore > 0 &&
|
|
204
|
+
context.reputationScore !== undefined &&
|
|
205
|
+
context.reputationScore < thresholds.minReputationScore) {
|
|
206
|
+
return {
|
|
207
|
+
action: thresholds.lowReputationAction,
|
|
208
|
+
reason: `Reputation score (${context.reputationScore}) below minimum (${thresholds.minReputationScore})`,
|
|
209
|
+
matchType: 'threshold',
|
|
210
|
+
redirectUrl: thresholds.lowReputationAction === 'redirect' ? this.policy.redirectUrl : undefined,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Evaluate custom policy rules
|
|
217
|
+
*/
|
|
218
|
+
evaluateRules(context) {
|
|
219
|
+
const rules = this.policy.rules;
|
|
220
|
+
if (!rules?.length)
|
|
221
|
+
return null;
|
|
222
|
+
// Sort rules by priority (lower = higher priority)
|
|
223
|
+
const sortedRules = [...rules]
|
|
224
|
+
.filter((r) => r.enabled !== false)
|
|
225
|
+
.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
|
|
226
|
+
for (const rule of sortedRules) {
|
|
227
|
+
if (this.matchesRule(context, rule)) {
|
|
228
|
+
return {
|
|
229
|
+
action: rule.action,
|
|
230
|
+
reason: rule.description ?? rule.name,
|
|
231
|
+
ruleId: rule.id,
|
|
232
|
+
ruleName: rule.name,
|
|
233
|
+
redirectUrl: rule.action === 'redirect' ? rule.redirectUrl : undefined,
|
|
234
|
+
message: rule.message,
|
|
235
|
+
matchType: 'rule',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check if context matches a rule (all conditions must match)
|
|
243
|
+
*/
|
|
244
|
+
matchesRule(context, rule) {
|
|
245
|
+
// All conditions must match (AND logic)
|
|
246
|
+
return rule.conditions.every((condition) => this.matchesCondition(context, condition));
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Check if context matches a single condition
|
|
250
|
+
*/
|
|
251
|
+
matchesCondition(context, condition) {
|
|
252
|
+
const value = this.getContextValue(context, condition.field);
|
|
253
|
+
const targetValue = condition.value;
|
|
254
|
+
// Handle null/undefined - these operators should return true when value is missing
|
|
255
|
+
if (value === undefined || value === null) {
|
|
256
|
+
return (condition.operator === 'notEquals' ||
|
|
257
|
+
condition.operator === 'notIn' ||
|
|
258
|
+
condition.operator === 'notContains');
|
|
259
|
+
}
|
|
260
|
+
switch (condition.operator) {
|
|
261
|
+
case 'equals':
|
|
262
|
+
return this.compareEqual(value, targetValue, condition.caseInsensitive);
|
|
263
|
+
case 'notEquals':
|
|
264
|
+
return !this.compareEqual(value, targetValue, condition.caseInsensitive);
|
|
265
|
+
case 'contains':
|
|
266
|
+
return typeof value === 'string' && typeof targetValue === 'string'
|
|
267
|
+
? condition.caseInsensitive
|
|
268
|
+
? value.toLowerCase().includes(targetValue.toLowerCase())
|
|
269
|
+
: value.includes(targetValue)
|
|
270
|
+
: false;
|
|
271
|
+
case 'notContains':
|
|
272
|
+
return typeof value === 'string' && typeof targetValue === 'string'
|
|
273
|
+
? condition.caseInsensitive
|
|
274
|
+
? !value.toLowerCase().includes(targetValue.toLowerCase())
|
|
275
|
+
: !value.includes(targetValue)
|
|
276
|
+
: true;
|
|
277
|
+
case 'startsWith':
|
|
278
|
+
return typeof value === 'string' && typeof targetValue === 'string'
|
|
279
|
+
? condition.caseInsensitive
|
|
280
|
+
? value.toLowerCase().startsWith(targetValue.toLowerCase())
|
|
281
|
+
: value.startsWith(targetValue)
|
|
282
|
+
: false;
|
|
283
|
+
case 'endsWith':
|
|
284
|
+
return typeof value === 'string' && typeof targetValue === 'string'
|
|
285
|
+
? condition.caseInsensitive
|
|
286
|
+
? value.toLowerCase().endsWith(targetValue.toLowerCase())
|
|
287
|
+
: value.endsWith(targetValue)
|
|
288
|
+
: false;
|
|
289
|
+
case 'greaterThan':
|
|
290
|
+
return typeof value === 'number' && typeof targetValue === 'number'
|
|
291
|
+
? value > targetValue
|
|
292
|
+
: false;
|
|
293
|
+
case 'lessThan':
|
|
294
|
+
return typeof value === 'number' && typeof targetValue === 'number'
|
|
295
|
+
? value < targetValue
|
|
296
|
+
: false;
|
|
297
|
+
case 'greaterThanOrEquals':
|
|
298
|
+
return typeof value === 'number' && typeof targetValue === 'number'
|
|
299
|
+
? value >= targetValue
|
|
300
|
+
: false;
|
|
301
|
+
case 'lessThanOrEquals':
|
|
302
|
+
return typeof value === 'number' && typeof targetValue === 'number'
|
|
303
|
+
? value <= targetValue
|
|
304
|
+
: false;
|
|
305
|
+
case 'matches':
|
|
306
|
+
if (typeof value !== 'string' || typeof targetValue !== 'string') {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const flags = condition.caseInsensitive ? 'i' : '';
|
|
311
|
+
return new RegExp(targetValue, flags).test(value);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
case 'in':
|
|
317
|
+
return Array.isArray(targetValue)
|
|
318
|
+
? targetValue.some((v) => this.compareEqual(value, v, condition.caseInsensitive))
|
|
319
|
+
: false;
|
|
320
|
+
case 'notIn':
|
|
321
|
+
return Array.isArray(targetValue)
|
|
322
|
+
? !targetValue.some((v) => this.compareEqual(value, v, condition.caseInsensitive))
|
|
323
|
+
: true;
|
|
324
|
+
default:
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get a value from the context by field name
|
|
330
|
+
*/
|
|
331
|
+
getContextValue(context, field) {
|
|
332
|
+
switch (field) {
|
|
333
|
+
case 'agentType':
|
|
334
|
+
return context.agentType;
|
|
335
|
+
case 'agentName':
|
|
336
|
+
return context.agentName;
|
|
337
|
+
case 'agentVendor':
|
|
338
|
+
return context.agentVendor;
|
|
339
|
+
case 'clientDid':
|
|
340
|
+
return context.clientDid;
|
|
341
|
+
case 'agentDid':
|
|
342
|
+
return context.agentDid;
|
|
343
|
+
case 'confidence':
|
|
344
|
+
return context.confidence;
|
|
345
|
+
case 'reputationScore':
|
|
346
|
+
return context.reputationScore;
|
|
347
|
+
case 'riskLevel':
|
|
348
|
+
return context.riskLevel;
|
|
349
|
+
case 'path':
|
|
350
|
+
return context.path;
|
|
351
|
+
case 'method':
|
|
352
|
+
return context.method;
|
|
353
|
+
case 'origin':
|
|
354
|
+
return context.origin;
|
|
355
|
+
case 'userAgent':
|
|
356
|
+
return context.userAgent;
|
|
357
|
+
case 'signatureVerified':
|
|
358
|
+
return context.signatureVerified;
|
|
359
|
+
case 'isAuthenticated':
|
|
360
|
+
return context.isAuthenticated;
|
|
361
|
+
case 'hasValidDelegation':
|
|
362
|
+
return context.hasValidDelegation;
|
|
363
|
+
default:
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Compare two values for equality
|
|
369
|
+
*/
|
|
370
|
+
compareEqual(a, b, caseInsensitive) {
|
|
371
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
372
|
+
return caseInsensitive ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
373
|
+
}
|
|
374
|
+
return a === b;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Sanitize context for logging (mask sensitive values)
|
|
378
|
+
*/
|
|
379
|
+
sanitizeContext(context) {
|
|
380
|
+
return {
|
|
381
|
+
agentType: context.agentType,
|
|
382
|
+
agentName: context.agentName,
|
|
383
|
+
agentVendor: context.agentVendor,
|
|
384
|
+
clientDid: context.clientDid ? `${context.clientDid.slice(0, 20)}...` : undefined,
|
|
385
|
+
agentDid: context.agentDid ? `${context.agentDid.slice(0, 20)}...` : undefined,
|
|
386
|
+
confidence: context.confidence,
|
|
387
|
+
reputationScore: context.reputationScore,
|
|
388
|
+
riskLevel: context.riskLevel,
|
|
389
|
+
path: context.path,
|
|
390
|
+
method: context.method,
|
|
391
|
+
signatureVerified: context.signatureVerified,
|
|
392
|
+
isAuthenticated: context.isAuthenticated,
|
|
393
|
+
hasValidDelegation: context.hasValidDelegation,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
exports.PolicyService = PolicyService;
|
|
398
|
+
exports.default = PolicyService;
|
|
399
|
+
//# sourceMappingURL=policy.service.js.map
|
|
@@ -77,7 +77,28 @@ class ToolContextBuilder {
|
|
|
77
77
|
// This includes tokenUsage, cookieFormat, apiHeaders for credential providers
|
|
78
78
|
const tokenData = await this.config.tokenResolver.resolveTokenDataFromDid(userDid, provider, toolProtection.requiredScopes);
|
|
79
79
|
if (!tokenData) {
|
|
80
|
-
//
|
|
80
|
+
// IDP token not available - check if delegation token exists
|
|
81
|
+
// If delegation exists, tool can still execute without IDP token
|
|
82
|
+
// (the tool handler should check for idpToken if it needs external API access)
|
|
83
|
+
if (delegationToken) {
|
|
84
|
+
this.config.logger("[ToolContextBuilder] IDP token not found, but delegation exists - returning minimal context", {
|
|
85
|
+
toolName,
|
|
86
|
+
userDid: userDid.substring(0, 20) + "...",
|
|
87
|
+
provider,
|
|
88
|
+
scopes: toolProtection.requiredScopes,
|
|
89
|
+
note: "Tool can execute with delegation-only authorization",
|
|
90
|
+
});
|
|
91
|
+
// Return minimal context with delegation info
|
|
92
|
+
// Tools that need IDP token for external API calls should check for idpToken
|
|
93
|
+
return {
|
|
94
|
+
provider,
|
|
95
|
+
scopes: toolProtection.requiredScopes,
|
|
96
|
+
userDid,
|
|
97
|
+
sessionId,
|
|
98
|
+
delegationToken,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// No delegation token either - throw OAuthRequiredError to trigger auth flow
|
|
81
102
|
this.config.logger("[ToolContextBuilder] Token not available, throwing OAuthRequiredError", {
|
|
82
103
|
toolName,
|
|
83
104
|
userDid: userDid.substring(0, 20) + "...",
|
|
@@ -21,6 +21,11 @@ export interface OAuthRequiredErrorOptions {
|
|
|
21
21
|
userDid?: string;
|
|
22
22
|
/** Optional session ID */
|
|
23
23
|
sessionId?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Auth type from tool protection config (e.g., "password", "oauth")
|
|
26
|
+
* Used to determine correct consent flow when catching the error
|
|
27
|
+
*/
|
|
28
|
+
authType?: string;
|
|
24
29
|
}
|
|
25
30
|
/**
|
|
26
31
|
* Error thrown when a tool requires OAuth but IDP token is not available
|
|
@@ -35,6 +40,7 @@ export declare class OAuthRequiredError extends Error {
|
|
|
35
40
|
readonly resumeToken?: string;
|
|
36
41
|
readonly userDid?: string;
|
|
37
42
|
readonly sessionId?: string;
|
|
43
|
+
readonly authType?: string;
|
|
38
44
|
constructor(options: OAuthRequiredErrorOptions);
|
|
39
45
|
}
|
|
40
46
|
//# sourceMappingURL=oauth-required-error.d.ts.map
|
|
@@ -22,6 +22,7 @@ class OAuthRequiredError extends Error {
|
|
|
22
22
|
resumeToken;
|
|
23
23
|
userDid;
|
|
24
24
|
sessionId;
|
|
25
|
+
authType;
|
|
25
26
|
constructor(options) {
|
|
26
27
|
const { toolName, requiredScopes, provider, oauthUrl } = options;
|
|
27
28
|
super(`OAuth required for tool "${toolName}" with provider "${provider}". ` +
|
|
@@ -34,6 +35,7 @@ class OAuthRequiredError extends Error {
|
|
|
34
35
|
this.resumeToken = options.resumeToken;
|
|
35
36
|
this.userDid = options.userDid;
|
|
36
37
|
this.sessionId = options.sessionId;
|
|
38
|
+
this.authType = options.authType;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
exports.OAuthRequiredError = OAuthRequiredError;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kya-os/mcp-i-core",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.17",
|
|
4
4
|
"description": "Core runtime and types for MCP-I framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
"default": "./dist/*.js"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"clean": "rm -rf dist .turbo node_modules",
|
|
28
|
+
"prepublishOnly": "npm run build && node ../create-mcpi-app/scripts/validate-no-workspace.js"
|
|
29
|
+
},
|
|
21
30
|
"dependencies": {
|
|
22
31
|
"@kya-os/contracts": "^1.7.20",
|
|
23
32
|
"jose": "^5.6.3",
|
|
@@ -33,13 +42,5 @@
|
|
|
33
42
|
},
|
|
34
43
|
"publishConfig": {
|
|
35
44
|
"access": "public"
|
|
36
|
-
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsc",
|
|
39
|
-
"test": "vitest run",
|
|
40
|
-
"test:coverage": "vitest run --coverage",
|
|
41
|
-
"test:watch": "vitest",
|
|
42
|
-
"lint": "eslint .",
|
|
43
|
-
"clean": "rm -rf dist .turbo node_modules"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|
|
@@ -583,7 +583,7 @@ async function runAudit() {
|
|
|
583
583
|
console.log('================================================================================\n');
|
|
584
584
|
|
|
585
585
|
const verifier = createSchemaVerifier({
|
|
586
|
-
schemasBaseUrl: 'https://
|
|
586
|
+
schemasBaseUrl: 'https://schema.modelcontextprotocol-identity.io/xmcp-i',
|
|
587
587
|
});
|
|
588
588
|
|
|
589
589
|
const implementations = createSampleImplementations();
|