Skip to content

MCP tools reference

hal0 mounts two FastMCP (Streamable-HTTP) servers on the main FastAPI app: hal0-admin at /mcp/admin and hal0-memory at /mcp/memory.

Every admin tool is registered through one generic factory, so every tool’s call signature is {"args": {...per-tool schema...}}. The advertised JSON schema per tool merges path args (required strings, derived from the REST route template) with a hand-authored set of named body/query field hints, and always allows additional properties to pass through undeclared.

Tools are autogenerated from the live FastAPI route table and re-keyed onto stable names — the tool-name-to-route mapping in source (TOOL_NAME_ALIASES) is the ground truth if this table ever drifts.

Return shape for every admin tool is dict[str, Any]:

  • success (autonomous): the raw REST JSON response — slot_list, provider_list, and upstream_list wrap their bare-list REST response as {"<key>": [...], "count": N}.
  • pending approval (gated): {"status": "pending_approval", "approval_id": "...", "detail": "..."}.
  • error: {"status": "error", "error": {"code": ..., ...}}.

Tools fall into three gating tiers. Treat the frozensets in source (AUTONOMOUS_READ_TOOLS, AUTONOMOUS_WRITE_TOOLS, GATED_TOOLS) as ground truth if this table drifts.

Tool REST route
slot_list GET /api/slots
slot_status GET /api/slots/{name}
slot_metrics GET /api/slots/metrics
slot_capacity GET /api/slots/capacity
slot_by_name GET /api/slots/by-name/{name}
slot_by_id GET /api/slots/by-id/{slot_id}
slot_resolved GET /api/slots/{name}/resolved
slot_state GET /api/slots/{name}/state
port_list GET /api/ports
model_list GET /api/models
model_show GET /api/models/{model_id}
model_scan_preview POST /api/models/scan/preview
model_catalogue GET /api/models/catalogue
model_update_check GET /api/models/updates/check
model_pulls_list GET /api/models/pulls
model_pull_status GET /api/models/{model_id}/pull/status
model_inspect POST /api/models/inspect — args: hf_repo (org/name) or hf_url
model_store GET /api/settings/models/store
hardware_probe GET /api/stats/hardware
system_info GET /api/system-info
capability_list GET /api/capabilities
provider_list GET /api/providers
version_info GET /api/status
upstream_list GET /api/upstreams
upstream_get GET /api/upstreams/{name}
upstream_test POST /api/upstreams/{name}/test
stack_list GET /api/stacks
stack_status GET /api/stacks/{slug}
profile_list GET /api/profiles
profile_status GET /api/profiles/{name}
profile_export POST /api/profiles/{name}/export
settings_get GET /api/settings
settings_schema GET /api/settings/schema
settings_apply_plan GET /api/settings/apply-plan
bench_runs GET /api/benchmarks/runs
bench_run_status GET /api/benchmarks/runs/{run_id}
bench_queue GET /api/benchmarks/queue
gpu_target_version in-process host probe, no REST hop
npu_status in-process host probe
env_report in-process host probe
model_store_probe in-process host probe — arg: path (required)
memory_search, memory_list, memory_recall delegate to hal0-memory — see below

Autonomous — write (no approval; reversible, low blast radius)

Section titled “Autonomous — write (no approval; reversible, low blast radius)”
Tool REST route Args
model_swap POST /api/slots/{name}/swap name (path), model_id (required)
model_assign PUT /api/slots/{name}/config name (path), model (required)
model_edit PUT /api/models/{model_id}
model_scan POST /api/models/scan prune (optional bool)
model_pull_cancel POST /api/models/{model_id}/pull/cancel
model_pull_delete DELETE /api/models/pulls/{model_id}
model_set_default POST /api/models/{model_id}/default model_id, default (bool, default true)
slot_load POST /api/slots/{name}/load
slot_unload POST /api/slots/{name}/unload
slot_edit PUT /api/slots/{name}/config
slot_set_defaults PATCH /api/slots/{name}/defaults
settings_reload POST /api/settings/reload
memory_add delegates to hal0-memory
memory_delete delegates to hal0-memory — gates itself when deleting more than one id, or a foreign/list dataset

Gated (destructive — require operator approval)

Section titled “Gated (destructive — require operator approval)”
Tool REST route Args
model_pull POST /api/models/{model_id}/pull model_id (local, no slashes), hf_repo, hf_filename, mmproj_filename
model_delete DELETE /api/models/{model_id}
model_register POST /api/models
model_add POST /api/models/add-from-path
model_store_set POST /api/settings/models/store
model_store_migrate POST /api/settings/models/store/migrate
model_update POST /api/models/{model_id}/update
slot_create POST /api/slots name, model (required), type, port, image, runtime
slot_delete DELETE /api/slots/{name}
slot_restart POST /api/slots/{name}/restart
slot_rename POST /api/slots/{name}/rename name (current), new_name (required)
capability_set POST /api/capabilities/{slot}/{child}
config_write PUT /api/settings
provider_credential_write POST /api/providers/{name}/credentials
stack_create POST /api/stacks
stack_update PUT /api/stacks/{slug}
stack_apply POST /api/stacks/{slug}/apply
stack_import POST /api/stacks/import
stack_export POST /api/stacks/{slug}/export
stack_snapshot POST /api/stacks/snapshot
stack_delete DELETE /api/stacks/{slug}
profile_create POST /api/profiles
profile_update PUT /api/profiles/{name}
profile_import POST /api/profiles/import
profile_delete DELETE /api/profiles/{name}
bench_enqueue POST /api/benchmarks/queue
bench_control POST /api/benchmarks/control
bench_queue_delete DELETE /api/benchmarks/queue/{item_id}
logs_tail GET /api/logs output is secret-redacted
slot_logs GET /api/slots/{name}/logs output is secret-redacted
upstream_create POST /api/upstreams name (required), catalog_id, url, auth_value_env
upstream_update PATCH /api/upstreams/{name} name, enabled, advertise_models, model_filters
upstream_delete DELETE /api/upstreams/{name}

