Repository navigation
MCP profiles 1: settings and the profile resolver (nothing calls it yet) - #227
scuttletoby wants to merge 3 commits into
Conversation
Adds the four MCP profile settings keys (mcpProfiles, mcpProfile, mcpSubagentProfile, mcpThreadProfiles) to the fork settings seam, their schemas in packages/contracts/src/mcpProfile.ts, a replaceEntries patch rule (per-entry replace, keep the 500 newest thread picks), and the pure resolver in apps/server/src/fork/mcpProfile.ts. Nothing calls the resolver yet, so behaviour is unchanged: every thread resolves to "all". FORK_PERSON_ONLY_KEYS (every fork key not agent-writable) replaces the hand-written PERSON_ONLY_KEYS test with one that loads every T3 toolkit and fails if any tool input names a person-only key in any spelling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review lens (correctness + state) on ac0d1bd: SAFE WITH NITSNothing calls the resolver yet, so there is no runtime change today. Fix 1-3 before a later PR wires the resolver in (1 and 2 bite now via the settings file).
Resolver order, fail-closed cases, CI is also red on this head: typecheck Next: one fix round (items 1-3, a note for 4, 6 decided, CI fixed), then a re-check. |
…ocs) - A bad MCP value in the settings file no longer fails the whole file: it reads as an unreadable marker the resolver refuses; null means not set. - Project references: save accepts what read keeps; only a changed value is checked, so old odd values never block unrelated project saves. - Saves refuse inherit where it means nothing and names with no profile, with a message that names the fix (write-fork-setting save error). - Every T3 tool maps to a group or the ungrouped list; previewControls is in preview; tool_search, request_secret, send_to_user are ungrouped. - Person-only test counts aliased Tool.make and checks every toolkit loads. - Docs rows and sections for the six new settings; FORK_CHANGES line. - Fixes the web typecheck (profiles patch type matches the read type). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix round 1Head 1. Lenient read. A bad MCP value no longer fails the whole settings file.
2. Project keys: read and save agree. Chose "save accepts what read keeps".
3. Saves refuse
4. 500-pick cap. No small safe fix (keeping live picks needs to know which threads exist, which is PR3). Added the row "oldest pick dropped → falls back to its role default; pruning picks of deleted threads in PR3" to the failure table; doc at 5. Person-only test.
6. Groups.
CI.
Checks run here: 10 related test files, 270 passed. Typecheck clean for contracts, shared, server and web. The commit went through the full pre-commit hook (fmt, lint, knip); knip found one needless export, which is fixed. The PR body's failure table now covers every change (lenient read, project resend, refused save, 500 cap, new tools). The PR stays a draft. |
Re-check lens (round 1) on d913a75: SAFE WITH NITSRead-only re-check of fix round 1 (code reading; no node_modules in the reviewer's checkout, so no tests run). Round-1 items: 1 lenient read FIXED · 2 project keys FIXED · 3 save refuses Findings, worst first
Next: a fix round 2 for N1 (and N2, N5) before merge. |
Why
PR 1 of the MCP profile work (#145): a profile says which outside MCP servers and which groups of T3's own tools a thread gets. This PR adds only the settings and the rules that decide a thread's profile. Nothing calls the resolver yet, so nothing changes. Every thread still gets everything (
all). The provider adapters and the T3 tool filter come in later PRs.What is in it:
forkSettings.ts, from fork: delegate checks behind one fork module (SEAM) #174):mcpProfiles: named profiles.mcpProfile: the default profile,all.mcpSubagentProfile: the default for delegated children,inherit.mcpThreadProfiles: the owner's pick per thread.mcpProfileandmcpSubagentProfile.forkDefaults.ts, all off, and indocs/fork/README.mdandFORK_CHANGES.md.packages/contracts/src/mcpProfile.ts. Built-in profiles areallandt3-only. There are 12 T3 tool groups.waitsis new since the design: the fork's waits toolkit came later.(unreadable number). No profile can have that name, so a thread that would use it is refused, never widened toall.nullmeans "not set". A map that is not an object reads as empty.forkSettingsWriteProbleminpackages/shared/src/forkSettingsPatch.ts). A save is refused, with a message that names the fix, when it brings in:inheritwhere it means nothing (the default profile, a thread pick, a project's default);Only new or changed values are checked, so an old odd value never blocks an unrelated save.
apps/server/src/fork/mcpProfile.ts, which decides in this order:mcpSubagentProfilefor a delegated child, unless it isinherit; otherwisemcpProfile. A project's override wins over the server's.MCP_T3_TOOLKIT_GROUPS).previewControlsis inpreview.tool_search,request_secretandsend_to_userare ungrouped on purpose (MCP_T3_UNGROUPED_TOOLS): they are how a thread finds tools and reaches the person, not one feature. The T3 tool filter PR decides how profiles treat them.replaceEntriesinforkSettingsPatch.ts. Each entry is replaced whole, so two windows that save different picks at the same time both keep theirs. Thread picks keep the 500 newest.FORK_PERSON_ONLY_KEYS. It is every fork key exceptFORK_AGENT_WRITABLE_KEYS, so a new fork key is covered without a new row. Later PRs (M1) use it too.mcp_profile,MCP-Profile) at any depth. It replaces the old hand-writtenPERSON_ONLY_KEYStest. It loads every toolkit file and checks that each one's tools are all found. It countsTool.makeunder any import alias (Tool as T,make as m,* as Ai), and fails if aTool.makeappears outside the toolkit folders without a listed reason.Project keys: why save accepts what read keeps
A project's
mcpProfileandmcpSubagentProfileare read as any text, kept as written (only trimmed). Clients send a project's whole override back when they save its scripts or provider. So the save takes any text too, and the server refuses only a value that changed. The other choice was to turn""or odd case into "unset" on save. That would quietly change what the owner wrote, and turning a refused name likeLeaninto "unset" would widen that project to the server's default. Keeping the value means it stays refused until the owner fixes it, and unrelated saves still work.Upstream files touched
Two small seams, each marked
Fork:and pinned by a test:packages/contracts/src/settings.ts: awrite-fork-settingsave error with adetailmessage (3 lines plus comments).apps/server/src/serverSettings.ts: one call to the save check in each write path (the real one and the test one).What can go wrong (failure table)
What happens when this runs again, stops half-way, or meets existing, old or empty state?
{},all,inherit,{}. Every thread resolves toall, which is today's behaviour. Tested."", blank, missing,null)nullthread pick is dropped. Tested."mcpProfile": 5, a list, an object, a map that is not an object)(unreadable …), which the resolver refuses for any thread that uses it. Tested on the real settings layer."",Lean, a marker)inheritwhere it means nothingmcpProfile names MCP profile "lean", which is not set up: add that profile first, or pick "all" or "t3-only".Tested.all. Tested.allfor this thread, or fix the profile in Settings. Never falls back toall(design §9, #101).unknownToolGroupsso the UI can show it.inheritas a profile name, bad name, unknown or missing field, unknown group, blank server, bad thread id)all. A child the owner picked for still starts. Tested.Deferred to later PRs: refusing to delete a profile still in use, dropping picks for deleted threads (PR3), the server-side person-only write guard, and the "server not set up" note (PR2/PR3).
General vs project
Tests
packages/contracts/src/mcpProfile.test.ts: reading the file (old, unreadable, bad values, trim, project overrides) and refusing bad patches.apps/server/src/fork/mcpProfile.test.ts: table cases for every case in design §9, plus unreadable values.packages/shared/src/forkSettingsPatch.test.ts:replaceEntries(two saves at once, removing, newest order, keep 500, replace whole, end to end) and the save check.apps/server/src/serverSettings.forkSettings.test.ts: a file with bad MCP values loads and keeps every other setting; a refused save shows its message and writes nothing.apps/server/src/fork/personOnlySettings.test.ts: list, finding every T3 tool, every toolkit file loaded, aliasedTool.make, no person-only key in any input, every tool in a group or the ungrouped list.packages/shared/src/forkDocs.test.ts: the new settings have rows and sections.🤖 Generated with Claude Code