@try-works/dsh-model-as-tool 0.1.2 → 0.3.6
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/bin/dsh-tool-mode-credentials.mjs +20 -0
- package/bin/dsh-tool-mode.mjs +202 -0
- package/catalog/desert-ant-models.json +693 -693
- package/client/client.model-tools.js +592 -0
- package/client/index.js +1679 -0
- package/lib/adapters/index.d.ts.map +1 -1
- package/lib/adapters/index.js +4 -4
- package/lib/bindings.d.ts.map +1 -1
- package/lib/bindings.js +26 -25
- package/lib/capability.d.ts.map +1 -1
- package/lib/capability.js +17 -0
- package/lib/capture-path.d.ts +97 -0
- package/lib/capture-path.d.ts.map +1 -0
- package/lib/capture-path.js +152 -0
- package/lib/diagnostics.d.ts +166 -0
- package/lib/diagnostics.d.ts.map +1 -0
- package/lib/diagnostics.js +135 -0
- package/lib/extension-dependencies.d.ts +197 -0
- package/lib/extension-dependencies.d.ts.map +1 -0
- package/lib/extension-dependencies.js +369 -0
- package/lib/extension-dispatch.d.ts +117 -0
- package/lib/extension-dispatch.d.ts.map +1 -0
- package/lib/extension-dispatch.js +124 -0
- package/lib/extension-registrar.d.ts +100 -0
- package/lib/extension-registrar.d.ts.map +1 -0
- package/lib/extension-registrar.js +134 -0
- package/lib/extensions-api.d.ts +217 -0
- package/lib/extensions-api.d.ts.map +1 -0
- package/lib/extensions-api.js +17 -0
- package/lib/extensions-remote.d.ts +54 -0
- package/lib/extensions-remote.d.ts.map +1 -0
- package/lib/extensions-remote.js +66 -0
- package/lib/extensions-service.d.ts +334 -0
- package/lib/extensions-service.d.ts.map +1 -0
- package/lib/extensions-service.js +1351 -0
- package/lib/harness-llm.d.ts +138 -0
- package/lib/harness-llm.d.ts.map +1 -0
- package/lib/harness-llm.js +218 -0
- package/lib/harness-serving.d.ts +95 -0
- package/lib/harness-serving.d.ts.map +1 -0
- package/lib/harness-serving.js +157 -0
- package/lib/headless.d.ts +97 -0
- package/lib/headless.d.ts.map +1 -0
- package/lib/headless.js +349 -0
- package/lib/http-acquisition.d.ts +27 -0
- package/lib/http-acquisition.d.ts.map +1 -0
- package/lib/http-acquisition.js +136 -0
- package/lib/index.d.ts +250 -7
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2625 -70
- package/lib/invocation-accounting.d.ts +169 -0
- package/lib/invocation-accounting.d.ts.map +1 -0
- package/lib/invocation-accounting.js +148 -0
- package/lib/legacy-migration.d.ts +95 -0
- package/lib/legacy-migration.d.ts.map +1 -0
- package/lib/legacy-migration.js +429 -0
- package/lib/managed-workers.d.ts +151 -0
- package/lib/managed-workers.d.ts.map +1 -0
- package/lib/managed-workers.js +249 -0
- package/lib/mcp/client-grant.d.ts +74 -0
- package/lib/mcp/client-grant.d.ts.map +1 -0
- package/lib/mcp/client-grant.js +60 -0
- package/lib/mcp/credential-cli.d.ts +38 -0
- package/lib/mcp/credential-cli.d.ts.map +1 -0
- package/lib/mcp/credential-cli.js +148 -0
- package/lib/mcp/credential-store.d.ts +143 -0
- package/lib/mcp/credential-store.d.ts.map +1 -0
- package/lib/mcp/credential-store.js +454 -0
- package/lib/mcp/host-binding.d.ts +25 -0
- package/lib/mcp/host-binding.d.ts.map +1 -1
- package/lib/mcp/host-binding.js +107 -14
- package/lib/mcp/http.d.ts +68 -18
- package/lib/mcp/http.d.ts.map +1 -1
- package/lib/mcp/http.js +390 -62
- package/lib/mcp/oauth.d.ts +149 -0
- package/lib/mcp/oauth.d.ts.map +1 -0
- package/lib/mcp/oauth.js +396 -0
- package/lib/mcp/protocol.d.ts +46 -7
- package/lib/mcp/protocol.d.ts.map +1 -1
- package/lib/mcp/protocol.js +63 -16
- package/lib/mcp/rate-limit.d.ts +68 -0
- package/lib/mcp/rate-limit.d.ts.map +1 -0
- package/lib/mcp/rate-limit.js +114 -0
- package/lib/mcp/sdk-v2.d.ts +17 -0
- package/lib/mcp/sdk-v2.d.ts.map +1 -0
- package/lib/mcp/sdk-v2.js +181 -0
- package/lib/mcp/server.d.ts +11 -1
- package/lib/mcp/server.d.ts.map +1 -1
- package/lib/mcp/server.js +80 -13
- package/lib/mcp/stdio.d.ts.map +1 -1
- package/lib/mcp/stdio.js +9 -1
- package/lib/mcp/subscriptions.d.ts +43 -1
- package/lib/mcp/subscriptions.d.ts.map +1 -1
- package/lib/mcp/subscriptions.js +90 -9
- package/lib/model-qualification.d.ts +192 -0
- package/lib/model-qualification.d.ts.map +1 -0
- package/lib/model-qualification.js +178 -0
- package/lib/planning.d.ts +246 -0
- package/lib/planning.d.ts.map +1 -0
- package/lib/planning.js +457 -0
- package/lib/prepared-assets.d.ts +178 -0
- package/lib/prepared-assets.d.ts.map +1 -0
- package/lib/prepared-assets.js +286 -0
- package/lib/provider-bridge.d.ts +37 -0
- package/lib/provider-bridge.d.ts.map +1 -1
- package/lib/provider-bridge.js +189 -12
- package/lib/ptc-authority.d.ts +140 -0
- package/lib/ptc-authority.d.ts.map +1 -0
- package/lib/ptc-authority.js +216 -0
- package/lib/ptc-runtime.d.ts +156 -0
- package/lib/ptc-runtime.d.ts.map +1 -0
- package/lib/ptc-runtime.js +191 -0
- package/lib/publication.d.ts +2 -1
- package/lib/publication.d.ts.map +1 -1
- package/lib/publication.js +13 -5
- package/lib/receipt.d.ts.map +1 -1
- package/lib/receipt.js +8 -0
- package/lib/title-proposal.d.ts +194 -0
- package/lib/title-proposal.d.ts.map +1 -0
- package/lib/title-proposal.js +244 -0
- package/lib/tool-mode-run.d.ts +235 -0
- package/lib/tool-mode-run.d.ts.map +1 -0
- package/lib/tool-mode-run.js +283 -0
- package/lib/types.d.ts +3 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/update-surface.d.ts +79 -0
- package/lib/update-surface.d.ts.map +1 -0
- package/lib/update-surface.js +224 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/lang-detect.d.ts +16 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/lang-detect.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/lang-detect.js +85 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/stt-transcribe.d.ts +20 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/stt-transcribe.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/stt-transcribe.js +112 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts +82 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +274 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +26 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/index.js +48 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/meta.d.ts +104 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/meta.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/meta.js +55 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +107 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +246 -0
- package/node_modules/@try-works/tool-mode-contracts/package.json +34 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +31 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +31 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +185 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
- package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
- package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
- package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
- package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
- package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
- package/package.json +50 -17
- package/remote/spec.mjs +439 -0
- package/remote/typert.host.mjs +14 -0
- package/remote/typert.remote-client.mjs +16 -0
- package/lib/adapters/tongue.d.ts +0 -31
- package/lib/adapters/tongue.d.ts.map +0 -1
- package/lib/adapters/tongue.js +0 -93
- package/lib/adapters/voz.d.ts +0 -28
- package/lib/adapters/voz.d.ts.map +0 -1
- package/lib/adapters/voz.js +0 -102
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The embedded LOCAL OAuth 2.1 authorization server + resource-server verifier
|
|
3
|
+
* (P26-15).
|
|
4
|
+
*
|
|
5
|
+
* P26-15 permits exactly two implementations: a tested authorization-server
|
|
6
|
+
* integration, or a properly implemented embedded/local server — and forbids
|
|
7
|
+
* inventing token cryptography. This module is the second: the signing and
|
|
8
|
+
* verification primitives are Node's OWN WebCrypto (`crypto.subtle` HMAC-SHA256),
|
|
9
|
+
* and only the JWT envelope around them is assembled here. No cipher, mode or
|
|
10
|
+
* hash is hand-rolled.
|
|
11
|
+
*
|
|
12
|
+
* The shape follows the parts of RFC 6749/6750/8414/9728 a resource server and a
|
|
13
|
+
* local issuer need:
|
|
14
|
+
*
|
|
15
|
+
* - HS256-signed JWTs with `iss`, `sub` (client id), `aud`, `scope`, `iat`, `exp`
|
|
16
|
+
* and a unique `jti`;
|
|
17
|
+
* - RFC 6750 challenges: `WWW-Authenticate: Bearer error="invalid_token"|...`
|
|
18
|
+
* with `scope="..."` on insufficient_scope;
|
|
19
|
+
* - per-client credentials with INDEPENDENT revocation (a revoked client's tokens
|
|
20
|
+
* fail while every other client keeps working);
|
|
21
|
+
* - RFC 8414 authorization-server metadata and RFC 9728 protected-resource
|
|
22
|
+
* metadata, so a client can discover issuer/audience/token endpoint.
|
|
23
|
+
*
|
|
24
|
+
* The static-token mode remains available as CUSTOM authentication and still
|
|
25
|
+
* claims nothing; this module is what a deployment turns on when it wants the
|
|
26
|
+
* OAuth profile, and `authProfile()` reports which mode is live.
|
|
27
|
+
*
|
|
28
|
+
* @module @try-works/dsh-model-as-tool/mcp/oauth
|
|
29
|
+
*/
|
|
30
|
+
import type { ClientCredentialStore } from './credential-store.ts';
|
|
31
|
+
/** One registered client of the local authorization server. */
|
|
32
|
+
export interface OAuthClient {
|
|
33
|
+
/** The client's identity, recorded in the token's `sub` and in receipts. */
|
|
34
|
+
readonly clientId: string;
|
|
35
|
+
/** The client's secret, the shared symmetric key for HS256. */
|
|
36
|
+
readonly secret: string;
|
|
37
|
+
/** The scope this client may be granted. */
|
|
38
|
+
readonly scope: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
/** Configuration for the embedded authorization server. */
|
|
41
|
+
export interface LocalOAuthServerConfig {
|
|
42
|
+
/** The issuer URI, stamped into every token's `iss`. */
|
|
43
|
+
readonly issuer: string;
|
|
44
|
+
/** The resource the tokens target, stamped into `aud`. */
|
|
45
|
+
readonly audience: string;
|
|
46
|
+
/** Token lifetime in seconds. */
|
|
47
|
+
readonly tokenLifetimeSeconds?: number;
|
|
48
|
+
/** Clock skew tolerated on verification, in seconds. */
|
|
49
|
+
readonly leewaySeconds?: number;
|
|
50
|
+
/**
|
|
51
|
+
* The durable credential store (SEC-01).
|
|
52
|
+
*
|
|
53
|
+
* When present, generated and operator-provisioned clients are PERSISTED
|
|
54
|
+
* owner-only, revocation survives restarts, and the server hydrates its
|
|
55
|
+
* registry from the store at construction. A generation that cannot be
|
|
56
|
+
* persisted registers nothing (fail-closed). Absent keeps the earlier
|
|
57
|
+
* in-memory behavior.
|
|
58
|
+
*/
|
|
59
|
+
readonly store?: ClientCredentialStore;
|
|
60
|
+
}
|
|
61
|
+
/** A generated credential, with the secret returned exactly once. */
|
|
62
|
+
export interface GeneratedClientCredential {
|
|
63
|
+
readonly clientId: string;
|
|
64
|
+
/** Crypto-random, shown once here and never re-echoed (SEC-01). */
|
|
65
|
+
readonly secret: string;
|
|
66
|
+
}
|
|
67
|
+
/** Why verification failed, in RFC 6750 error-code vocabulary. */
|
|
68
|
+
export type OAuthTokenError = {
|
|
69
|
+
readonly code: 'invalid_request';
|
|
70
|
+
readonly description: string;
|
|
71
|
+
} | {
|
|
72
|
+
readonly code: 'invalid_token';
|
|
73
|
+
readonly description: string;
|
|
74
|
+
} | {
|
|
75
|
+
readonly code: 'insufficient_scope';
|
|
76
|
+
readonly description: string;
|
|
77
|
+
readonly scope: string;
|
|
78
|
+
};
|
|
79
|
+
/** A verified token's claims. */
|
|
80
|
+
export interface VerifiedToken {
|
|
81
|
+
readonly clientId: string;
|
|
82
|
+
readonly scope: readonly string[];
|
|
83
|
+
readonly audience: string;
|
|
84
|
+
readonly jti: string;
|
|
85
|
+
}
|
|
86
|
+
/** The embedded local authorization server. */
|
|
87
|
+
export interface LocalOAuthServer {
|
|
88
|
+
/** Register or replace a client credential. */
|
|
89
|
+
upsertClient(client: OAuthClient): void;
|
|
90
|
+
/**
|
|
91
|
+
* GENERATE a fresh client credential with a crypto-random secret (SEC-01).
|
|
92
|
+
*
|
|
93
|
+
* The secret is returned EXACTLY ONCE, in the returned object; every later surface
|
|
94
|
+
* names only the clientId. The generation is fail-closed: when the durable store is
|
|
95
|
+
* present and the credential cannot be persisted, nothing is registered and the
|
|
96
|
+
* call throws — a credential that a restart would forget must never appear usable.
|
|
97
|
+
*/
|
|
98
|
+
generateClient(options: {
|
|
99
|
+
readonly clientId: string;
|
|
100
|
+
readonly scope?: readonly string[];
|
|
101
|
+
}): GeneratedClientCredential;
|
|
102
|
+
/** Revoke ONE client; its tokens fail, every other client is untouched. */
|
|
103
|
+
revokeClient(clientId: string): void;
|
|
104
|
+
/** Issue an access token for a client, or undefined for an unknown/revoked client. */
|
|
105
|
+
issueAccessToken(clientId: string, scope?: readonly string[]): Promise<string>;
|
|
106
|
+
/**
|
|
107
|
+
* Verify a presented bearer token: signature, issuer, audience, expiry and
|
|
108
|
+
* scope, against the live client registry and revocation set.
|
|
109
|
+
*/
|
|
110
|
+
verify(token: string, requiredScope?: readonly string[]): Promise<{
|
|
111
|
+
readonly ok: true;
|
|
112
|
+
readonly token: VerifiedToken;
|
|
113
|
+
} | {
|
|
114
|
+
readonly ok: false;
|
|
115
|
+
readonly error: OAuthTokenError;
|
|
116
|
+
}>;
|
|
117
|
+
/** RFC 8414 authorization-server metadata. */
|
|
118
|
+
authorizationServerMetadata(): Record<string, unknown>;
|
|
119
|
+
/** RFC 9728 protected-resource metadata. */
|
|
120
|
+
protectedResourceMetadata(): Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create the embedded local authorization server.
|
|
124
|
+
*
|
|
125
|
+
* The signing key is DERIVED per client from its secret (SHA-256), so each
|
|
126
|
+
* client's tokens are signed with a key only that client's secret can produce —
|
|
127
|
+
* and revoking a client is removing its secret from the registry, which is the
|
|
128
|
+
* one-sided operation a resource server can perform without consulting the
|
|
129
|
+
* authorization server.
|
|
130
|
+
* @param config - issuer, audience and lifetimes.
|
|
131
|
+
* @returns the server.
|
|
132
|
+
*/
|
|
133
|
+
export declare function createLocalOAuthServer(config: LocalOAuthServerConfig): LocalOAuthServer;
|
|
134
|
+
/** Build the RFC 6750 challenge header for a verification failure. */
|
|
135
|
+
export declare function oauthChallenge(error: OAuthTokenError, realm?: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* The HTTP status RFC 6750 §3.1 assigns to each token error.
|
|
138
|
+
*
|
|
139
|
+
* All three are 401-shaped failures, but the RFC distinguishes them so a client
|
|
140
|
+
* can tell "malformed request, fix it" (400) from "credential bad, re-auth"
|
|
141
|
+
* (401) from "credential fine, grant too small" (403). Flattening every
|
|
142
|
+
* challenge to 401 was the F4 gap: a client receiving 400 for a revoked token
|
|
143
|
+
* would re-send the same request, and one receiving 401 for insufficient scope
|
|
144
|
+
* would re-authenticate pointlessly instead of asking for the scope.
|
|
145
|
+
*/
|
|
146
|
+
export declare function oauthErrorStatus(error: OAuthTokenError): 400 | 401 | 403;
|
|
147
|
+
/** Extract the error code from a challenge, for tests and diagnostics. */
|
|
148
|
+
export declare function challengeErrorCode(challenge: string): string | undefined;
|
|
149
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/mcp/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAElE,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAED,2DAA2D;AAC3D,MAAM,WAAW,sBAAsB;IACrC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,iCAAiC;IACjC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC,wDAAwD;IACxD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,qBAAqB,CAAA;CACvC;AAED,qEAAqE;AACrE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,kEAAkE;AAClE,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAClE;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjG,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;IACvC;;;;;;;OAOG;IACH,cAAc,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,yBAAyB,CAAA;IACrH,2EAA2E;IAC3E,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,sFAAsF;IACtF,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9E;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAC7D;QAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;KAAE,GACpD;QAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;KAAE,CAC1D,CAAA;IACD,8CAA8C;IAC9C,2BAA2B,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtD,4CAA4C;IAC5C,yBAAyB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrD;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CA8TvF;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,SAAc,GAAG,MAAM,CAQlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAMxE;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE"}
|
package/lib/mcp/oauth.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The embedded LOCAL OAuth 2.1 authorization server + resource-server verifier
|
|
3
|
+
* (P26-15).
|
|
4
|
+
*
|
|
5
|
+
* P26-15 permits exactly two implementations: a tested authorization-server
|
|
6
|
+
* integration, or a properly implemented embedded/local server — and forbids
|
|
7
|
+
* inventing token cryptography. This module is the second: the signing and
|
|
8
|
+
* verification primitives are Node's OWN WebCrypto (`crypto.subtle` HMAC-SHA256),
|
|
9
|
+
* and only the JWT envelope around them is assembled here. No cipher, mode or
|
|
10
|
+
* hash is hand-rolled.
|
|
11
|
+
*
|
|
12
|
+
* The shape follows the parts of RFC 6749/6750/8414/9728 a resource server and a
|
|
13
|
+
* local issuer need:
|
|
14
|
+
*
|
|
15
|
+
* - HS256-signed JWTs with `iss`, `sub` (client id), `aud`, `scope`, `iat`, `exp`
|
|
16
|
+
* and a unique `jti`;
|
|
17
|
+
* - RFC 6750 challenges: `WWW-Authenticate: Bearer error="invalid_token"|...`
|
|
18
|
+
* with `scope="..."` on insufficient_scope;
|
|
19
|
+
* - per-client credentials with INDEPENDENT revocation (a revoked client's tokens
|
|
20
|
+
* fail while every other client keeps working);
|
|
21
|
+
* - RFC 8414 authorization-server metadata and RFC 9728 protected-resource
|
|
22
|
+
* metadata, so a client can discover issuer/audience/token endpoint.
|
|
23
|
+
*
|
|
24
|
+
* The static-token mode remains available as CUSTOM authentication and still
|
|
25
|
+
* claims nothing; this module is what a deployment turns on when it wants the
|
|
26
|
+
* OAuth profile, and `authProfile()` reports which mode is live.
|
|
27
|
+
*
|
|
28
|
+
* @module @try-works/dsh-model-as-tool/mcp/oauth
|
|
29
|
+
*/
|
|
30
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
31
|
+
const ENCODER = new TextEncoder();
|
|
32
|
+
/** Base64url without padding, the JWT alphabet. */
|
|
33
|
+
function b64url(bytes) {
|
|
34
|
+
return Buffer.from(bytes).toString('base64url');
|
|
35
|
+
}
|
|
36
|
+
/** Decode one base64url JWT part to its JSON value. */
|
|
37
|
+
function decodePart(part) {
|
|
38
|
+
if (part === undefined || part.length === 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
|
|
42
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
43
|
+
? parsed
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create the embedded local authorization server.
|
|
52
|
+
*
|
|
53
|
+
* The signing key is DERIVED per client from its secret (SHA-256), so each
|
|
54
|
+
* client's tokens are signed with a key only that client's secret can produce —
|
|
55
|
+
* and revoking a client is removing its secret from the registry, which is the
|
|
56
|
+
* one-sided operation a resource server can perform without consulting the
|
|
57
|
+
* authorization server.
|
|
58
|
+
* @param config - issuer, audience and lifetimes.
|
|
59
|
+
* @returns the server.
|
|
60
|
+
*/
|
|
61
|
+
export function createLocalOAuthServer(config) {
|
|
62
|
+
const lifetime = config.tokenLifetimeSeconds ?? 900;
|
|
63
|
+
const leeway = config.leewaySeconds ?? 30;
|
|
64
|
+
const clients = new Map();
|
|
65
|
+
const revoked = new Map(); // clientId -> secret hash, so a re-registration restores access
|
|
66
|
+
// SEC-01: hydrate the registry from the durable store, so generated credentials
|
|
67
|
+
// survive a restart. Revoked clients are ABSENT from the store (revocation deletes),
|
|
68
|
+
// so everything listed is live.
|
|
69
|
+
for (const stored of config.store?.list() ?? []) {
|
|
70
|
+
if (stored.scheme !== 'oauth-resource-server')
|
|
71
|
+
continue;
|
|
72
|
+
clients.set(stored.clientId, { clientId: stored.clientId, secret: stored.secret, scope: stored.scope });
|
|
73
|
+
}
|
|
74
|
+
// The key type is inferred: naming `CryptoKey` here collides with the DOM value
|
|
75
|
+
// of the same name in the Node type surface.
|
|
76
|
+
const keyFor = (secret) => {
|
|
77
|
+
const digest = createHash('sha256').update(secret).digest();
|
|
78
|
+
return crypto.subtle.importKey('raw', digest, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* The LIVE registry entry for one client id.
|
|
82
|
+
*
|
|
83
|
+
* When the durable store exists it is the SOLE authority: the store row is
|
|
84
|
+
* read fresh on every issue and verify, and there is NO fallback to the
|
|
85
|
+
* construction-time hydration —
|
|
86
|
+
*
|
|
87
|
+
* - a row ABSENT from the store is an unknown/revoked client, even if this
|
|
88
|
+
* process hydrated the id earlier (delete-then-recreate invalidates old
|
|
89
|
+
* tokens; deletion through any surface takes effect on the next call);
|
|
90
|
+
* - a row with the WRONG SCHEME (static-token where an OAuth row was
|
|
91
|
+
* hydrated) is REJECTED as an OAuth client — scheme confusion must not
|
|
92
|
+
* resurrect a hydrated entry;
|
|
93
|
+
* - a credential generated after this server was constructed ISSUES
|
|
94
|
+
* immediately (the earlier design answered "unknown client" until
|
|
95
|
+
* restart).
|
|
96
|
+
*
|
|
97
|
+
* The in-memory map serves ONLY the store-less mode.
|
|
98
|
+
*/
|
|
99
|
+
const liveClient = (clientId) => {
|
|
100
|
+
if (config.store !== undefined) {
|
|
101
|
+
const stored = config.store.get(clientId);
|
|
102
|
+
if (stored === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (stored.scheme !== 'oauth-resource-server')
|
|
105
|
+
return undefined;
|
|
106
|
+
return { clientId: stored.clientId, secret: stored.secret, scope: stored.scope };
|
|
107
|
+
}
|
|
108
|
+
return clients.get(clientId);
|
|
109
|
+
};
|
|
110
|
+
/** Validate scope names once, so a bad name is refused at registration. */
|
|
111
|
+
const assertScopeNames = (scope) => {
|
|
112
|
+
// Scope names are RFC 6749 scope-tokens: no whitespace. A name containing a
|
|
113
|
+
// space can never be satisfied (issue joins with ' ', verify splits), so
|
|
114
|
+
// registering one is a configuration error and is refused here rather than
|
|
115
|
+
// silently issuing credentials that can never authorize (review F-g).
|
|
116
|
+
for (const name of scope) {
|
|
117
|
+
if (!/^[A-Za-z0-9._:\/-]+$/.test(name)) {
|
|
118
|
+
throw new Error(`scope name ${JSON.stringify(name)} is not an RFC 6749 scope-token`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const register = (client) => {
|
|
123
|
+
assertScopeNames(client.scope);
|
|
124
|
+
// SEC-01 FAIL-CLOSED: persist FIRST, register only when the credential is durable —
|
|
125
|
+
// a generation that a restart would forget must never appear usable.
|
|
126
|
+
if (config.store !== undefined) {
|
|
127
|
+
config.store.upsert({
|
|
128
|
+
clientId: client.clientId,
|
|
129
|
+
secret: client.secret,
|
|
130
|
+
scheme: 'oauth-resource-server',
|
|
131
|
+
scope: client.scope,
|
|
132
|
+
createdAt: new Date().toISOString(),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
clients.set(client.clientId, client);
|
|
136
|
+
revoked.delete(client.clientId);
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
upsertClient(client) {
|
|
140
|
+
register(client);
|
|
141
|
+
},
|
|
142
|
+
generateClient(options) {
|
|
143
|
+
if (typeof options.clientId !== 'string' || options.clientId.length === 0 || options.clientId.length > 64) {
|
|
144
|
+
throw new Error('clientId must be a non-empty string of at most 64 characters');
|
|
145
|
+
}
|
|
146
|
+
if (clients.has(options.clientId) || config.store?.get(options.clientId) !== undefined) {
|
|
147
|
+
throw new Error(`a credential for ${JSON.stringify(options.clientId)} already exists; revoke it first`);
|
|
148
|
+
}
|
|
149
|
+
// SEC-01: high entropy — 32 crypto-random bytes, base64url-encoded. The secret
|
|
150
|
+
// is generated SERVER-SIDE and returned exactly once from this function.
|
|
151
|
+
const secret = randomBytes(32).toString('base64url');
|
|
152
|
+
register({ clientId: options.clientId, secret, scope: options.scope ?? ['mcp:call'] });
|
|
153
|
+
return { clientId: options.clientId, secret };
|
|
154
|
+
},
|
|
155
|
+
revokeClient(clientId) {
|
|
156
|
+
/**
|
|
157
|
+
* Revocation is decided by the LIVE authority, never by the hydrated map:
|
|
158
|
+
* a client generated AFTER this server was constructed (a "late client")
|
|
159
|
+
* exists only in the durable store, and revoking it must delete the
|
|
160
|
+
* durable record just like any other. The revocation FINGERPRINT is the
|
|
161
|
+
* hash of the client's own SECRET — scoped to the credential, not the id —
|
|
162
|
+
* so a legitimate re-generation of the same id with a fresh secret is
|
|
163
|
+
* never blocked by an old revocation.
|
|
164
|
+
*/
|
|
165
|
+
const client = liveClient(clientId);
|
|
166
|
+
if (client !== undefined) {
|
|
167
|
+
revoked.set(clientId, createHash('sha256').update(client.secret).digest('hex'));
|
|
168
|
+
// Durable revocation: the store entry is DELETED, so the credential does not
|
|
169
|
+
// resurrect on the next boot. A persistence failure surfaces to the caller;
|
|
170
|
+
// the in-memory revocation still stands for this process.
|
|
171
|
+
config.store?.delete(clientId);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
async issueAccessToken(clientId, scope) {
|
|
175
|
+
const client = liveClient(clientId);
|
|
176
|
+
if (client === undefined)
|
|
177
|
+
return undefined;
|
|
178
|
+
// SECRET-SCOPED revocation: the fingerprint is the hash of the revoked
|
|
179
|
+
// secret itself, so re-generating the same id with a FRESH secret issues
|
|
180
|
+
// again, while a revoked secret never does.
|
|
181
|
+
const revokedHash = revoked.get(clientId);
|
|
182
|
+
if (revokedHash !== undefined && revokedHash === createHash('sha256').update(client.secret).digest('hex')) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
const grantedScope = scope === undefined || scope.length === 0
|
|
186
|
+
? [...client.scope]
|
|
187
|
+
: [...new Set(scope.filter(name => client.scope.includes(name)))];
|
|
188
|
+
const now = Math.floor(Date.now() / 1000);
|
|
189
|
+
const header = b64url(ENCODER.encode(JSON.stringify({ alg: 'HS256', typ: 'JWT' })));
|
|
190
|
+
const payload = b64url(ENCODER.encode(JSON.stringify({
|
|
191
|
+
iss: config.issuer,
|
|
192
|
+
sub: client.clientId,
|
|
193
|
+
aud: config.audience,
|
|
194
|
+
scope: grantedScope.join(' '),
|
|
195
|
+
iat: now,
|
|
196
|
+
exp: now + lifetime,
|
|
197
|
+
jti: `jti_${now.toString(36)}_${Math.random().toString(36).slice(2, 10)}`,
|
|
198
|
+
})));
|
|
199
|
+
const signingInput = `${header}.${payload}`;
|
|
200
|
+
const signature = await crypto.subtle.sign('HMAC', await keyFor(client.secret), ENCODER.encode(signingInput));
|
|
201
|
+
return `${signingInput}.${b64url(new Uint8Array(signature))}`;
|
|
202
|
+
},
|
|
203
|
+
async verify(token, requiredScope) {
|
|
204
|
+
const parts = token.split('.');
|
|
205
|
+
if (parts.length !== 3) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
error: { code: 'invalid_request', description: 'the credential is not a JWT with three parts' },
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const header = decodePart(parts[0]);
|
|
212
|
+
const payload = decodePart(parts[1]);
|
|
213
|
+
if (header?.alg !== 'HS256' || payload === undefined) {
|
|
214
|
+
return {
|
|
215
|
+
ok: false,
|
|
216
|
+
error: { code: 'invalid_request', description: 'the credential is not an HS256 JWT with a JSON payload' },
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const clientId = payload.sub;
|
|
220
|
+
if (typeof clientId !== 'string') {
|
|
221
|
+
return {
|
|
222
|
+
ok: false,
|
|
223
|
+
error: { code: 'invalid_request', description: 'the credential carries no subject' },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// The LIVE registry entry: the durable store first, so a rotated or
|
|
227
|
+
// deleted credential is judged by the CURRENT secret and scope, never by
|
|
228
|
+
// the construction-time snapshot.
|
|
229
|
+
const client = liveClient(clientId);
|
|
230
|
+
if (client === undefined) {
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
error: { code: 'invalid_token', description: 'the credential is from an unknown client' },
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
// SIGNATURE FIRST: only a credential signed with the client's own key may
|
|
237
|
+
// present claims about that client, so nothing below reads an unsigned claim
|
|
238
|
+
// — including the revocation state, which a forged credential must not be
|
|
239
|
+
// able to probe (F2: the revoked check sat here before and answered an
|
|
240
|
+
// unsigned token claiming sub=X, revealing whether X is revoked).
|
|
241
|
+
const signingInput = `${parts[0]}.${parts[1]}`;
|
|
242
|
+
const signaturePart = parts[2];
|
|
243
|
+
const presented = Buffer.from(signaturePart, 'base64url');
|
|
244
|
+
const expected = new Uint8Array(await crypto.subtle.sign('HMAC', await keyFor(client.secret), ENCODER.encode(signingInput)));
|
|
245
|
+
const matches = presented.length === expected.length && timingSafeEqual(presented, expected);
|
|
246
|
+
if (!matches) {
|
|
247
|
+
return {
|
|
248
|
+
ok: false,
|
|
249
|
+
error: { code: 'invalid_token', description: 'the credential signature does not verify' },
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const revokedHash = revoked.get(clientId);
|
|
253
|
+
if (revokedHash !== undefined && revokedHash === createHash('sha256').update(client.secret).digest('hex')) {
|
|
254
|
+
return {
|
|
255
|
+
ok: false,
|
|
256
|
+
error: { code: 'invalid_token', description: 'the credential is revoked' },
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// SEC-01: a client deleted from the durable store by ANY surface — the local
|
|
260
|
+
// revokeClient, the host's management action, or another process — is revoked
|
|
261
|
+
// for this process too, on the very next verification. The live lookup above
|
|
262
|
+
// consults the store; this is the in-memory fallback check.
|
|
263
|
+
if (config.store !== undefined && config.store.get(clientId) === undefined) {
|
|
264
|
+
return {
|
|
265
|
+
ok: false,
|
|
266
|
+
error: { code: 'invalid_token', description: 'the credential is revoked' },
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// Issuer and audience are validated AFTER the signature: only then are they
|
|
270
|
+
// facts the issuer asserted rather than text an attacker wrote.
|
|
271
|
+
if (payload.iss !== config.issuer) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
error: { code: 'invalid_token', description: 'the credential was issued by a different authorization server' },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const audience = payload.aud;
|
|
278
|
+
const audiences = Array.isArray(audience) ? audience : [audience];
|
|
279
|
+
if (!audiences.includes(config.audience)) {
|
|
280
|
+
return {
|
|
281
|
+
ok: false,
|
|
282
|
+
error: { code: 'invalid_token', description: `the credential audience does not include ${config.audience}` },
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const exp = payload.exp;
|
|
286
|
+
const now = Math.floor(Date.now() / 1000);
|
|
287
|
+
if (typeof exp !== 'number' || now > exp + leeway) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
error: { code: 'invalid_token', description: 'the credential has expired' },
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const iat = payload.iat;
|
|
294
|
+
if (typeof iat === 'number' && iat > now + leeway) {
|
|
295
|
+
return {
|
|
296
|
+
ok: false,
|
|
297
|
+
error: { code: 'invalid_token', description: 'the credential is not yet valid' },
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const scope = typeof payload.scope === 'string'
|
|
301
|
+
? payload.scope.split(' ').filter(Boolean)
|
|
302
|
+
: [];
|
|
303
|
+
/**
|
|
304
|
+
* F1 FIX (review): the presented scope is CAPPED at the client's registered
|
|
305
|
+
* grant. Issuance already caps it, but with symmetric keys a client can
|
|
306
|
+
* self-mint a token claiming ANY scope — verification is therefore the only
|
|
307
|
+
* place the per-client grant can be enforced, and it was not. A claim the
|
|
308
|
+
* registry does not admit is refused here, never trusted.
|
|
309
|
+
*/
|
|
310
|
+
const overGrant = scope.filter(name => !client.scope.includes(name));
|
|
311
|
+
if (overGrant.length > 0) {
|
|
312
|
+
return {
|
|
313
|
+
ok: false,
|
|
314
|
+
error: {
|
|
315
|
+
code: 'invalid_token',
|
|
316
|
+
description: `the credential claims scope beyond this client's grant: ${overGrant.join(', ')}`,
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const required = requiredScope ?? [];
|
|
321
|
+
const missing = required.filter(name => !scope.includes(name));
|
|
322
|
+
if (missing.length > 0) {
|
|
323
|
+
return {
|
|
324
|
+
ok: false,
|
|
325
|
+
error: {
|
|
326
|
+
code: 'insufficient_scope',
|
|
327
|
+
description: `the credential lacks required scope: ${missing.join(', ')}`,
|
|
328
|
+
scope: required.join(' '),
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
ok: true,
|
|
334
|
+
token: {
|
|
335
|
+
clientId,
|
|
336
|
+
scope,
|
|
337
|
+
audience: config.audience,
|
|
338
|
+
jti: typeof payload.jti === 'string' ? payload.jti : '',
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
authorizationServerMetadata() {
|
|
343
|
+
return {
|
|
344
|
+
issuer: config.issuer,
|
|
345
|
+
// NO token_endpoint (F3, review): issuance is programmatic-only here, so
|
|
346
|
+
// advertising an endpoint the transport never serves would be an RFC 8414
|
|
347
|
+
// lie. A client that finds this issuer knows credentials come from the
|
|
348
|
+
// operator's configuration, not from a token request.
|
|
349
|
+
response_types_supported: ['token'],
|
|
350
|
+
token_endpoint_auth_methods_supported: ['client_secret_post'],
|
|
351
|
+
grant_types_supported: ['client_credentials'],
|
|
352
|
+
};
|
|
353
|
+
},
|
|
354
|
+
protectedResourceMetadata() {
|
|
355
|
+
return {
|
|
356
|
+
resource: config.audience,
|
|
357
|
+
authorization_servers: [config.issuer],
|
|
358
|
+
bearer_methods_supported: ['header'],
|
|
359
|
+
scopes_supported: [...new Set([...clients.values()].flatMap(client => client.scope))],
|
|
360
|
+
};
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/** Build the RFC 6750 challenge header for a verification failure. */
|
|
365
|
+
export function oauthChallenge(error, realm = 'tool-mode') {
|
|
366
|
+
const params = [
|
|
367
|
+
`realm="${realm}"`,
|
|
368
|
+
`error="${error.code}"`,
|
|
369
|
+
`error_description="${error.description.replaceAll('"', "'")}"`,
|
|
370
|
+
];
|
|
371
|
+
if (error.code === 'insufficient_scope')
|
|
372
|
+
params.push(`scope="${error.scope}"`);
|
|
373
|
+
return `Bearer ${params.join(', ')}`;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* The HTTP status RFC 6750 §3.1 assigns to each token error.
|
|
377
|
+
*
|
|
378
|
+
* All three are 401-shaped failures, but the RFC distinguishes them so a client
|
|
379
|
+
* can tell "malformed request, fix it" (400) from "credential bad, re-auth"
|
|
380
|
+
* (401) from "credential fine, grant too small" (403). Flattening every
|
|
381
|
+
* challenge to 401 was the F4 gap: a client receiving 400 for a revoked token
|
|
382
|
+
* would re-send the same request, and one receiving 401 for insufficient scope
|
|
383
|
+
* would re-authenticate pointlessly instead of asking for the scope.
|
|
384
|
+
*/
|
|
385
|
+
export function oauthErrorStatus(error) {
|
|
386
|
+
switch (error.code) {
|
|
387
|
+
case 'invalid_request': return 400;
|
|
388
|
+
case 'insufficient_scope': return 403;
|
|
389
|
+
case 'invalid_token': return 401;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** Extract the error code from a challenge, for tests and diagnostics. */
|
|
393
|
+
export function challengeErrorCode(challenge) {
|
|
394
|
+
return /error="([^"]+)"/.exec(challenge)?.[1];
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=oauth.js.map
|
package/lib/mcp/protocol.d.ts
CHANGED
|
@@ -46,8 +46,15 @@ export declare const META_KEYS: {
|
|
|
46
46
|
* Error codes.
|
|
47
47
|
*
|
|
48
48
|
* The reserved range belongs to the protocol. P26-03 is explicit that custom
|
|
49
|
-
* errors must not be allocated there,
|
|
50
|
-
*
|
|
49
|
+
* errors must not be allocated there, and the pinned revision partitions the
|
|
50
|
+
* bands: `-32768..-32000` is JSON-RPC's own predefined-error allocation,
|
|
51
|
+
* `-32020..-32099` is reserved for MCP-defined codes (allocated sequentially,
|
|
52
|
+
* and this module declares exactly those the revision defines), while
|
|
53
|
+
* `-32000..-32019` is the IMPLEMENTATION-DEFINED sub-range — the specification
|
|
54
|
+
* will never define codes there, and receivers must not assume cross-
|
|
55
|
+
* implementation semantics. `rateLimited` lives in that sub-range deliberately:
|
|
56
|
+
* it is a documented implementation allocation, never a minted code in the
|
|
57
|
+
* reserved band.
|
|
51
58
|
*/
|
|
52
59
|
export declare const ERROR_CODES: {
|
|
53
60
|
/** Standard JSON-RPC. */
|
|
@@ -60,6 +67,16 @@ export declare const ERROR_CODES: {
|
|
|
60
67
|
readonly headerMismatch: -32020;
|
|
61
68
|
readonly unsupportedClientCapability: -32021;
|
|
62
69
|
readonly unsupportedProtocolVersion: -32022;
|
|
70
|
+
/**
|
|
71
|
+
* Implementation-defined (-32000..-32019, never allocated by the specification).
|
|
72
|
+
*
|
|
73
|
+
* The revision defines NO rate-limit error, so a refusal uses this documented
|
|
74
|
+
* implementation code rather than pretending the request was invalid (-32600) or
|
|
75
|
+
* minting a code in the MCP-reserved band. The HTTP 429 status carries the
|
|
76
|
+
* semantics; receivers that do not know this code still get a conformant
|
|
77
|
+
* `JSONRPCErrorResponse` envelope and a `Retry-After` header.
|
|
78
|
+
*/
|
|
79
|
+
readonly rateLimited: -32001;
|
|
63
80
|
};
|
|
64
81
|
/** The convention for a tool that ran and returned a domain failure (P26-04). */
|
|
65
82
|
export interface ToolErrorPayload {
|
|
@@ -136,21 +153,43 @@ export interface FeatureDeclaration {
|
|
|
136
153
|
* cannot advertise a feature it does not have by accident.
|
|
137
154
|
*/
|
|
138
155
|
export declare const ADVERTISED_FEATURES: readonly FeatureDeclaration[];
|
|
156
|
+
/**
|
|
157
|
+
* What the server may advertise, given what is ACTUALLY WIRED (P26-12).
|
|
158
|
+
*
|
|
159
|
+
* The static matrix declared `subscriptions/listen` as implemented unconditionally — while
|
|
160
|
+
* the only production transport never received a registry and therefore answered `-32601`
|
|
161
|
+
* for it. An advertised feature that no client can obtain is worse than an absent one: a
|
|
162
|
+
* client plans around it and fails later.
|
|
163
|
+
*
|
|
164
|
+
* The feature is now conditional on the wiring, and the DEFAULT is `false` because that is
|
|
165
|
+
* the production reality. A caller that has genuinely composed a subscription registry says
|
|
166
|
+
* so; nothing else can make the claim true by declaring it.
|
|
167
|
+
*
|
|
168
|
+
* @param wiring - which optional surfaces are composed.
|
|
169
|
+
* @returns the advertised-feature matrix for this deployment.
|
|
170
|
+
*/
|
|
171
|
+
export declare function advertisedFeatures(wiring?: {
|
|
172
|
+
readonly subscriptions?: boolean;
|
|
173
|
+
}): readonly FeatureDeclaration[];
|
|
139
174
|
/**
|
|
140
175
|
* The capabilities object this server may advertise.
|
|
141
176
|
*
|
|
142
|
-
* Derived from
|
|
143
|
-
*
|
|
144
|
-
* @returns the capability names that are genuinely implemented.
|
|
177
|
+
* Derived from the matrix rather than written twice, so the two cannot disagree.
|
|
178
|
+
* @param wiring - which optional surfaces are composed.
|
|
179
|
+
* @returns the capability names that are genuinely implemented HERE.
|
|
145
180
|
*/
|
|
146
|
-
export declare function implementedCapabilities(
|
|
181
|
+
export declare function implementedCapabilities(wiring?: {
|
|
182
|
+
readonly subscriptions?: boolean;
|
|
183
|
+
}): string[];
|
|
147
184
|
/**
|
|
148
185
|
* Whether a client's REQUIRED capability is one this server supports (P26-03).
|
|
149
186
|
*
|
|
150
187
|
* @param required - capability names the client declared as required.
|
|
151
188
|
* @returns the unsupported names, empty when all are supported.
|
|
152
189
|
*/
|
|
153
|
-
export declare function unsupportedRequiredCapabilities(required: readonly string[]
|
|
190
|
+
export declare function unsupportedRequiredCapabilities(required: readonly string[], wiring?: {
|
|
191
|
+
readonly subscriptions?: boolean;
|
|
192
|
+
}): string[];
|
|
154
193
|
/**
|
|
155
194
|
* The caching fields every cacheable result must carry (P26-14).
|
|
156
195
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/mcp/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,eAAe,CAAA;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAAuB,CAAA;AAEhF,6CAA6C;AAC7C,eAAO,MAAM,SAAS;;;;;CAKZ,CAAA;AAEV
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/mcp/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,eAAe,CAAA;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAAuB,CAAA;AAEhF,6CAA6C;AAC7C,eAAO,MAAM,SAAS;;;;;CAKZ,CAAA;AAEV;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW;IACtB,yBAAyB;;;;;;IAMzB,oBAAoB;;;;IAIpB;;;;;;;;OAQG;;CAEK,CAAA;AAEV,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC9E;AAED,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC1C;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IACxE,QAAQ,CAAC,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC/D;AAED,wEAAwE;AACxE,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAkB,IAAI,eAAc;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAE3B,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAK1E;AAED,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,eAAe,CA+CjG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAWlE;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,kBAAkB,EAmB5D,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAE;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,kBAAkB,EAAE,CAMnH;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,GAAE;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,EAAE,CAEnG;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,MAAM,GAAE;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAChD,MAAM,EAAE,CAMV;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,IAAI;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAE/F;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC9D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAOjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAC1B,KAAK,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACnF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB;AAED,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6BhF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAiBpE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAK5D"}
|