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.
83 lines (82 loc) • 6.07 kB
JavaScript
/**
* # mcp/protocol — the JSON-RPC 2.0 + MCP wire vocabulary the Kestrel MCP face speaks (kestrel-djm.7)
*
* The small, dependency-free wire surface the {@link ./server.ts KestrelMcpServer} dispatcher answers over:
* the JSON-RPC 2.0 envelope (`resources/list` · `resources/read` · `tools/list` · `tools/call` · the
* `initialize` handshake) plus the MCP resource / tool result shapes. This module holds NO Kestrel runtime
* semantics and imports NOTHING — it is pure types + own constants (the same zero-dependency discipline the
* djm.2 protocol modules keep). Every djm.2/djm.4 protocol OBJECT the face returns rides VERBATIM inside a
* {@link ToolResult.structuredContent} or a {@link ResourceContents.text}; this module only frames it.
*
* ── DESIGN FORK LOGGED (hard rules forbid .beads writes — recorded here like djm.5's sdk/types.ts note) ──
* (1) TRANSPORT: hand-rolled JSON-RPC 2.0, NOT `@modelcontextprotocol/sdk` (absent from node_modules; a heavy
* dep on the package graph the 'mcp' keyword advertises but never installed). The MCP wire is a small
* JSON-RPC 2.0 surface a zero-dependency dispatcher serves — no new dep, no drag on the light/heavy seam.
* (2) RESOURCE URIs: `kestrel://catalog/<index>` (the static djm.8 CatalogEntry mirror the SDK's `catalog()`
* returns — the tail is the enumeration index; the catalog is compared order-independently) and
* `kestrel://artifact/<encodeURIComponent(ref)>` (the immutable, content-addressed finalized artifacts,
* addressed by the SDK's own artifact `ref`). A resource READ returns the djm.2 object VERBATIM as JSON.
* (3) TOOL NAMING: dot-namespaced under `kestrel.` — one tool per SDK verb ({@link TOOL}).
* (4) FAILURE TAXONOMY: a TRANSPORT failure (malformed frame / bad request) → a JSON-RPC ERROR
* ({@link PARSE_ERROR} / {@link INVALID_REQUEST}); a KESTREL REFUSAL (a typed SDK error) → a tool RESULT
* with `isError:true` + `structuredContent.refusal.code`; a STAND_DOWN → a normal tool RESULT. The three
* are distinct, caller-distinguishable shapes (see the server's dispatch).
*/
/* ─────────────────────── JSON-RPC 2.0 reserved error codes ─────────────────── */
/** Invalid JSON was received — the TRANSPORT signal for a malformed MCP frame (the broken-pipe class). */
export const PARSE_ERROR = -32700;
/** The JSON was not a valid JSON-RPC Request object. */
export const INVALID_REQUEST = -32600;
/** The method does not exist / is not available. */
export const METHOD_NOT_FOUND = -32601;
/** Invalid method parameter(s). */
export const INVALID_PARAMS = -32602;
/** Internal JSON-RPC error (an unexpected server fault — kept DISTINCT from a typed Kestrel refusal). */
export const INTERNAL_ERROR = -32603;
/* ───────────────────────────── MCP resource shapes ─────────────────────────── */
/** The resource URI schemes (see the fork note): the static djm.8 catalog listings, the immutable artifacts,
* and the SINGLE ex-ante catalog pricing block (kestrel-adge) when the catalog serves one. */
export const CATALOG_URI_PREFIX = "kestrel://catalog/";
export const ARTIFACT_URI_PREFIX = "kestrel://artifact/";
/** The one ex-ante pricing resource URI (kestrel-adge) — advertised only when `sdk.catalog()` carries a
* `pricing` block; reading it returns the {@link import("../protocol/catalog.ts").CatalogPricing} verbatim. */
export const PRICING_URI = "kestrel://catalog-pricing";
/** Build the immutable-artifact URI for a content-addressed artifact `ref` (the SDK's own artifact handle). */
export const artifactUri = (ref) => `${ARTIFACT_URI_PREFIX}${encodeURIComponent(ref)}`;
/* ──────────────────────────────── MCP tool shapes ──────────────────────────── */
/** The tool names — one tool per SDK verb, dot-namespaced under `kestrel.`. Nothing the SDK can do is
* missing from the MCP face (the complete trial + incremental Session lifecycle + Grade + continuation). */
export const TOOL = {
validate: "kestrel.validate",
open: "kestrel.session.open",
start: "kestrel.session.start",
advance: "kestrel.session.advance",
revise: "kestrel.session.revise",
submit: "kestrel.session.submit",
resume: "kestrel.session.resume",
finalize: "kestrel.session.finalize",
grade: "kestrel.grade",
operationResume: "kestrel.operation.resume",
// ── The LOCAL operator-secret store (kestrel-jh9w.3). Machine-first parity with the `kestrel secrets`
// CLI verb: an agent self-installs its BYOK keys ONCE into `~/.kestrel/.env` and they persist across
// context windows / worktrees (credential residency). set/unset/list are the ONLY three verbs — there is
// deliberately no `get`: NO face ever returns a secret VALUE, only internal resolution reads them.
secretsSet: "kestrel.secrets.set",
secretsUnset: "kestrel.secrets.unset",
secretsList: "kestrel.secrets.list",
};
/* ───────────────────────── the MCP protocol-error carrier ──────────────────── */
/**
* A TRANSPORT-layer protocol error the dispatcher raises for a malformed frame / bad request / invalid
* params — carried out of a handler and mapped to a JSON-RPC `error` with {@link jsonRpcCode}. Deliberately
* carries NO `code: string` field, so it is NEVER mistaken for a typed Kestrel refusal (which the tool path
* detects by a string `code`): the two failure classes stay distinct and caller-distinguishable (fail-closed).
*/
export class McpProtocolError extends Error {
name = "McpProtocolError";
jsonRpcCode;
constructor(jsonRpcCode, message) {
super(message);
this.jsonRpcCode = jsonRpcCode;
}
}