Repository navigation
Conversation
The test connects one MCP server for each toolset subset in both modes, 64 round trips in all. Under load it passed the 5 s default, and each new tool adds to it. It now builds the tool name pattern once as one alternation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add custom-field-values.ts, the shared base for set_custom_field and update_custom_field. It finds the account's active definition by api_slug and loads its options. It checks a value against the field's data_type: text, number, YYYY-MM-DD date, ISO 8601 datetime with a time zone, boolean, one option for enum and a list of options for enum_multi. A value that does not fit returns invalid_value with the expected form and, for enums, allowed_values. The checks run before any write because Terminal49 stores an empty value for an enum option it does not know, which would silently clear a field. It also reads a field's current value on a container, shipment or tracking request. An enum value of '' or an enum_multi of [] counts as no value, since that is how Terminal49 reports options that were removed. Container and shipment fields both apply to a tracking request, because Terminal49 copies the value to the records it creates. A 401 or 403 becomes credential_not_supported when /me says custom fields need a signed-in user, and FeatureNotEnabled when /me says the account lacks them. The definition loading in list_custom_field_definitions is now exported for reuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
set_custom_field fills an empty custom field on a container, shipment or tracking request. It is tier A in the operations toolset. It finds the field by api_slug and checks the value against the field's data_type and options before it sends anything. A value that does not fit returns invalid_value, an unknown slug returns unknown_field, and a shipment field on a container returns field_not_applicable. A tracking request takes both kinds of field. When the field already has a value, it returns already_set with current_value and changes nothing, and points to update_custom_field when the values differ. Otherwise it creates the value and returns set with what Terminal49 stored. A 422 returns noop. Terminal49 has no create-only write, so a value that lands between the read and the POST is replaced. The description tells the model not to run two calls on the same field in parallel. A field whose data_type the tool cannot write, such as reference, returns unsupported_field before the record is read. Terminal49 copies a tracking request's value to its shipment or containers only when the value is first created. Once the shipment exists, a later change on the tracking request does not reach the copies, and a new value replaces theirs. So on a tracking request that already created its shipment, the tool returns use_shipment_or_container with shipment_id and changes nothing. The description tells the model to set the field on the shipment or its containers instead. A tracking request can hold one value per container for a container field. Terminal49 does not say which value belongs to which container, so the tool returns ambiguous_field with the values and changes nothing. The api_slug input takes upper and lower case letters, digits and underscores, as Terminal49 does. Each result carries the record and its label, like the reference number tools. The audit event names the record and never the value. The server instructions tell the model to read the definitions with list_custom_field_definitions first. The sentence is left out when either tool is not served. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update_custom_field replaces or clears a custom field value on a container, shipment or tracking request. It is tier C in the operations toolset, because Terminal49 keeps no copy of the old value. It takes value to replace the field or clear: true to remove it. Passing both or neither is an input error. A new value goes through the same data_type and option checks as set_custom_field. Called without a token, it reads the field and returns a preview with the record, the field, the current value, the proposed value and the action. The token is bound to the current value, so a value that changed after the preview returns stale_preview. A field with no value returns not_set, and a new value equal to the current one returns unchanged. A tracking request that holds one value per container for the field returns ambiguous_field with the values. A tracking request that already created its shipment returns use_shipment_or_container, because the change would not reach the shipment or its containers. A clear on a field whose data_type no tool can write, such as reference, returns unsupported_field. None of these issues a token. Called with a valid token, it sends PATCH for a new value or DELETE to clear. It returns updated or cleared with previous_value, and names the undo: update_custom_field after an overwrite, set_custom_field after a clear. A 422 returns noop. Terminal49 answers a DELETE on an empty field with 204, so the 404 branch that returns not_set is defensive. set_custom_field's description now points to update_custom_field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin the SDK calls set_custom_field and update_custom_field make: the definitions and options reads, the record and custom_fields reads, and setCustomField, updateCustomField or deleteCustomField with the record id, the api_slug and the checked value. The fixtures cover a shipment, a tracking request and a container. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run set_custom_field and update_custom_field through the HTTP handler and the MCP client, with a new server for each call. One test sets a field, gets already_set on a second set, then overwrites and clears it with confirmed previews across separate requests. Others check that invalid_value and credential_not_supported come back with isError false, that value plus clear returns the fixed input error, that a 404 names the missing record, and that read-only mode serves neither tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a round trip to eval/writes.eval.ts on the first shipment: set the first short_text shipment field with set_custom_field, overwrite it and clear it with update_custom_field, each with a confirmed preview. A finally block clears the field if an assertion fails after the set. Terminal49 rejects custom field changes from API keys. So the case runs as the signed-in sandbox user in MCP_EVAL_SANDBOX_BEARER, and skips without that variable. Before any write, it calls whoami with both credentials. It fails when the bearer's account is not the MCP_EVAL_SANDBOX_TOKEN account. Like the other write evals, it also needs MCP_EVAL_ENABLE_WRITE and MCP_EVAL_SANDBOX_TOKEN. The case skips when the account has no shipment or no short_text shipment field. It also skips when the field already has a value, so it never changes a value it did not set. It fails, and does not skip, when the user cannot load the account's custom field definitions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TOOLS_OVERVIEW.md describes set_custom_field and update_custom_field: their tiers, inputs, the value each data_type takes, previews and results. It covers already_set, unchanged, not_set, invalid_value, unknown_field, field_not_applicable, unsupported_field, ambiguous_field, use_shipment_or_container, credential_not_supported and noop, and adds an example request. It says set_custom_field cannot block a concurrent write. It says a change on a tracking request does not reach the shipment or containers it created. The tool count is 30, and 21 by default. The package README and MCP_README.md list both tools in the operations tables, and the README names them in the Toolsets section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The MCP home page documents set_custom_field and update_custom_field: parameters, the value each data_type takes, results, previews and REST links. The results include noop, ambiguous_field and use_shipment_or_container, and the number limit matches the tool. It says set_custom_field cannot block a concurrent write, and that a change on a tracking request does not reach the shipment or containers it created. It lists them in the operations toolset, which the hosted server does not serve yet. The Not yet supported note now names custom field values among the operations tools. The quickstart lists both tools in the operations table, and the confirmation secret row names update_custom_field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add relink_document to the documents_write toolset as a tier A write. It reads the document, asks Terminal49 to match it to shipments and containers again, and returns accepted with the links it had before the call. Linking runs in the background. A 422 returns noop, a feature 403 returns FeatureNotEnabled, and a 429 returns rate_limited. documents_write is not a default toolset, so the default listing does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add update_document to the documents_write toolset as a tier C write with three actions. set_type sets the document type by hand, reclassify has Terminal49 choose the type again, and reextract has it read the file again. Each action can replace the type or the extracted data, so each one needs a confirmed preview. set_type checks the code against GET /documents/types before the preview, because Terminal49 answers an unknown code with a 500. An unknown code returns unknown_document_type with the allowed codes, and a type the account already set returns unchanged. Neither issues a token. When the classifier chose the same type, set_type still previews and writes, so the account can confirm it. The token is bound to the type, its source, the notes, the latest extraction and updated_at, so a document that changed after the preview gets a new preview. reclassify and reextract run in the background and return accepted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move stringLinks from list_documents to api-fields.ts so the webhook read tools can keep only string pagination links the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add list_webhooks, a read in the opt-in developer toolset. It lists the account's webhooks, or reads one by webhook_id, with whether each is active, its events, and the scheme, host and path of its URL. With include_event_catalog it adds every event a webhook can subscribe to. A malformed catalog category or event is dropped, and the rest are kept. The tool reads only named fields. The signing secret becomes has_secret and each header becomes its name, so neither value is held after parsing. The URL loses its query string, fragment, user name and password. Any path segment or subdomain can be a key the receiver checks, so the tool does not guess which ones are. The path keeps only its first segment, and only when it is short lowercase words. The host keeps its leftmost label only when it is a short lowercase word. Every other part becomes [redacted]. A feature 403 returns FeatureNotEnabled. developer is not a default toolset, so the default listing does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add list_webhook_notifications, a read in the opt-in developer toolset. It lists the notifications Terminal49 created for the account, or reads one by notification_id. Each row has the event, delivery_status, created_at, the webhook it went to and the record it is about. The tool never asks the API to include the webhook, whose record carries the signing secret, and it reads no payload. The API offers no filters on this endpoint, so the tool offers none either. A feature 403 returns FeatureNotEnabled. This differs from the proposal, which offered an opt-in, truncated include_payload for one notification. A payload is large, holds text written outside the account, and has a different shape for each event, so it would need its own redaction and injection handling. The tool leaves it out for now. Without it, a row names the record behind the notification, such as a container_updated_event, but not the container. So the tool answers which notifications were sent and delivered, not which container one was about. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin the SDK calls relink_document, update_document, list_webhooks and list_webhook_notifications make, including that the notification list asks for no includes and that only set_type reads the document types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ient Add transport tests that set a document type with a confirmed preview across separate stateless requests, relink a document, and return the fixed errors for bad input and 404s. They also check that the default listing leaves out all four tools and that read-only mode keeps only the webhook reads. Add a telemetry test with Sentry recording tool inputs and outputs. A webhook secret, header values, a URL query string and a key in the URL path never reach the tool output, the logs, Sentry or PostHog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ChatGPT and Claude submission files must name no tool outside the default toolsets, anywhere in the file. The test derives the opt-in tools from the registry and asserts that the document fix and webhook tools are among them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The read suite checks that list_webhooks returns no secret, header value or URL query string, and that list_webhook_notifications returns delivery rows. Both skip when the gateway does not serve the developer toolset. The sandbox write suite relinks the first document and previews a reextract without confirming it, because a document fix cannot be undone exactly. It skips when the gateway does not serve documents_write. It still needs MCP_EVAL_ENABLE_WRITE and MCP_EVAL_SANDBOX_TOKEN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TOOLS_OVERVIEW.md describes relink_document and update_document in the documents_write toolset, and list_webhooks and list_webhook_notifications in the developer toolset. It covers the update_document actions, preview, token binding and results, and which parts of a webhook URL are shown. It says the webhook tools never return a secret, header values or a URL query string. The tool count is 34, and 21 by default. The package README and MCP_README.md list the four tools with their toolsets, and the README names them in the Toolsets section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/mcp/home.mdx describes relink_document and update_document under Write tools, and list_webhooks and list_webhook_notifications under a new Developer tools section, each with REST links. The toolset list names their tools, and the Not yet supported table now covers webhook setup only. The quickstart lists the four tools with their toolsets and adds update_document to the confirmation secret row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ceea554f27
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… value A clear checks no value, so it no longer fails when the options endpoint is unavailable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
describeWebhookUrl kept a short lowercase first path segment and leftmost host label, so a receiver key such as https://example.com/secret reached the MCP client and telemetry. Spelling cannot tell a route from a key, so the URL now keeps only the scheme and the host's last two labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 49992b0ba9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pping the single-record custom field tools set_custom_field from main, now confirmed through the write kit, covers filling, overwriting and clearing a field on up to 50 records, so this branch's set_custom_field (which collides with it by name) and update_custom_field go, with their value checker, fixtures, tests, eval case, docs and feature-map entries, and the helper exports only they used. The tree now matches mcp-v2/05-refs-parties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the confirmed batch record writes and the removal of the single-record party and custom field tools into the document and webhook branch. update_document declares confirms: 'always', the docs count the 35 default tools and the four opt-in ones, and the eval keeps only the document preview case beside the tracking and reference number cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shadow risk calibration
Calibration result: agreement. Shadow risk never blocks or enables merge. Keep it in observation mode until roughly 50 pull requests have human-reviewed labels, then calibrate before considering any policy use. |
The documents_write and developer toolsets are listed with their tools two lines below. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mcp-v2/07-document-writes
The branch's docs/openapi.json matches main's, so the generated collection should too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad096e8326
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
* feat(mcp)!: turn on write tools by default The default toolsets are now core, documents, operations and documents_write. A server with no T49_MCP_TOOLSETS lists 32 tools. The developer toolset stays opt-in. Both store submissions now list the eleven write tools. The ChatGPT entries also carry the annotations and a justification for each hint. The tools/list and instructions snapshots follow the new default. The server instructions now carry the write rules and the confirmation rule by default, because the default listing has tools that need a confirmed preview. BREAKING CHANGE: The default tool list now includes the write tools stop_shipment_tracking, resume_shipment_tracking, cancel_tracking_request, add_reference_numbers, remove_reference_numbers, assign_party, unassign_party, set_custom_field, update_custom_field, relink_document and update_document, alongside track_container and the reads. To stay read-only, send the header X-T49-MCP-Read-Only: true, or set T49_MCP_READ_ONLY=true on a self-hosted or stdio server. To keep the previous list, send X-T49-MCP-Toolsets: core,documents or set T49_MCP_TOOLSETS=core,documents. An HTTP server needs T49_MCP_CONFIRMATION_SECRET for the tools that need a confirmed preview. Without it they refuse to run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mcp): ask the model to report each write and its undo tool With writes on by default, the server instructions now tell the model to say what a write changed and to name the tool in the result's undo field. A result with an error field is explained to the user, not repeated in a loop. The rule appears only when the session serves a write tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): test when the sandbox write evals run The write evals now read their credentials through resolveSandboxConfig in eval/client.ts. It returns null unless MCP_EVAL_ENABLE_WRITE is 1 or true and MCP_EVAL_SANDBOX_TOKEN is set, so the suite skips cleanly. It never reads MCP_EVAL_TOKEN or MCP_EVAL_BEARER, so a write eval cannot reach the read eval's account. The read suite reads the same flag through writesEnabled. The write suite now fails when the gateway does not list every default write tool, so a gateway that lost a write toolset cannot pass with every case skipped. MCP_EVAL_ALLOW_SPARSE=1 allows the skips, as in the read suite. The sandbox account also holds demo data, so the write cases change only eval fixtures, chosen in eval/write-fixtures.ts by exact match: the one shipment with reference number MCP-EVAL-FIXTURE, the party named "MCP Eval (do not use)", and the short_text custom field mcp_eval_fixture. A case skips when its fixture is missing and never falls back to another record. assign_party creates the party on the first run. Each case undoes its write in a finally block. The live relink_document call is gone, and update_document is only previewed on a document attached to the fixture shipment. New unit tests pin each case and each selection rule. The default test run now includes eval/**/*.test.ts, so these tests run in vp run test and in PR CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: run the MCP write evals on the sandbox account On pushes to main, a new mcp-sandbox-write-eval job runs eval/writes.eval.ts with MCP_EVAL_ENABLE_WRITE=1 and the MCP_EVAL_SANDBOX_TOKEN secret, after the mcp job passes. It skips when the secret is unset. It runs only the write suite, so the read suite never gets the write flag. The custom field case needs a short-lived user token, so it skips in CI. The job first waits until Vercel reports a successful production deployment of the pushed commit. The eval checks the default write tools, so it must not run against the build production served before the push. Every run edits the same sandbox shipment, so the job has a concurrency group and runs never overlap. A unit test checks that only this job sets the write flag, that it runs only on pushes to main, that it keeps the concurrency group, and that it waits for the deployment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: describe the write tools in the store submissions The ChatGPT and Claude listing descriptions now say the connector can change the account at the user's request, and that a change which removes or replaces data waits for the user to confirm a preview. The Claude listing adds three use cases for the write tools. The MCP Registry description in server.json now says the server can stop or resume tracking and edit references, parties, custom fields and documents. Its version moves to 1.1.0 so the registry takes the change. A test keeps that description within the 100 characters the registry allows. The ChatGPT test case for listing shipments is replaced by one that removes a reference number only after the user approves the preview. The submission test now requires that case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: describe the MCP write tools as on by default The hosted server now serves the operations and documents_write toolsets by default. The MCP overview gives the default count of 32 tools, the 20 left in read-only mode, and the header that keeps the old 21. The toolset list, the tools reference note, the write tools section and the Not yet supported note no longer say the hosted server leaves the write tools out. The quickstart gives the new T49_MCP_TOOLSETS default and says HTTP servers need T49_MCP_CONFIRMATION_SECRET unless they are read-only. Each client setup page now says the tool list includes write tools. The Cursor and other clients pages show the read-only header. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): describe the write tools as on by default The package README, TOOLS_OVERVIEW.md and the root MCP_README.md now give the default of 32 tools, with operations and documents_write in the default toolsets and developer still opt-in. They say how to serve only reads, or the 21 tools of the earlier default. MCP_README.md also lists update_document among the tools that return a preview first, and documents T49_MCP_CONFIRMATION_SECRET. WORKOS_MCP_SETUP.md marks T49_MCP_CONFIRMATION_SECRET as required. The directory preflight checks that production has the secret and runs the sandbox write evals, and no longer says only track_container creates data. The eval README describes the CI write eval job and its secret. .env.local.example shows the new T49_MCP_TOOLSETS default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): describe the write eval fixtures and their setup The write evals now change only eval fixtures in the sandbox account, which also holds demo data. The eval README names the three fixtures and the one-time setup: add MCP-EVAL-FIXTURE to one demo shipment and create the short_text custom field mcp_eval_fixture. The first run creates the party "MCP Eval (do not use)". The README also drops the live relink case and says the document case is preview-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): skip the party eval when a near-match party could be linked With no party named exactly "MCP Eval (do not use)", the eval let assign_party create it by name. assign_party matches names ignoring case and punctuation, so it could link another party instead. The eval now creates the party only when the name search finds no party at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * docs: describe both confirmation channels in the release notes and connector submission Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): remove the eval party role even when its assign response is lost The party write eval called assign_party before its cleanup was armed and unassigned only the role id from the response. A lost response or a failed call left the role on the fixture shipment until a later run. The assign now runs inside the try, and on failure the cleanup reads the fixture's party roles and unassigns every notify_party role held by "MCP Eval (do not use)", which only this eval assigns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): update the tools/list snapshot for the add_reference_numbers description Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * chore: restore main's Postman collection The branch's docs/openapi.json matches main's, so the generated collection should too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 84ca2d926f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
This reverts commit 84ca2d9.
Brings in the base as of 6354100: main's 2 KB instruction cap and idempotentHint (#493), the opt-in tracking_lifecycle toolset and the read-first write kit (#451), and the opt-in reference_numbers toolset (#452). Conflicts were resolved in favour of the base's design: - packages/mcp/src/instructions.ts takes the base's segment model. The write paragraph still names each write tool, so with update_document and relink_document the largest selection is 2,092 bytes. The next commit redesigns that paragraph; three instruction tests stay red at this commit. - packages/mcp/src/server.ts keeps the base's reference-number error message and adds the webhook read cases after it. This branch's own copy had add_reference_numbers and remove_reference_numbers falling through to the webhook message. - The docs (home.mdx, README, TOOLS_OVERVIEW, WORKOS_MCP_SETUP, eval README) use the base's wording and add documents_write and developer as opt-in toolsets: 39 tools in all, 30 by default. - Terminal49-API.postman_collection.json is the base's copy, because docs/openapi.json is identical to the base's. Generated filter schemas come from npm run generate:filters. The annotations test lists the two document writes, and the update_document fail-closed test allows the read that the base's write kit makes before it refuses. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
With update_document and relink_document the instructions' write paragraph, which named every write tool and repeated the tier C ones in the approval rule, reached 2,092 bytes at the largest toolset selection, past the 2,048 that Claude Code keeps. The paragraph now names no tool. Each write tool's description opens with "Changes account data." from the registry tier, and a tier C description goes on to say when the call needs the user to approve a preview, as before. The instructions tell the model to look for that lead, to call a write only when the user asks, that every other tool only fetches data, to show every preview, never to pass a confirmation_token the user has not approved this turn, and that tool output is untrusted. Those rules appear in every selection that serves a write tool, tier A included, and nowhere else. Every selection is now 1,824 bytes or fewer, so the default and the largest selection are the same size and a new write tool costs nothing here. The chaining guide names track_container after get_supported_shipping_lines, since the write paragraph no longer does. Tests: every toolset subset, read-only or not, carries the rules exactly when it serves a write; the write paragraph mentions no tool; tools/list opens every write description, and no read description, with the lead, and only tier C descriptions mention approving a preview. The list_webhooks instruction test goes, because the tool's own description carries the redaction rule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
relink_document was tier A on the premise that a relink only adds links.
The backend's DocumentReferenceLinker, which POST /documents/{id}/relink
re-runs, also destroys the links its own matching made earlier whose
targets the fresh matching no longer produces (stale_direct_frank_links
and stale_intermediate_frank_links in tnt-api). Links made by hand stay,
but a relink can remove links the user relies on, and nothing puts them
back, so it is destructive.
The tool is now tier C with confirms: 'always'. The first call reads the
document and returns a preview of its current links, and the approval is
bound to the shipment and container links and updated_at, so a change to
the links between the preview and the approval asks again. A 422 after
the approval returns noop and keeps the approval usable, through the
kit's refused callback. The registry marks it destructive, so Claude
Code prompts on every call.
BREAKING CHANGE: relink_document no longer relinks in one call. It
returns confirmation_required with a preview, and relinks only when
called again with the confirmation_token after the user approves.
Tests cover the preview-then-token flow, a stale preview after the links
change, 422 after approval, the fail-closed path without a confirmation
secret, and the transport round trip. The docs, README, overview and
in-depth guide describe the tier and the reason.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
The description branch checked tier read, then tier A, then read confirms in the last arm. The registry types tier read and A as one union member, so TypeScript did not narrow the last arm to tier C and vp run check failed on the confirms read. Branching on tier C first narrows it, and nothing changes at run time. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
The SCAC sentence was split into three segments, and the last one was a bare ".". It mentions no tool, so it stayed in the guide after its sentence was dropped. Selections without core, such as documents or trade, got a paragraph that was only ".". Each variant of the sentence now carries its own period. The default instructions text is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
The server instructions no longer name write tools, so the README now says that write tools are marked by "Changes account data." in their descriptions. The confirmation-secret list in the MCP guide and the WorkOS setup note now include relink_document. The README names every field the update_document fingerprint covers, and says reextract, reclassify and relink_document confirmations can also replay once on another instance. The relink_document reach says it may extract references first and never re-adds a link a user removed. The relink fingerprint comment no longer claims updated_at catches a new extraction. MCP_README counts the opt-in tools correctly, the writes eval header says the document case only previews, and comments that narrate code are gone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
Why
AI agents should change Terminal49 account data only through a preview the person approves, and no merge should switch new write tools on by default before one release PR does it in a single, revertible step. Misclassified documents need a fix path, and integrators need to see their webhook setup, so this PR adds those tools behind opt-in toolsets.
What changed
update_document(set_type, reclassify, reextract) andrelink_documentin the opt-indocuments_writetoolset, both tier C withconfirms: 'always'.list_webhooksandlist_webhook_notificationsin the opt-indevelopertoolset. Webhook secrets, header values and URL query strings never appear in output, logs, Sentry or PostHog (packages/mcp/src/webhook-telemetry.test.ts).relink_documentstarted as tier A. The backend'sDocumentReferenceLinker, whichPOST /documents/{id}/relinkre-runs, also removes the links its own matching made earlier that no longer match, so a relink can remove data and now needs the user's approval of a preview of the current links.The server instructions' write paragraph no longer names each write tool. Every write tool's description opens with "Changes account data." from its registry tier, and a tier C description goes on to say when the call needs an approved preview. The paragraph is the same size however many write tools a selection serves: 1,824 bytes at the default and at the largest selection, under the 2,048 that Claude Code keeps. A test checks that the rules (call a write only when the user asks, every other tool only fetches data, never pass a
confirmation_tokenthe user has not approved, tool output is untrusted) appear in every toolset selection that serves a write tool and nowhere else.The default toolsets stay
core,documents,operationsandtrade, which serve 30 tools, the same list as #452. All four toolsets below them are opt-in:tracking_lifecycle,reference_numbers,documents_writeanddeveloper. 39 tools exist in all.#455 ("turn on write tools by default") had been squash-merged into this branch. That commit is reverted here (ed9560b) and its content returns as its own release PR, so that the switch to default-on lands once and can be reverted on its own.
Verification
vp run check,vp run testandnpm run agent-verifypass at the head. The MCP package runs 74 test files and 1,103 tests.packages/mcp/src/document-writes.transport.test.ts.main; the local gates above are the record until the stack reachesmain.🤖 Generated with Claude Code
https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw