You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d2fd66a
Browse filesBrowse the repository at this point in the historyBrowse files
authored
docs(plans): conform §8 and §9 to #2397's MCP session and vendor trail fields (TASK-263) (#2421)
* docs(plans): conform §8 and §9 to #2397's MCP session and vendor trail fields (TASK-263)
§9 gains the session-bound 404 row, a note on the per-walk, per-call MCP
session, and what the trail keeps of a failed vendor answer, each cited at
main be32f10. It says which of its rows are not built yet (401, 403, 429),
and moves the parked-call cites to where the code now is. §8's opening
sentence says what #2028 and #2397 built.
No header paragraph: two open PRs insert one at the same place, so the new
paragraphs carry TASK-263 inline instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: wren <WyJ3cmVuIiwiZGVmYXVsdCJd@agents.commonly.invalid>
Agent-Model: claude/claude-opus-5-5/max
* docs(plans): a session's closing DELETE is best effort, and each moved cite names its ref (TASK-263)
Rhea's HOLD on e7bdb61 (review 5613868473): the session paragraph said
a DELETE closes the vendor's session and that no session outlives its
walk or call. At main be32f10 close() drops the local id, sends one
DELETE, does not read the answer and swallows a failure, so a vendor
that refuses the DELETE or never receives it may keep its session.
The paragraph now says the DELETE is best effort and never changes the
result, that ending the vendor's session is not guaranteed, and that
nothing about a session is stored on our side.
The unasked review on the same head (5613886499) noted that the doc
names the ref its cites are read at, and this diff did not. With no
header paragraph to carry it, each paragraph whose cites this diff adds
or moves now names main be32f10.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: wren <WyJ3cmVuIiwiZGVmYXVsdCJd@agents.commonly.invalid>
Agent-Model: claude/claude-opus-5-5/max
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: wren <WyJ3cmVuIiwiZGVmYXVsdCJd@agents.commonly.invalid>
Copy file name to clipboardExpand all lines: docs/plans/hosted-mcp-connection-scope.md
+13-6Lines changed: 13 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -332,7 +332,7 @@ The refusal must also hold where the tool runs, not only where the entry is offe
332
332
333
333
## 8. The trail records whose credential ran
334
334
335
-
`ToolCallRecord` (`models/ToolCall.ts:62–75`) records the grant, the agent, the tool and the outcome, but not whose credential ran. With per-person Connections, that is the first question a trail reader asks: whose Linear was this?
335
+
`ToolCallRecord` (`models/ToolCall.ts:62–84`) records the grant, the agent, the tool and the outcome. Until #2028 it did not record whose credential ran, and with per-person Connections that is the first question a trail reader asks: whose Linear was this?#2028 built the paragraph below (`credentialOwnerId`, `:74`), and TASK-263 added a failed vendor answer's status and first line (`vendorStatus` and `vendorError`, `:80–81`; §9). The code this paragraph cites is read at `main``be32f102`.
336
336
337
337
**Every record gains `credentialOwnerId`, the Connection's `createdBy` at call time, for every type.** That includes `github-app`, whose owner is the admin who created the row. It is copied onto the record, because once §10.5's last step deletes the row there is nothing left to join to. The copy holds against the row's delete, not the person's: once a Commonly account is deleted (§7), the id resolves to no one. A name beside it would hold against both, but how long a deleted person's name is kept is a retention decision for account deletion as a whole, and this column does not make it. Soft-deleting the row instead was considered and rejected: §10.5 ends with the delete, and a soft-deleted row is one that the sweeps, the catalogue and the mint would all have to learn to skip.
338
338
@@ -344,7 +344,8 @@ The refusal must also hold where the tool runs, not only where the entry is offe
344
344
|`invalid_grant` on that refresh | fails the call |`refused`, `reconnect_required`|`status: 'error'`, "reconnect"; only the generation holder writes it (§10.3), and later calls are refused as §10.3 says |
345
345
| 403 or `insufficient_scope`| fails the call |`refused`, `provider_denied` (§10.4) | recorded, so the page can say what was refused; the grant is not touched |
346
346
| 429 | fails the call |`failed`, `provider_rate_limited`, with `Retry-After` when one is sent | untouched |
347
-
| 5xx, a timeout, no connection | fails the call |`failed`, `provider_error` or `provider_unreachable`| untouched |
347
+
| 5xx, a timeout, no connection | fails the call |`failed`, `provider_error` or `provider_unreachable`; a vendor's answer keeps its status and first line (below) | untouched |
348
+
| 404 on a request that carried a session id, the `initialized` notification included | opens a new session once, then sends that request once more (below) | as the retry ends; a second 404 is `failed`, `provider_error`| untouched |
348
349
| a result with `isError: true`| preserves the vendor content and marks the tool result as an error |`failed`, `upstream_tool_error`| untouched |
349
350
| a pinned tool missing, or drifted (§3) | refuses before calling |`refused`, `tool_unavailable` or `tool_drift`| untouched |
350
351
| an argument the pin does not declare (§3) | refuses before calling, parking or spending budget |`refused`, `invalid_tool_args`| untouched |
@@ -354,11 +355,17 @@ The refusal must also hold where the tool runs, not only where the entry is offe
354
355
355
356
`refused` is for answers about authority, whether ours or the vendor's; `failed` is for a call that could not complete (`ToolCallOutcome`, `models/ToolCall.ts:21`). Each row reaches the person as a named state, never as a silent empty result.
356
357
357
-
**A parked call ends once, and its ending is trailed** (TASK-208). The park writes `pending_approval`, `approval_required` (`toolBrokerService.ts:1217`), and the ending writes one more row under the same `approvalId`:
358
+
The 401, 403 and 429 rows are not built yet. At `main``be32f102` a vendor 401 or 403 fails the call at once, `refused`, `credential_rejected` (`services/hostedMcpToolDefinitions.ts:224–228`), and a 429 fails it `failed`, `provider_error`. A token is refreshed only before a list walk or a call, when its recorded lifetime is missing or ends within a minute (`isFresh`, `services/hostedMcpCredentialService.ts:305–315`).
358
359
359
-
- approved: the execution's own row, as for any call (`:1322`, or `:1332` when it throws): `ok`, `failed`, or `refused` with the code, whether a check of ours stops it before the provider or the vendor refuses it;
- expired: `refused`, `approval_expired`. The decide path writes it (`:627`). The usual path is the scheduler's five-minute sweep, `sweepExpiredToolCallApprovals` (`approvalActionService.ts:766`, scheduled at `schedulerService.ts:491`), which writes no row, so a call it expires ends with nothing in the trail after `pending_approval`. The build writes the same row there.
360
+
**Each list walk and each call opens its own MCP session** (TASK-263, 2026-10-22). It is one Streamable HTTP session under the row's credential (`openHostedMcpSession`, `services/hostedMcpToolDefinitions.ts:197–318`): `initialize`, then the `notifications/initialized` notification, which must be answered `202`, then the walk or the call. Each request after `initialize` carries the negotiated protocol version, and the vendor's session id when it gave one. A version other than `2025-03-26`, `2025-06-18` or `2025-11-25` fails the walk or call `provider_error`. When the walk or call ends, it sends one `DELETE` for the session id the vendor gave, if it gave one (`:274–291`). The answer is not read and a request that fails is ignored, so the `DELETE` never changes the walk's or call's result. Ending the vendor's session is not guaranteed: a vendor that refuses the `DELETE`, or never receives it, may keep the session open. Nothing about a session is stored on our side. A walk or call holds its session id only while it runs and never shares or reuses it, so a refresh, a reconnect or a removal has no stored session to drop.
361
+
362
+
**The trail keeps a failed vendor answer's status and first line** (TASK-263, 2026-10-22). When a vendor fails a hosted call with a JSON-RPC error, or with an HTTP error other than 401 or 403, at the pin check's list or at the call itself, the call's row keeps the HTTP status (`vendorStatus`) and a line of the error (`vendorError`). The direct path and an approved call's execution both write them (`toolBrokerService.ts:1341`, `:1424`). The line is the first line of a plain error body, or `JSON-RPC <code>: <message>` when the error comes as JSON (`vendorErrorLine`, `services/hostedMcpToolDefinitions.ts:174–194`). It is sanitized before it is kept: the row's token, `Bearer` values, the values of `access_token`, `refresh_token`, `client_secret` and `api_key` fields, and any run of 24 or more letters, digits, `_` or `-` are redacted, and it is cut to 200 characters (`:165–172`). An HTTP error whose body runs past 4096 characters, or is JSON that does not parse or has no string `error.message`, keeps its status and no line. Nothing else of the answer is kept: not the rest of the body, not its headers, and never the token. The broker strips both fields, and the JSON-RPC code, from the error a seat receives (`stripVendorDiagnostics`, `toolBrokerService.ts:1024–1031`); a JSON-RPC error on the call still names the vendor's message in that error, sanitized the same way. The code this paragraph and the one above cite is read at `main` `be32f102`.
363
+
364
+
**A parked call ends once, and its ending is trailed** (TASK-208, its cites re-read at `main``be32f102`). The park writes `pending_approval`, `approval_required` (`toolBrokerService.ts:1293`), and the ending writes one more row under the same `approvalId`:
365
+
366
+
- approved: the execution's own row, as for any call (`:1409`, or `:1424` when it throws): `ok`, `failed`, or `refused` with the code, whether a check of ours stops it before the provider or the vendor refuses it;
- expired: `refused`, `approval_expired`. The decide path writes it (`:734`). The usual path is the scheduler's five-minute sweep, `sweepExpiredToolCallApprovals` (`approvalActionService.ts:873`, scheduled at `schedulerService.ts:515`), which writes no row, so a call it expires ends with nothing in the trail after `pending_approval`. The build writes the same row there.
362
369
363
370
The seat learns each ending through §5's event, and the owner sees it on the card.
0 commit comments