README.md

September 20, 2026 · View on GitHub

typesafe-mcp

evaluate is a stdio MCP server for TypeSafe Jev. Coding agents send observed state plus typed questions and get back probabilities, choices, and scores they can branch on in code.

Jev is a decision primitive. The host still reasons, edits, and executes.

Latest release GitHub stars License: MIT Go version

Install | Setup | The tool | Runtime | Quality gates | Contributing

┌────────────────┐   evaluate tool    ┌──────────┐   POST /v1/systemone   ┌──────────────┐
│ Claude Code    │ ─────────────────▶ │ evaluate │ ─────────────────────▶ │ TypeSafe API │
│ Claude Desktop │   MCP stdio        │  binary  │     or /decisions      │  or          │
│ Codex          │                    │          │     retries 429 / 529  │  OpenRouter  │
│ pi · OMP       │ ◀───────────────── │          │ ◀───────────────────── │              │
│ PyThinker · …  │   typed JSON       │          │                        │              │
└────────────────┘                    └──────────┘                        └──────────────┘

The tool contract is canonical in this binary: description, field docs, and agent guidance live in cmd/evaluate/tools.go. Claude Code, Claude Desktop, and Codex register that MCP surface. Stock pi has no MCP client, so evaluate setup pi writes an extension that registers the same tool and talks MCP to this binary. Pi that already loads MCP servers (pi-mcp-adapter and similar) registers evaluate mcp in that adapter's config, same as any other stdio server. Adapters own transport and registration only; they do not author Jev semantics.

Install

macOS and Linux, amd64 and arm64:

curl -fsSL https://raw.githubusercontent.com/PyModel/typesafe-mcp/main/install.sh | sh

Installs to ~/.local/bin. Add it to PATH if needed: export PATH="$HOME/.local/bin:$PATH". With Go 1.27+:

go install github.com/PyModel/typesafe-mcp/cmd/evaluate@latest

evaluate update upgrades in place from a checksum-verified GitHub release.

Setup

Get a key at https://console.typesafe.ai/

TYPESAFE_API_KEY=your-key evaluate setup

That finds the agents installed on this machine, lists them, and asks which to register with — Enter takes all of them. --yes skips the question; --host omp --host opencode names hosts outright. Installing the binary does not register it: run this once after install.sh.

HostHow it is registered
Claude Code, Codextheir own mcp add CLI (user scope)
Claude Desktopclaude_desktop_config.json
pi (stock)the evaluate.ts extension, below
OpenCodemcp in ~/.config/opencode/opencode.json[c]
OMP (Oh My Pi)~/.omp/agent/mcp.json
PyThinker~/.pythinker-code/mcp.json (or $PYTHINKER_CODE_HOME)
Cursor~/.cursor/mcp.json
Gemini CLI, Qwen CodemcpServers in ~/.gemini/settings.json, ~/.qwen/settings.json
VS Code (Copilot)servers in the user mcp.json
Zedcontext_servers in ~/.config/zed/settings.json
Windsurf, Roo Code, Crush, Amp, Factory Droideach one's own MCP config file

Config files are edited in place: the evaluate entry is inserted or replaced, and everything else — comments in the JSONC ones included — stays as written. A file that does not parse is refused, not rewritten. Aider has no MCP client; Warp keeps MCP servers inside the app (Settings → AI → MCP).

evaluate setup mcp is the older, narrower form: Claude Code (user scope) and Codex when their CLIs are on PATH, and Claude Desktop when the app is installed. Every TYPESAFE_* variable in the shell is copied into the client config, plus OPENROUTER_API_KEY. Re-run to refresh.

Already on OpenRouter? Use that key and evaluate calls OpenRouter's Decisions endpoint, billed there:

OPENROUTER_API_KEY=your-key evaluate setup mcp

TYPESAFE_API_KEY wins if both are set, so a leftover OpenRouter key cannot reroute an existing TypeSafe setup. OpenRouter's Decisions path is still /api/alpha/ and may move.

For stock pi:

evaluate setup pi

Writes ~/.pi/agent/extensions/evaluate.ts (or $PI_CODING_AGENT_DIR/extensions/). Run /reload in pi. The extension reads the API key from the shell pi runs in; nothing is baked into the file.

