Repository navigation
Conversation
add_reference_numbers adds reference numbers, such as PO numbers, to a container, shipment or tracking request. It is tier A in the operations toolset. The API replaces the whole ref_numbers list on every PATCH. The tool reads the current list, sends the union of that list and the new numbers, and reads the record back. The result has the list before (previous), the list after (ref_numbers), and which numbers were added or already there. When every number is already on the record it returns already_present and makes no PATCH, so a repeated call changes nothing. The API has no ETag, so a race is reported, not prevented. concurrent_change_detected is true when the list read back differs from the list sent. If the read-back fails after the write, the result still says added, with the sent list, concurrent_change_detected null and a note. A 422 returns noop. A 404 names the record type and id. The PATCH replaces the list, so a list the tool cannot read must not read as empty. A list with an entry that is not a string, or no list at all, returns unreadable_reference_numbers and sends nothing. Null entries hold no number and are left out. Matching trims spaces on both sides. A number on the record with surrounding spaces counts as already there, so the tool never adds a near-duplicate of it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
remove_reference_numbers removes reference numbers from a container, shipment or tracking request. It is tier C in the operations toolset, because searches, filters and integrations that use a removed number stop finding the record. Called without a token, it reads the record and returns a preview with the list now, the list after, the numbers it would remove and the numbers that are not on the record. The token is bound to the whole current list, so a list that changed after the preview returns stale_preview. When none of the numbers are on the record it returns not_present and no token. Called with a valid token, it sends the difference and reads the record back, like add_reference_numbers, and reports concurrent_change_detected. A 422 returns noop. The result names add_reference_numbers as the undo. Matching trims spaces on both sides, as in add_reference_numbers. Each number to remove can be up to 1000 characters, not 64, so a long number added another way can still be removed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
list_parties now loads parties through loadParties and ranks names through rankParties and matchRank, all exported, so assign_party can find a party by name the same way. Each party row is parsed on its own, so one malformed row is skipped and the rest of the page is kept. getParty is exported too. The tool's output does not change. Co-Authored-By: Claude Opus 5.5 <[email protected]>
list_parties now matches a search on a party's company name, name or nickname, and ranks each party by its best matching name. A party with only a nickname, or with a blank company name, is now listed under its next name. Before, the tool skipped such a party. The display name is unchanged: the company name, else the name, else the nickname. loadParties keeps every name of each party, so assign_party can match a name the same way. Co-Authored-By: Claude Opus 5.5 <[email protected]>
assign_party links a party, such as a customer, shipper or dray carrier, to a container or shipment in a role. It is tier A in the operations toolset. A container accepts only pickup_dray_carrier, so any other role on a container is refused before any request. The caller passes party_id, or party_name to match one party exactly by its company name, name or nickname, ignoring case and punctuation, the same names list_parties searches. Two exact matches return ambiguous_party, and no match returns party_not_found; both list candidates and change nothing. With create_party_if_missing the tool creates the party, but only when no party has that name. If Terminal49 refuses the create with 422, the tool looks the name up again and reuses the party another call created. If it still finds none, the result says the party probably exists and asks for party_id. Names are matched against at most the first 1000 parties. When the account has more, the tool neither uses nor creates a party by name. It returns party_lookup_incomplete and asks for party_id. Because any of a party's names counts, a name the account already uses is never created again. The tool reads the record's party roles first. When the party already holds the role it returns already_present and makes no write, so a repeated call changes nothing. A 422 on the assignment reads the roles again and returns already_present if the role now exists, else noop. When the tool created the party and the assignment then fails, the result carries party_created true and created_party, so the caller knows the party now exists. That result is noop after a 422, rate_limited after a 429, and party_not_linked after any other failure. When another call linked the created party in the same role first, the result is already_present, still with party_created true and created_party. The result lists other parties in the same role, and names unassign_party as the undo. Creating a party is a second write, on the account and not the record. The tool emits a separate audit event for it, with entity type party, the new party id and outcome created, so the party stays traceable whatever the assignment's outcome. The audit schema gains the party entity type. Co-Authored-By: Claude Opus 5.5 <[email protected]>
unassign_party removes one party role from a container or shipment. It is tier C in the operations toolset. Only the link is removed; the party and its other assignments stay. To replace a party, a caller unassigns the old one and then calls assign_party. Called without a token, it reads the record's party roles and returns a preview with the role and the party, plus a token bound to the role's id, role name and party. A role id that is not on the record returns not_assigned with the current roles and no token. Called with a valid token, it deletes the role. A 404 on the delete means another call removed it first, so the result is not_assigned. The result names assign_party as the undo. The server instructions now tell the model to read party roles with get_container or get_shipment_details before unassign_party. The sentence is left out when those tools are not served. The fake API in tests now answers a 204 with no body, as a real server does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ools contracts.test.ts now covers add_reference_numbers, remove_reference_numbers, assign_party and unassign_party. Each case calls the executor with a stubbed SDK client and checks the SDK call it makes and the result it returns. A tier C case previews first and then writes with the token from that preview. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…client refs-parties.transport.test.ts runs each tool through the HTTP handler and the MCP client, building a new server per call as the stateless gateway does. It covers an add then remove round trip and an assign then unassign round trip, with the confirmation token carried between requests, and checks each result against its output schema. It also checks that party_not_found is a soft result, that a non-dray role on a container gets a fixed error, and that a 404 names the record type and id for all four tools. Co-Authored-By: Claude Opus 5.5 <[email protected]>
writes.eval.ts gains two round trips on the sandbox account's first shipment. One adds a unique reference number and removes it with a confirmed preview. The other assigns the first party as notify_party and unassigns it with a confirmed preview. The party case skips when the party already holds that role, so it never removes a role it did not create. Both undo their change in a finally block when an assertion fails. The cases still need MCP_EVAL_ENABLE_WRITE and MCP_EVAL_SANDBOX_TOKEN, and skip when the gateway does not serve the tools. Co-Authored-By: Claude Opus 5.5 <[email protected]>
TOOLS_OVERVIEW.md describes add_reference_numbers, remove_reference_numbers, assign_party and unassign_party: their tiers, inputs, previews and results. It covers the union and difference merge, the read-back and concurrent_change_detected, party matching by name, party-create dedupe, already_present and not_assigned. It adds example requests that use them. The tool count is 28, and 21 by default. The package README and MCP_README.md list the four tools in the operations tables, and the README names them in the Toolsets and Confirming writes sections. list_parties search now matches a party's company name, name or nickname, and the docs say so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The MCP home page documents add_reference_numbers, remove_reference_numbers, assign_party and unassign_party: parameters, results, previews and REST links. It lists them in the operations toolset, which the hosted server does not serve yet. The Not yet supported table and note no longer send reference number edits to the REST API. The quickstart lists the four tools in the operations table, and the confirmation secret row names every tool that needs it. list_parties search now matches a party's company name, name or nickname, and the home page and quickstart say so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
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. |
…g it as complete loadParties replaced a malformed page with an empty one and dropped rows it could not read, then reported the list as complete. assign_party could then create a party that already existed. The page is now parsed strictly. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e every number remove_reference_numbers accepts numbers up to 1000 characters but named add_reference_numbers, which caps at 64, as its undo. A removal of a longer number now omits undo and says how to restore it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25bc64d661
ℹ️ 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".
Brings main's trade intelligence tools up the stack. The default toolsets serve 28 tools and all toolsets 35. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The tool leaves out undo when a removed number is longer than the 64 characters add_reference_numbers accepts, and its message says to re-add the number in the dashboard. The docs promised the undo unconditionally. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps this branch's generated Postman collection. Both sides were generated by the Postman workflow, which writes random ids, so they always conflict. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4a1bc02ff
ℹ️ 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".
| const sent = [...previous, ...added]; | ||
| try { | ||
| await writeReferenceNumbers(client, entity_type, entity_id, sent); |
There was a problem hiding this comment.
Preserve concurrent reference updates before replacing the list
When another client adds a reference after this initial read but before this PATCH, the request sends the stale previous list plus this call's additions and silently deletes the other client's value. The subsequent read-back equals sent, so concurrent_change_detected is also false; for example, [A] can become [A, B] concurrently and then be overwritten with [A, C]. Because the API replaces the entire list, this operation needs an atomic/conditional update mechanism rather than relying on the post-write comparison.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed, and there is no atomic option to use. docs/openapi.json gives PATCH /containers/{id}, /shipments/{id} and /tracking_requests/{id} only a full ref_numbers array: no append or remove endpoint, and no ETag, If-Match or version field anywhere in the spec. Re-reading right before the PATCH wouldn't help, because the tool already reads immediately before it writes, so the window stays one round trip either way. The read-back can't see a value overwritten before the PATCH, as you say. So I've made the contract honest instead of implying detection: 5405034 adds to the tool description that a number added by someone else between the read and the write is lost and concurrent_change_detected doesn't report it, and corrects the module comment. 26ff193 says the same in docs/mcp/home.mdx, and 8ea2e2d updates the 08 tools/list snapshot. remove_reference_numbers has the same one-round-trip window inside its confirmed call, since the confirming call re-reads the list, checks the token's fingerprint against it, then PATCHes. A change after the preview is caught as stale_preview; one inside that final round trip isn't. A real fix needs a conditional update or append/remove endpoint in the API. Separately, 6fb888e adds a test that a remove token replayed after add_reference_numbers restores the list is refused (reason used, from b310174 on #451).
There was a problem hiding this comment.
Dismissed with a concrete reason. The finding is real but cannot be fixed in this repo. docs/openapi.json gives the PATCH routes only a full ref_numbers array, with no append endpoint, ETag or version field. The tool description and docs state that a number added between the read and the write is lost and concurrent_change_detected does not report it (5405034, 26ff193). A real fix needs a conditional update in the Terminal49 API. Leaving the thread open for that follow-up.
Generated by Claude Code
| const added = await client.callTool('add_reference_numbers', { | ||
| ...target, | ||
| add: [refNumber], | ||
| }); | ||
| needsRemove = structured(added).status === 'added'; |
There was a problem hiding this comment.
Arm reference cleanup before issuing the write
In the live write eval, if callTool reaches the server and adds the reference but its response is lost or the client rejects, execution enters finally while needsRemove is still false, leaving the generated reference on the sandbox shipment. This contradicts the eval's round-trip cleanup guarantee and can pollute later runs; set the cleanup flag before the write, as the stop-tracking case already does.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Right for this branch's eval. The live write eval only runs from 08 (mcp-sandbox-write-eval in ci.yml), and 08 rewrote it to touch only the three demo-account fixtures, so 05 to 07's version, which writes to the first shipment and party on the account, is not the one to fix. On 08 the reference case already arms its cleanup before the write: removed starts false and the finally sends remove_reference_numbers unless the remove succeeded, and a number that was never added comes back not_present with nothing changed (ef21140).
There was a problem hiding this comment.
Dismissed for this branch. The live write eval runs only from the 08 branch, which replaces this file with a fixture-only version whose reference case arms cleanup before the write (ef21140, per the earlier reply). Editing it here would only conflict with 08, and #452 does not touch other mcp-v2 branches.
Generated by Claude Code
| const assigned = structured( | ||
| await client.callTool('assign_party', { | ||
| ...target, | ||
| role: 'notify_party', | ||
| party_id: partyId, |
There was a problem hiding this comment.
Protect party assignment with cleanup before awaiting it
This assignment occurs before the cleanup try/finally is established. If the server creates the role but the response is lost, or if the returned payload fails the assertion before line 224, the test exits without discovering and removing the new role; subsequent runs then see already_present and skip the coverage entirely. Record the pre-existing roles and wrap the assignment itself in cleanup logic that can locate any newly created matching role.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in the eval that runs, on 08 (9e026b3). 05 to 07's eval doesn't run in CI and is replaced on 08 by the fixture-only version, so I fixed it there rather than on this branch. The assign_party call now sits inside the try. On failure, the finally reads the fixture shipment with get_shipment_details include_party_roles and unassigns every notify_party role held by "MCP Eval (do not use)". Only this eval assigns that party, so every such role is one an eval run created, and a role whose assign response was lost is removed too. The selector matches the exact party name, the role, and the party id once known. Its unit tests in write-fixtures.test.ts failed before the selector existed. The eval README describes the cleanup.
There was a problem hiding this comment.
Moot. The assign_party eval case no longer exists on this branch: the tool was removed in bcb0cde in favour of set_party_role, and grep assign_party finds nothing in code at 86a6e98.
Generated by Claude Code
Also trims the token sentences from the remove_reference_numbers and unassign_party descriptions, which the registrar's tier C clause now covers, and names both tools in the README and in-depth guide. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…st feature map Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…used remove_reference_numbers caught its 422 and unassign_party its 404 inside the write, so the kit never saw the refusal and the confirmation stayed spent although nothing changed. Both now answer the refusal from the kit's refused callback, after the confirmation is released. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 790f8176c9
ℹ️ 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".
| try { | ||
| return await refused(error); | ||
| } catch (failure) { | ||
| return createdNotLinked(failure, record, party); |
There was a problem hiding this comment.
Recheck the role before declaring a created party unlinked
When create_party_if_missing creates the party and the subsequent assignment commits but its response is lost or returns a 5xx, this catch converts the ambiguous failure into party_not_linked without reading the roles again. The caller is therefore told definitively that the party is unlinked even though the role may exist; re-read the party roles and return already_present when the assignment actually landed before reporting it as unlinked.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Moot. assign_party, including create_party_if_missing, was removed in bcb0cde. set_party_role requires an existing party_id and never creates a party.
Generated by Claude Code
Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9ed292a555
ℹ️ 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".
| const added = after.find( | ||
| (other) => other.role === role && other.party_id === party.id, | ||
| ); | ||
| if (added) return alreadyPresent(added, created ? party : undefined); |
There was a problem hiding this comment.
Use the post-conflict roles for co-assignees
When the POST returns 422 because another request assigned this party concurrently, this branch finds the new role in after but alreadyPresent still derives other_parties_in_role from the pre-write inRole snapshot. Any other same-role parties added during that window are therefore omitted, so callers can be incorrectly told there are no additional assignees and fail to remove them during a replacement workflow; build this result from the freshly read after roles.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Brings main and the confirmed batch record writes into the reference number and party branch. Every tier C tool now declares when it asks, so remove_reference_numbers and unassign_party gain confirms: 'always'. The docs, manifests and test tables cover the nine operations tools the default toolsets serve at this point in the stack. Co-Authored-By: Claude Opus 5.5 <[email protected]>
set_party_role from main covers both: add attaches a party at once, and remove detaches it after the user approves a preview of every record that changes, on up to 50 records per call. The single-record tools, their tests, fixtures, eval case, feature-map entries, manifest entries and the party helpers only they used go with them. The reference number transport test keeps its reference cases under a name that says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The docs site, tool overview, READMEs and eval notes describe set_party_role in their place and count the seven operations tools. Co-Authored-By: Claude Opus 5.5 <[email protected]>
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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a4a1754af
ℹ️ 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".
The branch's docs/openapi.json matches main's, so the generated collection should too. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolves conflicts in favour of the write kit's design. The reference number tools move to a new opt-in reference_numbers toolset so no default toolset gains a write tool, the instructions' write paragraph is tightened to fit 2,048 bytes with all toolsets on, the not-found message names the reference record, and the docs describe the toolset and drop assign_party. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
A change between the preview and the approval gets a new preview. A change that lands while the confirmed write is running can still be lost, because Terminal49 replaces the whole list and has no conditional update. Say so in the tool description, the MCP home page, and the tools overview, matching add_reference_numbers. Also drop comments that only narrate the code. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
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 <[email protected]> Claude-Session: https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw
Why
Tagging records with PO numbers is the most common account edit.
What changed
add_reference_numbers(tier A, union with read-back) andremove_reference_numbers(tier C, confirmed preview) on containers, shipments and tracking requests.list_partiesmatches a search against every name a party goes by.Both reference number tools are in a new opt-in
reference_numberstoolset, not in the default toolsets. A deployment serves them only whenT49_MCP_TOOLSETSlistsreference_numbers, so merging this PR adds no write tool to the defaulttools/list, which stays at 30 tools.This PR no longer adds
assign_partyandunassign_party. Main'sset_party_role, confirmed through the write kit in #451, covers both:addattaches a party at once, andremovedetaches it after the user approves a preview, on up to 50 records per call. The two single record tools, their tests, fixtures, eval case, manifest entries, docs and the helpers only they used are gone.remove_reference_numbersdeclaresconfirms: 'always'.Verification
vp run check,vp run test(1,012 MCP tests) andnpm run agent-verifypass at the head.packages/mcp/src/reference-numbers.transport.test.ts.🤖 Generated with Claude Code
https://claude.ai/code/session_01V8cPhgia7FZpMP7q5BJ5nw