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
TypeScript
/**
* # 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