If pi already loads MCP servers (pi-mcp-adapter and similar), add this binary to the adapter config (~/.pi/agent/mcp.json, or .mcp.json in the project):

{
  "mcpServers": {
    "evaluate": {
      "command": "/absolute/path/to/evaluate",
      "args": ["mcp"]
    }
  }
}

Reload pi. The adapter inherits TYPESAFE_API_KEY or OPENROUTER_API_KEY from the shell pi runs in, unless you set env on the server. Use one registration: the extension and an mcp.json entry both expose a tool named evaluate.

Any other MCP-capable agent or CLI: register the same stdio server by hand — command /absolute/path/to/evaluate, args ["mcp"], with TYPESAFE_API_KEY or OPENROUTER_API_KEY in its env — using the mcpServers entry above in whatever file that host reads. The tool, its schema, and its guidance are identical on every host. Details: cmd/evaluate/AGENTS.md.

HostAttach with
Every host in the table aboveevaluate setup
Claude Code, Codex, Claude Desktop onlyevaluate setup mcp
pi (stock) onlyevaluate setup pi
pi with an MCP adapter, other MCP clientsmanual stdio registration of evaluate mcp

With the TypeSafe skill

TypeSafe ships an official agent skill that teaches a coding agent the question types, patterns, and cookbooks. The two are complementary, not alternatives:

Gives the agentUse it when
TypeSafe skillknowledge: how to design questions, thresholds, and System One patternsthe agent is writing code that calls TypeSafe
evaluate (this repo)a tool: a live route to Jev from inside the agent loopthe agent itself needs a judgment now — classify, route, score, validate, check a claim against evidence
# Claude Code
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai

# other agents
npx skills add typesafe-ai/skills --skill typesafe-ai

evaluate does not depend on the skill: its tool description and server instructions carry the guidance needed to call it correctly.

The tool

typesafe-mcp architecture — one evaluate tool between MCP hosts and Jev

Interactive map with themes, path tracing, and a relationship lens: docs/architecture.html.

One tool, evaluate. Read-only. Request:

FieldRequiredMeaning
stateyesEvidence to judge: plain text, or a JSON object/array with named fields. What you observed, not your verdict about it.
questionsyesMap of question id → {type, instructions, criteria?}. Ids are for your code; Jev never sees them, so instructions must carry the full meaning.
modelnoDefaults to jev-latest on TypeSafe, or ~typesafe/jev-latest on OpenRouter.
TypeCriteriaAnswer
nouloptional {true, false} descriptionsProbability the condition holds, in [0, 1]. No separate confidence. Near 0.5 is uncertain, not medium intensity.
choicerequired map of option → description (or null). Include a no-match option when nothing may fit. Max 255 options.Chosen option, per-option probabilities, confidence (how peaked the distribution is).
scorerequired ordered array of level descriptions, low to high (2–10 levels)Probability-weighted position, 0-indexed: N levels score 0 to N-1, so 3.87 over 5 levels sits between levels 3 and 4, not 3.87/5. Response includes legend and per-level probabilities.

Malformed noul / choice / score criteria are rejected locally, with questions["id"].criteria in the error, before anything is sent. The documented answer-space limits are enforced locally too: more than 255 choice options or 10 score levels fail before anything reaches the API. Unknown question types pass through; the API documents more types than this tool.

Successful responses are validated against your request before they reach you: every question id must be answered exactly once, and each answer is checked against the criteria you sent — distribution keys, score range, probability sums, confidence. A malformed or partial upstream response becomes a tool error naming the offending answer, never silent garbage.

Independent questions over the same state belong in one call. They run in parallel and cannot see each other's answers.

Do not put your conclusion in state. Jev treats that as evidence, and the confidence that comes back is agreement with you, not an independent check.

Example:

Use evaluate to decide whether this ticket is urgent and which team should own it: Help! My payouts have been failing for 3 days.

{
  "state": "Help! My payouts have been failing for 3 days.",
  "questions": {
    "is_urgent": {"type": "noul", "instructions": "Does this convey urgency?"},
    "department": {"type": "choice", "instructions": "Which team should handle this?",
      "criteria": {"billing": "Payments, refunds", "technical": "Bugs, outages", "sales": "Pricing"}}
  }
}

