UNPKG

kestrel.markets

Version:

A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.

75 lines 5.86 kB
/** * # mcp/server — the MCP face as a LOSSLESS projection of the djm.5 SDK (kestrel-djm.7) * * `createKestrelMcpServer({ transport })` builds `createSdk(transport)` INTERNALLY (pass `localTransport()` * for in-process execution or `remoteTransport({ baseUrl, fetch })` for the managed backend) and exposes it * as an MCP server: a JSON-RPC 2.0 dispatcher over RESOURCES (the static djm.8 catalog + the immutable, * content-addressed artifacts) and TOOLS (validate, the incremental Session lifecycle, Grade, and Operation * continuation). It holds NO runtime semantics of its own — every method marshals an MCP request into an SDK * call and marshals the SDK's djm.2 protocol object back out VERBATIM. It is one of ADR-0004's four equal * faces (http / sdk / cli / mcp); it adds no Kestrel statement kind. * * PROJECTION-ONLY: for every RUNTIME semantic the only Kestrel import is the ONE SDK barrel (`../sdk/index.ts` * — `createSdk` + `SdkLocalError` + the transport-neutral types). The face reaches NO runtime-semantics module * (session/engine/fill/blotter/bus/lang, or the djm.8 loader) directly — those live behind the SDK's * transport. Even catalog discovery goes through `sdk.catalog()`, not the loader (grep-provable, pinned by * tests/mcp.projection.test.ts). * * ONE DOCUMENTED EXCEPTION — the LOCAL operator-secret store (kestrel-jh9w.3): `kestrel.secrets.set/unset/list` * manage `~/.kestrel/.env` directly through `../cli/credentials.ts`, NOT through the SDK transport, because the * secret store is a local-process operator concern (the same one the `kestrel secrets` CLI verb owns), not a * runtime statement kind and not something that crosses a transport. It is NOT a runtime-semantics module (it is * `/cli/`, outside the projection test's forbidden set) and adds no fifth Kestrel statement kind. The invariant * these tools uphold is the store's own: NO face ever returns a secret VALUE — `list` is names-only, there is no * `get`, and the `set` result names only the key + path. The jh9w.5.1 refusals (the *LIVE* paper-only refusal + * the [A-Z_][A-Z0-9_]* key-name format) are single-sourced in `checkOperatorSecretKey`, so they fire IDENTICALLY * on this MCP path and the CLI path — a guard wired to only one face is the wiring-evasion shape this forbids. * * LOSSLESS/CANONICAL: a `tools/call` returns the SDK's protocol object VERBATIM in `structuredContent` * (exact Kestrel bytes in the pentad `body.bytes`, the TYPED SessionFrame struct in a `Delivery.frame` — * `frameRoot` binds its canonical JSON; a Rendering of it is the caller's, via `src/frame` — the protocol * Blotter, the portable GradeResult, the conformance root, the artifact roots, the OperationResumption). A * resource READ returns the djm.2 object VERBATIM as JSON. Nothing is summarized / repaired / rewritten. * * CONTINUATION AS DATA: a live `KestrelSession` cannot cross the wire, so `kestrel.session.open` projects the * SDK's `Gated<KestrelSession>` to `{ gated:false, sessionId }` — the deterministic SessionId is the durable * handle every later verb carries; the server holds the live session keyed by it — or, when gated, to * `{ gated:true, payment }`: the 402/Offer as BYTE-IDENTICAL structured continuation DATA (never a rewrite, * never a JSON-RPC error). * * FAILURE TAXONOMY (fail-closed, distinguishable): a TRANSPORT failure (malformed frame / bad request / * invalid params) is a JSON-RPC `error`; a typed KESTREL REFUSAL (an `SdkLocalError` / `KestrelClientError`, * detected by its string `code`) is a tool RESULT with `isError:true` + `structuredContent.refusal.code`; a * STAND_DOWN is a normal `SessionResponse` tool RESULT (`ok:true`, disposition `stood-down`). Three distinct * shapes the caller can switch on — never conflated. An unexpected fault is a JSON-RPC internal error, kept * distinct from a domain refusal. */ import { type Transport } from "../sdk/index.ts"; import { type JsonRpcRequest, type JsonRpcResponse } from "./protocol.ts"; /** How to build the MCP face: the ONE transport CHOICE it projects (`localTransport()` for in-process * execution, `remoteTransport({ baseUrl, fetch })` for the managed backend). No other knob. */ export interface KestrelMcpServerOptions { readonly transport: Transport; } /** * The MCP server as an in-process JSON-RPC 2.0 dispatcher. `handle(request)` answers a PARSED request; * `handleFrame(rawLine)` answers a RAW stdio frame (a malformed frame fails closed as a JSON-RPC parse * error). The stdio pipe wiring ({@link ../mcp/stdio.ts serveStdio}) is a thin adapter over this dispatcher. */ export interface KestrelMcpServer { /** * Answer a parsed JSON-RPC 2.0 message. Never throws. A REQUEST (an `id` member present) always yields a * {@link JsonRpcResponse}; a NOTIFICATION (no `id` member — JSON-RPC 2.0 §4.1) yields `undefined`, because * both JSON-RPC and MCP FORBID responding to one. Every real MCP client sends `notifications/initialized` * as the mandatory third handshake step — a server that answers it puts an uncorrelatable `id:null` frame * on the wire and trips 'unknown message ID' faults client-side. */ handle(request: JsonRpcRequest): Promise<JsonRpcResponse | undefined>; /** Answer a raw stdio frame: parse it, then {@link handle} it. A malformed frame → JSON-RPC parse error; * a well-formed NOTIFICATION frame → `undefined` (nothing may be written to the wire for it). */ handleFrame(rawLine: string): Promise<JsonRpcResponse | undefined>; } /** Build the MCP face over a transport CHOICE. Constructs `createSdk(transport)` internally; holds no runtime * semantics of its own. */ export declare function createKestrelMcpServer(options: KestrelMcpServerOptions): KestrelMcpServer; //# sourceMappingURL=server.d.ts.map