From 71d5fc0f7574f1e234ecf77c7ae65918639eccf8 Mon Sep 17 00:00:00 2001 From: Chris Hendrickson Date: Thu, 23 Apr 2026 16:13:53 -0400 Subject: [PATCH] Add MCP debug client and VS Code MCP config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tools/mcp_client.dart: spawns the compiled dew binary, connects via stdioChannel, initializes the MCP handshake, lists all tools, and calls kanban_list_tickets — useful for verifying the server without a real client - .vscode/mcp.json: proper VS Code MCP server config using ${workspaceFolder} to point at the compiled binary with 'mcp serve' args Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .vscode/mcp.json | 9 +++ tools/mcp_client.dart | 132 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 .vscode/mcp.json create mode 100644 tools/mcp_client.dart diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..abc173d --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,9 @@ +{ + "servers": { + "dew": { + "type": "stdio", + "command": "${workspaceFolder}/.project/toolchain/bin/dew", + "args": ["mcp", "serve"] + } + } +} diff --git a/tools/mcp_client.dart b/tools/mcp_client.dart new file mode 100644 index 0000000..df44cf3 --- /dev/null +++ b/tools/mcp_client.dart @@ -0,0 +1,132 @@ +/// Manual MCP client for debugging the Dew MCP server. +/// +/// Usage: +/// dart run tools/mcp_client.dart [path/to/dew/binary] +/// +/// Defaults to .project/toolchain/bin/dew if no path is given. +library; + +import 'dart:async'; +import 'dart:io'; + +import 'package:dart_mcp/client.dart'; +import 'package:dart_mcp/stdio.dart'; + +void main(List args) async { + final binaryPath = args.isNotEmpty + ? args.first + : '.project/toolchain/bin/dew'; + + print('=== Dew MCP Debug Client ==='); + print('Binary: $binaryPath'); + print(''); + + // Log all raw JSON-RPC traffic. + final protocolLog = StreamController(); + protocolLog.stream.listen((msg) => stderr.writeln('[proto] $msg')); + + // Start the server process. + print('Starting server process...'); + final process = await Process.start( + binaryPath, + ['mcp', 'serve'], + // Forward server stderr to our stderr so startup messages are visible. + ); + + // Print server stderr in real time. + process.stderr + .transform(SystemEncoding().decoder) + .listen((line) => stderr.write('[server] $line')); + + unawaited( + process.exitCode.then((code) { + if (code != 0) { + stderr.writeln('[client] Server process exited with code $code'); + } + }), + ); + + final client = MCPClient( + Implementation(name: 'dew-debug-client', version: '0.1.0'), + ); + + final connection = client.connectServer( + stdioChannel(input: process.stdout, output: process.stdin), + protocolLogSink: protocolLog.sink, + ); + + unawaited(connection.done.then((_) { + stderr.writeln('[client] Connection closed.'); + process.kill(); + })); + + // --- Initialise --- + print('Sending initialize...'); + late InitializeResult initResult; + try { + initResult = await connection.initialize( + InitializeRequest( + protocolVersion: ProtocolVersion.latestSupported, + capabilities: client.capabilities, + clientInfo: client.implementation, + ), + ).timeout(const Duration(seconds: 5)); + } on TimeoutException { + stderr.writeln('[client] Timed out waiting for initialize response.'); + process.kill(); + exit(1); + } catch (e) { + stderr.writeln('[client] Initialize failed: $e'); + process.kill(); + exit(1); + } + + print('Server: ${initResult.serverInfo?.name} ${initResult.serverInfo?.version}'); + print('Protocol: ${initResult.protocolVersion}'); + print(''); + + if (initResult.capabilities.tools == null) { + stderr.writeln('[client] Server does not advertise tools capability.'); + await client.shutdown(); + process.kill(); + exit(1); + } + + connection.notifyInitialized(); + + // --- List tools --- + print('Listing tools...'); + final toolsResult = await connection.listTools(ListToolsRequest()); + if (toolsResult.tools.isEmpty) { + print(' (no tools registered)'); + } else { + for (final tool in toolsResult.tools) { + print(' • ${tool.name}: ${tool.description ?? "(no description)"}'); + } + } + + print(''); + + // --- Call kanban_list_tickets --- + print('Calling kanban_list_tickets...'); + try { + final result = await connection.callTool( + CallToolRequest(name: 'kanban_list_tickets', arguments: {}), + ); + if (result.isError == true) { + print(' Error: ${result.content.map((c) => (c as TextContent).text).join()}'); + } else { + print(' Result:'); + for (final c in result.content) { + print(' ${(c as TextContent).text}'); + } + } + } catch (e) { + print(' Failed: $e'); + } + + print(''); + print('Done. Shutting down.'); + await client.shutdown(); + process.kill(); +}