mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
## Summary Adds a stdio-based Fabro MCP server so MCP clients can manage Fabro workflow runs through the authenticated `fabro` CLI, without a separate MCP auth flow. ## What Changed - Adds `fabro mcp start`, `fabro mcp config`, and `fabro mcp init <agent>` for launching and configuring the MCP server. - Introduces a new `fabro-mcp-server` crate with run-management tools: - `fabro_run_create` - `fabro_run_search` - `fabro_run_interact` - `fabro_run_gather` - `fabro_run_events` - Reuses the CLI's authenticated server connection behavior, including OAuth refresh, dev-token/local-server handling, explicit server targets, proxy behavior, and stdio env/cwd isolation. - Moves shared run-manifest construction into `fabro-manifest` so CLI runs and MCP-created runs use the same override semantics. - Extends MCP client stdio support with configured cwd and exact environment handling for reliable spawned-server tests. --------- Co-authored-by: fabro-sh-0530[bot] <281434857+fabro-sh-0530[bot]@users.noreply.github.com> Co-authored-by: Fabro <noreply@fabro.sh>
106 lines
2.6 KiB
Python
106 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Minimal MCP server for integration testing over stdio.
|
|
|
|
Speaks JSON-RPC 2.0 over stdin/stdout per the MCP specification.
|
|
Exposes a single tool: echo(message) -> message.
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
SERVER_INFO = {
|
|
"name": "test-echo-server",
|
|
"version": "0.1.0",
|
|
}
|
|
|
|
TOOL = {
|
|
"name": "echo",
|
|
"description": "Echo back the message",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"message": {"type": "string", "description": "Message to echo"}
|
|
},
|
|
"required": ["message"],
|
|
},
|
|
}
|
|
|
|
|
|
def handle_request(req):
|
|
method = req.get("method")
|
|
req_id = req.get("id")
|
|
params = req.get("params", {})
|
|
|
|
if method == "initialize":
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": req_id,
|
|
"result": {
|
|
"protocolVersion": "2025-03-26",
|
|
"capabilities": {"tools": {}},
|
|
"serverInfo": SERVER_INFO,
|
|
},
|
|
}
|
|
|
|
if method == "tools/list":
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": req_id,
|
|
"result": {"tools": [TOOL]},
|
|
}
|
|
|
|
if method == "tools/call":
|
|
tool_name = params.get("name")
|
|
arguments = params.get("arguments", {})
|
|
if tool_name == "echo":
|
|
msg = arguments.get("message", "")
|
|
if msg == "__cwd__":
|
|
msg = os.getcwd()
|
|
elif msg.startswith("__env:") and msg.endswith("__"):
|
|
key = msg[len("__env:") : -len("__")]
|
|
msg = os.environ.get(key, "")
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": req_id,
|
|
"result": {
|
|
"content": [{"type": "text", "text": msg}],
|
|
},
|
|
}
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": req_id,
|
|
"result": {
|
|
"content": [{"type": "text", "text": f"unknown tool: {tool_name}"}],
|
|
"isError": True,
|
|
},
|
|
}
|
|
|
|
# Notifications (no id) — just ignore
|
|
if req_id is None:
|
|
return None
|
|
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": req_id,
|
|
"error": {"code": -32601, "message": f"Method not found: {method}"},
|
|
}
|
|
|
|
|
|
def main():
|
|
for line in sys.stdin:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
req = json.loads(line)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
|
|
resp = handle_request(req)
|
|
if resp is not None:
|
|
sys.stdout.write(json.dumps(resp) + "\n")
|
|
sys.stdout.flush()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|