From 7571ef62fc15607ad7d8a13adc6b6cfd96846d88 Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 2 Feb 2026 07:31:40 +0000 Subject: [PATCH] feat(code): add MCP server integration for core CLI (#112) Add MCP server that exposes core CLI commands as tools: - core_go_test: Run Go tests with filter and coverage options - core_dev_health: Check development environment health - core_dev_commit: Create commits with message and repo selection Returns structured JSON responses for AI agent consumption. Migrated from core-claude PR #56. Co-authored-by: Claude Opus 4.5 --- claude/code/commands/serve-mcp.md | 21 +++++ claude/code/scripts/mcp/run.sh | 132 ++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 claude/code/commands/serve-mcp.md create mode 100755 claude/code/scripts/mcp/run.sh diff --git a/claude/code/commands/serve-mcp.md b/claude/code/commands/serve-mcp.md new file mode 100644 index 0000000..44a28ae --- /dev/null +++ b/claude/code/commands/serve-mcp.md @@ -0,0 +1,21 @@ +--- +name: serve-mcp +description: Starts the MCP server for the core CLI. +args: "" +--- + +# MCP Server + +Starts the MCP server to expose core CLI commands as tools. + +## Usage + +``` +/code:serve-mcp +``` + +## Action + +```bash +"${CLAUDE_PLUGIN_ROOT}/scripts/mcp/run.sh" +``` diff --git a/claude/code/scripts/mcp/run.sh b/claude/code/scripts/mcp/run.sh new file mode 100755 index 0000000..f618ad2 --- /dev/null +++ b/claude/code/scripts/mcp/run.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# +# MCP Server script for the core-claude plugin. +# This script reads a JSON MCP request from stdin, executes the corresponding +# core CLI command, and prints a JSON response to stdout. +# + +set -e + +# Read the entire input from stdin +request_json=$(cat) + +# --- Input Validation --- +if ! echo "$request_json" | jq . > /dev/null 2>&1; then + echo '{"status": "error", "message": "Invalid JSON request."}' + exit 1 +fi + +# --- Request Parsing --- +tool_name=$(echo "$request_json" | jq -r '.tool_name') +params=$(echo "$request_json" | jq '.parameters') + +# --- Command Routing --- +case "$tool_name" in + "core_go_test") + filter=$(echo "$params" | jq -r '.filter // ""') + coverage=$(echo "$params" | jq -r '.coverage // false') + + # Build the command + cmd_args=("go" "test") + [ -n "$filter" ] && cmd_args+=("--filter=$filter") + [ "$coverage" = "true" ] && cmd_args+=("--coverage") + ;; + + "core_dev_health") + cmd_args=("dev" "health") + ;; + + "core_dev_commit") + message=$(echo "$params" | jq -r '.message // ""') + if [ -z "$message" ]; then + echo '{"status": "error", "message": "Missing required parameter: message"}' + exit 1 + fi + + cmd_args=("dev" "commit" "-m" "$message") + + repos=$(echo "$params" | jq -r '.repos // "[]"') + if [ "$(echo "$repos" | jq 'length')" -gt 0 ]; then + # Read repos into a bash array + mapfile -t repo_array < <(echo "$repos" | jq -r '.[]') + cmd_args+=("${repo_array[@]}") + fi + ;; + + *) + echo "{\"status\": \"error\", \"message\": \"Unknown tool: $tool_name\"}" + exit 1 + ;; +esac + +# --- Command Execution --- +# The 'core' command is expected to be in the PATH of the execution environment. +output=$(core "${cmd_args[@]}" 2>&1) +exit_code=$? + +# --- Response Formatting --- +if [ $exit_code -eq 0 ]; then + status="success" +else + status="error" +fi + +# Default response is just the raw output +result_json=$(jq -n --arg raw "$output" '{raw: $raw}') + +# Structured Response Parsing +if [ "$tool_name" = "core_go_test" ]; then + if [ "$status" = "success" ]; then + # Use awk for more robust parsing of the test output. + # This is less brittle than grepping for exact lines. + outcome=$(printf "%s" "$output" | awk '/^PASS$/ {print "PASS"}') + coverage=$(printf "%s" "$output" | awk '/coverage:/ {print $2}') + summary=$(printf "%s" "$output" | awk '/^ok\s/ {print $0}') + + result_json=$(jq -n \ + --arg outcome "${outcome:-UNKNOWN}" \ + --arg coverage "${coverage:--}" \ + --arg summary "${summary:--}" \ + --arg raw_output "$output" \ + '{ + outcome: $outcome, + coverage: $coverage, + summary: $summary, + raw_output: $raw_output + }') + else + # In case of failure, the output is less predictable. + # We'll grab what we can, but the raw output is most important. + outcome=$(printf "%s" "$output" | awk '/^FAIL$/ {print "FAIL"}') + summary=$(printf "%s" "$output" | awk '/^FAIL\s/ {print $0}') + result_json=$(jq -n \ + --arg outcome "${outcome:-FAIL}" \ + --arg summary "${summary:--}" \ + --arg raw_output "$output" \ + '{ + outcome: $outcome, + summary: $summary, + raw_output: $raw_output + }') + fi +elif [ "$tool_name" = "core_dev_health" ]; then + if [ "$status" = "success" ]; then + # Safely parse the "key: value" output into a JSON array of objects. + # This uses jq to be robust against special characters in the output. + result_json=$(printf "%s" "$output" | jq -R 'capture("(?[^:]+):\\s*(?.*)")' | jq -s '{services: .}') + else + # On error, just return the raw output + result_json=$(jq -n --arg error "$output" '{error: $error}') + fi +elif [ "$tool_name" = "core_dev_commit" ]; then + if [ "$status" = "success" ]; then + result_json=$(jq -n --arg message "$output" '{message: $message}') + else + result_json=$(jq -n --arg error "$output" '{error: $error}') + fi +fi + +response=$(jq -n --arg status "$status" --argjson result "$result_json" '{status: $status, result: $result}') +echo "$response" + +exit 0