T-130: MCP server over HTTP+SSE for /ide integration
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
Seventh slice of T-99. clide now advertises itself to Claude Code's /ide command and serves a working MCP endpoint over HTTP+SSE per D-73 (the Q-33 transport decision, locked in this commit). What lands: * D-73 — MCP transport for /ide is SSE over HTTP. Resolves Q-33; references D-68 + D-72. * lib/src/ipc/mcp_server.dart — McpServer class. localhost HTTP listener on a random port; GET /sse opens a long-lived SSE stream with an initial endpoint event carrying the session id; POST /messages?sessionId=... accepts JSON-RPC requests and replies via the matching SSE stream. JSON-RPC handlers for initialize, tools/list, tools/call. * Discovery file at $HOME/.claude/ide/<pid>.lock with the workspace + url so `/ide` can find us. Removed on stop. * The two /ide minimum tools (mcp__ide__getDiagnostics, mcp__ide__executeCode) ship as stubs — real implementations need the analyzer integration / a clide eval surface, both follow-ups. * main.dart starts the MCP server alongside the unix IPC server on daemonClientFactory and project switch. Failure non-fatal — the UI runs without MCP. * 12 server tests cover lifecycle (start/stop, lock file), unknown paths, full JSON-RPC round-trip for all four methods, error responses, and edge cases (unknown session, malformed JSON, notification without id). The "Claude Code's /ide discovers and connects" smoke is deferred to T-131 wrap-up since it needs a real Claude Code session against the running app — out of scope for unit/widget tests. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ import 'package:clide/src/editor/registry.dart' show EditorRegistry;
|
||||
import 'package:clide/src/git/client.dart';
|
||||
import 'package:clide/src/cli/argv_dispatch.dart';
|
||||
import 'package:clide/src/ipc/envelope.dart';
|
||||
import 'package:clide/src/ipc/mcp_server.dart';
|
||||
import 'package:clide/src/ipc/paths.dart' show workspaceSocketPath;
|
||||
import 'package:clide/src/ipc/server.dart';
|
||||
import 'package:clide/src/panes/event_sink.dart';
|
||||
@@ -86,6 +87,11 @@ Future<void> main() async {
|
||||
// back to it over the socket so all IPC — including from UI widgets
|
||||
// in the same process — goes through the wire contract (T-127).
|
||||
IpcServer? ipcServer;
|
||||
// MCP server (T-130, per D-68 + D-73). Localhost HTTP+SSE, advertised
|
||||
// via $HOME/.claude/ide/<pid>.lock so Claude Code's /ide command
|
||||
// discovers it. Restarted alongside the unix server on project
|
||||
// switch so the discovery file reports the current workspace.
|
||||
McpServer? mcpServer;
|
||||
final ipcLog = Logger();
|
||||
|
||||
Future<void> swapIpcServer(DaemonDispatcher dispatcher, Directory workRoot) async {
|
||||
@@ -96,6 +102,11 @@ Future<void> main() async {
|
||||
ipcLog.warn('ipc', 'stop failed during swap: $e');
|
||||
ipcLog.debug('ipc', '$st');
|
||||
}
|
||||
try {
|
||||
await mcpServer?.stop();
|
||||
} catch (e) {
|
||||
ipcLog.warn('mcp', 'stop failed during swap: $e');
|
||||
}
|
||||
final server = IpcServer(
|
||||
dispatcher: dispatcher,
|
||||
workspaceRoot: workRoot.path,
|
||||
@@ -109,6 +120,14 @@ Future<void> main() async {
|
||||
ipcLog.error('ipc', 'server start failed', error: e, stackTrace: st);
|
||||
return;
|
||||
}
|
||||
final mcp = McpServer(workspaceRoot: workRoot.path, log: ipcLog);
|
||||
mcpServer = mcp;
|
||||
try {
|
||||
await mcp.start();
|
||||
} catch (e, st) {
|
||||
ipcLog.warn('mcp', 'MCP server start failed (non-fatal): $e');
|
||||
ipcLog.debug('mcp', '$st');
|
||||
}
|
||||
// Point the in-process DaemonClient at the new socket. On first
|
||||
// boot (no client yet) the daemonClientFactory below kicks it
|
||||
// off; on project switch we just reconnect to the new path.
|
||||
|
||||
Reference in New Issue
Block a user