mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-01 02:04:24 +00:00
Petri's Fabro frontend refused a bundle naming an environment it did not declare and every MCP catalog reference, so the fixtures declared `[environments.local]` and the server's catalogs never reached Petri. Pin Petri at c874b86, where the frontend reads `[environments.<id>]` and `[run.environment]` from every settings layer (bundle over project over the host's layer, key by key), takes the environment a launch selected over the layers, and resolves `[run.agent.mcps.<name>] id = "..."` against a catalog the host binds. The server hands Petri its environment catalog as `[environments.<id>]` tables of the settings layer it already passes, the intent's environment as the launch's selection (`Launch::environment`, as the intent overrides the bundle in Fabro's own resolution), and its MCP catalog as `RuntimeSpec::mcp_catalog_toml`, one inline entry per definition keyed by id. Offline validation hands Petri the seeded catalog the same way, so `fabro validate` accepts `[run.environment] id = "local"`. The fixtures drop the `[environments.local]` tables they carried for this; the secrets test keeps its own, on purpose. Scenario tests cover a bundle naming a catalog environment (its image lowered, and run on Docker when the plugin and a daemon are there), a bundle's own table winning key by key, the server refusing an unknown environment before Petri, and a catalog MCP reference whose tool the agent session lists (an echo server under `test/mcp/`). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
80 lines
2.3 KiB
Python
Executable file
80 lines
2.3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""A one-tool MCP server over stdio for the server's scenario tests.
|
|
|
|
Speaks JSON-RPC 2.0, one message per line on stdin and stdout, as the MCP
|
|
specification describes for the stdio transport. It exposes ``echo(message)``,
|
|
which answers the message, so a test can see the server's tool reach an
|
|
agent session's tool list. Dependency-free.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
|
|
SERVER_INFO = {"name": "fabro-test-echo", "version": "1.0.0"}
|
|
PROTOCOL_VERSION = "2025-03-26"
|
|
|
|
TOOLS = [
|
|
{
|
|
"name": "echo",
|
|
"description": "Echo back the message",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {"message": {"type": "string"}},
|
|
"required": ["message"],
|
|
},
|
|
}
|
|
]
|
|
|
|
|
|
def handle(request):
|
|
"""Answer one request, or ``None`` for a notification."""
|
|
method = request.get("method")
|
|
request_id = request.get("id")
|
|
params = request.get("params") or {}
|
|
if method == "initialize":
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": request_id,
|
|
"result": {
|
|
"protocolVersion": params.get("protocolVersion", PROTOCOL_VERSION),
|
|
"capabilities": {"tools": {}},
|
|
"serverInfo": SERVER_INFO,
|
|
},
|
|
}
|
|
if method == "ping":
|
|
return {"jsonrpc": "2.0", "id": request_id, "result": {}}
|
|
if method == "tools/list":
|
|
return {"jsonrpc": "2.0", "id": request_id, "result": {"tools": TOOLS}}
|
|
if method == "tools/call":
|
|
message = (params.get("arguments") or {}).get("message", "")
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": request_id,
|
|
"result": {"content": [{"type": "text", "text": message}]},
|
|
}
|
|
if request_id is None:
|
|
return None
|
|
return {
|
|
"jsonrpc": "2.0",
|
|
"id": request_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:
|
|
request = json.loads(line)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
response = handle(request)
|
|
if response is not None:
|
|
sys.stdout.write(json.dumps(response) + "\n")
|
|
sys.stdout.flush()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|