@mcp-i/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +390 -0
  3. package/dist/auth/handshake.d.ts +104 -0
  4. package/dist/auth/handshake.d.ts.map +1 -0
  5. package/dist/auth/handshake.js +230 -0
  6. package/dist/auth/handshake.js.map +1 -0
  7. package/dist/auth/index.d.ts +3 -0
  8. package/dist/auth/index.d.ts.map +1 -0
  9. package/dist/auth/index.js +2 -0
  10. package/dist/auth/index.js.map +1 -0
  11. package/dist/auth/types.d.ts +31 -0
  12. package/dist/auth/types.d.ts.map +1 -0
  13. package/dist/auth/types.js +7 -0
  14. package/dist/auth/types.js.map +1 -0
  15. package/dist/delegation/audience-validator.d.ts +9 -0
  16. package/dist/delegation/audience-validator.d.ts.map +1 -0
  17. package/dist/delegation/audience-validator.js +17 -0
  18. package/dist/delegation/audience-validator.js.map +1 -0
  19. package/dist/delegation/bitstring.d.ts +37 -0
  20. package/dist/delegation/bitstring.d.ts.map +1 -0
  21. package/dist/delegation/bitstring.js +117 -0
  22. package/dist/delegation/bitstring.js.map +1 -0
  23. package/dist/delegation/cascading-revocation.d.ts +45 -0
  24. package/dist/delegation/cascading-revocation.d.ts.map +1 -0
  25. package/dist/delegation/cascading-revocation.js +148 -0
  26. package/dist/delegation/cascading-revocation.js.map +1 -0
  27. package/dist/delegation/delegation-graph.d.ts +49 -0
  28. package/dist/delegation/delegation-graph.d.ts.map +1 -0
  29. package/dist/delegation/delegation-graph.js +99 -0
  30. package/dist/delegation/delegation-graph.js.map +1 -0
  31. package/dist/delegation/did-key-resolver.d.ts +64 -0
  32. package/dist/delegation/did-key-resolver.d.ts.map +1 -0
  33. package/dist/delegation/did-key-resolver.js +154 -0
  34. package/dist/delegation/did-key-resolver.js.map +1 -0
  35. package/dist/delegation/did-web-resolver.d.ts +83 -0
  36. package/dist/delegation/did-web-resolver.d.ts.map +1 -0
  37. package/dist/delegation/did-web-resolver.js +218 -0
  38. package/dist/delegation/did-web-resolver.js.map +1 -0
  39. package/dist/delegation/index.d.ts +21 -0
  40. package/dist/delegation/index.d.ts.map +1 -0
  41. package/dist/delegation/index.js +21 -0
  42. package/dist/delegation/index.js.map +1 -0
  43. package/dist/delegation/outbound-headers.d.ts +81 -0
  44. package/dist/delegation/outbound-headers.d.ts.map +1 -0
  45. package/dist/delegation/outbound-headers.js +139 -0
  46. package/dist/delegation/outbound-headers.js.map +1 -0
  47. package/dist/delegation/outbound-proof.d.ts +43 -0
  48. package/dist/delegation/outbound-proof.d.ts.map +1 -0
  49. package/dist/delegation/outbound-proof.js +52 -0
  50. package/dist/delegation/outbound-proof.js.map +1 -0
  51. package/dist/delegation/statuslist-manager.d.ts +44 -0
  52. package/dist/delegation/statuslist-manager.d.ts.map +1 -0
  53. package/dist/delegation/statuslist-manager.js +126 -0
  54. package/dist/delegation/statuslist-manager.js.map +1 -0
  55. package/dist/delegation/storage/memory-graph-storage.d.ts +70 -0
  56. package/dist/delegation/storage/memory-graph-storage.d.ts.map +1 -0
  57. package/dist/delegation/storage/memory-graph-storage.js +145 -0
  58. package/dist/delegation/storage/memory-graph-storage.js.map +1 -0
  59. package/dist/delegation/storage/memory-statuslist-storage.d.ts +19 -0
  60. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +1 -0
  61. package/dist/delegation/storage/memory-statuslist-storage.js +33 -0
  62. package/dist/delegation/storage/memory-statuslist-storage.js.map +1 -0
  63. package/dist/delegation/utils.d.ts +49 -0
  64. package/dist/delegation/utils.d.ts.map +1 -0
  65. package/dist/delegation/utils.js +131 -0
  66. package/dist/delegation/utils.js.map +1 -0
  67. package/dist/delegation/vc-issuer.d.ts +56 -0
  68. package/dist/delegation/vc-issuer.d.ts.map +1 -0
  69. package/dist/delegation/vc-issuer.js +80 -0
  70. package/dist/delegation/vc-issuer.js.map +1 -0
  71. package/dist/delegation/vc-verifier.d.ts +112 -0
  72. package/dist/delegation/vc-verifier.d.ts.map +1 -0
  73. package/dist/delegation/vc-verifier.js +280 -0
  74. package/dist/delegation/vc-verifier.js.map +1 -0
  75. package/dist/index.d.ts +45 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +53 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/logging/index.d.ts +2 -0
  80. package/dist/logging/index.d.ts.map +1 -0
  81. package/dist/logging/index.js +2 -0
  82. package/dist/logging/index.js.map +1 -0
  83. package/dist/logging/logger.d.ts +23 -0
  84. package/dist/logging/logger.d.ts.map +1 -0
  85. package/dist/logging/logger.js +82 -0
  86. package/dist/logging/logger.js.map +1 -0
  87. package/dist/middleware/index.d.ts +7 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +7 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/with-mcpi.d.ts +152 -0
  92. package/dist/middleware/with-mcpi.d.ts.map +1 -0
  93. package/dist/middleware/with-mcpi.js +472 -0
  94. package/dist/middleware/with-mcpi.js.map +1 -0
  95. package/dist/proof/errors.d.ts +49 -0
  96. package/dist/proof/errors.d.ts.map +1 -0
  97. package/dist/proof/errors.js +61 -0
  98. package/dist/proof/errors.js.map +1 -0
  99. package/dist/proof/generator.d.ts +65 -0
  100. package/dist/proof/generator.d.ts.map +1 -0
  101. package/dist/proof/generator.js +163 -0
  102. package/dist/proof/generator.js.map +1 -0
  103. package/dist/proof/index.d.ts +4 -0
  104. package/dist/proof/index.d.ts.map +1 -0
  105. package/dist/proof/index.js +4 -0
  106. package/dist/proof/index.js.map +1 -0
  107. package/dist/proof/verifier.d.ts +108 -0
  108. package/dist/proof/verifier.d.ts.map +1 -0
  109. package/dist/proof/verifier.js +299 -0
  110. package/dist/proof/verifier.js.map +1 -0
  111. package/dist/providers/base.d.ts +64 -0
  112. package/dist/providers/base.d.ts.map +1 -0
  113. package/dist/providers/base.js +19 -0
  114. package/dist/providers/base.js.map +1 -0
  115. package/dist/providers/index.d.ts +3 -0
  116. package/dist/providers/index.d.ts.map +1 -0
  117. package/dist/providers/index.js +3 -0
  118. package/dist/providers/index.js.map +1 -0
  119. package/dist/providers/memory.d.ts +33 -0
  120. package/dist/providers/memory.d.ts.map +1 -0
  121. package/dist/providers/memory.js +102 -0
  122. package/dist/providers/memory.js.map +1 -0
  123. package/dist/session/index.d.ts +2 -0
  124. package/dist/session/index.d.ts.map +1 -0
  125. package/dist/session/index.js +2 -0
  126. package/dist/session/index.js.map +1 -0
  127. package/dist/session/manager.d.ts +77 -0
  128. package/dist/session/manager.d.ts.map +1 -0
  129. package/dist/session/manager.js +251 -0
  130. package/dist/session/manager.js.map +1 -0
  131. package/dist/types/protocol.d.ts +320 -0
  132. package/dist/types/protocol.d.ts.map +1 -0
  133. package/dist/types/protocol.js +229 -0
  134. package/dist/types/protocol.js.map +1 -0
  135. package/dist/utils/base58.d.ts +31 -0
  136. package/dist/utils/base58.d.ts.map +1 -0
  137. package/dist/utils/base58.js +104 -0
  138. package/dist/utils/base58.js.map +1 -0
  139. package/dist/utils/base64.d.ts +13 -0
  140. package/dist/utils/base64.d.ts.map +1 -0
  141. package/dist/utils/base64.js +99 -0
  142. package/dist/utils/base64.js.map +1 -0
  143. package/dist/utils/crypto-service.d.ts +37 -0
  144. package/dist/utils/crypto-service.d.ts.map +1 -0
  145. package/dist/utils/crypto-service.js +153 -0
  146. package/dist/utils/crypto-service.js.map +1 -0
  147. package/dist/utils/did-helpers.d.ts +156 -0
  148. package/dist/utils/did-helpers.d.ts.map +1 -0
  149. package/dist/utils/did-helpers.js +193 -0
  150. package/dist/utils/did-helpers.js.map +1 -0
  151. package/dist/utils/ed25519-constants.d.ts +18 -0
  152. package/dist/utils/ed25519-constants.d.ts.map +1 -0
  153. package/dist/utils/ed25519-constants.js +21 -0
  154. package/dist/utils/ed25519-constants.js.map +1 -0
  155. package/dist/utils/index.d.ts +5 -0
  156. package/dist/utils/index.d.ts.map +1 -0
  157. package/dist/utils/index.js +5 -0
  158. package/dist/utils/index.js.map +1 -0
  159. package/package.json +105 -0
  160. package/src/__tests__/integration/full-flow.test.ts +362 -0
  161. package/src/__tests__/providers/base.test.ts +173 -0
  162. package/src/__tests__/providers/memory.test.ts +332 -0
  163. package/src/__tests__/utils/mock-providers.ts +319 -0
  164. package/src/__tests__/utils/node-crypto-provider.ts +93 -0
  165. package/src/auth/handshake.ts +411 -0
  166. package/src/auth/index.ts +11 -0
  167. package/src/auth/types.ts +40 -0
  168. package/src/delegation/__tests__/audience-validator.test.ts +110 -0
  169. package/src/delegation/__tests__/bitstring.test.ts +346 -0
  170. package/src/delegation/__tests__/cascading-revocation.test.ts +624 -0
  171. package/src/delegation/__tests__/delegation-graph.test.ts +623 -0
  172. package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
  173. package/src/delegation/__tests__/did-web-resolver.test.ts +467 -0
  174. package/src/delegation/__tests__/outbound-headers.test.ts +230 -0
  175. package/src/delegation/__tests__/outbound-proof.test.ts +179 -0
  176. package/src/delegation/__tests__/statuslist-manager.test.ts +515 -0
  177. package/src/delegation/__tests__/utils.test.ts +185 -0
  178. package/src/delegation/__tests__/vc-issuer.test.ts +487 -0
  179. package/src/delegation/__tests__/vc-verifier.test.ts +1029 -0
  180. package/src/delegation/audience-validator.ts +24 -0
  181. package/src/delegation/bitstring.ts +160 -0
  182. package/src/delegation/cascading-revocation.ts +224 -0
  183. package/src/delegation/delegation-graph.ts +143 -0
  184. package/src/delegation/did-key-resolver.ts +181 -0
  185. package/src/delegation/did-web-resolver.ts +270 -0
  186. package/src/delegation/index.ts +33 -0
  187. package/src/delegation/outbound-headers.ts +193 -0
  188. package/src/delegation/outbound-proof.ts +90 -0
  189. package/src/delegation/statuslist-manager.ts +219 -0
  190. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
  191. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
  192. package/src/delegation/storage/memory-graph-storage.ts +178 -0
  193. package/src/delegation/storage/memory-statuslist-storage.ts +42 -0
  194. package/src/delegation/utils.ts +189 -0
  195. package/src/delegation/vc-issuer.ts +137 -0
  196. package/src/delegation/vc-verifier.ts +440 -0
  197. package/src/index.ts +264 -0
  198. package/src/logging/__tests__/logger.test.ts +366 -0
  199. package/src/logging/index.ts +6 -0
  200. package/src/logging/logger.ts +91 -0
  201. package/src/middleware/__tests__/with-mcpi.test.ts +504 -0
  202. package/src/middleware/index.ts +16 -0
  203. package/src/middleware/with-mcpi.ts +766 -0
  204. package/src/proof/__tests__/proof-generator.test.ts +483 -0
  205. package/src/proof/__tests__/verifier.test.ts +488 -0
  206. package/src/proof/errors.ts +75 -0
  207. package/src/proof/generator.ts +255 -0
  208. package/src/proof/index.ts +22 -0
  209. package/src/proof/verifier.ts +449 -0
  210. package/src/providers/base.ts +68 -0
  211. package/src/providers/index.ts +15 -0
  212. package/src/providers/memory.ts +130 -0
  213. package/src/session/__tests__/session-manager.test.ts +342 -0
  214. package/src/session/index.ts +7 -0
  215. package/src/session/manager.ts +332 -0
  216. package/src/types/protocol.ts +596 -0
  217. package/src/utils/__tests__/base58.test.ts +281 -0
  218. package/src/utils/__tests__/base64.test.ts +239 -0
  219. package/src/utils/__tests__/crypto-service.test.ts +530 -0
  220. package/src/utils/__tests__/did-helpers.test.ts +156 -0
  221. package/src/utils/base58.ts +115 -0
  222. package/src/utils/base64.ts +116 -0
  223. package/src/utils/crypto-service.ts +209 -0
  224. package/src/utils/did-helpers.ts +210 -0
  225. package/src/utils/ed25519-constants.ts +23 -0
  226. package/src/utils/index.ts +9 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * DID:key Resolver
