Baton 1.0: one open spec, three SDKs
Baton helps the people who use your MCP server get a great experience from their agent. Today the Baton wire spec and our Python, TypeScript and Go SDKs reach 1.0.
Baton records what happens when an AI agent calls your MCP server: which tool it called, what it sent, what came back, and what the agent was trying to do. The SDKs send those events to a collector. That can be our hosted Console, or any HTTPS endpoint that speaks the spec.
Here is what is supported, how fast it is, and what comes next.
The spec is open
The spec lives in its own repo, under Apache 2.0: github.com/good-timing/baton-spec.
It holds:
SPEC.md, the protocol in prose. The event format is §11.4.events.schema.json, a JSON Schema for every event type.vectors/, real events captured from an SDK run, for your tests.scrub/cases.json, inputs and the output our default scrubber gives.collector/, a test collector that fails on purpose: slow, 500, 429, hang, disconnect and more.bench/bench.json, the benchmark every SDK runs.
All three SDKs test against these files in CI. If you want to write an SDK in another language, or your own collector, start here.
What's supported
| Python | TypeScript | Go | |
|---|---|---|---|
| Package | baton-sdk | @goodtiming/baton-sdk | github.com/good-timing/baton-go |
| Runtime | Python 3.11–3.14 | Node 20+ | Go 1.25+ |
| MCP libraries | mcp 1.21.1+ and 2.x; fastmcp 2.14 to 4.x | @modelcontextprotocol/sdk 1.30+; @modelcontextprotocol/server 2.x | modelcontextprotocol/go-sdk 1.6.1+ |
| Tested in CI | Python 3.11, 3.12, 3.13, 3.14; mcp 1.21.1, 1.25.0, 1.27.2, 2.0.0; fastmcp 2.14.7, 3.4.2, 4.0.2, 4.1.5 | Node 20 and 22; SDK 1.x and 2.x | go-sdk 1.6.1, 1.7.0, 1.8.0 and the latest release |
| MCP servers | yes | yes | yes |
| Library API (no MCP) | yes | yes | not yet |
Go 1.0 covers MCP servers only. The library API comes in a later 1.x release, and it only adds things.
The READMEs stay current; this table is as of today. Python · TypeScript · Go
Getting started
One call wraps your server. In Python:
from mcp.server.mcpserver import MCPServer
from baton import install_baton
mcp = MCPServer("echo-server")
install_baton(mcp, dsn="https://baton_pk_...@baton.goodtiming.ai/ten_.../echo-server")
TypeScript is withBaton(server, { dsn }). Go is baton.Install(server, impl, nil, baton.Config{...}). Each README has the full example.
Without a DSN, the SDK installs nothing and your server starts as usual.
How fast it is
Our first rule is that Baton never slows down or breaks your server. On a small tool call, Baton adds under 1 ms at the median in all three SDKs.
Every release runs the same benchmark, defined in the spec repo (bench/bench.json). It runs on GitHub's standard ubuntu-latest runner, which has 2 shared CPUs. It also runs the calls against a collector that never answers and against an address where nothing listens. Calls were no slower in either case. Baton drops and counts those events instead of making your server wait.
Your numbers in production will differ. The raw results for 1.0.0 are on each SDK's bench-results branch: Python · TypeScript · Go.
What leaves your server
Only what crosses the MCP call: the tool name, its arguments, the result, the error, timing, and what the client says about itself. Baton never reads your logs, your database or anything else in your process.
You can turn off results (result_capture_mode="off" in Python). Arguments are always sent, so a scrubber runs on them by default. It matches patterns like emails, keys, tokens and card numbers. It is pattern matching, so it does not catch everything; a plain name or address passes through. You can replace it with your own.
What you can set. All three SDKs have these settings in 1.0. The names below are Python's. TypeScript uses the same names in camelCase, and Go uses Config fields.
| Setting | What it controls | Default |
|---|---|---|
result_capture_mode | Whether tool results are sent | "full" |
intent_param_mode | Whether the agent must say what it is trying to do (user_goal) on each call | "required" |
report_mode | Whether the agent gets a tool to report problems with your server | "off" |
scrubber | What is removed from arguments and results before they leave | built-in patterns |
settings="console" (experimental) | Reads the settings above from the Console instead of your code, so they can differ by label | off |
Some of these change what the agent spends on each call, in tokens and time. Next week's post measures that.
What's next
All of this is additive and keeps the 1.0 promise:
- Detailed cost benchmarks. A post next week covers what Baton costs on the agent side and on the vendor side.
- Configuring Baton in real time. Change the settings above from the Console, with no deploy, to control agent-side and vendor-side cost. It is experimental today (
settings="console").
Experimental. A few fields and options are marked experimental and may change in a minor release. The spec lists them in SPEC §13, and each SDK's README lists its own.
Tell us what you want next. If you want Baton in another language, such as C#, the spec repo has what you need to build an SDK, and we would like to hear about it. Open an issue there and tell us what you are building.
Python · TypeScript · Go · Spec · Docs