@nanobpm/urban 0.84.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/core/modules/api.d.ts +5 -0
- package/dist/runtime/core/modules/api.js +2 -2
- package/dist/runtime/core/modules/mcp.d.ts +57 -0
- package/dist/runtime/core/modules/mcp.js +709 -0
- package/dist/runtime/core/modules/surfaces.js +11 -0
- package/package.json +2 -1
- package/src/runtime/core/modules/api.ts +2 -2
- package/src/runtime/core/modules/mcp.test.ts +687 -0
- package/src/runtime/core/modules/mcp.ts +814 -0
- package/src/runtime/core/modules/surfaces.test.ts +16 -0
- package/src/runtime/core/modules/surfaces.ts +11 -0
|
@@ -13,6 +13,10 @@ export interface ApiBinding {
|
|
|
13
13
|
* for free — the UI reads the SAME OpenAPI document the runtime routes + validates from. */
|
|
14
14
|
docs?: boolean | string;
|
|
15
15
|
}
|
|
16
|
+
/** The canonical, non-configurable mount prefix for an app's OpenAPI operations. Operations always
|
|
17
|
+
* live under `/app/api` so page actions, links, and any `callRoute` can name an operation path as a
|
|
18
|
+
* stable framework constant (`/app/api/<op path>`) — there is no per-app `base` knob to drift. */
|
|
19
|
+
export declare const API_BASE = "/app/api";
|
|
16
20
|
/**
|
|
17
21
|
* Derive the ABSOLUTE spec + operations-server URLs the Swagger UI page should use, from the docs
|
|
18
22
|
* page's own address (`pageHref`), the mount-root-relative docs path (`docsBase`, e.g.
|
|
@@ -97,6 +101,7 @@ export declare class NotImplemented extends Error {
|
|
|
97
101
|
export declare function defineOperation<Req extends OperationContract = DefaultContract, Res = unknown>(_id: string, handler: OperationHandler<Req, Res>): OperationHandler<Req, Res>;
|
|
98
102
|
/** Resolve an operation delegate from a loaded module: `default` (when a function) else `handler`. */
|
|
99
103
|
export declare function resolveOperationHandler(mod: Record<string, unknown>): OperationHandler | undefined;
|
|
104
|
+
export declare function readApiBinding(manifest: unknown): ApiBinding | undefined;
|
|
100
105
|
/** The Swagger UI route for an app's `api` binding, or `undefined` when there is no api surface
|
|
101
106
|
* or docs are disabled. Lets other surfaces (e.g. the pages shell's "API docs" badge) link to
|
|
102
107
|
* the docs without re-deriving the path. */
|
|
@@ -19,7 +19,7 @@ import { collectOperations, isSafeOperationId, operationsWithoutId, operationsWi
|
|
|
19
19
|
/** The canonical, non-configurable mount prefix for an app's OpenAPI operations. Operations always
|
|
20
20
|
* live under `/app/api` so page actions, links, and any `callRoute` can name an operation path as a
|
|
21
21
|
* stable framework constant (`/app/api/<op path>`) — there is no per-app `base` knob to drift. */
|
|
22
|
-
const API_BASE = "/app/api";
|
|
22
|
+
export const API_BASE = "/app/api";
|
|
23
23
|
/** Pinned Swagger UI dist served from a CDN, so the docs UI adds zero bundle/runtime deps to an
|
|
24
24
|
* Urban app (matching the deps-free spirit of the rest of the surface — validators are standalone,
|
|
25
25
|
* ADR 0058 Consequences). Pinned (not `latest`) for reproducibility. */
|
|
@@ -147,7 +147,7 @@ export function resolveOperationHandler(mod) {
|
|
|
147
147
|
return mod.handler;
|
|
148
148
|
return undefined;
|
|
149
149
|
}
|
|
150
|
-
function readApiBinding(manifest) {
|
|
150
|
+
export function readApiBinding(manifest) {
|
|
151
151
|
const raw = manifest && typeof manifest === "object" ? Reflect.get(manifest, "api") : undefined;
|
|
152
152
|
if (!raw || typeof raw !== "object")
|
|
153
153
|
return undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { AppApi, RuntimeContext } from "../context.ts";
|
|
2
|
+
import type { HttpRequest } from "../host.ts";
|
|
3
|
+
import { type Route } from "../router.ts";
|
|
4
|
+
export interface McpHandle {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
routes: Route[];
|
|
7
|
+
/** Whether the surface actually answers vs. 404s (config-enabled). The route is always mounted
|
|
8
|
+
* for a stable address, so callers deriving an "active surfaces" list must gate on this. */
|
|
9
|
+
readonly enabled: boolean;
|
|
10
|
+
describe(): Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/** Mint a fresh MCP session id, host-agnostically. Prefers the Web Crypto `randomUUID` (a global,
|
|
13
|
+
* not `node:crypto`) where present, with a time+random fallback so a host without `crypto` never
|
|
14
|
+
* throws — mirroring how core treats Web Crypto as optional (see `mintRootRequestKey`). */
|
|
15
|
+
export declare function newSessionId(): string;
|
|
16
|
+
/** Bound the live-session map to `max` by evicting least-recently-used entries (the `Map`'s leading
|
|
17
|
+
* keys, given `touchSession` re-inserts on use), closing each victim best-effort so its transport /
|
|
18
|
+
* server releases. This is the categorical fix for the unbounded-growth vector: no client can pin
|
|
19
|
+
* more than `max` server/transport pairs, whatever its `initialize` cadence. Exported for a direct
|
|
20
|
+
* unit test of the LRU eviction contract. */
|
|
21
|
+
export declare function evictExcessSessions<S>(sessions: Map<string, S>, max: number, close: (session: S) => Promise<void>): Promise<void>;
|
|
22
|
+
/** The resolved MCP access policy for this app. */
|
|
23
|
+
interface McpConfig {
|
|
24
|
+
/** Whether the MCP surface answers at all. Defaults ON. */
|
|
25
|
+
readonly enabled: boolean;
|
|
26
|
+
/** Whether non-loopback callers are allowed. Defaults OFF (loopback-only). */
|
|
27
|
+
readonly allowRemote: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the MCP access policy from the manifest (`mcp: { enabled?, allowRemote? }`, read
|
|
31
|
+
* reflectively until the app schema folds the field in — mirroring how `readApiBinding` reads
|
|
32
|
+
* `api`) and the environment (`URBAN_MCP_ENABLED`, `URBAN_MCP_ALLOW_REMOTE`). The env flags win so
|
|
33
|
+
* an operator can force the surface off (or open it to a non-loopback bind) without editing the
|
|
34
|
+
* manifest. Enabled defaults ON and access defaults to loopback-only ("defaults ON for loopback").
|
|
35
|
+
*/
|
|
36
|
+
export declare function readMcpConfig(manifest: unknown, env: (name: string) => string | undefined): McpConfig;
|
|
37
|
+
/** Whether the request targets a loopback host — the best-effort loopback signal available at the
|
|
38
|
+
* host seam (which carries no peer address). A missing Host header is treated as loopback (a direct
|
|
39
|
+
* local client that sent none). */
|
|
40
|
+
export declare function isLoopbackRequest(req: HttpRequest): boolean;
|
|
41
|
+
/** The `required` keys a tool's `inputSchema` declares but the call omitted. Presence is decided
|
|
42
|
+
* solely by {@link isBlankArg} — the single source of truth also used for path args — so a value is
|
|
43
|
+
* "provided" unless it is absent (`undefined`/`null`) or a blank/whitespace-only string. A required
|
|
44
|
+
* non-string input (e.g. a numeric/boolean/object arg) that is present is therefore accepted, not
|
|
45
|
+
* wrongly reported missing. Derived from the schema's own `required` array so declaring a field
|
|
46
|
+
* required is enough to have missing (or whitespace-only) calls rejected rather than silently
|
|
47
|
+
* substituted with an empty string. */
|
|
48
|
+
export declare function missingRequiredArgs(inputSchema: Record<string, unknown>, args: Record<string, unknown>): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Mount the runtime-served MCP surface at `/app/mcp` (ADR 0067). Returns a single `*`-method route
|
|
51
|
+
* (mounted unconditionally, like `/app/agent`); the handler bridges the host request/response seam
|
|
52
|
+
* to the SDK's Web-Standard Streamable-HTTP transport, maintaining one MCP session per
|
|
53
|
+
* `mcp-session-id`. The tool projection reuses the SAME OpenAPI enumeration and dispatch path as
|
|
54
|
+
* `mountApi`, so a tool call is equivalent to the corresponding HTTP call.
|
|
55
|
+
*/
|
|
56
|
+
export declare function mountMcp(ctx: RuntimeContext, app: AppApi, apiRoutes: Route[]): McpHandle;
|
|
57
|
+
export {};
|