From 0b74ed969cc77f3a6f7911373e3a41253b92b7eb Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 1 Oct 2026 18:40:38 +0700 Subject: [PATCH 1/6] test(mcp): freeze the v1 tool surface of both servers Dumped on mcp 1.26 / pydantic-ai 1.78, the last lock before the move to the 2.x majors (FEAT-130): name, description, inputSchema and outputSchema of every tool under the full profile. The parity test that reads it lands with the bump. --- tests/fixtures/mcp_tool_surface_v1.json | 4786 +++++++++++++++++++++++ 1 file changed, 4786 insertions(+) create mode 100644 tests/fixtures/mcp_tool_surface_v1.json diff --git a/tests/fixtures/mcp_tool_surface_v1.json b/tests/fixtures/mcp_tool_surface_v1.json new file mode 100644 index 000000000..d76597a17 --- /dev/null +++ b/tests/fixtures/mcp_tool_surface_v1.json @@ -0,0 +1,4786 @@ +{ + "condor": { + "control_agent": { + "description": "Run and steer live agent instances (start / stop / pause / resume).\n\n An *instance* is one running session of an agent's loop, identified by\n ``agent_id``. Start one from a loop \u2014 or from a bare agent slug, since\n every agent is loopable. See `manage_agents` for the identities and\n `manage_loops` for the playbooks; read what a running instance did with\n `trading_agent_journal_read`.\n\n STOP vs SHUTDOWN: \"stop\" halts the loop and KEEPS its open positions.\n \"shutdown\" is the emergency exit \u2014 it winds down this session's positions and\n executors per the agent's shutdown.md policy (closes perp, keeps spot by\n default). Reach for \"stop\" unless the user wants to be out of the market.\n\n Actions:\n - \"list\": All running instances with status.\n - \"start\": Start a session (requires loop_id, optional config overrides).\n loop_id may be a BARE AGENT SLUG \u2014 the agent then runs its only\n loop, or a default playbook created from its identity on first start.\n - \"stop\": Stop a running instance, keeping positions (requires agent_id).\n - \"shutdown\": Stop AND wind down positions/executors (requires agent_id).\n - \"pause\": Pause a running instance (requires agent_id).\n - \"resume\": Resume a paused instance (requires agent_id).\n - \"get_state\" / \"set_state\": Read/write the instance's own scratch state\n (requires agent_id; set_state also requires key). State is for cursors and\n counters a loop would otherwise re-derive every tick \u2014 anything worth\n *remembering* belongs in `manage_memory`. The namespace is derived from\n agent_id, so an instance only ever sees its own.\n\n Args:\n action: The lifecycle action to take on the loop.\n agent_id: The running instance (everything except list and start).\n loop_id: Loop key \"agent_slug.loop_slug\", or a bare agent slug\n (start only).\n config: Overrides on the loop's defaults, for \"start\" only: agent_key,\n model_base_url (for LM Studio/vLLM), execution_mode, frequency_sec,\n tick_timeout_sec (wall-clock budget for one tick's agent session;\n 0 = runtime default of 600s), total_amount_quote, trading_context,\n risk_limits, server_name, max_ticks.\n key: State key. get_state reads one key, or the whole state if omitted;\n set_state requires it.\n value: JSON-serializable value to store (set_state).\n expires_in: TTL in seconds for the stored value (set_state); None = no expiry.\n clear: Delete ``key`` instead of writing it (set_state).\n\n Returns:\n Action-specific result dict.\n ", + "inputSchema": { + "properties": { + "action": { + "enum": [ + "list", + "start", + "stop", + "pause", + "resume", + "shutdown", + "get_state", + "set_state", + "list_agents", + "start_agent", + "stop_agent", + "pause_agent", + "resume_agent", + "shutdown_agent" + ], + "title": "Action", + "type": "string" + }, + "agent_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Id" + }, + "clear": { + "default": false, + "title": "Clear", + "type": "boolean" + }, + "config": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Config" + }, + "expires_in": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires In" + }, + "key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key" + }, + "loop_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Loop Id" + }, + "value": { + "default": null, + "title": "Value" + } + }, + "required": [ + "action" + ], + "title": "control_agentArguments", + "type": "object" + }, + "name": "control_agent", + "outputSchema": null + }, + "delegate": { + "description": "Hand work to another agent \u2014 detached, or blocking for an answer.\n\n DELEGATE is the ONLY way to reach another agent, and it has two shapes:\n\n - **\"start\"** hands a goal-oriented task to a DETACHED agent that works\n autonomously until done, then notifies the user \u2014 while you stay free to do\n other things. Use it for \"go build/scan/produce X and ping me when\n finished\" (e.g. \"create a routine that scans SOL pools\"). Add\n ``on_complete=\"resume\"`` when you want the answer yourself, then end your\n turn: you are woken with it.\n - **\"ask\"** BLOCKS and returns the agent's answer as text. Use it when you\n need another domain's answer to carry on with your own reasoning right now\n (\"what range should this LP use?\") and the answer is not itself the\n deliverable the user is waiting on.\n\n Prefer \"start\" whenever there is a user watching: they get a task id, a\n progress list and a transcript instead of a frozen turn. Prefer \"ask\" when\n there is not \u2014 a tick or a background worker runs for nobody's conversation,\n so \"resume\" has nothing to wake and \"notify\" would send the answer to the\n user instead of to you.\n\n Either way the agent runs unrestricted with full auto-approve, so reach for\n another agent only when you trust it with the task.\n\n The user tracks a delegation on whichever surface they are on \u2014 the\n /delegations command in Telegram, the Tasks list in the chat's context dock\n on the dashboard \u2014 and is pinged automatically when it finishes. Never invent\n a status command (there is no \"/task\", and /delegations exists only in\n Telegram); \"start\" returns a next_steps hint already worded for this session's\n surface, so relay that rather than guessing.\n\n Actions:\n - \"start\": Begin a delegation (requires agent, task). Returns immediately with\n {\"task_id\", \"status\": \"running\", \"next_steps\"} \u2014 does NOT wait for completion.\n - \"ask\": Ask an agent and WAIT for its answer (requires agent, task). Returns\n {\"agent\", \"answer\"}. Refused if you are yourself answering an ask (depth\n stops at one) or if you name your own slug.\n - \"list\": List in-flight/finished delegations (task_id, agent, status).\n - \"get\": Get a delegation's status + result/error (requires task_id).\n - \"stop\": Cancel a running delegation (requires task_id).\n\n An agent may pass its OWN slug to start a background copy of itself (FEAT-041)\n \u2014 same identity, playbooks and memory, running unattended on a long job while\n the interactive session stays responsive. That copy cannot spawn a further copy\n of itself; the recursion stops at depth one.\n\n Args:\n action: What to do with the agent or the delegation.\n agent: Agent slug to reach (for start/ask). For \"start\" your own slug is\n allowed and means \"a background session of me\"; for \"ask\" it is\n refused, because asking yourself is a round trip through your own\n brain.\n task: The one-off task or question, in plain language (for start/ask).\n task_id: Delegation id returned by start (for get/stop).\n on_complete: What this conversation gets when the task ends (for start).\n \"notify\" (default) pings the user with the result AND writes the\n outcome into THIS conversation \u2014 recorded in the transcript and shown\n in the chat as it lands, so an already-open session sees it without a\n reload. What it does not do is give you a turn: the result is there\n for the human to read, not handed back to you. So never tell the user\n the result will not reach the chat. \"resume\" additionally hands the\n result back to you in a new turn of THIS conversation, so use it when\n you intend to do something with the result yourself (\"research X,\n then draft the summary\"). With \"resume\" you must end your turn after\n starting the task: you will be woken with the answer.\n timeout_sec: Wall-clock budget for the whole background task, in seconds\n (for start). Omit it (0) for the default of 900s. This knob only\n goes UP: 900s is also the floor, and a smaller number is raised back\n to it, so do not try to give a task less. Raise it when you KNOW the\n job is long \u2014 several routines to build, a research sweep, a\n multi-step backtest \u2014 because a task that outlives its budget is\n cut off mid-run and loses whatever it had not finished. The ceiling\n is 1800s: an agent session has its own ~31-minute hard stop, so\n asking for more only delays the same cut-off. For work bigger than\n that, split it across delegations.\n context: Extra context for the agent \u2014 relevant numbers, the user's\n intent (for ask).\n\n Returns:\n Action-specific result dict.\n ", + "inputSchema": { + "properties": { + "action": { + "enum": [ + "start", + "ask", + "list", + "get", + "stop" + ], + "title": "Action", + "type": "string" + }, + "agent": { + "default": "", + "title": "Agent", + "type": "string" + }, + "context": { + "default": "", + "title": "Context", + "type": "string" + }, + "on_complete": { + "default": "notify", + "enum": [ + "notify", + "resume" + ], + "title": "On Complete", + "type": "string" + }, + "task": { + "default": "", + "title": "Task", + "type": "string" + }, + "task_id": { + "default": "", + "title": "Task Id", + "type": "string" + }, + "timeout_sec": { + "default": 0, + "title": "Timeout Sec", + "type": "integer" + } + }, + "required": [ + "action" + ], + "title": "delegateArguments", + "type": "object" + }, + "name": "delegate", + "outputSchema": null + }, + "get_available_models": { + "description": "List every model/provider currently usable for a trading agent's ``agent_key``.\n\n Use this when helping the user pick a model for an agent they are building\n (agent_builder skill) \u2014 recommend from what is ACTUALLY configured, not a\n hardcoded default. Read-only; never returns key values.\n\n Args:\n openrouter_query: Optional substring to filter the OpenRouter catalog by\n slug or name (e.g. \"claude\", \"deepseek\", \"free\"). Empty = no filter.\n openrouter_limit: Max OpenRouter models to return (default 20, cheapest\n input price first). ``total_matching`` reports how many matched.\n\n Returns a dict with:\n - acp_clis: subscription/CLI bridges (claude-code, gemini, copilot, \u2026),\n each with ``agent_key``, ``available`` \u2014 the CLI is installed and\n launchable \u2014 and ``logged_in``: true a credential for its interactive\n login (Claude/Google/GitHub/OpenAI) was found on this machine, false none\n was found where that CLI keeps one, null no marker exists to read\n (Copilot) or the bridge isn't installed. ``available`` alone does NOT\n mean signed in, and ``logged_in`` is a heuristic (a credential file can\n still hold an expired token), so treat a bridge with ``logged_in`` false\n or null as a candidate to CONFIRM with the user, and prefer a credential\n you can verify (a set ``cloud_keys`` provider, or a loaded local model)\n when recommending unprompted.\n - cloud_keys: {provider: bool} \u2014 whether OPENROUTER/OPENAI/ANTHROPIC/GROQ/\n GOOGLE keys are set in the environment.\n - custom_endpoints: the user's own saved OpenAI-compatible endpoints, each\n with ``name``, ``base_url``, ``reachable`` and the chat ``models`` it\n serves (``agent_key`` set to ``custom@:``). The\n strongest signal here \u2014 the user added these deliberately and they are\n re-validated on every call \u2014 so prefer a reachable one when it fits.\n - local: {ollama, lmstudio} each with ``base_url``, ``reachable``, and the\n ``models`` currently loaded on that server (empty if not running).\n - openrouter: tool-capable catalog (``models`` with ``agent_key`` set to\n ``openrouter:``, plus in/out $/Mtok and context). The catalog is\n public so this is ALWAYS present \u2014 no key needed to recommend. Check\n ``key_present``: false means these are options that need OPENROUTER_API_KEY\n added (Settings) before they can run; recommend a runnable option first.\n\n Guidance for choosing: correctness-critical or high-capital agents \u2192 a strong\n model (a capable OpenRouter model when its key is set, or a subscription ACP\n bridge the user confirms is signed in); simple\n report/watch loops or privacy/offline needs \u2192 a loaded local model or a cheap\n OpenRouter one. An agent's ``tools`` allowlist binds on every model, ACP\n bridges included: a tool it leaves out is never mounted.\n ", + "inputSchema": { + "properties": { + "openrouter_limit": { + "default": 20, + "title": "Openrouter Limit", + "type": "integer" + }, + "openrouter_query": { + "default": "", + "title": "Openrouter Query", + "type": "string" + } + }, + "title": "get_available_modelsArguments", + "type": "object" + }, + "name": "get_available_models", + "outputSchema": null + }, + "manage_agent_controllers": { + "description": "The Hummingbot controllers you OWN: source + sample configs in your folder,\n pushed to the active server on demand. Read the `controller_sources` skill\n before your first sync.\n\n Layout: controllers//.py, optional controllers//CONTROLLER.md\n (description, `type:` when it cannot be inferred), and\n controllers//sample_configs/