@nylorun/runtime 0.16.0-beta → 0.19.0-beta
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/CHANGELOG.md +207 -0
- package/README.md +105 -63
- package/dist/adapters/execution/restate.d.ts +1 -3
- package/dist/adapters/execution/restate.js +4 -31
- package/dist/adapters/replication/pgoutput.js +1 -1
- package/dist/adapters/sandbox/local.js +1 -1
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/api/ag-ui/endpoint.js +1 -2
- package/dist/api/ag-ui/routes.d.ts +1 -1
- package/dist/api/ag-ui/routes.js +11 -13
- package/dist/api/ag-ui/translate.d.ts +1 -3
- package/dist/api/ag-ui/translate.js +2 -17
- package/dist/api/components.d.ts +4783 -2588
- package/dist/api/components.js +19 -21
- package/dist/api/gate/routes.d.ts +7 -8
- package/dist/api/gate/routes.js +58 -52
- package/dist/api/http/app.d.ts +6 -0
- package/dist/api/http/app.js +20 -14
- package/dist/api/http/define.d.ts +22 -13
- package/dist/api/http/define.js +43 -39
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +25 -184
- package/dist/api/http/routes/artifacts.js +3 -7
- package/dist/api/http/routes/files.d.ts +8 -0
- package/dist/api/http/routes/files.js +77 -0
- package/dist/api/http/routes/keys.d.ts +10 -0
- package/dist/api/http/routes/keys.js +64 -0
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +48 -0
- package/dist/api/http/routes/reads.d.ts +66 -0
- package/dist/api/http/routes/reads.js +127 -0
- package/dist/api/http/routes/sandboxes.d.ts +2 -2
- package/dist/api/http/routes/sandboxes.js +8 -7
- package/dist/api/http/routes/sessions.js +31 -20
- package/dist/api/http/routes/tenant.js +15 -18
- package/dist/api/http/routes/vaults.d.ts +13 -3
- package/dist/api/http/routes/vaults.js +136 -64
- package/dist/api/openapi.d.ts +10 -11
- package/dist/api/openapi.js +292 -68
- package/dist/artifacts/links.js +6 -6
- package/dist/artifacts/service.js +1 -1
- package/dist/core/flow-host.d.ts +44 -23
- package/dist/core/flow-host.js +88 -106
- package/dist/core/provider.d.ts +4 -1
- package/dist/core/provider.js +6 -1
- package/dist/core/sandbox-routes.d.ts +0 -9
- package/dist/core/sandbox-routes.js +2 -21
- package/dist/execution/memory.d.ts +4 -5
- package/dist/execution/memory.js +11 -25
- package/dist/execution/types.d.ts +3 -33
- package/dist/execution/types.js +2 -12
- package/dist/gates/http-tool.d.ts +86 -0
- package/dist/gates/http-tool.js +197 -0
- package/dist/gates/mcp-handler.d.ts +3 -0
- package/dist/gates/mcp-handler.js +6 -4
- package/dist/gates/meter.d.ts +2 -2
- package/dist/gates/meter.js +6 -0
- package/dist/gates/tenant-vaults.d.ts +19 -14
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/gates/tool-calls.d.ts +7 -15
- package/dist/gates/tool-calls.js +4 -4
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +23 -47
- package/dist/gates/tool-contract.d.ts +27 -39
- package/dist/gates/tool-contract.js +30 -21
- package/dist/gates/tool-gate.d.ts +25 -24
- package/dist/gates/tool-gate.js +11 -7
- package/dist/harness/calls.d.ts +10 -4
- package/dist/harness/calls.js +15 -6
- package/dist/harness/executors.d.ts +1 -1
- package/dist/harness/executors.js +17 -12
- package/dist/harness/main.d.ts +2 -2
- package/dist/harness/main.js +5 -13
- package/dist/harness/service.d.ts +4 -5
- package/dist/harness/service.js +9 -2
- package/dist/harness-api/in-process.js +2 -2
- package/dist/harness-api/record.d.ts +6 -3
- package/dist/harness-api/record.js +65 -72
- package/dist/harness-api/server.d.ts +1 -9
- package/dist/harness-api/server.js +19 -17
- package/dist/harness-api/start.js +1 -5
- package/dist/harness-api/workspace.js +2 -3
- package/dist/harness-api/ws-server.js +1 -1
- package/dist/host/app.d.ts +2 -10
- package/dist/host/app.js +24 -44
- package/dist/host/config-for.d.ts +5 -5
- package/dist/host/config-for.js +2 -3
- package/dist/host/config.d.ts +0 -11
- package/dist/host/create-host.d.ts +3 -40
- package/dist/host/create-host.js +18 -91
- package/dist/host/environment.d.ts +0 -3
- package/dist/host/environment.js +0 -9
- package/dist/host/gates.d.ts +4 -1
- package/dist/host/gates.js +9 -3
- package/dist/host/http.d.ts +1 -2
- package/dist/host/http.js +1 -7
- package/dist/host/index.d.ts +2 -2
- package/dist/host/index.js +2 -2
- package/dist/host/main.js +55 -40
- package/dist/host/operate.d.ts +29 -0
- package/dist/host/operate.js +223 -0
- package/dist/host/stack-config.d.ts +23 -20
- package/dist/host/stack-config.js +28 -44
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/infra/workers.d.ts +1 -1
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +10 -2
- package/dist/management-openapi.json +6491 -0
- package/dist/mcp/connect.d.ts +8 -4
- package/dist/mcp/connect.js +8 -27
- package/dist/mcp/pool.d.ts +6 -9
- package/dist/mcp/pool.js +8 -18
- package/dist/mcp/snapshot.d.ts +8 -2
- package/dist/mcp/snapshot.js +29 -2
- package/dist/media.d.ts +1 -1
- package/dist/media.js +1 -1
- package/dist/model/pi-model.js +3 -0
- package/dist/openapi.json +6228 -15615
- package/dist/reads/access.d.ts +4 -0
- package/dist/reads/access.js +12 -0
- package/dist/reads/cursor.d.ts +11 -0
- package/dist/reads/cursor.js +80 -0
- package/dist/reads/types.d.ts +25 -0
- package/dist/reads/types.js +1 -0
- package/dist/sandbox/manager.d.ts +16 -0
- package/dist/sandbox/manager.js +40 -0
- package/dist/sandbox/session-sandbox.js +13 -3
- package/dist/sandbox/skills.d.ts +13 -0
- package/dist/sandbox/skills.js +46 -0
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/ownership.d.ts +9 -0
- package/dist/store/ownership.js +13 -0
- package/dist/store/postgres/connect.d.ts +2 -0
- package/dist/store/postgres/connect.js +29 -0
- package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
- package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
- package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
- package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
- package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
- package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
- package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
- package/dist/store/postgres/reads.d.ts +3 -0
- package/dist/store/postgres/reads.js +272 -0
- package/dist/store/postgres/schema.d.ts +423 -594
- package/dist/store/postgres/schema.js +84 -101
- package/dist/store/postgres/store.js +96 -248
- package/dist/store/postgres/tenant.d.ts +34 -2
- package/dist/store/postgres/tenant.js +16 -32
- package/dist/store/types.d.ts +44 -119
- package/dist/store/types.js +0 -1
- package/dist/streams/control.d.ts +0 -7
- package/dist/streams/control.js +0 -13
- package/dist/streams/types.d.ts +2 -20
- package/dist/streams/types.js +1 -1
- package/dist/tenant/advance.d.ts +3 -3
- package/dist/tenant/advance.js +10 -7
- package/dist/tenant/auth.d.ts +24 -15
- package/dist/tenant/auth.js +44 -55
- package/dist/tenant/cause.d.ts +3 -3
- package/dist/tenant/cause.js +1 -1
- package/dist/tenant/commands.d.ts +2 -18
- package/dist/tenant/commands.js +38 -179
- package/dist/tenant/context.d.ts +33 -45
- package/dist/tenant/definition-files.d.ts +24 -0
- package/dist/tenant/definition-files.js +103 -0
- package/dist/tenant/effects.d.ts +20 -18
- package/dist/tenant/effects.js +59 -133
- package/dist/tenant/ephemeral.d.ts +21 -8
- package/dist/tenant/ephemeral.js +11 -12
- package/dist/tenant/host-token.js +5 -5
- package/dist/tenant/identity-file.d.ts +75 -0
- package/dist/tenant/identity-file.js +202 -0
- package/dist/tenant/issuers.d.ts +48 -0
- package/dist/tenant/issuers.js +311 -0
- package/dist/tenant/jwt.js +3 -3
- package/dist/tenant/module.d.ts +1 -4
- package/dist/tenant/module.js +3 -12
- package/dist/tenant/operator-keys.d.ts +44 -0
- package/dist/tenant/operator-keys.js +87 -0
- package/dist/tenant/outbound.d.ts +40 -1
- package/dist/tenant/outbound.js +167 -18
- package/dist/tenant/paths.d.ts +1 -1
- package/dist/tenant/paths.js +1 -3
- package/dist/tenant/principals.d.ts +13 -13
- package/dist/tenant/principals.js +35 -36
- package/dist/tenant/run-token.js +6 -6
- package/dist/tenant/runtime.d.ts +5 -3
- package/dist/tenant/runtime.js +29 -15
- package/dist/tenant/sandboxes.d.ts +9 -6
- package/dist/tenant/sandboxes.js +16 -5
- package/dist/tenant/scheduler.d.ts +1 -5
- package/dist/tenant/scheduler.js +2 -5
- package/dist/tenant/session-streams.d.ts +8 -11
- package/dist/tenant/session-streams.js +48 -32
- package/dist/tenant/session.d.ts +8 -41
- package/dist/tenant/session.js +0 -37
- package/dist/tenant/sessions.d.ts +2 -2
- package/dist/tenant/sessions.js +17 -9
- package/dist/tenant/signing-keys.d.ts +3 -2
- package/dist/tenant/signing-keys.js +5 -5
- package/dist/tenant/skill-tool.d.ts +20 -0
- package/dist/tenant/skill-tool.js +72 -0
- package/dist/tenant/slim.d.ts +1 -1
- package/dist/tenant/slim.js +1 -1
- package/dist/tenant/status.js +7 -12
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +7 -3
- package/dist/tenant/streams.d.ts +3 -11
- package/dist/tenant/streams.js +2 -29
- package/dist/tenant/sweep.js +0 -5
- package/dist/tenant/transcript.d.ts +2 -2
- package/dist/tenant/transcript.js +2 -2
- package/dist/tenant/types.d.ts +33 -19
- package/dist/tenant/worker.d.ts +1 -3
- package/dist/tenant/worker.js +3 -8
- package/dist/vault/crypto.d.ts +7 -0
- package/dist/vault/crypto.js +16 -0
- package/dist/vault/oauth.d.ts +55 -0
- package/dist/vault/oauth.js +186 -0
- package/dist/vault/service.d.ts +54 -7
- package/dist/vault/service.js +261 -17
- package/dist/vault/sources.d.ts +60 -0
- package/dist/vault/sources.js +196 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
- package/dist/admin-openapi.json +0 -604
- package/dist/api/http/routes/actions.d.ts +0 -8
- package/dist/api/http/routes/actions.js +0 -76
- package/dist/api/http/routes/endpoints.d.ts +0 -7
- package/dist/api/http/routes/endpoints.js +0 -100
- package/dist/host/admin-api.d.ts +0 -23
- package/dist/host/admin-api.js +0 -74
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/mcp/stdio.d.ts +0 -17
- package/dist/mcp/stdio.js +0 -82
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/actions.d.ts +0 -14
- package/dist/tenant/actions.js +0 -29
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- package/dist/tenant/delivery-token.d.ts +0 -41
- package/dist/tenant/delivery-token.js +0 -104
- package/dist/tenant/delivery.d.ts +0 -62
- package/dist/tenant/delivery.js +0 -481
- package/dist/tenant/endpoints.d.ts +0 -67
- package/dist/tenant/endpoints.js +0 -90
- package/dist/tenant/subject-limits.d.ts +0 -16
- package/dist/tenant/subject-limits.js +0 -45
- package/dist/tenant/tokens.d.ts +0 -14
- package/dist/tenant/tokens.js +0 -187
package/dist/tenant/worker.js
CHANGED
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - `TenantWorker` is one open Tenant's handlers: `advance(sessionId, signal)` (§10.5, in
|
|
6
6
|
* `advance.ts`), `sweep()` (the Tenant sweep, in `sweep.ts`) and, optionally,
|
|
7
|
-
* `
|
|
7
|
+
* `sandbox(sandboxId, trigger, signal)` (one pod sandbox's reconcile).
|
|
8
8
|
* - `TenantWorkers` is the registry. Its `handlers` are the `WorkerHandlers` an execution is
|
|
9
9
|
* started with; they dispatch by `tenantId` to the registered `TenantWorker`. A Tenant
|
|
10
10
|
* registers when it opens and unregisters when it closes. A call for a Tenant that is not
|
|
11
11
|
* open here asks the optional `resolve` hook (the Host's, which waits for its Tenant to
|
|
12
12
|
* open, and knows no other); without one,
|
|
13
|
-
* the advance
|
|
14
|
-
*
|
|
15
|
-
* that was not delivered stays pending.
|
|
13
|
+
* the advance returns `done` and the sweep does nothing. Nothing is lost: the Tenant's
|
|
14
|
+
* sweep re-wakes its orphaned sessions once it is open again.
|
|
16
15
|
* - `TenantExecution` pairs an execution with its registry. The Host creates one per process
|
|
17
16
|
* (`host/execution.ts`) and passes it to its Tenant; a Tenant opened without one
|
|
18
17
|
* (tests, ephemeral) gets its own in-process `MemoryExecution`.
|
|
@@ -136,10 +135,6 @@ export class TenantWorkers {
|
|
|
136
135
|
sweep: async (tenantId) => {
|
|
137
136
|
await (await this.find(tenantId))?.sweep();
|
|
138
137
|
},
|
|
139
|
-
deliver: async (tenantId, actionId, signal) => {
|
|
140
|
-
const worker = await this.find(tenantId);
|
|
141
|
-
return worker?.deliver ? worker.deliver(actionId, signal) : DONE;
|
|
142
|
-
},
|
|
143
138
|
sandbox: async (tenantId, sandboxId, trigger, signal) => {
|
|
144
139
|
const worker = await this.find(tenantId);
|
|
145
140
|
return worker?.sandbox ? worker.sandbox(sandboxId, trigger, signal) : {};
|
package/dist/vault/crypto.d.ts
CHANGED
|
@@ -10,3 +10,10 @@ export declare function encryptSecret(kek: Buffer, aad: Buffer, plaintext: Buffe
|
|
|
10
10
|
kekId: string;
|
|
11
11
|
};
|
|
12
12
|
export declare function decryptSecret(kek: Buffer, aad: Buffer, nonce: Buffer, ciphertext: Buffer, wrappedDek: Buffer, expectedKekId: string): Buffer;
|
|
13
|
+
/**
|
|
14
|
+
* `encryptSecret` in one column: nonce, wrapped DEK, then ciphertext and tag. For rows that
|
|
15
|
+
* seal several short secrets under one `kekId` (`oauth_pending`, F9 C2).
|
|
16
|
+
*/
|
|
17
|
+
export declare function sealBytes(kek: Buffer, aad: Buffer, plaintext: Buffer): Buffer;
|
|
18
|
+
/** Opens what `sealBytes` sealed; `VaultCryptoError` when it cannot. */
|
|
19
|
+
export declare function openBytes(kek: Buffer, aad: Buffer, sealed: Buffer, expectedKekId: string): Buffer;
|
package/dist/vault/crypto.js
CHANGED
|
@@ -78,3 +78,19 @@ function unwrapKey(kek, wrapped) {
|
|
|
78
78
|
throw new VaultCryptoError();
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
/** The wrapped DEK's length: its nonce, the 32-byte key and the tag. */
|
|
82
|
+
const WRAPPED_DEK_BYTES = IV_BYTES + 32 + TAG_BYTES;
|
|
83
|
+
/**
|
|
84
|
+
* `encryptSecret` in one column: nonce, wrapped DEK, then ciphertext and tag. For rows that
|
|
85
|
+
* seal several short secrets under one `kekId` (`oauth_pending`, F9 C2).
|
|
86
|
+
*/
|
|
87
|
+
export function sealBytes(kek, aad, plaintext) {
|
|
88
|
+
const sealed = encryptSecret(kek, aad, plaintext);
|
|
89
|
+
return Buffer.concat([sealed.nonce, sealed.wrappedDek, sealed.ciphertext]);
|
|
90
|
+
}
|
|
91
|
+
/** Opens what `sealBytes` sealed; `VaultCryptoError` when it cannot. */
|
|
92
|
+
export function openBytes(kek, aad, sealed, expectedKekId) {
|
|
93
|
+
if (sealed.length < IV_BYTES + WRAPPED_DEK_BYTES + TAG_BYTES)
|
|
94
|
+
throw new VaultCryptoError();
|
|
95
|
+
return decryptSecret(kek, aad, sealed.subarray(0, IV_BYTES), sealed.subarray(IV_BYTES + WRAPPED_DEK_BYTES), sealed.subarray(IV_BYTES, IV_BYTES + WRAPPED_DEK_BYTES), expectedKekId);
|
|
96
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AuthorizationServerMetadata, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
2
|
+
import { HttpError } from "../tenant/http.js";
|
|
3
|
+
export type TokenEndpointAuth = "none" | "client_secret_basic" | "client_secret_post";
|
|
4
|
+
/** Where and how to sign in to an MCP server. */
|
|
5
|
+
export interface OAuthServer {
|
|
6
|
+
readonly authorizationServerUrl: string;
|
|
7
|
+
readonly metadata: AuthorizationServerMetadata;
|
|
8
|
+
/** The protected resource's own `resource` (RFC 9728), sent as the RFC 8707 indicator. */
|
|
9
|
+
readonly resource?: string;
|
|
10
|
+
/** The scopes the protected resource names, space separated. */
|
|
11
|
+
readonly scope?: string;
|
|
12
|
+
}
|
|
13
|
+
/** The client this installation signs in as. */
|
|
14
|
+
export interface OAuthClient {
|
|
15
|
+
readonly clientId: string;
|
|
16
|
+
readonly clientSecret?: string;
|
|
17
|
+
readonly tokenEndpointAuth: TokenEndpointAuth;
|
|
18
|
+
}
|
|
19
|
+
/** A failed step, as the Tenant API answers it: a refused address is the Host's rule (400). */
|
|
20
|
+
export declare function oauthFailure(step: string, error: unknown): HttpError;
|
|
21
|
+
/**
|
|
22
|
+
* RFC 9728 protected resource metadata, then RFC 8414 (or OpenID) metadata of its first
|
|
23
|
+
* authorization server. A server without the former is its own authorization server (the MCP
|
|
24
|
+
* spec's fallback); a refused address is never a fallback.
|
|
25
|
+
*/
|
|
26
|
+
export declare function discoverOAuthServer(url: string, fetchFn: typeof fetch): Promise<OAuthServer>;
|
|
27
|
+
/**
|
|
28
|
+
* The client to sign in as: the operator's `clientId` (a public client) when given, else one
|
|
29
|
+
* registered now (RFC 7591) when the server offers registration, else `oauth_client_required`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function oauthClient(server: OAuthServer, options: {
|
|
32
|
+
clientId?: string;
|
|
33
|
+
redirectUri: string;
|
|
34
|
+
fetchFn: typeof fetch;
|
|
35
|
+
}): Promise<OAuthClient>;
|
|
36
|
+
/** The authorization URL, with a fresh S256 PKCE verifier. */
|
|
37
|
+
export declare function authorizationUrl(server: OAuthServer, client: OAuthClient, options: {
|
|
38
|
+
redirectUri: string;
|
|
39
|
+
state: string;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
authorizeUrl: string;
|
|
42
|
+
codeVerifier: string;
|
|
43
|
+
}>;
|
|
44
|
+
/** Adds the client's authentication to a token request, as `tokenEndpointAuth` says. */
|
|
45
|
+
export declare function addClientAuthentication(client: OAuthClient, headers: Headers, params: URLSearchParams): void;
|
|
46
|
+
/** Exchanges the authorization code (with its PKCE verifier) for tokens. */
|
|
47
|
+
export declare function exchangeCode(options: {
|
|
48
|
+
tokenEndpoint: string;
|
|
49
|
+
client: OAuthClient;
|
|
50
|
+
code: string;
|
|
51
|
+
codeVerifier: string;
|
|
52
|
+
redirectUri: string;
|
|
53
|
+
resource?: string;
|
|
54
|
+
fetchFn: typeof fetch;
|
|
55
|
+
}): Promise<OAuthTokens>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The network half of an MCP OAuth connect (F9 C2): discovery, dynamic client registration,
|
|
3
|
+
* the authorization URL and the code exchange, with the MCP SDK's auth helpers over the fetch
|
|
4
|
+
* it is given (the Host's `guardedFetch` in the gateway). `VaultService.startOAuth` and
|
|
5
|
+
* `finishOAuth` call these outside any transaction and hold the secrets; nothing here stores
|
|
6
|
+
* or logs one.
|
|
7
|
+
*/
|
|
8
|
+
import { discoverAuthorizationServerMetadata, discoverOAuthProtectedResourceMetadata, exchangeAuthorization, registerClient, startAuthorization, } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
9
|
+
import { OAuthError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
|
|
10
|
+
import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
|
|
11
|
+
import { HttpError } from "../tenant/http.js";
|
|
12
|
+
import { OutboundFailed, OutboundRefused } from "../tenant/outbound.js";
|
|
13
|
+
/** A failed step, as the Tenant API answers it: a refused address is the Host's rule (400). */
|
|
14
|
+
export function oauthFailure(step, error) {
|
|
15
|
+
if (error instanceof HttpError)
|
|
16
|
+
return error;
|
|
17
|
+
if (error instanceof OutboundRefused)
|
|
18
|
+
return new HttpError(400, `${error.message} (OAuth ${step})`, { code: "request_rejected" });
|
|
19
|
+
if (error instanceof OAuthError)
|
|
20
|
+
return new HttpError(502, `The authorization server refused the ${step} (${error.errorCode})`, {
|
|
21
|
+
code: "oauth_failed",
|
|
22
|
+
});
|
|
23
|
+
if (error instanceof OutboundFailed)
|
|
24
|
+
return new HttpError(502, `The OAuth ${step} failed: ${error.message}`, { code: "oauth_failed" });
|
|
25
|
+
return new HttpError(502, `The OAuth ${step} failed: the authorization server's answer was not usable`, {
|
|
26
|
+
code: "oauth_failed",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function httpUrl(value, what) {
|
|
30
|
+
let url;
|
|
31
|
+
try {
|
|
32
|
+
url = new URL(value);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new HttpError(502, `The authorization server's ${what} is not a URL`, { code: "oauth_failed" });
|
|
36
|
+
}
|
|
37
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
38
|
+
throw new HttpError(502, `The authorization server's ${what} is not an http(s) URL`, {
|
|
39
|
+
code: "oauth_failed",
|
|
40
|
+
});
|
|
41
|
+
return url;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* RFC 9728 protected resource metadata, then RFC 8414 (or OpenID) metadata of its first
|
|
45
|
+
* authorization server. A server without the former is its own authorization server (the MCP
|
|
46
|
+
* spec's fallback); a refused address is never a fallback.
|
|
47
|
+
*/
|
|
48
|
+
export async function discoverOAuthServer(url, fetchFn) {
|
|
49
|
+
let authorizationServerUrl;
|
|
50
|
+
let resource;
|
|
51
|
+
let scope;
|
|
52
|
+
try {
|
|
53
|
+
const protectedResource = await discoverOAuthProtectedResourceMetadata(url, {}, fetchFn);
|
|
54
|
+
authorizationServerUrl = protectedResource.authorization_servers?.[0];
|
|
55
|
+
if (protectedResource.resource) {
|
|
56
|
+
if (!checkResourceAllowed({ requestedResource: url, configuredResource: protectedResource.resource }))
|
|
57
|
+
throw new HttpError(502, `The MCP server's protected resource metadata names another resource (${protectedResource.resource})`, { code: "oauth_failed" });
|
|
58
|
+
resource = protectedResource.resource;
|
|
59
|
+
}
|
|
60
|
+
if (protectedResource.scopes_supported?.length)
|
|
61
|
+
scope = protectedResource.scopes_supported.join(" ");
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof OutboundRefused || error instanceof HttpError)
|
|
65
|
+
throw oauthFailure("discovery", error);
|
|
66
|
+
// No RFC 9728 metadata: the MCP server is its own authorization server.
|
|
67
|
+
}
|
|
68
|
+
authorizationServerUrl ??= new URL("/", url).href;
|
|
69
|
+
httpUrl(authorizationServerUrl, "address");
|
|
70
|
+
let metadata;
|
|
71
|
+
try {
|
|
72
|
+
metadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw oauthFailure("discovery", error);
|
|
76
|
+
}
|
|
77
|
+
if (!metadata)
|
|
78
|
+
throw new HttpError(502, `No OAuth authorization server metadata at ${new URL(authorizationServerUrl).origin}: the MCP server does not support OAuth sign-in`, { code: "oauth_failed" });
|
|
79
|
+
httpUrl(metadata.authorization_endpoint, "authorization endpoint");
|
|
80
|
+
httpUrl(metadata.token_endpoint, "token endpoint");
|
|
81
|
+
return {
|
|
82
|
+
authorizationServerUrl,
|
|
83
|
+
metadata,
|
|
84
|
+
...(resource === undefined ? {} : { resource }),
|
|
85
|
+
...(scope === undefined ? {} : { scope }),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function authMethodOf(value, secret) {
|
|
89
|
+
if (value === undefined)
|
|
90
|
+
return secret ? "client_secret_basic" : "none";
|
|
91
|
+
if (value === "none" || value === "client_secret_basic" || value === "client_secret_post") {
|
|
92
|
+
if (value !== "none" && !secret)
|
|
93
|
+
throw new HttpError(502, "The authorization server registered a confidential client without a secret", {
|
|
94
|
+
code: "oauth_failed",
|
|
95
|
+
});
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
throw new HttpError(502, `The authorization server's client authentication ${String(value)} is not supported`, {
|
|
99
|
+
code: "oauth_failed",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The client to sign in as: the operator's `clientId` (a public client) when given, else one
|
|
104
|
+
* registered now (RFC 7591) when the server offers registration, else `oauth_client_required`.
|
|
105
|
+
*/
|
|
106
|
+
export async function oauthClient(server, options) {
|
|
107
|
+
if (options.clientId)
|
|
108
|
+
return { clientId: options.clientId, tokenEndpointAuth: "none" };
|
|
109
|
+
if (!server.metadata.registration_endpoint)
|
|
110
|
+
throw new HttpError(400, "The authorization server offers no dynamic client registration: register a client with it and pass its id (--client-id)", { code: "oauth_client_required" });
|
|
111
|
+
httpUrl(server.metadata.registration_endpoint, "registration endpoint");
|
|
112
|
+
try {
|
|
113
|
+
const registered = await registerClient(server.authorizationServerUrl, {
|
|
114
|
+
metadata: server.metadata,
|
|
115
|
+
clientMetadata: {
|
|
116
|
+
client_name: "Nylorun",
|
|
117
|
+
redirect_uris: [options.redirectUri],
|
|
118
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
119
|
+
response_types: ["code"],
|
|
120
|
+
token_endpoint_auth_method: "none",
|
|
121
|
+
},
|
|
122
|
+
...(server.scope === undefined ? {} : { scope: server.scope }),
|
|
123
|
+
fetchFn: options.fetchFn,
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
clientId: registered.client_id,
|
|
127
|
+
...(registered.client_secret ? { clientSecret: registered.client_secret } : {}),
|
|
128
|
+
tokenEndpointAuth: authMethodOf(registered.token_endpoint_auth_method, registered.client_secret),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
throw oauthFailure("client registration", error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** The authorization URL, with a fresh S256 PKCE verifier. */
|
|
136
|
+
export async function authorizationUrl(server, client, options) {
|
|
137
|
+
try {
|
|
138
|
+
const started = await startAuthorization(server.authorizationServerUrl, {
|
|
139
|
+
metadata: server.metadata,
|
|
140
|
+
clientInformation: { client_id: client.clientId },
|
|
141
|
+
redirectUrl: options.redirectUri,
|
|
142
|
+
state: options.state,
|
|
143
|
+
...(server.scope === undefined ? {} : { scope: server.scope }),
|
|
144
|
+
...(server.resource === undefined ? {} : { resource: server.resource }),
|
|
145
|
+
});
|
|
146
|
+
return { authorizeUrl: started.authorizationUrl.href, codeVerifier: started.codeVerifier };
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
throw oauthFailure("authorization request", error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Adds the client's authentication to a token request, as `tokenEndpointAuth` says. */
|
|
153
|
+
export function addClientAuthentication(client, headers, params) {
|
|
154
|
+
if (client.tokenEndpointAuth === "client_secret_basic") {
|
|
155
|
+
const encoded = Buffer.from(`${encodeURIComponent(client.clientId)}:${encodeURIComponent(client.clientSecret ?? "")}`).toString("base64");
|
|
156
|
+
headers.set("authorization", `Basic ${encoded}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
params.set("client_id", client.clientId);
|
|
160
|
+
if (client.tokenEndpointAuth === "client_secret_post")
|
|
161
|
+
params.set("client_secret", client.clientSecret ?? "");
|
|
162
|
+
}
|
|
163
|
+
/** Exchanges the authorization code (with its PKCE verifier) for tokens. */
|
|
164
|
+
export async function exchangeCode(options) {
|
|
165
|
+
const { client } = options;
|
|
166
|
+
const clientInformation = {
|
|
167
|
+
client_id: client.clientId,
|
|
168
|
+
...(client.clientSecret ? { client_secret: client.clientSecret } : {}),
|
|
169
|
+
};
|
|
170
|
+
try {
|
|
171
|
+
return await exchangeAuthorization(options.tokenEndpoint, {
|
|
172
|
+
// Only the token endpoint is read from it.
|
|
173
|
+
metadata: { token_endpoint: options.tokenEndpoint },
|
|
174
|
+
clientInformation,
|
|
175
|
+
authorizationCode: options.code,
|
|
176
|
+
codeVerifier: options.codeVerifier,
|
|
177
|
+
redirectUri: options.redirectUri,
|
|
178
|
+
...(options.resource === undefined ? {} : { resource: options.resource }),
|
|
179
|
+
addClientAuthentication: (headers, params) => addClientAuthentication(client, headers, params),
|
|
180
|
+
fetchFn: options.fetchFn,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
throw oauthFailure("code exchange", error);
|
|
185
|
+
}
|
|
186
|
+
}
|
package/dist/vault/service.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { CreateCredentialRequest, CreateVaultRequest, CredentialInfo, CredentialSelection, HostModelProviderInfo, HostModelView, PutHostModelRequest, CustomModelSettings, RotateCredentialRequest, SelectHostModelRequest, VaultInfo } from "@nylorun/core/contracts";
|
|
2
2
|
import type { SessionStore, Tx, VaultCredentialRow } from "../store/types.js";
|
|
3
|
+
/** How long an MCP OAuth connect may take between its start and its callback (F9 C2). */
|
|
4
|
+
export declare const OAUTH_PENDING_MS: number;
|
|
3
5
|
export declare const HOST_VAULT_ID = "host";
|
|
4
6
|
export type HostModelSecret = {
|
|
5
7
|
provider: string;
|
|
@@ -25,20 +27,39 @@ export type AuthorizeResult = {
|
|
|
25
27
|
} | {
|
|
26
28
|
status: "authorized";
|
|
27
29
|
url: string;
|
|
28
|
-
headers
|
|
29
|
-
|
|
30
|
-
};
|
|
30
|
+
/** A vault credential's `authorization`, or the headers the credential resolver gave. */
|
|
31
|
+
headers: Record<string, string>;
|
|
31
32
|
} | {
|
|
32
33
|
status: "refused";
|
|
33
34
|
url: string;
|
|
34
35
|
credentialIds: string[];
|
|
35
36
|
reason: string;
|
|
36
37
|
};
|
|
38
|
+
/** `startOAuth`'s input: the route's body, the installation vault and the callback URL. */
|
|
39
|
+
export interface StartOAuthInput {
|
|
40
|
+
vaultId: string;
|
|
41
|
+
/** The MCP server's name, the credential's name. */
|
|
42
|
+
server: string;
|
|
43
|
+
url: string;
|
|
44
|
+
clientId?: string;
|
|
45
|
+
/** `<public URL>/v1/oauth/callback`. */
|
|
46
|
+
redirectUri: string;
|
|
47
|
+
}
|
|
48
|
+
/** The callback's query: `code`, or the authorization server's `error`. */
|
|
49
|
+
export interface FinishOAuthInput {
|
|
50
|
+
state: string;
|
|
51
|
+
code?: string;
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
37
54
|
export interface VaultServiceOptions {
|
|
38
55
|
store: SessionStore;
|
|
39
56
|
/** The Tenant key-encryption key. May read or create the key file. */
|
|
40
57
|
kek: () => Buffer;
|
|
41
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* OAuth refresh and MCP OAuth connect (discovery, registration, the code exchange), always
|
|
60
|
+
* outside a transaction: the Host's `guardedFetch` (`tenant/outbound.ts`) unless a test or
|
|
61
|
+
* an embedder injects one.
|
|
62
|
+
*/
|
|
42
63
|
fetch: typeof fetch;
|
|
43
64
|
/** How long a token endpoint may take to answer a refresh. Default 30 s. */
|
|
44
65
|
refreshTimeoutMs?: number;
|
|
@@ -52,10 +73,14 @@ export declare class VaultService {
|
|
|
52
73
|
private readonly refreshing;
|
|
53
74
|
constructor(options: VaultServiceOptions);
|
|
54
75
|
createVault(body: CreateVaultRequest): Promise<VaultInfo>;
|
|
55
|
-
|
|
76
|
+
/**
|
|
77
|
+
* One person's vaults, then (with `installation`) the installation vaults. No owner lists
|
|
78
|
+
* the installation vaults only.
|
|
79
|
+
*/
|
|
80
|
+
listVaults(ownerUserId: string | undefined, options?: {
|
|
81
|
+
installation?: boolean;
|
|
82
|
+
}): Promise<VaultInfo[]>;
|
|
56
83
|
getVault(id: string): Promise<VaultInfo>;
|
|
57
|
-
/** Another owner's vault, or the host vault, is the same 404 as a missing one. */
|
|
58
|
-
assertOwner(id: string, ownerUserId: string): Promise<void>;
|
|
59
84
|
deleteVault(id: string): Promise<{
|
|
60
85
|
id: string;
|
|
61
86
|
}>;
|
|
@@ -82,6 +107,28 @@ export declare class VaultService {
|
|
|
82
107
|
url: string;
|
|
83
108
|
serverName?: string;
|
|
84
109
|
}): Promise<AuthorizeResult>;
|
|
110
|
+
/**
|
|
111
|
+
* Starts signing the installation in to the MCP server at `url`: discovery, a client (the
|
|
112
|
+
* given id, or one registered now), an S256 PKCE verifier and a `state`, kept for ten minutes
|
|
113
|
+
* in a pending row (verifier and secret sealed, state hashed). Returns where to send the
|
|
114
|
+
* browser. Installation vaults only.
|
|
115
|
+
*/
|
|
116
|
+
startOAuth(input: StartOAuthInput): Promise<{
|
|
117
|
+
authorizeUrl: string;
|
|
118
|
+
expiresAt: string;
|
|
119
|
+
}>;
|
|
120
|
+
/**
|
|
121
|
+
* Finishes a connect from its callback: takes the pending row of `state` (deleted whatever
|
|
122
|
+
* happens next), exchanges the code, and seals an `oauth` credential bound to the server's
|
|
123
|
+
* URL in the installation vault, rotating the vault's OAuth credential for that URL when it
|
|
124
|
+
* has one.
|
|
125
|
+
*/
|
|
126
|
+
finishOAuth(input: FinishOAuthInput): Promise<{
|
|
127
|
+
vaultId: string;
|
|
128
|
+
credentialId: string;
|
|
129
|
+
}>;
|
|
130
|
+
/** An installation vault, else the 404 of a missing one (a user vault is a 400). */
|
|
131
|
+
private installationVault;
|
|
85
132
|
getHostModel(): Promise<HostModelView>;
|
|
86
133
|
listHostProviders(): Promise<{
|
|
87
134
|
providers: HostModelProviderInfo[];
|