Worker Thread
mcp.worker.ts is an independent Worker thread providing a bridge layer to manage MCP servers through the claude CLI. It complements McpService (SDK direct connection approach) by supporting reading MCP server records from Claude configuration files and performing add/remove/test operations via CLI.
File path: packages/desktop/app/main/workers/mcp.worker.ts
Architecture Diagram
graph LR
subgraph Main["Main Process"]
Router["McpRouter / Caller"]
end
subgraph Worker["Worker Thread"]
Port["MessagePort"]
McpWorkerClass["McpWorker"]
end
subgraph External["External"]
ClaudeCLI["claude CLI"]
ConfigFile["~/.claude/config.json"]
end
Router -->|postMessage| Port
Port -->|message event| McpWorkerClass
McpWorkerClass -->|spawn| ClaudeCLI
McpWorkerClass -->|readFile| ConfigFile
McpWorkerClass -->|postMessage| Port
Port -->|result/error| Router
Communication Protocol
Request Format
type WorkerAction =
| { id: number; action: 'list' }
| { id: number; action: 'add'; payload: McpServerInput }
| { id: number; action: 'addJson'; payload: McpServerJsonInput }
| { id: number; action: 'remove'; payload: McpServerRemoveInput }
| { id: number; action: 'test'; payload: McpServerRemoveInput }
| { id: number; action: 'discover'; payload: McpServerRemoveInput };
Response Format
type WorkerResponse =
| { id: number; result: unknown }
| { id: number; error: string };
Each request is paired with a response through the id field.
Supported Operations
| Action | Method | CLI Command | Description |
|---|---|---|---|
list | list() | None (read file directly) | Read server list from config.json |
add | add() | claude mcp add | Add server |
addJson | addJson() | claude mcp add-json | Batch add from JSON |
remove | remove() | claude mcp remove | Remove server |
test | test() | claude mcp test | Test connection |
discover | discover() | claude mcp inspect | Discover tools/resources/prompts |
list — Configuration File Reading
list() doesn't call the CLI but directly reads the ~/.claude/config.json file:
private async readClaudeConfig(): Promise<ClaudeConfig | null> {
const configPath = path.join(os.homedir(), '.claude', 'config.json');
const content = await fsp.readFile(configPath, 'utf8');
return JSON.parse(content);
}
Configuration File Structure
interface ClaudeConfig {
mcpServers?: Record<string, McpServerConfig & Record<string, unknown>>;
projects?: Record<string, {
mcpServers?: Record<string, McpServerConfig & Record<string, unknown>>;
}>;
}
Server Record Mapping
Records read from config.json need to be converted to internal format:
private toServerRecord(input): McpServerRecord {
// Transport type inference: transport > type > default 'stdio'
const transport = config.transport ?? config.type ?? 'stdio';
return {
id: `${scope}:${projectPath ?? 'global'}:${name}`,
name,
scope,
type: transport,
projectPath,
status: 'configured',
config
};
}
ID format: {scope}:{projectPath|global}:{name}
Scopes:
user— Globally configured servers (top levelmcpServers)local— Project-level configured servers (projects[path].mcpServers)
add — Add Server
Add servers via the claude mcp add CLI command:
Stdio Server
claude mcp add --scope user my-server \
-e API_KEY=xxx \
npx -y @example/mcp-server
Parameter construction:
['mcp', 'add', '--scope', scope, name]- Iterate
env, add-e KEY=VALUEparameters - Append
commandandargs
SSE/HTTP Server
claude mcp add --scope user my-server \
--transport sse \
--header "Authorization: Bearer token" \
https://api.example.com/mcp
Parameter construction:
['mcp', 'add', '--scope', scope, name]--transport sse|http- URL
- Iterate
headers, add--header "Key: Value"parameters
discover — Discover Capabilities
Use the claude mcp inspect command to get JSON format capability inventory:
claude mcp inspect --scope user my-server --format json
JSON Extraction
Extract JSON object from CLI output:
private extractJson(output: string) {
const match = /\{[\s\S]*\}/.exec(output);
if (!match) return null;
return JSON.parse(match[0]);
}
Result Normalization
Tools/resources/prompts lists are standardized to McpToolInfo[] format:
private normalizeToolList(value: any): McpToolInfo[] {
// Support string arrays and object arrays
// string → { name: string }
// { name, description } → McpToolInfo
}
Server Identifier Parsing
Server identifiers in Worker use scope:name format (different from McpService's UUID format):
private parseServerIdentifier(id?: string, scope?: string) {
const [prefix, ...rest] = id.split(':');
const name = rest.length ? rest.join(':') : prefix;
const normalizedScope = rest.length
? prefix as 'user' | 'local'
: scope ?? 'user';
return { scope: normalizedScope, name };
}
Examples:
user:my-server→{ scope: 'user', name: 'my-server' }local:/path/to/project:db-server→{ scope: 'local', name: '/path/to/project:db-server' }my-server(no prefix) →{ scope: 'user', name: 'my-server' }
CLI Execution
All CLI operations are executed via the runClaude() method:
private async runClaude(
args: string[],
options: { cwd?: string } = {}
): Promise<{ success: boolean; stdout: string; stderr: string }>
- Use
child_process.spawnto startclaudeprocess - Collect stdout and stderr
- Determine success/failure via process exit code
- Support working directory (
cwd) specification for local scope operations
Relationship Between Worker and McpService
| Feature | McpService (SDK Direct) | mcp.worker (CLI Bridge) |
|---|---|---|
| Connection Method | Direct use of MCP SDK | Through claude CLI |
| Process Model | Within main process | Independent Worker thread |
| Config Source | Electron Store (mcp-config) | ~/.claude/config.json |
| Tool Calling | Supported | Not supported |
| Purpose | Runtime connections and tool calling | Configuration management and CLI compatibility |
| Real-time | Real-time connection | On-demand operation |
Note: In the current version, McpService (SDK direct connection) is the primary runtime implementation. Worker serves as a supplement, providing configuration interoperability with Claude CLI. The configurations managed by both are independent (stored separately in Electron Store and ~/.claude/config.json).
Error Handling
Errors in the Worker thread are transmitted via the message protocol:
port.on('message', async (message: WorkerAction) => {
try {
// Handle request...
port.postMessage({ id: message.id, result: ... });
} catch (error) {
port.postMessage({
id: message.id,
error: error instanceof Error ? error.message : String(error)
});
}
});
All exceptions are caught and converted to error responses without crashing the Worker thread.
Related Files
- Connection Pool Management — McpService SDK direct connection implementation
- Tool Format Adapters — Tool format conversion
- Module Overview — Overall architecture