@nylorun/runtime 0.19.1-beta → 0.21.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 +90 -0
- package/README.md +22 -13
- package/dist/adapters/streams/s2.js +5 -11
- package/dist/api/components.d.ts +326 -205
- package/dist/api/components.js +3 -3
- package/dist/api/http/app.js +2 -0
- package/dist/api/http/define.d.ts +3 -2
- package/dist/api/http/define.js +10 -11
- package/dist/api/http/routes/mcp.d.ts +9 -0
- package/dist/api/http/routes/mcp.js +32 -0
- package/dist/api/http/routes/vaults.d.ts +1 -9
- package/dist/api/http/routes/vaults.js +3 -103
- package/dist/api/openapi.js +37 -6
- package/dist/artifacts/media-types.d.ts +2 -0
- package/dist/artifacts/media-types.js +5 -0
- package/dist/execution/types.d.ts +1 -1
- package/dist/gates/http-tool.d.ts +7 -0
- package/dist/gates/http-tool.js +34 -3
- package/dist/gates/mcp-handler.d.ts +4 -1
- package/dist/gates/mcp-handler.js +64 -8
- package/dist/gates/tenant-vaults.d.ts +9 -15
- package/dist/gates/tenant-vaults.js +9 -17
- package/dist/gates/tool-client.js +36 -17
- package/dist/gates/tool-contract.d.ts +19 -3
- package/dist/gates/tool-contract.js +9 -3
- package/dist/gates/tool-gate.d.ts +2 -2
- package/dist/harness/calls.d.ts +2 -0
- package/dist/harness/calls.js +13 -3
- package/dist/harness-api/record.d.ts +7 -6
- package/dist/harness-api/record.js +99 -17
- package/dist/harness-api/server.js +35 -27
- package/dist/harness-api/start.js +2 -1
- package/dist/host/app.d.ts +3 -0
- package/dist/host/app.js +33 -4
- package/dist/host/config-for.d.ts +0 -2
- package/dist/host/config-for.js +0 -1
- package/dist/host/create-host.d.ts +6 -0
- package/dist/host/create-host.js +1 -0
- package/dist/host/gates.js +0 -1
- package/dist/host/main.js +24 -7
- package/dist/host/stack-config.d.ts +5 -8
- package/dist/host/stack-config.js +9 -20
- package/dist/index.d.ts +0 -1
- package/dist/infra/index.d.ts +2 -1
- package/dist/infra/index.js +0 -1
- package/dist/infra/readiness.d.ts +6 -6
- package/dist/infra/readiness.js +2 -5
- package/dist/keys/client.js +1 -9
- package/dist/keys/keys.d.ts +11 -13
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +671 -378
- package/dist/mcp/connect.d.ts +159 -13
- package/dist/mcp/connect.js +538 -55
- package/dist/mcp/pool.d.ts +11 -2
- package/dist/mcp/pool.js +74 -27
- package/dist/mcp/preview.d.ts +35 -0
- package/dist/mcp/preview.js +215 -0
- package/dist/mcp/snapshot.d.ts +79 -5
- package/dist/mcp/snapshot.js +145 -27
- package/dist/model/catalog.d.ts +14 -0
- package/dist/model/catalog.js +14 -1
- package/dist/model/pi-model.js +36 -2
- package/dist/openapi.json +644 -266
- package/dist/redact.d.ts +19 -1
- package/dist/redact.js +42 -13
- package/dist/sandbox/session-sandbox.d.ts +7 -0
- package/dist/sandbox/session-sandbox.js +89 -28
- package/dist/store/postgres/connect.d.ts +10 -0
- package/dist/store/postgres/connect.js +30 -3
- package/dist/store/postgres/control.d.ts +14 -0
- package/dist/store/postgres/control.js +187 -0
- package/dist/store/postgres/drizzle/0015_control_signals.sql +11 -0
- package/dist/store/postgres/drizzle/0016_mcp_oauth_removed.sql +5 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +14 -0
- package/dist/store/postgres/schema.d.ts +122 -261
- package/dist/store/postgres/schema.js +22 -36
- package/dist/store/postgres/store.js +16 -18
- package/dist/store/postgres/tenant.d.ts +14 -0
- package/dist/store/postgres/tenant.js +22 -2
- package/dist/store/types.d.ts +42 -8
- package/dist/streams/types.d.ts +6 -29
- package/dist/streams/types.js +5 -4
- package/dist/tenant/artifact-tool.d.ts +9 -1
- package/dist/tenant/artifact-tool.js +113 -9
- package/dist/tenant/auth.d.ts +16 -6
- package/dist/tenant/auth.js +41 -17
- package/dist/tenant/commands.js +10 -6
- package/dist/tenant/context.d.ts +9 -6
- package/dist/tenant/control.d.ts +26 -0
- package/dist/tenant/control.js +35 -0
- package/dist/tenant/effects.d.ts +2 -2
- package/dist/tenant/effects.js +20 -5
- package/dist/tenant/ephemeral.d.ts +0 -6
- package/dist/tenant/ephemeral.js +1 -1
- package/dist/tenant/http.d.ts +4 -2
- package/dist/tenant/http.js +4 -2
- package/dist/tenant/identity-file.d.ts +16 -12
- package/dist/tenant/identity-file.js +41 -29
- package/dist/tenant/issuers.d.ts +4 -3
- package/dist/tenant/issuers.js +22 -16
- package/dist/tenant/outbound.d.ts +14 -5
- package/dist/tenant/outbound.js +21 -9
- package/dist/tenant/reset.d.ts +2 -2
- package/dist/tenant/reset.js +8 -2
- package/dist/tenant/resource-server.d.ts +56 -0
- package/dist/tenant/resource-server.js +62 -0
- package/dist/tenant/runtime.d.ts +1 -1
- package/dist/tenant/runtime.js +16 -15
- package/dist/tenant/scheduler.d.ts +1 -1
- package/dist/tenant/scheduler.js +1 -1
- package/dist/tenant/sessions.js +20 -3
- package/dist/tenant/store-pg.js +9 -0
- package/dist/tenant/streams.d.ts +16 -22
- package/dist/tenant/streams.js +7 -87
- package/dist/tenant/sweep.js +9 -1
- package/dist/tenant/tool-results.d.ts +57 -0
- package/dist/tenant/tool-results.js +298 -0
- package/dist/tenant/tool-search.d.ts +43 -0
- package/dist/tenant/tool-search.js +100 -0
- package/dist/tenant/transcript.d.ts +108 -0
- package/dist/tenant/transcript.js +13 -0
- package/dist/tenant/types.d.ts +0 -14
- package/dist/vault/crypto.d.ts +0 -7
- package/dist/vault/crypto.js +0 -16
- package/dist/vault/headers.d.ts +17 -0
- package/dist/vault/headers.js +61 -0
- package/dist/vault/service.d.ts +11 -68
- package/dist/vault/service.js +83 -438
- package/dist/vault/sources.d.ts +20 -48
- package/dist/vault/sources.js +25 -189
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/streams/control.d.ts +0 -13
- package/dist/streams/control.js +0 -25
- package/dist/vault/oauth.d.ts +0 -55
- package/dist/vault/oauth.js +0 -186
package/dist/api/components.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* global registry.
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { SessionListItemSchema, ModelCallSchema, SessionPageSchema, SessionManifestViewSchema, HistoryPageSchema, SessionUsageTotalsSchema, ModelCallsPageSchema, ModelCallExportPageSchema, SandboxPageSchema, JwksSchema, SigningKeyListSchema, SigningKeyViewSchema, RotateSigningKeysRequestSchema, RevokeSigningKeyRequestSchema, MeResponseSchema, TenantStatusSchema, ResetTenantRequestSchema, ResetTenantResponseSchema, SeedTenantConfigRequestSchema, SeedTenantConfigResponseSchema, HostModelCatalogSchema, TenantSandboxViewSchema, ModelUsageTotalsSchema, ModelBudgetsSchema, PutModelBudgetsRequestSchema, PutTenantSandboxRequestSchema, ListProvidersResponseSchema, HostModelViewSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, CreateVaultRequestSchema, VaultInfoSchema, ListVaultsResponseSchema, CreateCredentialRequestSchema, CredentialInfoSchema, ListCredentialsResponseSchema, RotateCredentialRequestSchema,
|
|
8
|
+
import { SessionListItemSchema, ModelCallSchema, SessionPageSchema, SessionManifestViewSchema, HistoryPageSchema, SessionUsageTotalsSchema, ModelCallsPageSchema, ModelCallExportPageSchema, SandboxPageSchema, JwksSchema, SigningKeyListSchema, SigningKeyViewSchema, RotateSigningKeysRequestSchema, RevokeSigningKeyRequestSchema, MeResponseSchema, TenantStatusSchema, ResetTenantRequestSchema, ResetTenantResponseSchema, SeedTenantConfigRequestSchema, SeedTenantConfigResponseSchema, HostModelCatalogSchema, TenantSandboxViewSchema, ModelUsageTotalsSchema, ModelBudgetsSchema, PutModelBudgetsRequestSchema, PutTenantSandboxRequestSchema, ListProvidersResponseSchema, HostModelViewSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, CreateVaultRequestSchema, VaultInfoSchema, ListVaultsResponseSchema, CreateCredentialRequestSchema, CredentialInfoSchema, ListCredentialsResponseSchema, RotateCredentialRequestSchema, McpPreviewRequestSchema, McpPreviewSchema, DeletedResponseSchema, ListAgentsResponseSchema, ListPublicAgentsResponseSchema, ListSessionsResponseSchema, LiveEventSchema, PutAgentRequestSchema, PutAgentResponseSchema, PutSessionRequestSchema, SessionItemsResponseSchema, SessionViewSchema, StreamClosedFrameSchema, AcceptedResponseSchema, SandboxToolOutcomeSchema, PutSandboxRequestSchema, SandboxViewSchema, ListSandboxesResponseSchema, DeleteSandboxResponseSchema, ListSandboxEventsResponseSchema, ArtifactViewSchema, ArtifactVersionViewSchema, ListArtifactsResponseSchema, UploadArtifactResponseSchema, DefinitionFileViewSchema, DeleteArtifactResponseSchema, CreateArtifactLinkRequestSchema, ArtifactLinkSchema, PutTenantArtifactsRequestSchema, TenantArtifactsViewSchema, ArtifactTreeSchema, ArtifactDiffSchema, SessionCommandSchema, ListOperatorKeysResponseSchema, PutOperatorKeyResponseSchema, DeleteOperatorKeyResponseSchema, OperatorKeySchema, ProtocolRejectedResponseSchema, RejectedResponseSchema, EVENT_SCHEMAS, EVENT_TYPES, } from "@nylorun/core/contracts";
|
|
9
9
|
function named(id, schema) {
|
|
10
10
|
return schema.meta({ id });
|
|
11
11
|
}
|
|
@@ -76,9 +76,9 @@ export const CreateCredentialRequest = named("CreateCredentialRequest", CreateCr
|
|
|
76
76
|
export const CredentialInfo = named("CredentialInfo", CredentialInfoSchema);
|
|
77
77
|
export const ListCredentialsResponse = named("ListCredentialsResponse", ListCredentialsResponseSchema);
|
|
78
78
|
export const RotateCredentialRequest = named("RotateCredentialRequest", RotateCredentialRequestSchema);
|
|
79
|
-
export const StartOAuthRequest = named("StartOAuthRequest", StartOAuthRequestSchema);
|
|
80
|
-
export const StartOAuthResponse = named("StartOAuthResponse", StartOAuthResponseSchema);
|
|
81
79
|
export const DeletedResponse = named("DeletedResponse", DeletedResponseSchema);
|
|
80
|
+
export const McpPreviewRequest = named("McpPreviewRequest", McpPreviewRequestSchema);
|
|
81
|
+
export const McpPreview = named("McpPreview", McpPreviewSchema);
|
|
82
82
|
export const Jwks = named("Jwks", JwksSchema);
|
|
83
83
|
export const SigningKeyList = named("SigningKeyList", SigningKeyListSchema);
|
|
84
84
|
export const SigningKeyView = named("SigningKeyView", SigningKeyViewSchema);
|
package/dist/api/http/app.js
CHANGED
|
@@ -20,6 +20,7 @@ import { fileRoutes } from "./routes/files.js";
|
|
|
20
20
|
import { keyRoutes } from "./routes/keys.js";
|
|
21
21
|
import { tenantRoutes } from "./routes/tenant.js";
|
|
22
22
|
import { vaultRoutes } from "./routes/vaults.js";
|
|
23
|
+
import { mcpRoutes } from "./routes/mcp.js";
|
|
23
24
|
let app;
|
|
24
25
|
/** The Tenant API, built on first use. */
|
|
25
26
|
export function tenantApi() {
|
|
@@ -48,6 +49,7 @@ function build() {
|
|
|
48
49
|
tenantRoutes(api);
|
|
49
50
|
keyRoutes(api);
|
|
50
51
|
vaultRoutes(api);
|
|
52
|
+
mcpRoutes(api);
|
|
51
53
|
accessRoutes(api);
|
|
52
54
|
meRoutes(api);
|
|
53
55
|
agUiRoutes(api);
|
|
@@ -27,7 +27,8 @@ export interface RouteAccess {
|
|
|
27
27
|
/** The subject scopes that reach the route: any one of them, none (`never`), or all (`any`). */
|
|
28
28
|
readonly scopes: readonly SubjectScope[] | "never" | "any";
|
|
29
29
|
/**
|
|
30
|
-
* Public data: a request with no credential at all (no `Authorization`) is served too. A
|
|
30
|
+
* Public data: a request with no credential at all (no `Authorization`) is served too. A
|
|
31
|
+
* credential that is sent is still checked, so a wrong one is `401 credential_invalid`.
|
|
31
32
|
*/
|
|
32
33
|
readonly anonymous?: boolean;
|
|
33
34
|
/**
|
|
@@ -49,7 +50,7 @@ export declare function authenticateCaller(c: Context<TenantEnv>,
|
|
|
49
50
|
anonymous?: boolean,
|
|
50
51
|
/** The keys the route takes; a Runtime API route's by default. */
|
|
51
52
|
keys?: KeyAccess): Promise<AuthScope>;
|
|
52
|
-
/** No route: authenticated first, so
|
|
53
|
+
/** No route: authenticated first, so only a known credential learns there is no such route. */
|
|
53
54
|
export declare function routeNotFound(c: Context<TenantEnv>): Promise<Response>;
|
|
54
55
|
/** Which keys a route's credentials take. */
|
|
55
56
|
export declare function keyAccess(access: RouteAccess): KeyAccess;
|
package/dist/api/http/define.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { PROTOCOL_VERSION } from "@nylorun/core/compatibility";
|
|
3
|
-
import { authenticate, requireScopes } from "../../tenant/auth.js";
|
|
3
|
+
import { authenticate, challengeMetadata, requireScopes, } from "../../tenant/auth.js";
|
|
4
4
|
import { fail } from "../../tenant/http.js";
|
|
5
5
|
import { ProtocolRejected, Rejected } from "../components.js";
|
|
6
6
|
const SCHEMES = {
|
|
@@ -38,7 +38,7 @@ keys) {
|
|
|
38
38
|
return { kind: "anonymous" };
|
|
39
39
|
return await authenticate(tenant, incoming, keys);
|
|
40
40
|
}
|
|
41
|
-
/** No route: authenticated first, so
|
|
41
|
+
/** No route: authenticated first, so only a known credential learns there is no such route. */
|
|
42
42
|
export async function routeNotFound(c) {
|
|
43
43
|
// Any known key, of either API, learns only that there is no such route.
|
|
44
44
|
await authenticateCaller(c, false, { application: true, management: true });
|
|
@@ -64,8 +64,9 @@ export function declaredRoute(method, segments) {
|
|
|
64
64
|
}
|
|
65
65
|
function authenticated(access) {
|
|
66
66
|
return async (c, next) => {
|
|
67
|
-
const
|
|
68
|
-
|
|
67
|
+
const keys = keyAccess(access);
|
|
68
|
+
const scope = await authenticateCaller(c, access.anonymous === true, keys);
|
|
69
|
+
requireScopes(scope, access.scopes, challengeMetadata(c.env.tenant, c.env.incoming, keys));
|
|
69
70
|
c.set("scope", scope);
|
|
70
71
|
await next();
|
|
71
72
|
};
|
|
@@ -99,15 +100,13 @@ export function tenantRoute(api, access, route, handler) {
|
|
|
99
100
|
request: { ...route.request, headers },
|
|
100
101
|
responses: {
|
|
101
102
|
400: rejected("Invalid headers, path, body or cursor"),
|
|
102
|
-
|
|
103
|
-
?
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
: {}),
|
|
103
|
+
401: rejected(takes("token")
|
|
104
|
+
? "No credential (`credential_required`), one the Tenant does not accept (`credential_invalid`), a token that expired (`token_expired`), or a token whose issuer's keys cannot be fetched now (`issuer_unavailable`). `WWW-Authenticate: Bearer` names the protected resource metadata (`resource_metadata`)"
|
|
105
|
+
: "No credential (`credential_required`), or one the Tenant does not accept (`credential_invalid`), with a `WWW-Authenticate: Bearer` challenge"),
|
|
107
106
|
403: rejected(takes("management")
|
|
108
107
|
? "Not allowed for this credential, a key of the other API (`key_role_mismatch`), a key from a browser (`origin_rejected`) or a management key acting for a subject"
|
|
109
|
-
: "Not allowed for this credential or subject scope, a key of the other API (`key_role_mismatch`), or an application key from a browser (`origin_rejected`)"),
|
|
110
|
-
404: rejected("Not found
|
|
108
|
+
: "Not allowed for this credential or subject scope (`scope_required`; a token also gets `WWW-Authenticate: Bearer error=\"insufficient_scope\"` and the scope), a key of the other API (`key_role_mismatch`), or an application key from a browser (`origin_rejected`)"),
|
|
109
|
+
404: rejected("Not found (`not_found`)"),
|
|
111
110
|
426: { description: "`Nylorun-Protocol` missing or unsupported", content: { "application/json": { schema: ProtocolRejected } } },
|
|
112
111
|
503: rejected("The Tenant's storage or streams are unavailable"),
|
|
113
112
|
...route.responses,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A remote MCP server's tools before an agent names it (`POST /v1/tenant/mcp/preview`, R2b C12,
|
|
3
|
+
* the Management API): a management key (or Studio's key, acting as itself), as the vault routes
|
|
4
|
+
* take. The keys service runs it (`Keys.previewMcp`), since it sends an installation vault's
|
|
5
|
+
* credential; this process never reads the plaintext.
|
|
6
|
+
*/
|
|
7
|
+
import type { OpenAPIHono } from "@hono/zod-openapi";
|
|
8
|
+
import type { TenantEnv } from "../app.js";
|
|
9
|
+
export declare function mcpRoutes(api: OpenAPIHono<TenantEnv>): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { McpPreviewRequestSchema } from "@nylorun/core/contracts";
|
|
3
|
+
import { McpPreview, McpPreviewRequest, Rejected } from "../../components.js";
|
|
4
|
+
import { readJson } from "../body.js";
|
|
5
|
+
import { tenantRoute } from "../define.js";
|
|
6
|
+
import { jsonResponse } from "../respond.js";
|
|
7
|
+
/** The vault routes' access: a management key. */
|
|
8
|
+
const MANAGEMENT = { credentials: ["management"], scopes: "never" };
|
|
9
|
+
const json = (schema, description) => ({
|
|
10
|
+
description,
|
|
11
|
+
content: { "application/json": { schema } },
|
|
12
|
+
});
|
|
13
|
+
export function mcpRoutes(api) {
|
|
14
|
+
tenantRoute(api, MANAGEMENT, {
|
|
15
|
+
method: "post",
|
|
16
|
+
path: "/v1/tenant/mcp/preview",
|
|
17
|
+
tags: ["MCP servers"],
|
|
18
|
+
summary: "Preview an MCP server's tools",
|
|
19
|
+
description: "Connects to the remote MCP server at `url` with the installation vault's credential for that URL (its headers and `via`; never an identity header), under the Host's address policy, lists its tools within 15 s and closes the connection. It never calls a tool. Each tool comes with the name the model would call it (`modelName`, made with `name`), its annotations and its input schema's size. A server that answers `401` is `authRequired`, with its RFC 9728 protected-resource metadata when it publishes any: it needs a person's sign-in, so reach it through a gateway (`via`), or with a key. No credential value is in the answer.",
|
|
20
|
+
request: {
|
|
21
|
+
body: { required: true, content: { "application/json": { schema: McpPreviewRequest } } },
|
|
22
|
+
},
|
|
23
|
+
responses: {
|
|
24
|
+
200: json(McpPreview, "The server's tools, or that it needs a credential (`authRequired`)"),
|
|
25
|
+
409: json(Rejected, "Several installation vaults hold a credential for the URL (name one with `vaultId`), or the one there cannot be used"),
|
|
26
|
+
502: json(Rejected, "The server could not be listed (`mcp_preview_failed`): `details.failure` is the code a tool call would have failed with, such as `mcp.unreachable` for an address the Host refuses"),
|
|
27
|
+
},
|
|
28
|
+
}, async (c) => {
|
|
29
|
+
const request = McpPreviewRequestSchema.parse(await readJson(c.req.raw));
|
|
30
|
+
return jsonResponse(200, await c.env.tenant.keys.previewMcp(request));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -3,16 +3,8 @@
|
|
|
3
3
|
* a session's tools use. Only a management key (or Studio's key, acting as itself) reaches these
|
|
4
4
|
* routes; an application key is `403 key_role_mismatch`, and nothing acts for a subject here. The
|
|
5
5
|
* installation's own vaults (`scope: "installation"`) attach to any session (`vaultIds`); a vault
|
|
6
|
-
* of one person (`ownerUserId`) attaches only to that person's sessions.
|
|
7
|
-
* credentials come from the operator's credential resolver (`vault/sources.ts`), not from these
|
|
8
|
-
* routes.
|
|
9
|
-
*
|
|
10
|
-
* MCP OAuth connect: a management key starts one into an installation vault (`/oauth/start`);
|
|
11
|
-
* the authorization server sends the browser back to the anonymous, unversioned
|
|
12
|
-
* `GET /v1/oauth/callback`. Both only route: the gateway's keys module does the OAuth.
|
|
6
|
+
* of one person (`ownerUserId`) attaches only to that person's sessions.
|
|
13
7
|
*/
|
|
14
8
|
import type { OpenAPIHono } from "@hono/zod-openapi";
|
|
15
9
|
import type { TenantEnv } from "../app.js";
|
|
16
|
-
/** Where the authorization server sends the browser back. */
|
|
17
|
-
export declare const OAUTH_CALLBACK_PATH = "/v1/oauth/callback";
|
|
18
10
|
export declare function vaultRoutes(api: OpenAPIHono<TenantEnv>): void;
|
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { CreateCredentialRequestSchema, CreateVaultRequestSchema, RotateCredentialRequestSchema,
|
|
3
|
-
import { CreateCredentialRequest, CreateVaultRequest, CredentialInfo, DeletedResponse, ListCredentialsResponse, ListVaultsResponse, RotateCredentialRequest,
|
|
4
|
-
import { HttpError } from "../../../tenant/http.js";
|
|
5
|
-
import { VaultError } from "../../../vault/error.js";
|
|
2
|
+
import { CreateCredentialRequestSchema, CreateVaultRequestSchema, RotateCredentialRequestSchema, } from "@nylorun/core/contracts";
|
|
3
|
+
import { CreateCredentialRequest, CreateVaultRequest, CredentialInfo, DeletedResponse, ListCredentialsResponse, ListVaultsResponse, RotateCredentialRequest, VaultInfo, } from "../../components.js";
|
|
6
4
|
import { readJson } from "../body.js";
|
|
7
5
|
import { tenantRoute } from "../define.js";
|
|
8
6
|
import { jsonResponse } from "../respond.js";
|
|
9
7
|
/** The Management API's vault routes (`/v1/tenant/vaults`): a management key. */
|
|
10
8
|
const MANAGEMENT = { credentials: ["management"], scopes: "never" };
|
|
11
|
-
/**
|
|
12
|
-
* The OAuth callback: a browser sent back by the authorization server, with no credential and no
|
|
13
|
-
* protocol header. The `state` it carries is the grant.
|
|
14
|
-
*/
|
|
15
|
-
const CALLBACK = {
|
|
16
|
-
credentials: ["application", "subject", "token"],
|
|
17
|
-
scopes: "any",
|
|
18
|
-
anonymous: true,
|
|
19
|
-
unversioned: true,
|
|
20
|
-
};
|
|
21
|
-
/** Where the authorization server sends the browser back. */
|
|
22
|
-
export const OAUTH_CALLBACK_PATH = "/v1/oauth/callback";
|
|
23
9
|
const json = (schema, description) => ({
|
|
24
10
|
description,
|
|
25
11
|
content: { "application/json": { schema } },
|
|
@@ -94,7 +80,7 @@ function vaultRoutesAt(api, base, access) {
|
|
|
94
80
|
path: `${base}/{vaultId}/credentials`,
|
|
95
81
|
tags: ["Vaults"],
|
|
96
82
|
summary: "Add a credential",
|
|
97
|
-
description: "A bearer token
|
|
83
|
+
description: "A bearer token, bound to the URL a tool may send it to.",
|
|
98
84
|
request: { params: vaultId, body: body(CreateCredentialRequest) },
|
|
99
85
|
responses: { 200: json(CredentialInfo, "The credential, without its secret") },
|
|
100
86
|
}, async (c) => {
|
|
@@ -147,93 +133,7 @@ function vaultRoutesAt(api, base, access) {
|
|
|
147
133
|
}, async (c) => {
|
|
148
134
|
return jsonResponse(200, await c.env.tenant.vault.deleteCredential(c.req.param("vaultId"), c.req.param("credentialId")));
|
|
149
135
|
});
|
|
150
|
-
tenantRoute(api, access, {
|
|
151
|
-
method: "post",
|
|
152
|
-
path: `${base}/{vaultId}/oauth/start`,
|
|
153
|
-
tags: ["Vaults"],
|
|
154
|
-
summary: "Start an MCP OAuth connect",
|
|
155
|
-
description: "Signs the installation in to the remote MCP server at `url` (declared as `server`) and stores its OAuth credential, bound to `url`, in this installation vault. The Runtime discovers the server's authorization server (RFC 9728, RFC 8414), registers itself (RFC 7591) unless `clientId` names a registered client, and answers the URL to open in a browser; the sign-in returns to `GET /v1/oauth/callback` within `expiresAt`. The callback URL is `NYLORUN_PUBLIC_URL` + `/v1/oauth/callback`, or this request's own origin when the Host has no public URL. Application keys acting for no one only. `oauth_client_required` when the server offers no registration and no `clientId` was given.",
|
|
156
|
-
request: { params: vaultId, body: body(StartOAuthRequest) },
|
|
157
|
-
responses: {
|
|
158
|
-
200: json(StartOAuthResponse, "Where to send the browser"),
|
|
159
|
-
502: {
|
|
160
|
-
description: "The authorization server, or its discovery, failed or refused (`oauth_failed`)",
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
}, async (c) => {
|
|
164
|
-
const request = StartOAuthRequestSchema.parse(await readJson(c.req.raw));
|
|
165
|
-
const tenant = c.env.tenant;
|
|
166
|
-
const base = tenant.config.publicUrl ?? new URL(c.req.url).origin;
|
|
167
|
-
return jsonResponse(200, await tenant.keys.startOAuth({
|
|
168
|
-
vaultId: c.req.param("vaultId"),
|
|
169
|
-
server: request.server,
|
|
170
|
-
url: request.url,
|
|
171
|
-
...(request.clientId === undefined ? {} : { clientId: request.clientId }),
|
|
172
|
-
redirectUri: `${base}${OAUTH_CALLBACK_PATH}`,
|
|
173
|
-
}));
|
|
174
|
-
});
|
|
175
136
|
}
|
|
176
137
|
export function vaultRoutes(api) {
|
|
177
138
|
vaultRoutesAt(api, "/v1/tenant/vaults", MANAGEMENT);
|
|
178
|
-
tenantRoute(api, CALLBACK, {
|
|
179
|
-
method: "get",
|
|
180
|
-
path: OAUTH_CALLBACK_PATH,
|
|
181
|
-
tags: ["Vaults"],
|
|
182
|
-
summary: "Finish an MCP OAuth connect",
|
|
183
|
-
description: "Where the authorization server sends the browser back with `code` and `state` (or `error`). Exchanges the code once and stores the credential; answers a small HTML page. Needs no credential and no `Nylorun-Protocol`. A `state` is used once, for ten minutes (`oauth_state_invalid`).",
|
|
184
|
-
request: {
|
|
185
|
-
query: z.object({
|
|
186
|
-
code: z.string().optional(),
|
|
187
|
-
state: z.string().optional(),
|
|
188
|
-
error: z.string().optional(),
|
|
189
|
-
}),
|
|
190
|
-
},
|
|
191
|
-
responses: {
|
|
192
|
-
200: { description: "Connected", content: { "text/html": { schema: z.string() } } },
|
|
193
|
-
502: { description: "The authorization server refused the code (`oauth_failed`)" },
|
|
194
|
-
},
|
|
195
|
-
}, async (c) => {
|
|
196
|
-
const state = c.req.query("state");
|
|
197
|
-
const code = c.req.query("code");
|
|
198
|
-
const error = c.req.query("error");
|
|
199
|
-
if (!state)
|
|
200
|
-
return page(400, "Sign-in failed", "The authorization server sent no state. Start the connect again.");
|
|
201
|
-
try {
|
|
202
|
-
await c.env.tenant.keys.finishOAuth({
|
|
203
|
-
state,
|
|
204
|
-
...(code === undefined ? {} : { code }),
|
|
205
|
-
...(error === undefined ? {} : { error }),
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
catch (failure) {
|
|
209
|
-
if (failure instanceof HttpError || failure instanceof VaultError)
|
|
210
|
-
return page(failure.status, "Sign-in failed", failure.message);
|
|
211
|
-
c.env.tenant.config.logger.error("oauth_callback_failed", {
|
|
212
|
-
error: failure instanceof Error ? failure.name : "unknown",
|
|
213
|
-
});
|
|
214
|
-
return page(500, "Sign-in failed", "The connect failed. Start it again.");
|
|
215
|
-
}
|
|
216
|
-
return page(200, "Connected", "Connected. You can close this tab.");
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
const ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
220
|
-
const escape = (text) => text.replace(/[&<>"']/g, (char) => ESCAPES[char]);
|
|
221
|
-
/** The callback's answer: a small page that loads nothing, is never cached and leaks no referrer. */
|
|
222
|
-
function page(status, title, message) {
|
|
223
|
-
const html = `<!doctype html>
|
|
224
|
-
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
225
|
-
<title>${escape(title)} · Nylorun</title>
|
|
226
|
-
<style>body{font:16px/1.5 system-ui,sans-serif;max-width:32rem;margin:15vh auto;padding:0 1rem;color:#1a1a1a;background:#fff}@media (prefers-color-scheme:dark){body{color:#eee;background:#111}}h1{font-size:1.25rem}</style>
|
|
227
|
-
</head><body><h1>${escape(title)}</h1><p>${escape(message)}</p></body></html>
|
|
228
|
-
`;
|
|
229
|
-
return new Response(html, {
|
|
230
|
-
status,
|
|
231
|
-
headers: {
|
|
232
|
-
"content-type": "text/html; charset=utf-8",
|
|
233
|
-
"cache-control": "no-store",
|
|
234
|
-
"referrer-policy": "no-referrer",
|
|
235
|
-
"content-security-policy": "default-src 'none'; style-src 'unsafe-inline'",
|
|
236
|
-
"x-content-type-options": "nosniff",
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
139
|
}
|
package/dist/api/openapi.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { OpenAPIRegistry, OpenApiGeneratorV32, } from "@asteasolutions/zod-to-openapi";
|
|
13
13
|
import { HOST_PROTOCOL } from "@nylorun/core/compatibility";
|
|
14
14
|
import { HealthResponseSchema, ReadyResponseSchema } from "@nylorun/core/contracts";
|
|
15
|
+
import { z } from "zod";
|
|
15
16
|
import { RUNTIME_VERSION } from "../version.js";
|
|
16
17
|
import { tenantApi } from "./http/app.js";
|
|
17
18
|
const PROTOCOL = {
|
|
@@ -25,9 +26,21 @@ const bearer = (description, bearerFormat) => ({
|
|
|
25
26
|
...(bearerFormat ? { bearerFormat } : {}),
|
|
26
27
|
description,
|
|
27
28
|
});
|
|
29
|
+
/** RFC 9728 protected resource metadata, as `tenant/resource-server.ts` builds it. */
|
|
30
|
+
const ProtectedResourceMetadataSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
resource: z.string().meta({ description: "The Runtime API: `NYLORUN_PUBLIC_URL`, else the origin the request reached" }),
|
|
33
|
+
authorization_servers: z
|
|
34
|
+
.array(z.string())
|
|
35
|
+
.meta({ description: "The identity file's issuers, in its order: a client usually signs in at the first" }),
|
|
36
|
+
scopes_supported: z.array(z.string()).meta({ description: "The scopes the issuers' tokens may carry, `studio` aside" }),
|
|
37
|
+
bearer_methods_supported: z.array(z.literal("header")),
|
|
38
|
+
resource_name: z.string(),
|
|
39
|
+
})
|
|
40
|
+
.meta({ id: "ProtectedResourceMetadata" });
|
|
28
41
|
/**
|
|
29
|
-
* `/health`, `/ready
|
|
30
|
-
* `/openapi.json` alias is served but left out of the documents.
|
|
42
|
+
* `/health`, `/ready`, the documents and the OAuth metadata: what every listener answers
|
|
43
|
+
* without a key. The `/openapi.json` alias is served but left out of the documents.
|
|
31
44
|
*/
|
|
32
45
|
function hostRoutes() {
|
|
33
46
|
const registry = new OpenAPIRegistry();
|
|
@@ -59,6 +72,20 @@ function hostRoutes() {
|
|
|
59
72
|
503: { description: "Not ready: `checks` says what is not" },
|
|
60
73
|
},
|
|
61
74
|
});
|
|
75
|
+
route({
|
|
76
|
+
method: "get",
|
|
77
|
+
path: "/.well-known/oauth-protected-resource",
|
|
78
|
+
tags: ["Host"],
|
|
79
|
+
summary: "Get the protected resource metadata",
|
|
80
|
+
description: "OAuth 2.0 Protected Resource Metadata (RFC 9728): the identity providers whose access tokens the Runtime API accepts (the identity file's trusted issuers) and their scopes. Nylorun is never the authorization server: a client signs in at one of `authorization_servers` and presents its token as `Authorization: Bearer`. A `401` from the Runtime API names this document in `WWW-Authenticate` (`resource_metadata`). Needs no key or `Nylorun-Protocol`, and accepts an `Origin`.",
|
|
81
|
+
responses: {
|
|
82
|
+
200: {
|
|
83
|
+
description: "The metadata",
|
|
84
|
+
content: { "application/json": { schema: ProtectedResourceMetadataSchema } },
|
|
85
|
+
},
|
|
86
|
+
404: { description: "The Runtime trusts no identity provider (no identity file)" },
|
|
87
|
+
},
|
|
88
|
+
});
|
|
62
89
|
route({
|
|
63
90
|
method: "get",
|
|
64
91
|
path: "/openapi/runtime.json",
|
|
@@ -164,11 +191,12 @@ const RUNTIME_TAGS = [
|
|
|
164
191
|
{
|
|
165
192
|
name: "Service",
|
|
166
193
|
group: "Service",
|
|
167
|
-
description: "Check a Runtime is up and speaks your protocol, ask who your credential is, and read the public keys of the tokens it signs. `/health`, `/ready`, the JWKS and the documents need no key.",
|
|
194
|
+
description: "Check a Runtime is up and speaks your protocol, ask who your credential is, find the identity providers it takes tokens from, and read the public keys of the tokens it signs. `/health`, `/ready`, the protected resource metadata, the JWKS and the documents need no key.",
|
|
168
195
|
operations: [
|
|
169
196
|
"GET /health",
|
|
170
197
|
"GET /ready",
|
|
171
198
|
"GET /v1/me",
|
|
199
|
+
"GET /.well-known/oauth-protected-resource",
|
|
172
200
|
"GET /v1/access/jwks",
|
|
173
201
|
"GET /openapi/runtime.json",
|
|
174
202
|
],
|
|
@@ -208,7 +236,7 @@ const MANAGEMENT_TAGS = [
|
|
|
208
236
|
},
|
|
209
237
|
{
|
|
210
238
|
name: "Vaults",
|
|
211
|
-
description: "The installation's credentials for agents' tools: API keys
|
|
239
|
+
description: "The installation's and each person's credentials for agents' tools: API keys, bound to the URL they are sent to. Sessions attach a vault by id. `admin.vaults`.",
|
|
212
240
|
operations: [
|
|
213
241
|
"POST /v1/tenant/vaults",
|
|
214
242
|
"GET /v1/tenant/vaults",
|
|
@@ -219,10 +247,13 @@ const MANAGEMENT_TAGS = [
|
|
|
219
247
|
"GET /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
|
|
220
248
|
"POST /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
|
|
221
249
|
"DELETE /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
|
|
222
|
-
"POST /v1/tenant/vaults/{vaultId}/oauth/start",
|
|
223
|
-
"GET /v1/oauth/callback",
|
|
224
250
|
],
|
|
225
251
|
},
|
|
252
|
+
{
|
|
253
|
+
name: "MCP servers",
|
|
254
|
+
description: "See a remote MCP server's tools before an agent names it, with the installation vault's credential for its URL: their model names, sizes and annotations, or that it needs a person's sign-in. `admin.mcp`.",
|
|
255
|
+
operations: ["POST /v1/tenant/mcp/preview"],
|
|
256
|
+
},
|
|
226
257
|
{
|
|
227
258
|
name: "Signing keys",
|
|
228
259
|
description: "The keys the Runtime signs capability links and run tokens with; their public halves are the Runtime API's JWKS. `admin.signingKeys`.",
|
|
@@ -11,3 +11,5 @@ export declare function contentTypeFor(name: string): string;
|
|
|
11
11
|
export declare function isModelImage(contentType: string): boolean;
|
|
12
12
|
/** Files the model reads as text: `text/*`, JSON, XML, YAML and their `+json`/`+xml` kin. */
|
|
13
13
|
export declare function isText(contentType: string): boolean;
|
|
14
|
+
/** The usual extension of a media type, for a name the Runtime gives a file; undefined if none. */
|
|
15
|
+
export declare function extensionFor(contentType: string): string | undefined;
|
|
@@ -78,3 +78,8 @@ export function isText(contentType) {
|
|
|
78
78
|
type.endsWith("+json") ||
|
|
79
79
|
type.endsWith("+xml"));
|
|
80
80
|
}
|
|
81
|
+
/** The usual extension of a media type, for a name the Runtime gives a file; undefined if none. */
|
|
82
|
+
export function extensionFor(contentType) {
|
|
83
|
+
const type = essence(contentType);
|
|
84
|
+
return Object.keys(BY_EXTENSION).find((extension) => BY_EXTENSION[extension] === type);
|
|
85
|
+
}
|
|
@@ -91,7 +91,7 @@ export interface SandboxResult {
|
|
|
91
91
|
export interface WorkerHandlers {
|
|
92
92
|
/**
|
|
93
93
|
* Advances one session (§10.5). `signal` aborts when the Worker stops or the
|
|
94
|
-
* session is cancelled through the control
|
|
94
|
+
* session is cancelled through the control bus. Throw only on
|
|
95
95
|
* infrastructure errors.
|
|
96
96
|
*/
|
|
97
97
|
advance(tenantId: string, sessionId: string, signal: AbortSignal): Promise<AdvanceResult>;
|
|
@@ -11,10 +11,17 @@ export declare const HTTP_ERROR_BODY_CHARS = 2000;
|
|
|
11
11
|
export type HttpOutcome = {
|
|
12
12
|
readonly kind: "completed";
|
|
13
13
|
readonly output: unknown;
|
|
14
|
+
/** How many credential values the answer echoed, replaced with `[redacted]` (R2b C8). */
|
|
15
|
+
readonly redacted?: number;
|
|
14
16
|
} | {
|
|
15
17
|
readonly kind: "failed";
|
|
16
18
|
readonly code: string;
|
|
17
19
|
readonly message: string;
|
|
20
|
+
/** With `credential_rejected`: the tool's `credential`, and the scope of its vault. */
|
|
21
|
+
readonly server?: string;
|
|
22
|
+
readonly vault?: "installation" | "user";
|
|
23
|
+
/** How many credential values the message had in it, replaced (R2b C8). */
|
|
24
|
+
readonly redacted?: number;
|
|
18
25
|
};
|
|
19
26
|
/**
|
|
20
27
|
* One declared HTTP tool of a session: what the gate finds it by in the pinned manifest. An
|
package/dist/gates/http-tool.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { AGENT_ID_HEADER, SESSION_ID_HEADER, TURN_ID_HEADER, } from "@nylorun/core/compatibility";
|
|
17
17
|
import { HTTP_TOOL_DEFAULT_TIMEOUT_MS, delegateManifest, flowHttpTarget, isWorkflowManifest, schemaFromJSON, } from "@nylorun/core/define";
|
|
18
|
+
import { scrubValues } from "../redact.js";
|
|
18
19
|
import { MAX_RESPONSE_BYTES, post } from "../tenant/outbound.js";
|
|
20
|
+
import { credentialSecrets } from "../vault/headers.js";
|
|
19
21
|
/** How much of a failed answer's body the model sees. */
|
|
20
22
|
export const HTTP_ERROR_BODY_CHARS = 2_000;
|
|
21
23
|
/** The HTTP tool `capabilityId`/`toolName` of the agent `agentId` (the root when absent). */
|
|
@@ -83,6 +85,9 @@ export async function callHttpTarget(target, input, options) {
|
|
|
83
85
|
case "too_large":
|
|
84
86
|
return failed("http.too-large", `The answer (HTTP ${result.status}) is larger than ${MAX_RESPONSE_BYTES} bytes`);
|
|
85
87
|
}
|
|
88
|
+
// The service answered, so the outcome is known; never retried, and its body is not shown (R2b C1).
|
|
89
|
+
if (result.status === 401)
|
|
90
|
+
return failed("credential_rejected", "The service answered HTTP 401: it rejected the credential, or needs one");
|
|
86
91
|
const text = result.body.toString("utf8");
|
|
87
92
|
if (result.status < 200 || result.status > 299) {
|
|
88
93
|
const body = text.length > HTTP_ERROR_BODY_CHARS ? `${text.slice(0, HTTP_ERROR_BODY_CHARS)}…` : text;
|
|
@@ -156,20 +161,26 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
156
161
|
: `'${tool.toolName}' is not an HTTP tool of the session`);
|
|
157
162
|
const { http } = declared;
|
|
158
163
|
let credential = {};
|
|
164
|
+
let identity;
|
|
165
|
+
let target = http;
|
|
166
|
+
let vault;
|
|
159
167
|
if (http.credential !== undefined) {
|
|
160
168
|
const authorized = await tenant.authorize(tool.sessionId, {
|
|
161
|
-
kind: "http",
|
|
162
169
|
url: http.url,
|
|
163
170
|
serverName: http.credential,
|
|
164
|
-
...(agentId === undefined ? {} : { agentId }),
|
|
165
171
|
});
|
|
166
172
|
if (authorized.status === "refused")
|
|
167
173
|
return failed("http.credential", `The credential '${http.credential}' was refused: ${authorized.reason}`);
|
|
168
174
|
if (authorized.status === "unauthenticated")
|
|
169
175
|
return failed("http.credential", `The session's vaults hold no credential '${http.credential}' for ${authorized.url}`);
|
|
170
176
|
credential = authorized.headers;
|
|
177
|
+
identity = authorized.identity;
|
|
178
|
+
vault = authorized.vault;
|
|
179
|
+
// A credential with `via` sends the call there, a gateway (R2b C2, Q9).
|
|
180
|
+
if (authorized.via !== undefined)
|
|
181
|
+
target = { ...http, url: authorized.via };
|
|
171
182
|
}
|
|
172
|
-
|
|
183
|
+
const answered = await callHttpTarget(target, call.input, {
|
|
173
184
|
headers: {
|
|
174
185
|
...credential,
|
|
175
186
|
[SESSION_ID_HEADER]: tool.sessionId,
|
|
@@ -181,6 +192,17 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
181
192
|
signal: options.signal,
|
|
182
193
|
...(declared.outputSchema === undefined ? {} : { outputSchema: declared.outputSchema }),
|
|
183
194
|
});
|
|
195
|
+
// Nothing the service echoes of the credential reaches the model, an event or the gate's
|
|
196
|
+
// record of the call: its answer and the start of an error body alike (R2b C8).
|
|
197
|
+
const outcome = scrubbedOutcome(answered, credentialSecrets(credential, identity));
|
|
198
|
+
if (outcome.kind !== "failed" || outcome.code !== "credential_rejected" || http.credential === undefined)
|
|
199
|
+
return outcome;
|
|
200
|
+
return {
|
|
201
|
+
...outcome,
|
|
202
|
+
message: `The service answered HTTP 401: it rejected the credential '${http.credential}' from the ${vault} vault`,
|
|
203
|
+
server: http.credential,
|
|
204
|
+
...(vault === undefined ? {} : { vault }),
|
|
205
|
+
};
|
|
184
206
|
}
|
|
185
207
|
}
|
|
186
208
|
/** The gates service's HTTP tool calls, with the Tenant's vault (`host/gates.ts`). */
|
|
@@ -195,3 +217,12 @@ export function gateHttpTools(vaults, policy, logger) {
|
|
|
195
217
|
function failed(code, message) {
|
|
196
218
|
return { kind: "failed", code, message };
|
|
197
219
|
}
|
|
220
|
+
/** `outcome` with each of `secrets` replaced by `[redacted]`, and how many were. */
|
|
221
|
+
function scrubbedOutcome(outcome, secrets) {
|
|
222
|
+
if (outcome.kind === "completed") {
|
|
223
|
+
const output = scrubValues(outcome.output, secrets);
|
|
224
|
+
return output.redacted === 0 ? outcome : { ...outcome, output: output.value, redacted: output.redacted };
|
|
225
|
+
}
|
|
226
|
+
const message = scrubValues(outcome.message, secrets);
|
|
227
|
+
return message.redacted === 0 ? outcome : { ...outcome, message: message.value, redacted: message.redacted };
|
|
228
|
+
}
|
|
@@ -18,7 +18,10 @@ export interface McpHandlerOptions {
|
|
|
18
18
|
readonly delivery?: OutboundPolicy;
|
|
19
19
|
}
|
|
20
20
|
export interface McpHandler {
|
|
21
|
-
|
|
21
|
+
/** Opens the connection; answers the server's instructions when it gave any (R2b C10). */
|
|
22
|
+
connect(tenantId: string | undefined, server: McpServerRef): Promise<McpAnswer<{
|
|
23
|
+
instructions: string;
|
|
24
|
+
} | null>>;
|
|
22
25
|
list(tenantId: string | undefined, server: McpServerRef, cursor: string | undefined, signal: AbortSignal): Promise<McpAnswer<McpToolPage>>;
|
|
23
26
|
/** One `tools/call`; the raw `CallToolResult` on success. */
|
|
24
27
|
call(tenantId: string | undefined, request: {
|