The tool returns the raw TypeSafe JSON, answers keyed by the same ids.

Runtime

  • 429 and 529 are retried with exponential backoff (up to 3 retries). Other API errors come back as tool errors.
  • HTTP timeout 60s. Response body capped at 16 MiB.
  • Safe under parallel calls: each call owns its retry policy, and concurrent requests cannot amplify retries or serialize into each other's backoff windows.
  • Single static binary: no Node, no Python runtime for the server.

How to write questions, batch, and read confidence: docs/jev/. Live TypeSafe docs: https://docs.typesafe.ai · https://docs.typesafe.ai/api

Quality gates

The tool's contract is pinned by tests at every layer:

  • Request matrix — malformed inputs, criteria shapes, answer-space limits, and model defaults, exercised through the real MCP boundary, with golden error texts and proof that errors never echo your state back.
  • Response matrix — every successful Jev response is validated against the questions that produced it (answer ids, criteria match, distributions) before it can cross the tool boundary.
  • Real stdio proof — a subprocess suite speaks newline-delimited JSON-RPC to the shipped binary: clean flow, hostile-frame behavior, and the stdout-is-protocol-only invariant.
  • Concurrency — parallel calls keep aggregate retries bounded and backoff windows overlapped.
  • Live gatetask smoke proves the implementation still agrees with the production API (opt-in, needs a key); any change that tightens what the tool accepts or rejects must pass it before landing.

CI runs offline and deterministic; live and model-driven checks are explicit opt-in tasks. Eval plan: docs/evals/PLAN.md · Product roadmap: docs/ROADMAP.md.

Evals

The tool-selection gate (docs/evals/PLAN.md §4–§6) measures whether a coding agent reaches for evaluate exactly when semantic judgment is the right primitive — and not otherwise. A real agent loop (temperature 0, five turns max) works against the shipped binary plus harness-side decoy tools (read_file, run_command, answer_directly); every tool call is graded against a rubric fixed at authoring time over a 20/20/10 corpus, three repetitions per scenario. A naive always-evaluate policy must fail the corpus (vacuity), or the numbers mean nothing.

Tool-selection gate metrics

Three numbers are pre-registered as pass thresholds: tool_precision (appropriate evaluate calls ÷ all evaluate calls), request_validity (schema-accepted calls ÷ attempted calls), and ambiguous_acceptance (ambiguous scenarios whose majority passed). The remaining metrics are reported, not gated. The first gated run — deepseek-v4-flash served locally — is red: precision 0.778 < 0.90 and request_validity 0.815 < 1.00. A second independent run at a later head reproduced every aggregate and the same seven failing scenarios exactly (trend chart above). Per the pre-registered failure attribution, every failure is single-model: six positives answered in prose (missed_call_rate 0.300) and one case where the model looped evaluate five times on schema-rejected arguments. That is a model limitation, not product ergonomics, so no tool-description rewrites are triggered.

Scenario outcomes

Gated metrics across runs

Run records live under evals/results/ — append-only, one JSON per run, committed so the history is auditable. Each record keeps every rep's observed tool calls, so a grading fix is re-scored offline instead of costing another model run (ADR-003). Regenerate the charts with task charts after a run; task eval runs the gate itself (opt-in: a local OpenAI-compatible model plus a TypeSafe or OpenRouter key).

Agent working practices

House rules: AGENTS.md. Pack (verification, orchestration, skills, prompts): agents/. Roles: roster/. Workflows: playbooks/. Failure principles: ANTIPATTERNS.md.

Contributing

Maintained by elkaix under PyModel. Issues and PRs: PyModel/typesafe-mcp.

Task: task check (scripts/check.sh) runs gofmt, go vet, and tests with -race in an environment with no keys, as CI does; task hooks installs it as git hooks (lint on commit, the full gate on push). task inspect opens the MCP Inspector against a local build. task smoke checks the live API. Contributor notes: AGENTS.md. Domain terms: docs/CONTEXT.md.

MIT. See LICENSE.