3
+ *
4
+ * Resolves did:key DIDs to DID Documents with verification methods.
5
+ * Supports Ed25519 keys (multicodec prefix 0xed01).
6
+ *
7
+ * did:key format: did:key:z<multibase-base58btc(<multicodec-prefix><public-key>)>
8
+ *
9
+ * For Ed25519:
10
+ * - Multicodec prefix: 0xed 0x01
11
+ * - Public key: 32 bytes
12
+ * - Multibase prefix: 'z' (base58btc)
13
+ *
14
+ * @see https://w3c-ccg.github.io/did-method-key/
15
+ */
16
+
17
+ import { base58Decode } from '../utils/base58.js';
18
+ import { base64urlEncodeFromBytes } from '../utils/base64.js';
19
+ import type { DIDResolver, DIDDocument, VerificationMethod } from './vc-verifier.js';
20
+ import { logger } from '../logging/index.js';
21
+
22
+ /** Ed25519 multicodec prefix (0xed 0x01) */
23
+ const ED25519_MULTICODEC_PREFIX = new Uint8Array([0xed, 0x01]);
24
+
25
+ /** Ed25519 public key length */
26
+ const ED25519_PUBLIC_KEY_LENGTH = 32;
27
+
28
+ /**
29
+ * Check if a DID is a valid did:key with Ed25519 key
30
+ *
31
+ * Ed25519 keys in did:key start with 'z6Mk' after the method prefix.
32
+ * The 'z' is the multibase prefix for base58btc, and '6Mk' is the
33
+ * base58-encoded prefix for Ed25519 (0xed 0x01).
34
+ *
35
+ * @param did - The DID to check
36
+ * @returns true if it's a valid did:key with Ed25519 key
37
+ */
38
+ export function isEd25519DidKey(did: string): boolean {
39
+ return did.startsWith('did:key:z6Mk');
40
+ }
41
+
42
+ /**
43
+ * Extract the public key bytes from a did:key DID
44
+ *
45
+ * @param did - The did:key DID
46
+ * @returns Public key bytes or null if invalid
47
+ */
48
+ export function extractPublicKeyFromDidKey(did: string): Uint8Array | null {
49
+ if (!did.startsWith('did:key:z')) {
50
+ return null;
51
+ }
52
+
53
+ try {
54
+ // Extract the multibase-encoded part (after 'did:key:')
55
+ const multibaseKey = did.replace('did:key:', '');
56
+
57
+ // Remove the 'z' multibase prefix (base58btc)
58
+ const base58Encoded = multibaseKey.slice(1);
59
+
60
+ // Decode from base58
61
+ const multicodecBytes = base58Decode(base58Encoded);
62
+
63
+ // Check for Ed25519 multicodec prefix (0xed 0x01)
64
+ if (
65
+ multicodecBytes.length < ED25519_MULTICODEC_PREFIX.length + ED25519_PUBLIC_KEY_LENGTH ||
66
+ multicodecBytes[0] !== ED25519_MULTICODEC_PREFIX[0] ||
67
+ multicodecBytes[1] !== ED25519_MULTICODEC_PREFIX[1]
68
+ ) {
69
+ return null;
70
+ }
71
+
72
+ // Extract the public key (bytes after the prefix)
73
+ return multicodecBytes.slice(ED25519_MULTICODEC_PREFIX.length);
74
+ } catch (error) {
75
+ logger.debug('Failed to extract public key from did:key', error);
76
+ return null;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Convert Ed25519 public key bytes to JWK format
82
+ *
83
+ * @param publicKeyBytes - 32-byte Ed25519 public key
84
+ * @returns JWK object
85
+ */
86
+ export function publicKeyToJwk(publicKeyBytes: Uint8Array): {
87
+ kty: string;
88
+ crv: string;
89
+ x: string;
90
+ } {
91
+ return {
92
+ kty: 'OKP',
93
+ crv: 'Ed25519',
94
+ x: base64urlEncodeFromBytes(publicKeyBytes),
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Create a DID:key resolver
100
+ *
101
+ * Returns a DIDResolver that can resolve did:key DIDs to DID Documents.
102
+ * Currently supports only Ed25519 keys.
103
+ *
104
+ * @returns DIDResolver implementation for did:key
105
+ */
106
+ export function createDidKeyResolver(): DIDResolver {
107
+ return {
108
+ resolve: async (did: string): Promise<DIDDocument | null> => {
109
+ // Check if it's a did:key with Ed25519
110
+ if (!isEd25519DidKey(did)) {
111
+ return null;
112
+ }
113
+
114
+ // Extract the public key
115
+ const publicKeyBytes = extractPublicKeyFromDidKey(did);
116
+ if (!publicKeyBytes) {
117
+ return null;
118
+ }
119
+
120
+ // Convert to JWK
121
+ const publicKeyJwk = publicKeyToJwk(publicKeyBytes);
122
+
123
+ // Get the multibase-encoded key for publicKeyMultibase
124
+ const multibaseKey = did.replace('did:key:', '');
125
+
126
+ // Construct the verification method
127
+ const verificationMethod: VerificationMethod = {
128
+ id: `${did}#keys-1`,
129
+ type: 'Ed25519VerificationKey2020',
130
+ controller: did,
131
+ publicKeyJwk,
132
+ publicKeyMultibase: multibaseKey,
133
+ };
134
+
135
+ // Construct and return the DID Document
136
+ return {
137
+ id: did,
138
+ verificationMethod: [verificationMethod],
139
+ authentication: [`${did}#keys-1`],
140
+ assertionMethod: [`${did}#keys-1`],
141
+ };
142
+ },
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Resolve a did:key DID synchronously
148
+ *
149
+ * Convenience function for cases where async is not needed.
150
+ *
151
+ * @param did - The did:key DID to resolve
152
+ * @returns DID Document or null if invalid
153
+ */
154
+ export function resolveDidKeySync(did: string): DIDDocument | null {
155
+ if (!isEd25519DidKey(did)) {
156
+ return null;
157
+ }
158
+
159
+ const publicKeyBytes = extractPublicKeyFromDidKey(did);
160
+ if (!publicKeyBytes) {
161
+ return null;
162
+ }
163
+
164
+ const publicKeyJwk = publicKeyToJwk(publicKeyBytes);
165
+ const multibaseKey = did.replace('did:key:', '');
166
+
167
+ const verificationMethod: VerificationMethod = {
168
+ id: `${did}#keys-1`,
169
+ type: 'Ed25519VerificationKey2020',
170
+ controller: did,
171
+ publicKeyJwk,
172
+ publicKeyMultibase: multibaseKey,
173
+ };
174
+
175
+ return {
176
+ id: did,
177
+ verificationMethod: [verificationMethod],
178
+ authentication: [`${did}#keys-1`],
179
+ assertionMethod: [`${did}#keys-1`],
180
+ };
181
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * DID:web Resolver
3
+ *
4
+ * Resolves did:web DIDs by fetching /.well-known/did.json from the domain.
5
+ * Supports both root domain DIDs and path-based DIDs.
6
+ *
7
+ * Examples:
8
+ * did:web:example.com → https://example.com/.well-known/did.json
9
+ * did:web:example.com:agents:bot1 → https://example.com/agents/bot1/did.json
10
+ *
11
+ * @see https://w3c-ccg.github.io/did-method-web/
12
+ */
13
+
14
+ import type { FetchProvider } from '../providers/base.js';
15
+ import type { DIDResolver, DIDDocument, VerificationMethod } from './vc-verifier.js';
16
+ import { logger } from '../logging/index.js';
17
+
18
+ /**
19
+ * Parsed components of a did:web DID
20
+ */
21
+ interface ParsedDidWeb {
22
+ domain: string;
23
+ path: string[];
24
+ }
25
+
26
+ /**
27
+ * Type guard for checking if value is a valid DID Document structure
28
+ */
29
+ function isValidDIDDocument(value: unknown): value is DIDDocument {
30
+ if (typeof value !== 'object' || value === null) {
31
+ return false;
32
+ }
33
+
34
+ const doc = value as Record<string, unknown>;
35
+
36
+ // id is required and must be a string
37
+ if (typeof doc['id'] !== 'string' || doc['id'].length === 0) {
38
+ return false;
39
+ }
40
+
41
+ // verificationMethod is optional but if present must be an array
42
+ if (doc['verificationMethod'] !== undefined) {
43
+ if (!Array.isArray(doc['verificationMethod'])) {
44
+ return false;
45
+ }
46
+
47
+ // Each verification method must have required fields
48
+ for (const vm of doc['verificationMethod']) {
49
+ if (!isValidVerificationMethod(vm)) {
50
+ return false;
51
+ }
52
+ }
53
+ }
54
+
55
+ return true;
56
+ }
57
+
58
+ /**
59
+ * Type guard for checking if value is a valid VerificationMethod
60
+ */
61
+ function isValidVerificationMethod(value: unknown): value is VerificationMethod {
62
+ if (typeof value !== 'object' || value === null) {
63
+ return false;
64
+ }
65
+
66
+ const vm = value as Record<string, unknown>;
67
+
68
+ // id, type, and controller are required strings
69
+ if (typeof vm['id'] !== 'string' || vm['id'].length === 0) {
70
+ return false;
71
+ }
72
+ if (typeof vm['type'] !== 'string' || vm['type'].length === 0) {
73
+ return false;
74
+ }
75
+ if (typeof vm['controller'] !== 'string' || vm['controller'].length === 0) {
76
+ return false;
77
+ }
78
+
79
+ return true;
80
+ }
81
+
82
+ /**
83
+ * Check if a DID is a did:web DID
84
+ *
85
+ * @param did - The DID to check
86
+ * @returns true if it's a did:web DID
87
+ */
88
+ export function isDidWeb(did: string): boolean {
89
+ return did.startsWith('did:web:');
90
+ }
91
+
92
+ /**
93
+ * Parse a did:web DID into its components
94
+ *
95
+ * @param did - The did:web DID to parse
96
+ * @returns Parsed components or null if invalid
97
+ */
98
+ export function parseDidWeb(did: string): ParsedDidWeb | null {
99
+ if (!isDidWeb(did)) {
100
+ return null;
101
+ }
102
+
103
+ // Remove the 'did:web:' prefix
104
+ const remainder = did.slice(8);
105
+
106
+ if (remainder.length === 0) {
107
+ return null;
108
+ }
109
+
110
+ // Split by ':' to get domain and path components
111
+ const parts = remainder.split(':');
112
+
113
+ // First part is the domain (URL-decoded)
114
+ const domain = decodeURIComponent(parts[0]!);
115
+
116
+ if (domain.length === 0) {
117
+ return null;
118
+ }
119
+
120
+ // Remaining parts form the path
121
+ const path = parts.slice(1).map((p) => decodeURIComponent(p));
122
+
123
+ return { domain, path };
124
+ }
125
+
126
+ /**
127
+ * Convert a did:web DID to its resolution URL
128
+ *
129
+ * did:web:example.com → https://example.com/.well-known/did.json
130
+ * did:web:example.com:path:to:doc → https://example.com/path/to/doc/did.json
131
+ *
132
+ * @param did - The did:web DID
133
+ * @returns The resolution URL or null if invalid
134
+ */
135
+ export function didWebToUrl(did: string): string | null {
136
+ const parsed = parseDidWeb(did);
137
+
138
+ if (!parsed) {
139
+ return null;
140
+ }
141
+
142
+ const { domain, path } = parsed;
143
+
144
+ // Build the URL
145
+ // Note: did:web specification requires HTTPS
146
+ let url = `https://${domain}`;
147
+
148
+ if (path.length === 0) {
149
+ // Root domain: use /.well-known/did.json
150
+ url += '/.well-known/did.json';
151
+ } else {
152
+ // Path-based: use /path/to/resource/did.json
153
+ url += '/' + path.join('/') + '/did.json';
154
+ }
155
+
156
+ return url;
157
+ }
158
+
159
+ /**
160
+ * DID:web resolver implementation
161
+ */
162
+ export class DidWebResolver implements DIDResolver {
163
+ private fetchProvider: FetchProvider;
164
+ private cache: Map<string, { document: DIDDocument; expiresAt: number }>;
165
+ private cacheTtl: number;
166
+
167
+ constructor(fetchProvider: FetchProvider, options?: { cacheTtl?: number }) {
168
+ this.fetchProvider = fetchProvider;
169
+ this.cache = new Map();
170
+ this.cacheTtl = options?.cacheTtl ?? 300_000; // 5 minutes default
171
+ }
172
+
173
+ /**
174
+ * Resolve a did:web DID to its DID Document
175
+ *
176
+ * @param did - The did:web DID to resolve
177
+ * @returns The DID Document or null if resolution fails
178
+ */
179
+ async resolve(did: string): Promise<DIDDocument | null> {
180
+ // Check if it's a did:web
181
+ if (!isDidWeb(did)) {
182
+ return null;
183
+ }
184
+
185
+ // Check cache
186
+ const cached = this.cache.get(did);
187
+ if (cached && Date.now() < cached.expiresAt) {
188
+ return cached.document;
189
+ }
190
+
191
+ // Convert to URL
192
+ const url = didWebToUrl(did);
193
+ if (!url) {
194
+ logger.warn(`[DidWebResolver] Invalid did:web format: ${did}`);
195
+ return null;
196
+ }
197
+
198
+ try {
199
+ // Fetch the DID document
200
+ const response = await this.fetchProvider.fetch(url);
201
+
202
+ if (!response.ok) {
203
+ logger.warn(`[DidWebResolver] HTTP ${response.status} fetching ${url}`);
204
+ return null;
205
+ }
206
+
207
+ // Parse JSON
208
+ let json: unknown;
209
+ try {
210
+ json = await response.json();
211
+ } catch {
212
+ logger.warn(`[DidWebResolver] Invalid JSON from ${url}`);
213
+ return null;
214
+ }
215
+
216
+ // Validate structure
217
+ if (!isValidDIDDocument(json)) {
218
+ logger.warn(`[DidWebResolver] Invalid DID Document structure from ${url}`);
219
+ return null;
220
+ }
221
+
222
+ // Verify the id matches the DID
223
+ if (json.id !== did) {
224
+ logger.warn(`[DidWebResolver] DID Document id mismatch: expected ${did}, got ${json.id}`);
225
+ return null;
226
+ }
227
+
228
+ // Cache the result
229
+ this.cache.set(did, {
230
+ document: json,
231
+ expiresAt: Date.now() + this.cacheTtl,
232
+ });
233
+
234
+ return json;
235
+ } catch (error) {
236
+ logger.warn(
237
+ `[DidWebResolver] Error resolving ${did}: ${error instanceof Error ? error.message : 'Unknown error'}`
238
+ );
239
+ return null;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Clear the resolution cache
245
+ */
246
+ clearCache(): void {
247
+ this.cache.clear();
248
+ }
249
+
250
+ /**
251
+ * Clear a specific entry from the cache
252
+ */
253
+ clearCacheEntry(did: string): void {
254
+ this.cache.delete(did);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Create a did:web resolver with the given fetch provider
260
+ *
261
+ * @param fetchProvider - Provider for making HTTP requests
262
+ * @param options - Optional configuration
263
+ * @returns DIDResolver implementation for did:web
264
+ */
265
+ export function createDidWebResolver(
266
+ fetchProvider: FetchProvider,
267
+ options?: { cacheTtl?: number }
268
+ ): DIDResolver {
269
+ return new DidWebResolver(fetchProvider, options);
270
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Delegation Module Exports (Platform-Agnostic)
3
+ *
4
+ * W3C VC-based delegation issuance and verification.
5
+ * Platform-specific adapters (Node.js, Cloudflare) provide signing/verification functions.
6
+ */
7
+
8
+ export * from './vc-issuer.js';
9
+ export * from './vc-verifier.js';
10
+ export * from './bitstring.js';
11
+ export * from './statuslist-manager.js';
12
+ export * from './delegation-graph.js';
13
+ export * from './cascading-revocation.js';
14
+ export * from './utils.js';
15
+ export * from './outbound-proof.js';
16
+ export * from './outbound-headers.js';
17
+ export * from './audience-validator.js';
18
+ export {
19
+ createDidKeyResolver,
20
+ resolveDidKeySync,
21
+ isEd25519DidKey,
22
+ extractPublicKeyFromDidKey,
23
+ publicKeyToJwk,
24
+ } from './did-key-resolver.js';
25
+ export {
26
+ DidWebResolver,
27
+ createDidWebResolver,
28
+ isDidWeb,
29
+ parseDidWeb,
30
+ didWebToUrl,
31
+ } from './did-web-resolver.js';
32
+ export { MemoryStatusListStorage } from './storage/memory-statuslist-storage.js';
33
+ export { MemoryDelegationGraphStorage } from './storage/memory-graph-storage.js';
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Outbound Delegation Headers
3
+ *
4
+ * Builds the full set of outbound delegation headers for forwarding
5
+ * delegation context to downstream services.
6
+ *
7
+ * Headers (MCP-I §7):
8
+ * - X-Agent-DID: the original agent's DID
9
+ * - X-Delegation-Chain: the delegation chain ID (vcId of the root delegation)
10
+ * - X-Session-ID: the current session ID
11
+ * - X-Delegation-Proof: a signed JWT proving the delegation is being forwarded
12
+ *
13
+ * Related Spec: MCP-I §7 — Outbound Delegation Propagation
14
+ */
15
+
16
+ import type { SessionContext, DelegationRecord } from '../types/protocol.js';
17
+ import type { CryptoProvider } from '../providers/base.js';
18
+ import { buildDelegationProofJWT, type Ed25519PrivateJWK } from './outbound-proof.js';
19
+ import { extractPublicKeyFromDidKey, isEd25519DidKey } from './did-key-resolver.js';
20
+ import { base64ToBytes, base64urlEncodeFromBytes } from '../utils/base64.js';
21
+ import { logger } from '../logging/index.js';
22
+
23
+ /**
24
+ * Header names for outbound delegation propagation
25
+ */
26
+ export const OUTBOUND_HEADER_NAMES = {
27
+ AGENT_DID: 'X-Agent-DID',
28
+ DELEGATION_CHAIN: 'X-Delegation-Chain',
29
+ SESSION_ID: 'X-Session-ID',
30
+ DELEGATION_PROOF: 'X-Delegation-Proof',
31
+ } as const;
32
+
33
+ /**
34
+ * Context required to build outbound delegation headers
35
+ */
36
+ export interface OutboundDelegationContext {
37
+ /** The current session context */
38
+ session: SessionContext;
39
+ /** The delegation record being forwarded */
40
+ delegation: DelegationRecord;
41
+ /** The MCP server's identity for signing the proof */
42
+ serverIdentity: {
43
+ did: string;
44
+ kid: string;
45
+ privateKey: string;
46
+ };
47
+ /** The downstream URL being called */
48
+ targetUrl: string;
49
+ }
50
+
51
+ /**
52
+ * Outbound delegation headers to attach to downstream requests
53
+ */
54
+ export interface OutboundDelegationHeaders {
55
+ 'X-Agent-DID': string;
56
+ 'X-Delegation-Chain': string;
57
+ 'X-Session-ID': string;
58
+ 'X-Delegation-Proof': string;
59
+ }
60
+
61
+ /**
62
+ * Extract hostname from a URL
63
+ */
64
+ function extractHostname(url: string): string {
65
+ try {
66
+ const parsed = new URL(url);
67
+ return parsed.hostname;
68
+ } catch {
69
+ logger.warn('Failed to parse target URL, using as-is', { url });
70
+ return url;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Convert base64 private key and DID to Ed25519 JWK format
76
+ */
77
+ function buildPrivateKeyJwk(
78
+ privateKeyBase64: string,
79
+ serverDid: string
80
+ ): Ed25519PrivateJWK {
81
+ // Decode the private key from base64
82
+ const privateKeyBytes = base64ToBytes(privateKeyBase64);
83
+
84
+ // Extract the 32-byte seed (handle both 32-byte and 64-byte formats)
85
+ const seed = privateKeyBytes.length === 64
86
+ ? privateKeyBytes.subarray(0, 32)
87
+ : privateKeyBytes;
88
+
89
+ // Extract public key from did:key
90
+ if (!isEd25519DidKey(serverDid)) {
91
+ throw new Error(`Server DID must be did:key with Ed25519: ${serverDid}`);
92
+ }
93
+
94
+ const publicKeyBytes = extractPublicKeyFromDidKey(serverDid);
95
+ if (!publicKeyBytes) {
96
+ throw new Error(`Failed to extract public key from DID: ${serverDid}`);
97
+ }
98
+
99
+ return {
100
+ kty: 'OKP',
101
+ crv: 'Ed25519',
102
+ x: base64urlEncodeFromBytes(publicKeyBytes),
103
+ d: base64urlEncodeFromBytes(seed),
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Build outbound delegation headers for forwarding to downstream services.
109
+ *
110
+ * When an MCP server calls a downstream service on behalf of an agent,
111
+ * it MUST forward the delegation context using these headers so the
112
+ * downstream service can independently verify the delegation chain.
113
+ *
114
+ * @param context - The delegation context including session, delegation, and server identity
115
+ * @param _cryptoProvider - CryptoProvider (reserved for future use)
116
+ * @returns Headers object to attach to the outbound request
117
+ *
118
+ * @throws {Error} If session is missing agentDid or sessionId
119
+ * @throws {Error} If delegation is missing vcId
120
+ * @throws {Error} If serverIdentity.did is not a valid Ed25519 did:key
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const headers = await buildOutboundDelegationHeaders({
125
+ * session,
126
+ * delegation,
127
+ * serverIdentity: { did: serverDid, kid: serverKid, privateKey },
128
+ * targetUrl: 'https://downstream-api.example.com/resource',
129
+ * }, cryptoProvider);
130
+ *
131
+ * // Attach headers to your HTTP request
132
+ * fetch(targetUrl, { headers });
133
+ * ```
134
+ */
135
+ export async function buildOutboundDelegationHeaders(
136
+ context: OutboundDelegationContext,
137
+ _cryptoProvider: CryptoProvider
138
+ ): Promise<OutboundDelegationHeaders> {
139
+ const { session, delegation, serverIdentity, targetUrl } = context;
140
+
141
+ // Validate required fields
142
+ if (!session.agentDid) {
143
+ throw new Error('Session must have agentDid for outbound delegation');
144
+ }
145
+
146
+ if (!session.sessionId) {
147
+ throw new Error('Session must have sessionId for outbound delegation');
148
+ }
149
+
150
+ if (!delegation.vcId) {
151
+ throw new Error('Delegation must have vcId for outbound delegation');
152
+ }
153
+
154
+ // Extract hostname for JWT audience
155
+ const targetHostname = extractHostname(targetUrl);
156
+
157
+ // Build the private key JWK from the server identity
158
+ const privateKeyJwk = buildPrivateKeyJwk(
159
+ serverIdentity.privateKey,
160
+ serverIdentity.did
161
+ );
162
+
163
+ // Build the delegation proof JWT
164
+ // Per MCP-I §7, the JWT has:
165
+ // - iss: serverDid (the MCP server forwarding the request)
166
+ // - sub: agentDid (the original agent)
167
+ // - aud: targetHostname (the downstream service)
168
+ // - scope: "delegation:propagate"
169
+ const jwt = await buildDelegationProofJWT({
170
+ agentDid: serverIdentity.did, // becomes iss (server forwarding)
171
+ userDid: session.agentDid, // becomes sub (original agent)
172
+ delegationId: delegation.id,
173
+ delegationChain: delegation.vcId,
174
+ scopes: ['delegation:propagate'],
175
+ privateKeyJwk,
176
+ kid: serverIdentity.kid,
177
+ targetHostname,
178
+ });
179
+
180
+ logger.debug('Built outbound delegation headers', {
181
+ agentDid: session.agentDid,
182
+ delegationChain: delegation.vcId,
183
+ sessionId: session.sessionId,
184
+ targetHostname,
185
+ });
186
+
187
+ return {
188
+ 'X-Agent-DID': session.agentDid,
189
+ 'X-Delegation-Chain': delegation.vcId,
190
+ 'X-Session-ID': session.sessionId,
191
+ 'X-Delegation-Proof': jwt,
192
+ };
193
+ }