feat(plugins): MCP plugin management, agent injection, and custom MCP import#261
feat(plugins): MCP plugin management, agent injection, and custom MCP import#261lucas77778 wants to merge 22 commits into
Conversation
This reverts commit 2833562.
This reverts commit 7b57c75.
This reverts commit 36da7b2.
This reverts commit 64df55c.
… service A plugin now composes one or more MCP servers; each server names its service and resolves its credential through the global service->binding map instead of a per-unit connector reference. Wire protocol 45.
…n warnings AGENT_MCP_CAPABLE replaces the resolver-private agent set so clients read the same truth. The resolver skips a declaredly expired local credential with the new expired-credential warning reason (wire 47), and start diagnostics now live on LiveSession so session.attach replays them to late attachers.
Deduped by unit+service+reason so attach replays never stack while distinct reasons for one dependency all stay visible.
Pure derivation from catalog + public config, mirroring the resolver's verdict vocabulary; the dev mock now fails invalid connector operations with the daemon's sanitized public message.
Card list of catalog units on a new Tools tab in both apps: localized label/description, enable switch writing PluginConfigSet.units, and per-server status badges derived from the plugin view model.
…ns (CODE-93) claude: Options.mcpServers record (spawn-time --mcp-config; merges with the user's own MCP config). codex: thread/start|resume config.mcp_servers (no type tag, headers->http_headers; startup failures surface as recoverable errors). opencode: per-session spawn Config.mcp (local/remote, env->environment, explicit enabled). Live-verified on claude 2.1.218: an injected stdio MCP server's tool was called end to end through a LinkCode session.
Adds a BYO MCP tier alongside the catalog: CustomMcpServer ({id,
enabled, server}) in the plugin config, injected into agent sessions
through the same resolved StartOptions.mcpServers path. Credentials are
inline and opaque to LinkCode — the public projection masks env/header
values to key lists, and a custom name colliding with a catalog server
is rejected at write time. The resolver shares the capability gate and
client-name precedence with catalog servers; plugin-warning gained a
custom-server source. A Tools-tab section imports/toggles/removes them.
Wire protocol 48.
Live-verified on claude 2.1.218: a custom stdio server imported through
the SDK was injected and its tool called end to end; the webview form
adds a server, shows only the masked credential key, and removes it.
…path Adversarial review found the name-collision invariant lived only in the engine's config.set guard: the dev-mock reimplementation skipped it (breaking engine parity), and nothing defended a bad-state config.json at resolution. The resolver now skips a custom server whose name is already taken (client / catalog / earlier custom) so it can never silently clobber another in the adapter's name-keyed MCP config, and the dev-mock now rejects colliding names like the daemon.
Greptile SummaryAdds daemon-backed MCP plugin management and injects resolved catalog and custom servers into supported agent sessions.
Confidence Score: 4/5The custom MCP import path should be fixed before merging because it can silently remove entered credentials and rejects names accepted by the shared server schema. Malformed environment-variable or header rows are ignored during form conversion, so an imported server can be stored without credentials the user entered and then fail when injected. packages/client/workbench/src/settings/plugins/custom-server-dialog.tsx also prevents importing schema-valid dotted names.
What T-Rex did
Important Files Changed
Sequence DiagramsequenceDiagram
participant U as Tools UI
participant C as Client SDK
participant D as Daemon config
participant R as MCP resolver
participant A as Agent adapter
U->>C: Add or enable MCP server
C->>D: config.set
D-->>C: Masked config projection
C-->>U: Refreshed Tools state
R->>D: Read plugin configuration
R->>R: Resolve bindings, credentials, capability, and names
R->>A: StartOptions.mcpServers
A->>A: Translate to provider-specific MCP configuration
Reviews (1): Last reviewed commit: "fix(plugins): enforce custom-server name..." | Re-trigger Greptile |
| const at = trimmed.indexOf(separator); | ||
| if (at <= 0) continue; | ||
| pairs[trimmed.slice(0, at).trim()] = trimmed.slice(at + 1).trim(); |
There was a problem hiding this comment.
| name: z | ||
| .string() | ||
| .trim() | ||
| .regex(/^[\w-]+$/, 'name'), |
There was a problem hiding this comment.
UI rejects schema-valid server names
The dialog restricts names to word characters and hyphens even though McpServerSchema accepts arbitrary strings, preventing users from importing otherwise valid names such as my.server through the Tools UI.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Builds out the Connector/plugin batch on the daemon and clients: a redesigned plugin schema, MCP injection into agent sessions, the Tools settings surface, and user-imported custom MCP servers.
What's in here
Schema redesign (CODE-382) — a plugin composes 1..N MCP servers (
McpPluginDescriptor.servers[]), and connector association moved from a per-unit binding to a global per-serviceserviceBindingsmap. Secrets never cross the wire (masked public projections).MCP capability + warnings (CODE-385) —
AGENT_MCP_CAPABLElives in@linkcode/schema(engine and clients read one truth); the resolver skips declaredly-expired credentials (expired-credential); plugin warnings are kept onLiveSessionand replayed onsession.attach; client-core folds them intoConversationViewModel.pluginWarnings.Data plane + Tools UI (CODE-386 / CODE-387) — workbench view models (
pluginUnitViews/pluginServiceViews/customServerViews), and a card-based Tools tab in desktop + webview: capability units with enablement + per-server status badges.MCP injection into agents (CODE-93) — claude / codex / opencode now consume
StartOptions.mcpServers(each via its own SDK channel; pi excluded by the capability matrix). Verified live on claude 2.1.218 — an injected stdio MCP server's tool was called end to end.Custom MCP import (CODE-407) — users add their own MCP servers on the Tools tab. Credentials are inline and masked to key-only on
config.get; the resolver injects enabled ones, sharing the capability gate and client-name precedence; name-uniqueness is enforced at write time, at resolution, and mirrored in the dev mock.Also merges
master(telemetry batch, chat-card refactor, CODE-388/391 data-plane work). Wire protocol 43 → 48 — daemon and every client must rebuild/restart together.Verification
pnpm check:ciclean, 1846 vitest passing. Live E2E: MCP injection (catalog + custom) drove a real claude session end to end; the Tools tab add/mask/remove flow verified in webview via Playwright. Two adversarial review passes (workflow) — confirmed findings fixed (expired-credential injection, fold dedup, dev-mock name-collision parity, resolver dedup defense-in-depth).Notes for review
packages/foundation/schema/src/wire/message.tscomments (44 collision → 46, then 47/48).serverreplacement for a future edit form).