@kya-os/mcp-i-core 1.4.14 → 1.4.15

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.
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Schema Registry
3
3
  *
4
- * Canonical list of all schemas from schemas.kya-os.ai
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://schemas.kya-os.ai/schema-index.json
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 schemas.kya-os.ai
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 schemas.kya-os.ai
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://schemas.kya-os.ai/schema-index.json
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://schemas.kya-os.ai/xmcp-i';
18
+ const SCHEMAS_BASE_URL = 'https://schema.modelcontextprotocol-identity.io/xmcp-i';
19
19
  /**
20
- * Complete registry of schemas from schemas.kya-os.ai
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://schemas.kya-os.ai';
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.BitstringManager = 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.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.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
- // Token not available - throw OAuthRequiredError to trigger auth flow
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) + "...",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-os/mcp-i-core",
3
- "version": "1.4.14",
3
+ "version": "1.4.15",
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://schemas.kya-os.ai/xmcp-i',
586
+ schemasBaseUrl: 'https://schema.modelcontextprotocol-identity.io/xmcp-i',
587
587
  });
588
588
 
589
589
  const implementations = createSampleImplementations();