Baton captures user intent straight from their agents, finds the friction, and generates fixes to improve your product.
Get startedYour customer, to their agent
They wanted a leadership dashboard tracking the Northwind launch: every project's health, dates, progress, and the flagged work needing intervention.
The Northwind project set was pulled with status, progress and dates, and each project summarised in turn, including the ones marked In Trouble and At Risk.18 calls
Every attempt to add a project-level table was rejected for an invalid field path. The agent tried the owner field seven ways, ran a field search, and tried again with the path the search returned. Same rejection.19 calls
It never recovered. The last five calls were the same rejected query with the field name reworded.5 calls
The session stopped still retrying the same rejected field path, leaving a dashboard with counts and a chart but no project-level table.
| User intent | Sessions | Sessions with friction | Most common error |
|---|---|---|---|
| Build a leadership dashboard | 34 | 26 | invalid field path |
| Find a project by name | 21 | 9 | invalid field path |
| Send assets to the asset library | 17 | 12 | folder search failed: 400 |
| Attach an approval workflow | 11 | 4 | missing project_id |
| Error pattern | Tool | Sessions | Calls | Fix location |
|---|---|---|---|---|
| Invalid field path(s) — call get_field_paths() to retrieve valid paths. | projects_find_data | 100 | 164 | tool description |
| Upstream service returned HTTP {n} Bad Request | projects_get_field_paths | 33 | 40 | tool implementation |
| Error pattern | Tool | Sessions | Calls | Fix location |
|---|---|---|---|---|
| ▼Invalid field path(s) — call get_field_paths() to retrieve valid paths. | projects_find_data | 100 | 164 | tool description |
|
The rule is already in the server's own docs CRITICAL — every `field_path` must be a verbatim path from `get_field_paths()`.
from the projects-mcp condition guide Prompt
Copy prompt
You are working in the repository that contains the projects MCP server.
Goal: stop a recurring agent error by putting a documented rule into the
tool description, where every agent receives it automatically.
The error, on the tool projects_find_data:
Invalid field path(s) — call get_field_paths() to retrieve valid paths.
It occurred on 164 calls across 100 sessions in the last observed window.
The rule that prevents it already exists in the server's own docs:
From: the projects-mcp condition guide
CRITICAL — every `field_path` must be a verbatim path from `get_field_paths()`.
Steps:
1. Find the tool definition for projects_find_data (name, description, input schema).
2. Append these lines to its description. Change nothing else.
Copy as a prompt for your coding agent, verification steps included. | ||||
| ▶Upstream service returned HTTP {n} Bad Request | projects_get_field_paths | 33 | 40 | tool implementation |
| ▶{n} validation errors for call[find_data] | projects_find_data | 21 | 37 | tool description |
| ▶Tool (search_records) input validation failed: array found, [object, null] expected | projects_search_records | 21 | 34 | tool description |
| ▶calling {v}: MCP error {n}: Unknown docs URI {v} | projects_read_docs | 16 | 18 | tool description |
| Show all 49 rows | ||||
More than just tool calls and HTTP response codes. When your MCP server can't satisfy a user request, your logs often show a 200 anyway. Baton writes the plain-language account: what the customer came for, what happened, and where it ended.
They wanted a leadership dashboard tracking the Northwind launch: every project's health, dates, progress, and the flagged work needing intervention.
The Northwind project set was pulled with status, progress and dates, and each project summarised in turn, including the ones marked In Trouble and At Risk.18 calls▸
Every attempt to add a project-level table was rejected for an invalid field path. The agent tried the owner field seven ways, ran a field search, and tried again with the path the search returned. Same rejection.19 calls▸
It never recovered. The last five calls were the same rejected query with the field name reworded.5 calls▸
The session stopped still retrying the same rejected field path, leaving a dashboard with counts and a chart but no project-level table.
Baton adds three parameters to every tool you already have: the goal, the expected result, and the task the agent is working through. The agent fills them in with each call, and Baton strips them out before your code runs. One added tool lets the agent send feedback directly: annotate, for friction as it happens. Your customers never see Baton.
Three parameters added to every tool you already have. The agent fills them in with each call; Baton strips them out before your code runs.
projects_find_data {
entity: "task",
fields: ["owner:name", "status"],
user_goal: "Add a project-level table to the dashboard",
expected_result: "Rows grouped by owner",
overall_task: "Build a leadership dashboard"
}The agent reports friction as it happens: what it tried, what it expected, what happened instead.
For a single MCP server you control
import { withBaton, HttpSink } from "@goodtiming/baton-sdk";
withBaton(server, {
vendorId: "acme",
vendorDisplayName: "Acme",
consentToken: process.env.BATON_CONSENT_TOKEN,
sink: new HttpSink(BATON_CONSOLE_URL, {
apiKey: process.env.BATON_API_KEY,
}),
});Every payload is scrubbed before it leaves your process: emails, keys, tokens, card numbers, and sensitive fields, on by default.
Keep everything inside your perimeter, send events to an endpoint you run, or use Baton's cloud, hosted single-tenant. The proxy can write straight to your own S3.
Every event is logged and inspectable. You define the capture schema, so what you don't capture can never reach us.
Events can also route to your own OpenTelemetry sink. Possible, supported, and rarely the place to start.
Your logs show the call and the status code. Baton captures what the customer was trying to do, whether they got it, and the fix. It runs alongside Sentry and LangSmith and sees the layer they don't: intent.
No. The SDK builds in with about five lines and your tools stay exactly as they are. If your customers reach you through agent-generated code, a Skill calling your API, the SDK's Library API instruments those calls instead.
Nowhere, by default. The open-source capture writes to a local file or a sink you choose: your own OpenTelemetry endpoint or your S3. PII is scrubbed before anything leaves. You only send to the Console if you choose to.
Everything is free to start, including the Console. Capture is open source, and light use of the Console costs nothing. You pay once you pass a monthly session threshold.
No. You instrument your side, once. Whatever agent your customers use, Claude, ChatGPT, Copilot, or anything else, keeps working exactly as before.