TypeScript Types for MCP
chartgen’s MCP surface has two type tracks on the client:
- Tool inputs — generated from each tool’s
inputSchemaintools/list. The Rust side owns the schemas; TypeScript interfaces are emitted from them. - Tool response payloads — hand-written Zod schemas, because MCP
does not yet require a formal
outputSchemaand chartgen’s response shapes live in Rust serializer functions (panel_result_to_series_json,panel_result_to_json,tool_get_indicators,tool_list_indicators).
Both tracks land in web/src/types/.
See ADR-0004 — codegen from MCP schemas + Zod at the boundary for the decision record.
Generated inputs
Section titled “Generated inputs”Source of truth: chartgen::mcp_schema::tools_list_result() in
src/mcp_schema.rs. Regenerate after any edit to that module (or to
handle_tools_list in src/mcp.rs):
./scripts/gen-mcp-types.shThe script runs cargo run --example gen_mcp_types, then feeds the
tools/list JSON through json-schema-to-typescript, writing
web/src/types/generated-input.ts. Each tool gets a
${PascalToolName}Input interface, e.g. GenerateChartInput,
ListIndicatorsInput, GetIndicatorsInput.
CI runs the same script and diffs against the committed file — the
mcp-types-drift job fails when they disagree.
Hand-written response schemas
Section titled “Hand-written response schemas”web/src/types/responses.ts exports Zod schemas for every response
shape the frontend consumes:
SeriesPayloadSchema—generate_chartwithformat=series.SummaryPayloadSchema—generate_chartwithformat=summary(also embedded informat=both).ListIndicatorsPayloadSchema—list_indicators.GetIndicatorsPayloadSchema—get_indicators.
Each schema is paired with a z.infer<> TS type. Where the Rust side
emits null for NaN via finite_or_null, the Zod schema uses
z.number().nullable().
These schemas are part of the wire contract: change them in the same PR
as any change to a response-shaping Rust function. The project-wide
CLAUDE.md enforces this.
Validation at the boundary
Section titled “Validation at the boundary”Parse incoming MCP responses with Zod before handing them to UI code:
import { SeriesPayloadSchema } from "./types";
const payload = SeriesPayloadSchema.parse(rawJson);// payload is now a typed SeriesPayload — safe to destructure.Zod raises a precise error identifying the exact field that disagreed —
far easier to diagnose than a generic undefined deep inside a
component tree.