Skip to content

feat(plugins): MCP plugin management, agent injection, and custom MCP import#261

Draft
lucas77778 wants to merge 24 commits into
masterfrom
chenyu/code-387
Draft

feat(plugins): MCP plugin management, agent injection, and custom MCP import#261
lucas77778 wants to merge 24 commits into
masterfrom
chenyu/code-387

Conversation

@lucas77778

Copy link
Copy Markdown
Member

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-service serviceBindings map. Secrets never cross the wire (masked public projections).

MCP capability + warnings (CODE-385)AGENT_MCP_CAPABLE lives in @linkcode/schema (engine and clients read one truth); the resolver skips declaredly-expired credentials (expired-credential); plugin warnings are kept on LiveSession and replayed on session.attach; client-core folds them into ConversationViewModel.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:ci clean, 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

  • Merge conflict resolution for the parallel wire bumps is recorded in packages/foundation/schema/src/wire/message.ts comments (44 collision → 46, then 47/48).
  • Custom MCP v1 editing = toggle + remove (engine already supports wholesale server replacement for a future edit form).
  • Follow-ups recorded on the issues: codex MCP approval/elicitation server-requests unhandled (CODE-93); managed/broker path still stubbed pending CODE-96/94/340.

… 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.
Master's 44 (CODE-388/391) and this branch's 44/45 (CODE-382) were
parallel bumps with distinct schemas; the merged schema takes 46.
…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.
Copilot AI review requested due to automatic review settings July 23, 2026 08:10
@linear-code

linear-code Bot commented Jul 23, 2026

Copy link
Copy Markdown

CODE-387

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

Adds end-to-end MCP plugin and custom-server management across the daemon, wire protocol, clients, settings UI, and supported agent adapters.

  • Introduces plugin, connector, custom-server, warning, and capability schemas with persisted daemon configuration and public secret-masked projections.
  • Resolves enabled catalog and custom MCP servers into agent sessions for Claude, Codex, and OpenCode.
  • Adds shared desktop and webview Tools settings surfaces for viewing, importing, toggling, and removing MCP servers.
  • Extends client state, SDK operations, development mocks, localization, and tests for the new plugin data plane.

Confidence Score: 4/5

The PR needs a fix before merging because the custom-server form can silently save unintended credentials when duplicate environment-variable or header keys are entered.

Duplicate credential keys pass form validation and are collapsed by last-write-wins parsing, causing accepted user input to be silently discarded before the MCP server configuration is persisted.

packages/client/workbench/src/settings/plugins/custom-server-dialog.tsx

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex reproduced that duplicate credential keys overwrite silently, producing an onSubmit payload with only env.TOKEN=[REDACTED] and closing the dialog (proof 0).
  • Playwright exercised the real webview mock at /settings/plugins, submitted the duplicate TOKEN entries, and verified the persisted server card shows a single Credentials: TOKEN entry (proof 0).
  • Baseline render and the add/masked-card/remove interaction were recorded on two videos, documenting the real flow (proof 1).
  • Three screenshots captured baseline, successful masked import, and restored empty state, illustrating UI states through the flow (proof 1).
  • Browser logs marked each assertion milestone with exit code 0, and the server log confirms Vite mock mode served the app, validating the run (proof 1).

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
packages/client/workbench/src/settings/plugins/custom-server-dialog.tsx Adds the custom MCP import form and malformed-line validation, but duplicate credential keys still pass validation and are silently overwritten during parsing.
packages/foundation/schema/src/model/plugin.ts Defines plugin catalogs, connectors, custom MCP servers, public projections, mutations, and warning contracts.
packages/host/engine/src/agent/provider-config.ts Applies plugin configuration mutations and produces the secret-masked public configuration returned to clients.
packages/host/engine/src/session/start-options-resolver.ts Resolves enabled catalog and custom MCP servers into agent start options with capability and credential checks.
packages/host/agent-adapter/src/native/claude-code.ts Injects resolved MCP servers through the Claude agent integration.
packages/host/agent-adapter/src/native/codex/adapter.ts Injects resolved MCP servers through the Codex agent integration.
packages/host/agent-adapter/src/native/opencode/adapter.ts Injects resolved MCP servers through the OpenCode agent integration.

Sequence Diagram

sequenceDiagram
participant U as Tools UI
participant C as Client SDK
participant D as Daemon
participant E as Engine resolver
participant A as Agent adapter
U->>C: Add or configure MCP server
C->>D: config.set
D->>D: Validate and persist plugin config
D-->>C: Public masked config
C-->>U: Render tool status
E->>D: Read enabled plugin configuration
E->>E: Resolve bindings and warnings
E->>A: Start session with mcpServers
Loading

Reviews (2): Last reviewed commit: "fix(plugins): validate custom MCP creden..." | Re-trigger Greptile

Comment thread packages/client/workbench/src/settings/plugins/custom-server-dialog.tsx Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 04:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@lucas77778
lucas77778 marked this pull request as draft July 24, 2026 04:16
if (trimmed === '') continue;
const at = trimmed.indexOf(separator);
if (at <= 0) continue;
pairs[trimmed.slice(0, at).trim()] = trimmed.slice(at + 1).trim();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Duplicate credential keys overwrite silently

When a user enters the same environment-variable or HTTP-header key more than once, every line passes validation but parsePairs assigns them to the same object property. The later value silently replaces the earlier one, causing the imported MCP server to authenticate with unintended credentials or start with incorrect configuration despite the form reporting success.

Artifacts

Repro: focused CustomServerDialog Vitest harness

  • Evidence file captured while the check ran.

Repro: focused Vitest configuration

  • Evidence file captured while the check ran.

Repro: passing runtime output showing no validation error and last-write-wins onSubmit payload

  • The full command output behind this check.

Repro: Playwright script exercising the real webview mock form

  • Evidence file captured while the check ran.

Repro: successful Playwright execution log

  • The full command output behind this check.

▶ Recording of the check

  • The flow T-Rex ran, recorded end to end.

Before: Custom Server Empty Form

  • What the screen looked like at this point in the check.

▶ Recording of the check

  • The flow T-Rex ran, recorded end to end.

Duplicate Credential UI Repro Poster

  • What the screen looked like at this point in the check.

Duplicate Credential UI Populated

  • What the screen looked like at this point in the check.

Duplicate Credential UI Submitted

  • What the screen looked like at this point in the check.

View artifacts

T-Rex Ran code and verified through T-Rex

Copilot AI review requested due to automatic review settings July 24, 2026 04:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants