Add global MCP/LSP settings, per-session Activity toggles, sidecar runtime integration, tests, and documentation updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
20 KiB
MCP / LSP Support Implementation Plan
Status: implemented v1 architecture reference
Intended execution order:
- Backend agent executes backend phases first.
- Frontend agent executes frontend phases second.
- Frontend work should begin only after the backend contracts, persistence, and diagnostics are stable.
Objective
Add first-class support for MCP servers and LSP-backed project intelligence in Eryx without fighting the existing architecture. The design should preserve Eryx's current strengths:
- Electron main as the desktop control plane
- preload + typed IPC as the renderer boundary
- shared TS contracts mirrored into the .NET sidecar
- .NET sidecar as the agent runtime
- OS keychain storage for secrets
This plan is intentionally backend-first and split into backend/frontend task groups so different agents can execute them in order.
The clarified product direction for v1 is:
- users add MCPs and LSPs globally in app settings
- users enable or disable those global MCPs/LSPs per session in the right-side panel that is currently used for activity
- enabled tooling becomes available to the agent for that session
Current state analysis
Application architecture today
src/main/EryxAppService.ts- central orchestration layer for workspace persistence, project/session lifecycle, sidecar calls, git refresh, and event emission
src/main/ipc/registerIpcHandlers.ts- single place where Electron IPC methods are registered
src/preload/index.ts- exposes the typed
eryxApibridge to the renderer
- exposes the typed
src/renderer/App.tsx- loads workspace + sidecar capabilities and wires subscriptions into the UI
src/shared/contracts/ipc.ts- typed renderer/main API contract
src/shared/contracts/sidecar.ts- typed main/sidecar protocol contract
sidecar/src/Eryx.AgentHost/Contracts/ProtocolModels.cs- C# mirror of the sidecar DTOs
sidecar/src/Eryx.AgentHost/Services/SidecarProtocolHost.cs- sidecar command dispatcher and capability provider
sidecar/src/Eryx.AgentHost/Services/CopilotWorkflowRunner.cs- actual agent runtime construction and activity emission
Signals that matter for MCP
CopilotWorkflowRunner.csalready loadsMcpServerToolCallContentvia reflection and reports MCP tool names throughTryGetToolName(...).CopilotWorkflowRunner.csbuilds aSessionConfigfor each agent and already ownsAvailableTools,WorkingDirectory, andOnPermissionRequest.- Scratchpad explicitly clears
AvailableTools, so scratchpad is currently a "no tools" product surface.
Signals that matter for LSP
package.jsonincludestypescript-language-serverand anlsp:typescriptscript, which is a good TypeScript-first bootstrap hint.- For v1, the implemented design starts LSP processes inside the sidecar as turn-scoped tool sessions so they can surface directly as agent tools without inventing a second bridge layer.
- The current session's project root remains the source of truth for LSP initialization.
Persistence and secrets today
src/main/persistence/workspaceRepository.tspersists a single additiveworkspace.json.src/shared/domain/workspace.tsdefines the root persisted shape.src/shared/domain/session.tsalready carries session-scoped overrides such asscratchpadConfig, which makes it the natural place for per-session MCP/LSP enablement.src/main/secrets/secretStore.tsalready wrapskeytar, so secrets should stay in the OS keychain and never be stored inworkspace.json.
Activity and diagnostics today
src/shared/domain/event.tsonly exposes coarse activity today:thinking,tool-calling,handoff,completed.src/renderer/components/ActivityPanel.tsxcan already show per-agent activity but not rich MCP/LSP traces.src/renderer/components/CopilotStatusCard.tsxandSettingsPanel.tsxalready establish a diagnostics-first UX pattern worth reusing for MCP/LSP health surfaces.
Recommended design decisions
1. Put global tooling definitions in workspace settings
Additive, non-secret config should live in WorkspaceState, for example through a new settings.tooling section.
Illustrative shape:
interface WorkspaceState {
projects: ProjectRecord[];
patterns: PatternDefinition[];
sessions: SessionRecord[];
selectedProjectId?: string;
selectedPatternId?: string;
selectedSessionId?: string;
lastUpdatedAt: string;
settings?: WorkspaceSettings;
}
interface WorkspaceSettings {
tooling?: {
mcpServers?: McpServerDefinition[];
lspProfiles?: LspProfileDefinition[];
};
}
Why this fits:
- it follows the current single-workspace persistence model
- in the current architecture, that single local workspace file already behaves like the app-wide store on this computer
- it remains backward compatible because the new fields are optional
- it keeps MCP/LSP definitions reusable across future sessions
2. Put enable/disable state on SessionRecord
The user wants MCP/LSP enablement to be controlled per session from the right-side panel. That means the durable selection should live on SessionRecord, not on ProjectRecord.
Illustrative shape:
interface SessionRecord {
id: string;
projectId: string;
patternId: string;
title: string;
titleSource?: SessionTitleSource;
createdAt: string;
updatedAt: string;
status: SessionStatus;
isPinned?: boolean;
isArchived?: boolean;
messages: ChatMessageRecord[];
lastError?: string;
scratchpadConfig?: ScratchpadSessionConfig;
tooling?: {
enabledMcpServerIds?: string[];
enabledLspProfileIds?: string[];
};
}
How this should behave:
- the settings screen owns the global registry of MCP and LSP definitions
- the session panel owns which global definitions are enabled for the current session
- the session's
projectIdprovides the project root for LSP initialization when the session is project-backed - scratchpad sessions should remain tool-free unless explicitly expanded later
3. Keep secrets out of persisted JSON
Use src/main/secrets/secretStore.ts for secret material. Persist only references in workspace config.
Recommended rule set:
workspace.jsonstores server/profile definitions and secret referenceskeytarstores secret values- runtime logs, process IDs, and raw diagnostics stay out of
workspace.json
4. MCP belongs in the .NET sidecar runtime
The sidecar already owns agent session creation, tool attachment, permission callbacks, and tool-call activity detection. MCP should build on that instead of inventing a second runtime stack in Electron or the renderer.
Recommended flow:
- Electron main resolves session-enabled MCP definitions.
EryxAppService.sendSessionMessage()enriches therun-turnpayload with runtime-ready MCP config.- The sidecar attaches MCP tools during
SessionConfigcreation. - The sidecar emits richer activity and error events back through the existing streaming channel.
5. LSP process ownership should start in the sidecar for v1
Implemented v1 LSP ownership:
- Electron main persists global definitions and resolves per-session selections.
- The sidecar starts the selected LSP server processes for a run, initializes them with the active project root, and exposes a focused tool surface through
AIFunctions. - The renderer only consumes typed config and per-session enablement state.
Why this shipped path fits v1:
- it keeps LSP capabilities closest to the agent tool runtime
- it avoids building a second transport bridge between Electron main and the sidecar before the tool surface is proven
- it preserves the user-facing model of "enable a tool for this session and let the agent use it"
6. Prefer one user-facing tooling model
The long-term product should not feel like "MCP over here, raw LSP over there" from a UX perspective.
Recommended path:
- implement direct MCP runtime support first
- implement LSP as a backend capability service second
- where feasible, expose LSP-backed actions to agents through MCP-like or tool-like abstractions instead of teaching the system two unrelated tool invocation models
Proposed data models
The exact names can change, but the structure should follow these boundaries.
MCP definitions
interface McpServerDefinition {
id: string;
name: string;
transport: 'stdio' | 'sse' | 'streamable-http';
command?: string;
args?: string[];
url?: string;
env?: McpEnvironmentBinding[];
cwdStrategy?: 'project-root' | 'custom';
customCwd?: string;
enabled?: boolean;
}
interface McpEnvironmentBinding {
key: string;
valueSource: 'literal' | 'secret-ref';
value?: string;
secretRef?: string;
}
LSP profiles
interface LspProfileDefinition {
id: string;
name: string;
languageIds: string[];
transport: 'stdio';
command: string;
args?: string[];
initialization?: {
rootStrategy: 'project-root' | 'workspace-roots';
initializationOptions?: Record<string, unknown>;
};
enabled?: boolean;
}
Runtime diagnostics
interface ToolingHealthSummary {
status: 'ready' | 'warning' | 'error' | 'disabled';
summary: string;
detail?: string;
checkedAt: string;
}
Use separate summaries for:
- workspace MCP health
- current session MCP enablement health
- LSP profile health
- current session LSP enablement health
Backend execution plan
Backend must be executed first.
B0. Domain and persistence foundation
Primary files:
src/shared/domain/workspace.tssrc/shared/domain/session.tssrc/main/persistence/workspaceRepository.tstests/shared/workspace.test.tstests/shared/session.test.ts
Tasks:
- add optional workspace-level tooling settings
- add optional session-level tooling enablement
- keep persisted migration additive and backward compatible
- ensure legacy workspaces load without mutation errors
- do not persist any secret values
Acceptance:
- old workspaces still load
- new fields round-trip through persistence
- tests cover legacy and new shapes
B1. Main-process tooling services and IPC
Primary files:
src/main/EryxAppService.tssrc/shared/contracts/ipc.tssrc/shared/contracts/channels.tssrc/main/ipc/registerIpcHandlers.tssrc/preload/index.ts
Recommended additions:
- CRUD for MCP server definitions
- CRUD for LSP profiles
- session enable/disable methods for both systems
- diagnostics/status methods
- secret-reference save/delete flows that route through
SecretStore
Acceptance:
- renderer gets a typed, renderer-safe API
- all config changes persist through
WorkspaceRepository - diagnostics can be queried without starting a session
- session enablement can be updated without mutating global definitions
B2. Sidecar protocol expansion for MCP
Primary files:
src/shared/contracts/sidecar.tssidecar/src/Eryx.AgentHost/Contracts/ProtocolModels.cssidecar/src/Eryx.AgentHost/Services/SidecarProtocolHost.cssrc/main/sidecar/sidecarProcess.tssidecar/tests/Eryx.AgentHost.Tests/SidecarProtocolHostTests.cs
Tasks:
- extend capabilities with MCP availability/diagnostics
- extend
run-turnwith selected MCP runtime bindings - optionally add dedicated MCP health validation commands if needed
- keep the protocol additive to avoid breaking existing clients
Acceptance:
- TS and C# contracts remain in sync
- protocol tests cover the new shapes
- existing capabilities and run-turn behavior still work without MCP config
B3. MCP runtime attachment in the sidecar
Primary files:
sidecar/src/Eryx.AgentHost/Services/CopilotWorkflowRunner.cs- new helper services under
sidecar/src/Eryx.AgentHost/Services/ sidecar/tests/Eryx.AgentHost.Tests/CopilotWorkflowRunnerTests.cs
Tasks:
- translate main-process MCP definitions into runtime-attached tools
- attach tools during
SessionConfigcreation - handle startup failures and unsupported transports cleanly
- emit richer activity when tool/server identity matters
- revisit the current blanket
ApprovePermissionAsync(...)behavior before enabling arbitrary external tools
Acceptance:
- project-backed runs can invoke enabled MCP tools
- tool failures surface clearly to the app
- scratchpad behavior remains intentionally restricted unless product scope changes
B4. LSP tool sessions in the sidecar
Primary files:
sidecar/src/Eryx.AgentHost/Services/LspToolSession.cssidecar/src/Eryx.AgentHost/Services/SessionToolingBundle.cspackage.jsonsidecar/tests/Eryx.AgentHost.Tests/*
Tasks:
- start selected LSP server processes inside the sidecar for the active run
- initialize them against the current session project root
- expose a focused read-oriented tool surface to agents
- keep lifecycle cleanup bound to the run/session bundle
- start with TypeScript-first support
Acceptance:
- a project-backed session can initialize at least one supported LSP profile
- agents can call the exposed LSP tools without any renderer-managed process lifecycle
- no raw LSP JSON-RPC is exposed to the renderer
B5. LSP bridge into agent runtime
Primary files:
src/main/EryxAppService.tssrc/shared/contracts/sidecar.tssidecar/src/Eryx.AgentHost/Contracts/ProtocolModels.cssidecar/src/Eryx.AgentHost/Services/SessionToolingBundle.cssidecar/src/Eryx.AgentHost/Services/LspToolSession.cs
Tasks:
- compose selected LSP profiles into the
run-turnpayload from Electron main - translate those profiles into sidecar-local tool sessions
- expose a stable subset of LSP-backed operations to agents
- avoid turning the renderer into a second IDE protocol stack
Recommended direction:
- use sidecar-local
AIFunctiontools for agent consumption - keep the exposed operations narrow and project-oriented
Acceptance:
- agents can request useful LSP-backed project intelligence during project runs
- the contract is observable and testable from the app side
B6. Backend hardening and validation
Primary files:
- impacted TS and C# tests
package.json
Validation commands:
bun testbun run sidecar:testbun run build
Hardening checklist:
- additive migration coverage
- bad configuration/error reporting coverage
- environment sanitization review for spawned tools/servers
- sidecar protocol regression coverage
Frontend execution plan
Frontend should start only after backend APIs and diagnostics are stable.
F1. Read-only diagnostics surfaces
Primary files:
src/renderer/App.tsxsrc/renderer/components/SettingsPanel.tsxsrc/renderer/components/CopilotStatusCard.tsx
Tasks:
- add MCP and LSP sections in settings
- surface backend-reported health, readiness, and counts
- reuse the current diagnostics-card style before introducing complex editors
Acceptance:
- users can see whether MCP/LSP are configured and healthy
- failures are understandable without opening logs
F2. Global configuration editors
Primary files:
src/renderer/components/SettingsPanel.tsx- new renderer components for MCP/LSP editors
src/renderer/App.tsx
Tasks:
- create/edit/delete MCP server definitions
- create/edit/delete LSP profiles
- manage secret references safely
- do not mix global definition editing with per-session toggles
- allow secret references without ever echoing raw secrets back into the renderer state
Acceptance:
- all backend CRUD flows are reachable through the UI
- global definitions are understandable and recoverable
F3. Session tooling panel controls
Primary files:
src/renderer/components/ActivityPanel.tsxsrc/renderer/App.tsx- supporting renderer state/helpers
Tasks:
- extend the current right-side activity panel with MCP/LSP enable/disable controls for the selected session
- show which global tools are available vs enabled for the session
- disable LSP/MCP controls when the selected session cannot use them, such as scratchpad if that restriction remains
- surface lightweight health/state next to each toggle
Acceptance:
- users can enable or disable global MCPs/LSPs per session from the right-side panel
- session toggles are persisted on the session record and survive app reloads
F4. Runtime visibility in chat/activity surfaces
Primary files:
src/renderer/components/ActivityPanel.tsxsrc/renderer/lib/sessionActivity.tssrc/shared/domain/event.ts- possibly
src/renderer/components/ChatPane.tsx
Tasks:
- show tool server name + tool name clearly
- distinguish MCP activity from LSP-backed activity if both appear
- show meaningful failure summaries
- leave room for a future richer timeline without blocking this phase
Acceptance:
- users can tell what tool/server was used during a run
- failures do not collapse into generic "tool-calling" noise
F5. Session-level polish and troubleshooting UX
Primary files:
src/renderer/components/ActivityPanel.tsxsrc/renderer/components/ChatPane.tsx- any supporting session detail surfaces
Tasks:
- show current session tooling status clearly
- add "why unavailable?" and "fix configuration" entry points
- optionally add compact badges or chips for active MCP/LSP selections
Acceptance:
- session-scoped tooling is visible without leaving the main workflow
- troubleshooting paths are discoverable
Handoff between backend and frontend agents
Backend agent handoff deliverables
The backend pass should hand off:
- finalized shared domain shapes
- finalized IPC contract
- finalized sidecar contract changes
- clear diagnostics payloads
- scratchpad/tool policy behavior
- finalized session enablement state shape
- test coverage for persistence, protocol, and runtime behavior
Frontend agent prerequisites
The frontend agent should not start until it has:
- stable backend CRUD endpoints
- stable health/diagnostic payloads
- stable activity event shapes
- at least one working MCP path and one working LSP profile path to design against
- stable session enable/disable semantics for the right-side panel
Risks and decisions to settle early
A. Define what "LSP support" means in v1
This is the biggest scope decision. Options include:
- agent-facing project intelligence only
- agent-facing intelligence plus light read-only diagnostics in the UI
- a much larger editor-like in-app LSP experience
Recommended default:
- agent-facing project intelligence first
- diagnostics UI second
- editor-like UX later
B. Decide the scratchpad policy
Current behavior is strongly tool-free. Keep that default unless product scope explicitly changes.
C. Decide the default session behavior
Need to decide whether new sessions start with:
- no MCPs/LSPs enabled by default
- a safe recommended subset enabled by default
- the same enablement as the previously selected session
Recommended default:
- start disabled by default until a session explicitly enables a tool
D. Decide provisioning and trust
Need explicit decisions for:
- bundled vs user-installed MCP/LSP servers
- which transports are allowed in v1
- whether arbitrary commands are allowed
- whether write-capable tools need approval
- whether LSP starts TypeScript-only first
E. Decide the approval/safety model
ApprovePermissionAsync(...) currently auto-approves. That is not a sufficient trust model for arbitrary MCP tooling. A v1 safety story is needed before enabling broad external tool execution.
F. Decide observability depth
Current activity events are coarse. MCP/LSP may require richer activity or trace events to stay understandable.
Recommended sequencing summary
- B0: persisted vocabulary and migration-safe shapes
- B1: main-process API and diagnostics
- B2: sidecar protocol expansion
- B3: MCP runtime support
- B4: Electron-main LSP service
- B5: LSP bridge into agent runtime
- F1-F5: frontend diagnostics, global editors, session toggles, visibility, and polish
Out of scope for the first implementation pass
- a full IDE/editor inside Eryx
- raw LSP transport in the renderer
- persisted secret values in workspace files
- broad packaging/install automation for every possible LSP/MCP ecosystem on day one
Suggested validation gate before frontend begins
Before handing to the frontend agent, the backend agent should be able to demonstrate:
- additive workspace persistence for MCP/LSP config
- at least one working MCP-backed agent flow in a project session
- one initialized, health-reporting LSP profile in Electron main
- per-session enable/disable persistence for selected MCPs/LSPs
- stable diagnostics payloads exposed to the renderer
- passing
bun test,bun run sidecar:test, andbun run build