Baton SDK
Pre-1.0. The public API is not yet stable. Breaking changes are flagged in SPEC §13 and CHANGELOG.md.
MCP standardizes how agents discover and call your tools. It does not capture why a call happened or whether it helped the user. Baton instruments those interactions on the vendor side, either by wrapping your MCP server or through direct library calls in your own code. It captures intent, the tool calls, expected outcomes and observed outcomes, plus friction signals, and hands each one to a sink. Correlation, policy and routing all happen downstream of the sink, not in this package.
The docs are at goodtiming.ai/docs.html: both integration paths, the proxy, the gateway and the Console, with the full configuration reference. This page is the short version.
Install
pip install baton-sdk
One line for both integration paths. It installs pydantic, anyio and httpx, and wraps the mcp or fastmcp your server already has.
Quickstart
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")
@mcp.tool()
async def your_tool(...): ...
On mcp 1.x the class is mcp.server.fastmcp.FastMCP; on standalone fastmcp it is fastmcp.FastMCP. install_baton detects which you passed and raises TypeError, before mutating anything, if it is neither.
Copy the DSN from /account. It packs four values: the collector to send to, your workspace, this server, and the key that binds them. The SDK unpacks them and builds the sink itself. Tools registered before and after the call are both captured.
Installing changes what your server advertises. Each tool handler is wrapped, a <vendor>_annotate tool is registered, the server's instructions are rewritten, and three intent parameters are added to every tool's schema and stripped again before your handler sees them. What gets injected.
If you distribute your server, put the DSN in your source. A stdio server runs on your user's machine, spawned by their MCP client, which passes it a fixed allowlist of environment variables (six names on macOS and Linux) plus whatever that user wrote in their own client config. Nothing from your .env is in either list. For a hosted server, set BATON_DSN and call install_baton(mcp) with no arguments.
Sending to your own collector instead, or trying the package before you have a key, means naming the parts rather than passing a DSN: Without a DSN.
Library API
For vendors whose customers reach the API through agent-generated code (the Skills pattern) rather than MCP tool calls. Same event envelope, same sinks, different emission boundary:
from baton import Client
client = Client(dsn="https://baton_pk_...@baton.goodtiming.ai/ten_.../your-vendor")
with client.trace(
tool_name="chat.completions.create",
intent="summarize the user's question",
expected_outcome="2-3 sentence answer",
) as trace:
response = vendor_client.chat.completions.create(...)
trace.observed(response)
AsyncClient is the async twin. Worked end to end in examples/skill_demo/.
PII scrubbing
On by default. baton.scrub.Scrubber walks every event payload and redacts email / Bearer … / sk-* / AKIA* / JWT / Luhn-validated card / NA-format phone, plus a list of sensitive field names. Opt out by passing a config instead of a bare DSN:
from baton import install_baton, VendorConfig
from baton.scrub import identity_scrub
install_baton(mcp, VendorConfig(
dsn="https://baton_pk_...@baton.goodtiming.ai/ten_.../echo-server",
scrubber=identity_scrub,
))
It is pattern matching, not a guarantee. {"name": "Jane Doe", "address": "12 Elm St"} passes through untouched, and a card number is redacted when its digits are contiguous but not when they are spaced or hyphenated. Decide what your server puts in tool params and results on that basis. Do not tell your users "PII is scrubbed" on the strength of it. What it does and does not catch.
Turning capture off
BATON_DISABLED=1 in the environment of the process running the server, and the SDK installs nothing at all: no wrapped tools, no annotation tool, no instructions rewrite, no collector connection, no background thread.
The switch belongs to whoever RUNS the server: your user for a server you distribute, you for one you host. If you pass this on to your users, say where the variable goes. For a server started by an MCP client it belongs in that client's config file, not in their shell, which never reaches the process. The long version.
More
| goodtiming.ai/docs.html | The docs: both integration paths, sinks, VendorConfig, the proxy, the gateway, the Console |
docs/SPEC.md |
The wire protocol. The contract a collector consumes |
docs/CHARTER.md |
Why the SDK is thin, and what deliberately lives downstream of the sink |
examples/ |
The four-rung sink ladder. Start with examples/01_stdout/demo.py, which needs no key |
CONTRIBUTING.md |
Dev setup, the make targets, PR conventions |
SECURITY.md |
Disclosure policy |
Apache-2.0. There is a TypeScript SDK too, which takes the same DSN and emits the same events.
For design-partner conversations: reach out via Good Timing.
Metadata
Release files for baton-sdk 0.8.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| baton_sdk-0.8.9.tar.gz | 240.9 kB | With provenance | Details for baton_sdk-0.8.9.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| baton_sdk-0.8.9-py3-none-any.whl | Python 3 | none | any | Details for baton_sdk-0.8.9-py3-none-any.whl |
Total release size: 426.4 kB
Release files / baton_sdk-0.8.9.tar.gz
| Download URL | baton_sdk-0.8.9.tar.gz |
|---|---|
| Size | 240.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e0826e47b762088761038c9860965e98613497df68886d7f475f1a536ca2ce4e
|
|
BLAKE2b-256 checksum How to use checksums |
6b4eabebd3e6a20b812ce71b351661488d5f4c5d496eb8333e5686aec197f6c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logRelease files / baton_sdk-0.8.9-py3-none-any.whl
| Download URL | baton_sdk-0.8.9-py3-none-any.whl |
|---|---|
| Size | 185.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ff491f64468f779084e19473c34484a6f2b470599921a3be26a56e69840f85f0
|
|
BLAKE2b-256 checksum How to use checksums |
c458ce9c7ae0aa748b30ce1de3def32e81a85681bfbbe631a0710d74a089ed97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logSecurity for baton-sdk 0.8.9
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for baton_sdk-0.8.9.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.