@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.
@@ -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 {};