@kya-os/mcp-i-core 1.7.0 → 1.8.1
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/README.md +1 -1
- package/dist/conformance/capability-verifier-adapter.d.ts +66 -0
- package/dist/conformance/capability-verifier-adapter.js +61 -0
- package/dist/conformance/index.d.ts +10 -0
- package/dist/conformance/index.js +19 -0
- package/dist/conformance/validate-level2.d.ts +203 -0
- package/dist/conformance/validate-level2.js +294 -0
- package/dist/conformance/vc-jwt-verifier.d.ts +55 -0
- package/dist/conformance/vc-jwt-verifier.js +147 -0
- package/dist/delegation/did-web-resolver.d.ts +60 -0
- package/dist/delegation/did-web-resolver.js +129 -0
- package/dist/delegation/outbound-proof.d.ts +3 -2
- package/dist/delegation/outbound-proof.js +3 -2
- package/dist/delegation/utils.d.ts +3 -1
- package/dist/index.d.ts +9 -63
- package/dist/index.js +22 -161
- package/dist/utils/base58.d.ts +4 -28
- package/dist/utils/base58.js +18 -98
- package/dist/utils/base64.d.ts +4 -29
- package/dist/utils/base64.js +18 -167
- package/dist/utils/did-helpers.d.ts +4 -165
- package/dist/utils/did-helpers.js +18 -217
- package/package.json +5 -7
- package/dist/auth/handshake.d.ts +0 -119
- package/dist/auth/handshake.js +0 -250
- package/dist/auth/index.d.ts +0 -6
- package/dist/auth/index.js +0 -11
- package/dist/auth/types.d.ts +0 -46
- package/dist/auth/types.js +0 -10
- package/dist/cache/oauth-config-cache.d.ts +0 -69
- package/dist/cache/oauth-config-cache.js +0 -76
- package/dist/cache/tool-protection-cache.d.ts +0 -89
- package/dist/cache/tool-protection-cache.js +0 -118
- package/dist/compliance/index.d.ts +0 -8
- package/dist/compliance/index.js +0 -24
- package/dist/compliance/schema-registry.d.ts +0 -50
- package/dist/compliance/schema-registry.js +0 -438
- package/dist/compliance/schema-verifier.d.ts +0 -110
- package/dist/compliance/schema-verifier.js +0 -518
- package/dist/config/remote-config.d.ts +0 -173
- package/dist/config/remote-config.js +0 -253
- package/dist/config.d.ts +0 -182
- package/dist/config.js +0 -148
- package/dist/identity/idp-token-resolver.d.ts +0 -69
- package/dist/identity/idp-token-resolver.js +0 -136
- package/dist/identity/idp-token-storage.interface.d.ts +0 -80
- package/dist/identity/idp-token-storage.interface.js +0 -14
- package/dist/identity/user-did-manager.d.ts +0 -228
- package/dist/identity/user-did-manager.js +0 -356
- package/dist/logging/index.d.ts +0 -5
- package/dist/logging/index.js +0 -10
- package/dist/logging/logger.d.ts +0 -33
- package/dist/logging/logger.js +0 -104
- package/dist/proof/generator.d.ts +0 -109
- package/dist/proof/generator.js +0 -236
- package/dist/proof/index.d.ts +0 -5
- package/dist/proof/index.js +0 -11
- package/dist/providers/base.d.ts +0 -95
- package/dist/providers/base.js +0 -47
- package/dist/providers/memory.d.ts +0 -42
- package/dist/providers/memory.js +0 -121
- package/dist/runtime/audit-logger.d.ts +0 -37
- package/dist/runtime/audit-logger.js +0 -9
- package/dist/runtime/base.d.ts +0 -375
- package/dist/runtime/base.js +0 -1491
- package/dist/runtime/ext-apps-constants.d.ts +0 -14
- package/dist/runtime/ext-apps-constants.js +0 -17
- package/dist/services/access-control.service.d.ts +0 -138
- package/dist/services/access-control.service.js +0 -721
- package/dist/services/authorization/authorization-registry.d.ts +0 -29
- package/dist/services/authorization/authorization-registry.js +0 -58
- package/dist/services/authorization/types.d.ts +0 -53
- package/dist/services/authorization/types.js +0 -10
- package/dist/services/batch-delegation.service.d.ts +0 -53
- package/dist/services/batch-delegation.service.js +0 -95
- package/dist/services/crypto.service.d.ts +0 -69
- package/dist/services/crypto.service.js +0 -225
- package/dist/services/delegation-error-formatter.d.ts +0 -124
- package/dist/services/delegation-error-formatter.js +0 -214
- package/dist/services/errors.d.ts +0 -49
- package/dist/services/errors.js +0 -66
- package/dist/services/index.d.ts +0 -9
- package/dist/services/index.js +0 -15
- package/dist/services/oauth-config.service.d.ts +0 -53
- package/dist/services/oauth-config.service.js +0 -141
- package/dist/services/oauth-provider-registry.d.ts +0 -88
- package/dist/services/oauth-provider-registry.js +0 -128
- package/dist/services/oauth-service.d.ts +0 -122
- package/dist/services/oauth-service.js +0 -442
- package/dist/services/oauth-token-retrieval.service.d.ts +0 -49
- package/dist/services/oauth-token-retrieval.service.js +0 -150
- package/dist/services/policy.service.d.ts +0 -130
- package/dist/services/policy.service.js +0 -399
- package/dist/services/proof-verifier.d.ts +0 -98
- package/dist/services/proof-verifier.js +0 -319
- package/dist/services/provider-resolver.d.ts +0 -89
- package/dist/services/provider-resolver.js +0 -196
- package/dist/services/provider-validator.d.ts +0 -55
- package/dist/services/provider-validator.js +0 -135
- package/dist/services/session-registration.service.d.ts +0 -80
- package/dist/services/session-registration.service.js +0 -174
- package/dist/services/storage.service.d.ts +0 -116
- package/dist/services/storage.service.js +0 -411
- package/dist/services/tool-context-builder.d.ts +0 -74
- package/dist/services/tool-context-builder.js +0 -215
- package/dist/services/tool-protection.service.d.ts +0 -193
- package/dist/services/tool-protection.service.js +0 -736
- package/dist/session/index.d.ts +0 -5
- package/dist/session/index.js +0 -11
- package/dist/session/manager.d.ts +0 -113
- package/dist/session/manager.js +0 -273
- package/dist/types/oauth-required-error.d.ts +0 -46
- package/dist/types/oauth-required-error.js +0 -42
- package/dist/types/tool-protection.d.ts +0 -144
- package/dist/types/tool-protection.js +0 -48
- package/dist/utils/cors.d.ts +0 -44
- package/dist/utils/cors.js +0 -71
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.js +0 -24
- package/dist/utils/storage-keys.d.ts +0 -120
- package/dist/utils/storage-keys.js +0 -217
package/README.md
CHANGED
|
@@ -151,7 +151,7 @@ import { MCPIRuntimeBaseV2 } from '@kya-os/mcp-i-core';
|
|
|
151
151
|
class MyPlatformRuntime extends MCPIRuntimeBaseV2 {
|
|
152
152
|
constructor() {
|
|
153
153
|
super({
|
|
154
|
-
cryptoProvider: new
|
|
154
|
+
cryptoProvider: new MyCryptoProvider(),
|
|
155
155
|
identityProvider: new MyIdentityProvider(),
|
|
156
156
|
storageProvider: new MyStorageProvider(),
|
|
157
157
|
nonceCacheProvider: new MyNonceCacheProvider(),
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CapabilityVerifier Adapter
|
|
3
|
+
*
|
|
4
|
+
* Adapts {@link verifyCapabilities} into the small, duck-typed verifier shape an
|
|
5
|
+
* entity-card consumer expects: given a list of capabilities and a verification
|
|
6
|
+
* context, return which capability names are `verified` (backed by a verified
|
|
7
|
+
* attestation) and which are `unverified` (self-declared or failed).
|
|
8
|
+
*
|
|
9
|
+
* The capability and verifier types are defined locally rather than imported
|
|
10
|
+
* from `@kya-os/mcp`: mcp-i-core pins `@kya-os/mcp@1.6.1`, which predates the
|
|
11
|
+
* entity-card export, so importing it would not resolve.
|
|
12
|
+
*
|
|
13
|
+
* Mapping:
|
|
14
|
+
* - bare-string capability → `unverified` (Level 1, self-declared)
|
|
15
|
+
* - L2 object capability → checked via {@link verifyCapabilities}; its
|
|
16
|
+
* `valid` names become `verified`, its `invalid` names become `unverified`.
|
|
17
|
+
*/
|
|
18
|
+
import { type CapabilityVCSignatureVerifier } from './validate-level2.js';
|
|
19
|
+
import type { DIDResolver } from '../delegation/vc-verifier.js';
|
|
20
|
+
/**
|
|
21
|
+
* Duck-typed capability. A bare string is a Level 1 self-declared capability;
|
|
22
|
+
* an object with a `name` (and optional `attestations`) is a Level 2 attested
|
|
23
|
+
* capability.
|
|
24
|
+
*/
|
|
25
|
+
export type DuckTypedCapability = string | {
|
|
26
|
+
name: string;
|
|
27
|
+
attestations?: readonly unknown[];
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
/** Context describing whose capabilities are being checked, and against whom. */
|
|
31
|
+
export interface CapabilityVerificationContext {
|
|
32
|
+
/** The subject (agent) DID the capabilities are claimed for. */
|
|
33
|
+
subjectDid: string;
|
|
34
|
+
/** Issuer DIDs trusted to attest capabilities. */
|
|
35
|
+
trustedIssuers: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
/** Partition of capability names by verification outcome. */
|
|
38
|
+
export interface CapabilityVerificationResult {
|
|
39
|
+
/** Capability names backed by a verified attestation. */
|
|
40
|
+
verified: string[];
|
|
41
|
+
/** Capability names that are self-declared or failed verification. */
|
|
42
|
+
unverified: string[];
|
|
43
|
+
}
|
|
44
|
+
/** The duck-typed verifier returned by {@link createCapabilityVerifier}. */
|
|
45
|
+
export type CapabilityVerifier = (capabilities: readonly DuckTypedCapability[], ctx: CapabilityVerificationContext) => Promise<CapabilityVerificationResult>;
|
|
46
|
+
/** Options for {@link createCapabilityVerifier}. */
|
|
47
|
+
export interface CreateCapabilityVerifierOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Signature verifier for Level 2 attestations. Defaults to a VC-JWT verifier
|
|
50
|
+
* (see {@link createVcJwtSignatureVerifier}).
|
|
51
|
+
*/
|
|
52
|
+
signatureVerifier?: CapabilityVCSignatureVerifier;
|
|
53
|
+
/**
|
|
54
|
+
* DID resolver for the default signature verifier. Ignored when a
|
|
55
|
+
* `signatureVerifier` is supplied.
|
|
56
|
+
*/
|
|
57
|
+
didResolver?: DIDResolver;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a capability verifier.
|
|
61
|
+
*
|
|
62
|
+
* @param options - Optional signature-verifier / DID-resolver overrides.
|
|
63
|
+
* @returns A verifier mapping capabilities to `{ verified, unverified }`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createCapabilityVerifier(options?: CreateCapabilityVerifierOptions): CapabilityVerifier;
|
|
66
|
+
//# sourceMappingURL=capability-verifier-adapter.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CapabilityVerifier Adapter
|
|
4
|
+
*
|
|
5
|
+
* Adapts {@link verifyCapabilities} into the small, duck-typed verifier shape an
|
|
6
|
+
* entity-card consumer expects: given a list of capabilities and a verification
|
|
7
|
+
* context, return which capability names are `verified` (backed by a verified
|
|
8
|
+
* attestation) and which are `unverified` (self-declared or failed).
|
|
9
|
+
*
|
|
10
|
+
* The capability and verifier types are defined locally rather than imported
|
|
11
|
+
* from `@kya-os/mcp`: mcp-i-core pins `@kya-os/mcp@1.6.1`, which predates the
|
|
12
|
+
* entity-card export, so importing it would not resolve.
|
|
13
|
+
*
|
|
14
|
+
* Mapping:
|
|
15
|
+
* - bare-string capability → `unverified` (Level 1, self-declared)
|
|
16
|
+
* - L2 object capability → checked via {@link verifyCapabilities}; its
|
|
17
|
+
* `valid` names become `verified`, its `invalid` names become `unverified`.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.createCapabilityVerifier = createCapabilityVerifier;
|
|
21
|
+
const validate_level2_js_1 = require("./validate-level2.js");
|
|
22
|
+
const vc_jwt_verifier_js_1 = require("./vc-jwt-verifier.js");
|
|
23
|
+
/**
|
|
24
|
+
* Create a capability verifier.
|
|
25
|
+
*
|
|
26
|
+
* @param options - Optional signature-verifier / DID-resolver overrides.
|
|
27
|
+
* @returns A verifier mapping capabilities to `{ verified, unverified }`.
|
|
28
|
+
*/
|
|
29
|
+
function createCapabilityVerifier(options = {}) {
|
|
30
|
+
const signatureVerifier = options.signatureVerifier ??
|
|
31
|
+
(0, vc_jwt_verifier_js_1.createVcJwtSignatureVerifier)({ didResolver: options.didResolver });
|
|
32
|
+
return async (capabilities, ctx) => {
|
|
33
|
+
const list = Array.isArray(capabilities) ? capabilities : [];
|
|
34
|
+
const bareStrings = [];
|
|
35
|
+
const attested = [];
|
|
36
|
+
for (const capability of list) {
|
|
37
|
+
if (typeof capability === 'string') {
|
|
38
|
+
bareStrings.push(capability);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
attested.push(capability);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const result = await (0, validate_level2_js_1.verifyCapabilities)(attested, {
|
|
45
|
+
agentDid: ctx.subjectDid,
|
|
46
|
+
trustedIssuers: [...ctx.trustedIssuers],
|
|
47
|
+
signatureVerifier,
|
|
48
|
+
});
|
|
49
|
+
const verified = result.valid;
|
|
50
|
+
const verifiedSet = new Set(verified);
|
|
51
|
+
// A capability verified via its L2 attestation must not also appear as
|
|
52
|
+
// unverified — e.g. when the input mixes a bare-string and an attested
|
|
53
|
+
// object for the same name. Exclude already-verified names, and dedupe.
|
|
54
|
+
const unverified = Array.from(new Set([
|
|
55
|
+
...bareStrings,
|
|
56
|
+
...result.invalid.map((entry) => entry.capability),
|
|
57
|
+
])).filter((name) => !verifiedSet.has(name));
|
|
58
|
+
return { verified, unverified };
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=capability-verifier-adapter.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP-I Conformance Level helpers.
|
|
3
|
+
*
|
|
4
|
+
* See docs/conformance-levels for the spec page (introduced in
|
|
5
|
+
* modelcontextprotocol-identity#46).
|
|
6
|
+
*/
|
|
7
|
+
export { verifyCapabilities, validateLevel2, DEFAULT_TRUSTED_ISSUERS, type CapabilityName, type CapabilityAttestation, type CapabilityAttestationCredential, type Level2Capability, type ManifestCapabilities, type CapabilityVCSignatureVerifier, type CapabilityStatusListResolver, type ValidateLevel2Options, type ValidateLevel2Result, type InvalidCapability, } from './validate-level2.js';
|
|
8
|
+
export { createVcJwtSignatureVerifier, createCombinedDidResolver, type VcJwtVerifierOptions, } from './vc-jwt-verifier.js';
|
|
9
|
+
export { createCapabilityVerifier, type DuckTypedCapability, type CapabilityVerificationContext, type CapabilityVerificationResult, type CapabilityVerifier, type CreateCapabilityVerifierOptions, } from './capability-verifier-adapter.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP-I Conformance Level helpers.
|
|
4
|
+
*
|
|
5
|
+
* See docs/conformance-levels for the spec page (introduced in
|
|
6
|
+
* modelcontextprotocol-identity#46).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createCapabilityVerifier = exports.createCombinedDidResolver = exports.createVcJwtSignatureVerifier = exports.DEFAULT_TRUSTED_ISSUERS = exports.validateLevel2 = exports.verifyCapabilities = void 0;
|
|
10
|
+
var validate_level2_js_1 = require("./validate-level2.js");
|
|
11
|
+
Object.defineProperty(exports, "verifyCapabilities", { enumerable: true, get: function () { return validate_level2_js_1.verifyCapabilities; } });
|
|
12
|
+
Object.defineProperty(exports, "validateLevel2", { enumerable: true, get: function () { return validate_level2_js_1.validateLevel2; } });
|
|
13
|
+
Object.defineProperty(exports, "DEFAULT_TRUSTED_ISSUERS", { enumerable: true, get: function () { return validate_level2_js_1.DEFAULT_TRUSTED_ISSUERS; } });
|
|
14
|
+
var vc_jwt_verifier_js_1 = require("./vc-jwt-verifier.js");
|
|
15
|
+
Object.defineProperty(exports, "createVcJwtSignatureVerifier", { enumerable: true, get: function () { return vc_jwt_verifier_js_1.createVcJwtSignatureVerifier; } });
|
|
16
|
+
Object.defineProperty(exports, "createCombinedDidResolver", { enumerable: true, get: function () { return vc_jwt_verifier_js_1.createCombinedDidResolver; } });
|
|
17
|
+
var capability_verifier_adapter_js_1 = require("./capability-verifier-adapter.js");
|
|
18
|
+
Object.defineProperty(exports, "createCapabilityVerifier", { enumerable: true, get: function () { return capability_verifier_adapter_js_1.createCapabilityVerifier; } });
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level 2 Capability Attestation Validator
|
|
3
|
+
*
|
|
4
|
+
* Implements the Level 2 conformance behavior described in the MCP-I spec:
|
|
5
|
+
* https://github.com/modelcontextprotocol-identity/modelcontextprotocol-identity/pull/46
|
|
6
|
+
*
|
|
7
|
+
* For each declared capability in the agent manifest, verify at least one
|
|
8
|
+
* attached Verifiable Credential where ALL of the following hold:
|
|
9
|
+
* - VC signature verifies (delegated to a platform-specific verifier)
|
|
10
|
+
* - issuer DID is in the trusted issuer registry
|
|
11
|
+
* - credentialSubject.id matches the agent DID
|
|
12
|
+
* - credentialSubject.capability matches the declared capability name
|
|
13
|
+
* - validUntil is in the future (and validFrom is in the past, when present)
|
|
14
|
+
*
|
|
15
|
+
* Revocation (StatusList2021) is OPTIONAL for v1 and not enforced here.
|
|
16
|
+
*
|
|
17
|
+
* Capabilities that fail validation are NOT thrown — they are returned in the
|
|
18
|
+
* `invalid` array along with the reasons, so callers can surface diagnostics
|
|
19
|
+
* to UI without a hard failure.
|
|
20
|
+
*
|
|
21
|
+
* The legacy Level 1 `string[]` form is accepted as input: every entry is
|
|
22
|
+
* returned under `valid` along with a global warning so callers can surface
|
|
23
|
+
* the missing attestation context.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Default trusted issuer registry.
|
|
27
|
+
*
|
|
28
|
+
* Matches the spec default: `did:web:knowthat.ai`. Callers MAY substitute or
|
|
29
|
+
* extend this allowlist via {@link ValidateLevel2Options.trustedIssuers}.
|
|
30
|
+
*/
|
|
31
|
+
export declare const DEFAULT_TRUSTED_ISSUERS: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* Capability name as declared in the agent manifest (e.g. `payments.transfer`).
|
|
34
|
+
*/
|
|
35
|
+
export type CapabilityName = string;
|
|
36
|
+
/**
|
|
37
|
+
* Minimal shape of a `CapabilityAttestationCredential` per the spec.
|
|
38
|
+
*
|
|
39
|
+
* Only the fields the validator inspects are typed strictly — additional
|
|
40
|
+
* fields (`@context`, `type`, `proof`, etc.) are passed through.
|
|
41
|
+
*/
|
|
42
|
+
export interface CapabilityAttestationCredential {
|
|
43
|
+
'@context'?: string | string[];
|
|
44
|
+
type?: string | string[];
|
|
45
|
+
issuer: string | {
|
|
46
|
+
id: string;
|
|
47
|
+
};
|
|
48
|
+
validFrom?: string;
|
|
49
|
+
validUntil?: string;
|
|
50
|
+
credentialSubject: {
|
|
51
|
+
id: string;
|
|
52
|
+
capability: string;
|
|
53
|
+
scope?: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
proof?: unknown;
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Single attestation entry attached to a Level 2 capability.
|
|
60
|
+
*
|
|
61
|
+
* The `vc` field MAY be a compact JWT string (per the spec example) OR a
|
|
62
|
+
* pre-parsed credential object (convenient for tests and callers that have
|
|
63
|
+
* already decoded the JWT).
|
|
64
|
+
*
|
|
65
|
+
* `issuer` and `type` are OPTIONAL envelope metadata hints — they are
|
|
66
|
+
* informational only and play NO role in trust evaluation. The validator
|
|
67
|
+
* derives the authoritative issuer from `vc.issuer` (the signed credential
|
|
68
|
+
* itself), so a wrong or attacker-supplied envelope `issuer` cannot grant
|
|
69
|
+
* trust. If you need to record the credential type, prefer `vc.type` inside
|
|
70
|
+
* the VC, which is covered by the signature.
|
|
71
|
+
*/
|
|
72
|
+
export interface CapabilityAttestation {
|
|
73
|
+
vc: string | CapabilityAttestationCredential;
|
|
74
|
+
/**
|
|
75
|
+
* Optional metadata hint. NOT consulted during validation — `vc.issuer` is
|
|
76
|
+
* authoritative for the trusted-issuer check.
|
|
77
|
+
*/
|
|
78
|
+
issuer?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Optional metadata hint. NOT consulted during validation.
|
|
81
|
+
*/
|
|
82
|
+
type?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Level 2 capability entry.
|
|
86
|
+
*/
|
|
87
|
+
export interface Level2Capability {
|
|
88
|
+
name: CapabilityName;
|
|
89
|
+
attestations: CapabilityAttestation[];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Manifest capabilities — accepts either the legacy Level 1 string array or
|
|
93
|
+
* the Level 2 object array. Mixed entries within a single Level 2 array are
|
|
94
|
+
* tolerated (string entries are dropped with a per-capability warning).
|
|
95
|
+
*/
|
|
96
|
+
export type ManifestCapabilities = readonly CapabilityName[] | readonly (Level2Capability | CapabilityName)[];
|
|
97
|
+
/**
|
|
98
|
+
* Pluggable signature verifier for capability attestations.
|
|
99
|
+
*
|
|
100
|
+
* Cryptographic verification (Ed25519, ES256, etc.) is platform-specific, so
|
|
101
|
+
* the validator delegates the signature check. Implementations should resolve
|
|
102
|
+
* the issuer DID, locate the verification method, and verify the proof.
|
|
103
|
+
*
|
|
104
|
+
* If no verifier is supplied, the validator emits a single global warning and
|
|
105
|
+
* treats every signature as valid — useful for development but unsafe in
|
|
106
|
+
* production. This mirrors `DelegationCredentialVerifier`'s behavior when no
|
|
107
|
+
* `signatureVerifier` is provided.
|
|
108
|
+
*/
|
|
109
|
+
export type CapabilityVCSignatureVerifier = (vc: CapabilityAttestationCredential, rawJwt: string | undefined) => Promise<{
|
|
110
|
+
valid: boolean;
|
|
111
|
+
reason?: string;
|
|
112
|
+
}>;
|
|
113
|
+
/**
|
|
114
|
+
* Reserved hook for StatusList2021-based revocation checks.
|
|
115
|
+
*
|
|
116
|
+
* Not invoked in v1. Reserved so callers can pass a resolver today and have
|
|
117
|
+
* it activate transparently once revocation enforcement lands.
|
|
118
|
+
*/
|
|
119
|
+
export type CapabilityStatusListResolver = (vc: CapabilityAttestationCredential) => Promise<{
|
|
120
|
+
revoked: boolean;
|
|
121
|
+
reason?: string;
|
|
122
|
+
}>;
|
|
123
|
+
/**
|
|
124
|
+
* Validator options.
|
|
125
|
+
*/
|
|
126
|
+
export interface ValidateLevel2Options {
|
|
127
|
+
/**
|
|
128
|
+
* The agent DID being validated. `credentialSubject.id` on each VC MUST
|
|
129
|
+
* match this exactly.
|
|
130
|
+
*/
|
|
131
|
+
agentDid: string;
|
|
132
|
+
/**
|
|
133
|
+
* Trusted issuer DID allowlist. Defaults to {@link DEFAULT_TRUSTED_ISSUERS}.
|
|
134
|
+
* If supplied, replaces (does NOT merge with) the default — callers wanting
|
|
135
|
+
* to extend the default should spread it themselves.
|
|
136
|
+
*/
|
|
137
|
+
trustedIssuers?: readonly string[];
|
|
138
|
+
/**
|
|
139
|
+
* Platform-specific signature verifier. If omitted, signatures are accepted
|
|
140
|
+
* unverified and a global warning is emitted.
|
|
141
|
+
*/
|
|
142
|
+
signatureVerifier?: CapabilityVCSignatureVerifier;
|
|
143
|
+
/**
|
|
144
|
+
* StatusList2021 revocation hook. Reserved — not invoked in v1.
|
|
145
|
+
*
|
|
146
|
+
* TODO(v1.x): wire this into the per-attestation check loop once the
|
|
147
|
+
* StatusList2021 contract is finalized for capability attestations.
|
|
148
|
+
*/
|
|
149
|
+
statusListResolver?: CapabilityStatusListResolver;
|
|
150
|
+
/**
|
|
151
|
+
* Clock injection for tests. Defaults to `new Date()`.
|
|
152
|
+
*/
|
|
153
|
+
now?: () => Date;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Per-capability failure record.
|
|
157
|
+
*/
|
|
158
|
+
export interface InvalidCapability {
|
|
159
|
+
capability: CapabilityName;
|
|
160
|
+
reasons: string[];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Validator result.
|
|
164
|
+
*
|
|
165
|
+
* - `valid` lists the capability names that passed validation.
|
|
166
|
+
* - `invalid` lists the capabilities that were declared but failed, with the
|
|
167
|
+
* accumulated reasons across every attached attestation.
|
|
168
|
+
* - `warnings` carries non-fatal diagnostics (e.g. "no signature verifier
|
|
169
|
+
* provided", or Level 1 input).
|
|
170
|
+
*/
|
|
171
|
+
export interface ValidateLevel2Result {
|
|
172
|
+
valid: CapabilityName[];
|
|
173
|
+
invalid: InvalidCapability[];
|
|
174
|
+
warnings: string[];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Verify an MCP-I agent's declared capability attestations (Level 2).
|
|
178
|
+
*
|
|
179
|
+
* Named for the action it performs — verifying capability ATTESTATIONS — rather
|
|
180
|
+
* than the conformance level. {@link validateLevel2} remains exported as a
|
|
181
|
+
* non-breaking `@deprecated` alias.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* const result = await verifyCapabilities(manifest.capabilities, {
|
|
186
|
+
* agentDid: 'did:web:securebank.example',
|
|
187
|
+
* trustedIssuers: ['did:web:knowthat.ai'],
|
|
188
|
+
* signatureVerifier: myEd25519Verifier,
|
|
189
|
+
* });
|
|
190
|
+
*
|
|
191
|
+
* console.log(result.valid); // ['payments.transfer']
|
|
192
|
+
* console.log(result.invalid); // [{ capability: 'accounts.read', reasons: [...] }]
|
|
193
|
+
* console.log(result.warnings); // []
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export declare function verifyCapabilities(capabilities: unknown, options: ValidateLevel2Options): Promise<ValidateLevel2Result>;
|
|
197
|
+
/**
|
|
198
|
+
* @deprecated Renamed to {@link verifyCapabilities}. Retained as a non-breaking
|
|
199
|
+
* alias — the two are referentially identical. Prefer `verifyCapabilities` in
|
|
200
|
+
* new code; this alias will be removed in a future major.
|
|
201
|
+
*/
|
|
202
|
+
export declare const validateLevel2: typeof verifyCapabilities;
|
|
203
|
+
//# sourceMappingURL=validate-level2.d.ts.map
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Level 2 Capability Attestation Validator
|
|
4
|
+
*
|
|
5
|
+
* Implements the Level 2 conformance behavior described in the MCP-I spec:
|
|
6
|
+
* https://github.com/modelcontextprotocol-identity/modelcontextprotocol-identity/pull/46
|
|
7
|
+
*
|
|
8
|
+
* For each declared capability in the agent manifest, verify at least one
|
|
9
|
+
* attached Verifiable Credential where ALL of the following hold:
|
|
10
|
+
* - VC signature verifies (delegated to a platform-specific verifier)
|
|
11
|
+
* - issuer DID is in the trusted issuer registry
|
|
12
|
+
* - credentialSubject.id matches the agent DID
|
|
13
|
+
* - credentialSubject.capability matches the declared capability name
|
|
14
|
+
* - validUntil is in the future (and validFrom is in the past, when present)
|
|
15
|
+
*
|
|
16
|
+
* Revocation (StatusList2021) is OPTIONAL for v1 and not enforced here.
|
|
17
|
+
*
|
|
18
|
+
* Capabilities that fail validation are NOT thrown — they are returned in the
|
|
19
|
+
* `invalid` array along with the reasons, so callers can surface diagnostics
|
|
20
|
+
* to UI without a hard failure.
|
|
21
|
+
*
|
|
22
|
+
* The legacy Level 1 `string[]` form is accepted as input: every entry is
|
|
23
|
+
* returned under `valid` along with a global warning so callers can surface
|
|
24
|
+
* the missing attestation context.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.validateLevel2 = exports.DEFAULT_TRUSTED_ISSUERS = void 0;
|
|
28
|
+
exports.verifyCapabilities = verifyCapabilities;
|
|
29
|
+
const utils_js_1 = require("../delegation/utils.js");
|
|
30
|
+
/**
|
|
31
|
+
* Default trusted issuer registry.
|
|
32
|
+
*
|
|
33
|
+
* Matches the spec default: `did:web:knowthat.ai`. Callers MAY substitute or
|
|
34
|
+
* extend this allowlist via {@link ValidateLevel2Options.trustedIssuers}.
|
|
35
|
+
*/
|
|
36
|
+
exports.DEFAULT_TRUSTED_ISSUERS = [
|
|
37
|
+
'did:web:knowthat.ai',
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Verify an MCP-I agent's declared capability attestations (Level 2).
|
|
41
|
+
*
|
|
42
|
+
* Named for the action it performs — verifying capability ATTESTATIONS — rather
|
|
43
|
+
* than the conformance level. {@link validateLevel2} remains exported as a
|
|
44
|
+
* non-breaking `@deprecated` alias.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const result = await verifyCapabilities(manifest.capabilities, {
|
|
49
|
+
* agentDid: 'did:web:securebank.example',
|
|
50
|
+
* trustedIssuers: ['did:web:knowthat.ai'],
|
|
51
|
+
* signatureVerifier: myEd25519Verifier,
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* console.log(result.valid); // ['payments.transfer']
|
|
55
|
+
* console.log(result.invalid); // [{ capability: 'accounts.read', reasons: [...] }]
|
|
56
|
+
* console.log(result.warnings); // []
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
async function verifyCapabilities(capabilities, options) {
|
|
60
|
+
const result = {
|
|
61
|
+
valid: [],
|
|
62
|
+
invalid: [],
|
|
63
|
+
warnings: [],
|
|
64
|
+
};
|
|
65
|
+
if (!Array.isArray(capabilities)) {
|
|
66
|
+
result.warnings.push('capabilities is not an array — no capabilities validated');
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
// Level 1 string[] passthrough: every entry is a bare capability name.
|
|
70
|
+
if (capabilities.length > 0 && capabilities.every((c) => typeof c === 'string')) {
|
|
71
|
+
result.valid.push(...capabilities);
|
|
72
|
+
result.warnings.push('Level 1 self-declared — no attestation verified');
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
const trustedIssuers = new Set(options.trustedIssuers ?? exports.DEFAULT_TRUSTED_ISSUERS);
|
|
76
|
+
const now = (options.now?.() ?? new Date()).getTime();
|
|
77
|
+
if (!options.signatureVerifier) {
|
|
78
|
+
result.warnings.push('No signatureVerifier provided — VC signatures accepted without cryptographic verification');
|
|
79
|
+
}
|
|
80
|
+
for (const entry of capabilities) {
|
|
81
|
+
// Mixed-array case: a bare string in a Level 2 array is unattested per
|
|
82
|
+
// spec ("MUST treat any capability supplied as a string … as unattested
|
|
83
|
+
// and therefore ignore it"). We surface it as invalid with a clear reason
|
|
84
|
+
// so callers can diagnose, rather than dropping silently.
|
|
85
|
+
if (typeof entry === 'string') {
|
|
86
|
+
result.invalid.push({
|
|
87
|
+
capability: entry,
|
|
88
|
+
reasons: ['capability declared as bare string with no attestations'],
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!isLevel2Capability(entry)) {
|
|
93
|
+
// Entry has a name but malformed/missing attestations — report in invalid.
|
|
94
|
+
if (typeof entry === 'object' &&
|
|
95
|
+
entry !== null &&
|
|
96
|
+
typeof entry.name === 'string') {
|
|
97
|
+
result.invalid.push({
|
|
98
|
+
capability: entry.name,
|
|
99
|
+
reasons: ['capability has invalid or missing attestations array'],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
result.warnings.push(`Skipping capability entry of unrecognized shape: ${safeStringify(entry)}`);
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const capabilityResult = await validateCapability(entry, {
|
|
108
|
+
agentDid: options.agentDid,
|
|
109
|
+
trustedIssuers,
|
|
110
|
+
now,
|
|
111
|
+
signatureVerifier: options.signatureVerifier,
|
|
112
|
+
});
|
|
113
|
+
if (capabilityResult.valid) {
|
|
114
|
+
result.valid.push(entry.name);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
result.invalid.push({
|
|
118
|
+
capability: entry.name,
|
|
119
|
+
reasons: capabilityResult.reasons,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* @deprecated Renamed to {@link verifyCapabilities}. Retained as a non-breaking
|
|
127
|
+
* alias — the two are referentially identical. Prefer `verifyCapabilities` in
|
|
128
|
+
* new code; this alias will be removed in a future major.
|
|
129
|
+
*/
|
|
130
|
+
exports.validateLevel2 = verifyCapabilities;
|
|
131
|
+
async function validateCapability(capability, ctx) {
|
|
132
|
+
const reasons = [];
|
|
133
|
+
if (!Array.isArray(capability.attestations) || capability.attestations.length === 0) {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
reasons: ['no attestations attached'],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
for (let i = 0; i < capability.attestations.length; i++) {
|
|
140
|
+
const attestation = capability.attestations[i];
|
|
141
|
+
const attestationLabel = `attestation[${i}]`;
|
|
142
|
+
const parsed = decodeAttestationVC(attestation);
|
|
143
|
+
if (!parsed.ok) {
|
|
144
|
+
reasons.push(`${attestationLabel}: ${parsed.reason}`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const { vc, rawJwt } = parsed;
|
|
148
|
+
const issuerDid = extractIssuerDid(vc);
|
|
149
|
+
if (!issuerDid) {
|
|
150
|
+
reasons.push(`${attestationLabel}: VC missing issuer`);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!ctx.trustedIssuers.has(issuerDid)) {
|
|
154
|
+
reasons.push(`${attestationLabel}: issuer ${issuerDid} not in trusted registry`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (vc.credentialSubject?.id !== ctx.agentDid) {
|
|
158
|
+
reasons.push(`${attestationLabel}: credentialSubject.id ${vc.credentialSubject?.id ?? '(missing)'} does not match agent DID ${ctx.agentDid}`);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (vc.credentialSubject?.capability !== capability.name) {
|
|
162
|
+
reasons.push(`${attestationLabel}: credentialSubject.capability ${vc.credentialSubject?.capability ?? '(missing)'} does not match declared capability ${capability.name}`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const validUntilMs = vc.validUntil ? Date.parse(vc.validUntil) : NaN;
|
|
166
|
+
if (!vc.validUntil || Number.isNaN(validUntilMs)) {
|
|
167
|
+
reasons.push(`${attestationLabel}: VC missing or unparseable validUntil`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (validUntilMs <= ctx.now) {
|
|
171
|
+
reasons.push(`${attestationLabel}: VC expired (validUntil ${vc.validUntil})`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (vc.validFrom) {
|
|
175
|
+
const validFromMs = Date.parse(vc.validFrom);
|
|
176
|
+
if (Number.isNaN(validFromMs)) {
|
|
177
|
+
reasons.push(`${attestationLabel}: unparseable validFrom`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (validFromMs > ctx.now) {
|
|
181
|
+
reasons.push(`${attestationLabel}: VC not yet valid (validFrom ${vc.validFrom})`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (ctx.signatureVerifier) {
|
|
186
|
+
let sig;
|
|
187
|
+
try {
|
|
188
|
+
sig = await ctx.signatureVerifier(vc, rawJwt);
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
192
|
+
reasons.push(`${attestationLabel}: signature verifier threw: ${message}`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (!sig.valid) {
|
|
196
|
+
reasons.push(`${attestationLabel}: signature invalid${sig.reason ? ` (${sig.reason})` : ''}`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// TODO(v1.x): when StatusList2021 hook lands, invoke
|
|
201
|
+
// ctx.statusListResolver here and reject revoked attestations. v1 skips
|
|
202
|
+
// revocation per the spec design doc.
|
|
203
|
+
return { valid: true, reasons: [] };
|
|
204
|
+
}
|
|
205
|
+
return { valid: false, reasons };
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Decode an attestation `vc` field (string JWT or object) into a credential.
|
|
209
|
+
*/
|
|
210
|
+
function decodeAttestationVC(attestation) {
|
|
211
|
+
if (attestation === null || typeof attestation !== 'object') {
|
|
212
|
+
return { ok: false, reason: 'attestation entry is not an object' };
|
|
213
|
+
}
|
|
214
|
+
const vc = attestation.vc;
|
|
215
|
+
if (typeof vc === 'string') {
|
|
216
|
+
const parsed = (0, utils_js_1.parseVCJWT)(vc);
|
|
217
|
+
if (!parsed) {
|
|
218
|
+
return { ok: false, reason: 'VC JWT could not be parsed' };
|
|
219
|
+
}
|
|
220
|
+
const inner = parsed.payload.vc;
|
|
221
|
+
if (!inner || typeof inner !== 'object') {
|
|
222
|
+
return { ok: false, reason: 'VC JWT payload missing vc claim' };
|
|
223
|
+
}
|
|
224
|
+
// Per W3C VC-JWT spec §6.3: promote JWT-level iss→vc.issuer and
|
|
225
|
+
// sub→vc.credentialSubject.id when absent from the embedded vc object so
|
|
226
|
+
// that externally-issued spec-compliant JWTs are not falsely rejected.
|
|
227
|
+
const merged = { ...inner };
|
|
228
|
+
if (!merged.issuer && typeof parsed.payload.iss === 'string') {
|
|
229
|
+
merged.issuer = parsed.payload.iss;
|
|
230
|
+
}
|
|
231
|
+
if (typeof parsed.payload.sub === 'string' &&
|
|
232
|
+
merged.credentialSubject &&
|
|
233
|
+
typeof merged.credentialSubject === 'object' &&
|
|
234
|
+
!merged.credentialSubject.id) {
|
|
235
|
+
merged.credentialSubject = {
|
|
236
|
+
...merged.credentialSubject,
|
|
237
|
+
id: parsed.payload.sub,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// Per W3C VC-JWT spec §6.3: also promote exp→validUntil and nbf→validFrom
|
|
241
|
+
// when absent from the embedded vc object, so externally-issued JWTs that
|
|
242
|
+
// carry validity only in the standard JWT claims are not falsely rejected.
|
|
243
|
+
// JWT exp/nbf are Unix seconds (NumericDate). Guard the conversion: an
|
|
244
|
+
// out-of-range claim yields an Invalid Date, which we leave unset (→ the VC
|
|
245
|
+
// fails closed at validation) rather than letting toISOString() throw a
|
|
246
|
+
// RangeError out of this otherwise-total decoder.
|
|
247
|
+
if (!merged.validUntil && typeof parsed.payload.exp === 'number') {
|
|
248
|
+
const d = new Date(parsed.payload.exp * 1000);
|
|
249
|
+
if (!Number.isNaN(d.getTime()))
|
|
250
|
+
merged.validUntil = d.toISOString();
|
|
251
|
+
}
|
|
252
|
+
if (!merged.validFrom && typeof parsed.payload.nbf === 'number') {
|
|
253
|
+
const d = new Date(parsed.payload.nbf * 1000);
|
|
254
|
+
if (!Number.isNaN(d.getTime()))
|
|
255
|
+
merged.validFrom = d.toISOString();
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
ok: true,
|
|
259
|
+
vc: merged,
|
|
260
|
+
rawJwt: vc,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (vc && typeof vc === 'object') {
|
|
264
|
+
return {
|
|
265
|
+
ok: true,
|
|
266
|
+
vc: vc,
|
|
267
|
+
rawJwt: undefined,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return { ok: false, reason: 'attestation.vc missing or invalid type' };
|
|
271
|
+
}
|
|
272
|
+
function extractIssuerDid(vc) {
|
|
273
|
+
if (typeof vc.issuer === 'string')
|
|
274
|
+
return vc.issuer;
|
|
275
|
+
if (vc.issuer && typeof vc.issuer === 'object' && typeof vc.issuer.id === 'string') {
|
|
276
|
+
return vc.issuer.id;
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
function isLevel2Capability(value) {
|
|
281
|
+
return (typeof value === 'object' &&
|
|
282
|
+
value !== null &&
|
|
283
|
+
typeof value.name === 'string' &&
|
|
284
|
+
Array.isArray(value.attestations));
|
|
285
|
+
}
|
|
286
|
+
function safeStringify(value) {
|
|
287
|
+
try {
|
|
288
|
+
return JSON.stringify(value);
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return String(value);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=validate-level2.js.map
|