board_crud, brain_chat, updater_apply, auth_rotate, auth_me, provider_credential_read, agent_sessions are explicitly excluded from the MCP surface (each with a documented reason in source) — not gaps, policy.

Unlike hal0-admin, each tool here registers with a real typed signature, so tools/list returns real per-field schemas, not an opaque args blob.

Tool Signature Required Returns
memory_add text, dataset?, tags?, metadata?, document_id? text (non-empty) {id, timestamp, operation_id?}
memory_search query, limit=10, dataset?, tags?, before?, after? query {results: [{id, text, score, timestamp, dataset, tags, source, metadata}]}
memory_list dataset?, cursor?, limit=50 none {items: [...], next_cursor}
memory_delete ids, dataset? ids (non-empty list) {deleted: int}; bulk deletes (>1 id) return {status: "pending_approval", approval_id, detail}
memory_recall query, max_tokens=4096, types?, dataset?, tags?, tags_match? query {results: [...]}

Validation errors return {"status": "error", "error": {"code": "mcp.memory_schema", "detail": ...}}.

source on memory_add is server-injected from the caller’s identity — a client cannot set it directly; attempting to pass it raises.

Namespace rule: dataset defaults to "shared"; the X-hal0-Private header promotes it to private:<client_id>.

Router: src/hal0/api/routes/mcp.py, mounted at /api/mcp. This manages installed 3rd-party MCP server registrations for the dashboard — distinct from the tool-calling protocol surface above. Tool invocation only happens over the actual MCP protocol at /mcp/admin and /mcp/memory; this REST surface cannot invoke a tool.

Method Path Request Response
GET /api/mcp/servers {servers: [...], count} — bundled servers introspected live via list_tools()/list_resources()/list_prompts(); installed non-bundled servers come from the registry with state="stopped" (no supervisor yet).
GET /api/mcp/clients {clients: [{id, name, role, host, since, connected_to}], count} — derived from the audit log.
GET /api/mcp/catalog {items: [...], categories} — static installable-MCP catalog (puppeteer, sqlite, gdrive, slack, …).
GET /api/mcp/stream SSE stream of mcp.tool.* events (audit-log tail, polled every 2s).
GET /api/mcp/{server_id}/logs query limit (1-500, default 100) {server, events: [...], count}.
GET /api/mcp/resolve query url (1-2048 chars) A ResolvedManifest: {id, name, description, spec, transport, tools, resources, prompts, env_required, source_kind, source_url?, author, verified}. Supports oci://, npm:/npx:, uvx:/uv:, git+https://, and generic https://.../manifest.json specs, with an SSRF guard blocking loopback/private/link-local/CGNAT targets.
POST /api/mcp/install {"url": "..."} or {"manifest": {...}} 201 {installed: <InstalledServer>}; 409 on a bundled-id collision or already-installed.
DELETE /api/mcp/{server_id} {uninstalled: server_id}; 409 mcp.bundled if server_id is a bundled server.
PATCH /api/mcp/{server_id}/config {"env"?: {str: str}, "enabled"?: bool} {server: <InstalledServer>}; 409 if bundled.
POST /api/mcp/{server_id}/{action} 501 stub (McpNotImplemented, code mcp.supervisor_unavailable) — start/stop/restart are not implemented yet.

InstalledServer fields (as used, not fully enumerated in this pass): id, name, description, spec, transport, tools, resources, prompts, env, enabled, source_url, author, verified, installed_at.

hal0 acts as an MCP client in two limited ways — it does not run a general-purpose MCP client session itself:

  1. Policy layer for bundled agents (hal0.agents.mcp_client.AgentMCPClient) — transport-agnostic. It loads /etc/hal0/agents/<name>.toml, resolves bearer tokens from env or a systemd credential, and classifies a tool call as allow, gated, blocked, unknown_server, or unknown_tool, with workspace-escape path sandboxing for filesystem-style MCP args. The actual wire-level MCP client that connects to other MCP servers is owned by the separate Hermes agent runtime, not this repo.
  2. Manifest resolver (hal0.mcp.manifest) — fetches and parses manifest JSON from arbitrary URLs so the dashboard can preview/install third-party MCP servers (backs GET /api/mcp/resolve and POST /api/mcp/install). This is manifest fetching only, with an SSRF guard blocking loopback/private/link-local/CGNAT targets — not live MCP protocol tool-calling.