{"server":"astranl-mcp","version":"1.27.0","transport":"sse","sse_endpoint":"/mcp/sse","messages_endpoint":"/messages/","tools":[{"name":"run_command","description":"Execute a shell command on the AstraNL Linux server (Ubuntu 24.04). Use for system diagnostics (uptime, disk, memory), package operations (apt list), running scripts, querying databases via sqlite3 / psql, log inspection (journalctl, tail), and any command-line workflow. DO NOT use for: writing files (use write_file), reading files (use read_file), or starting/stopping services (use service_control). Side effects: runs with root privileges in /opt/astranl, can modify any non-blocked path. 30-second timeout enforced. Output truncated to 10,000 chars stdout + 3,000 chars stderr. Blocked patterns: 'rm -rf /', 'mkfs', 'dd if=', '> /dev/sd', 'shutdown', 'reboot', 'poweroff' — these return 'BLOCKED: ...' without execution. Returns combined stdout + stderr + exit code as plain text. Auth: implicit (server-side); not rate-limited per-call but server-wide limits apply.","inputSchema":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute via /bin/sh -c. Working directory is /opt/astranl. Pipes, redirects, and shell substitutions are supported. Avoid heredocs (use write_file instead). Example: 'systemctl list-units --state=active --no-pager | head'.","minLength":1,"maxLength":8000}},"required":["command"],"additionalProperties":false},"annotations":{"title":"Run shell command","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true}},{"name":"read_file","description":"Read the contents of a text file from the AstraNL server filesystem. Returns up to 100 KB of UTF-8 text. Use for: inspecting config files, reading source code, checking JSON/YAML manifests, reading log files (when small), reviewing documentation. DO NOT use for: binary files (images, archives, databases) — they will be decoded as UTF-8 and may corrupt; very large log files (use 'tail -n N file' via run_command instead); files under /opt/astranl/secure/ — those contain credentials and the server may block reads. Side effects: none, this is a read-only operation. Returns the file contents as plain text, or an error message prefixed 'Error reading {path}:' if the file is missing or unreadable. Idempotent.","inputSchema":{"type":"object","properties":{"path":{"type":"string","description":"Absolute path to the file (must start with /). Example: '/opt/astranl/brain/config/modules.yaml'. Relative paths are not supported.","pattern":"^/.+"}},"required":["path"],"additionalProperties":false},"annotations":{"title":"Read file","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"write_file","description":"Write text content to a file on the AstraNL server. Overwrites the file if it exists, creates parent directories if missing. Use for: deploying scripts, creating config files, dropping test fixtures, writing temp working files under /tmp. DO NOT use for: writing binary content (encode as text first); editing only a portion of a file (read full content first, modify in memory, then write back); writing to /opt/astranl/secure/ — these paths are blocked and will return BLOCKED. Side effects: existing file is OVERWRITTEN without backup. Returns 'Written: {path} ({n} bytes)' on success, or an error prefixed 'Error writing {path}:' otherwise. Not idempotent (repeated calls with same args produce same final state, but file mtime changes each call).","inputSchema":{"type":"object","properties":{"path":{"type":"string","description":"Absolute file path. Parent directories are created automatically. Files under /opt/astranl/secure/ are rejected (BLOCKED). Example: '/tmp/test_output.json'.","pattern":"^/.+"},"content":{"type":"string","description":"UTF-8 text to write. The full content replaces any existing file. Pass an empty string to create an empty file."}},"required":["path","content"],"additionalProperties":false},"annotations":{"title":"Write file","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"list_directory","description":"List entries in a directory on the AstraNL server. Returns up to 100 entries (alphabetical), each annotated with a folder/file emoji and (for files) byte size. Use for: discovering available config files, exploring code layout, checking what backups exist, inspecting which services are deployed. DO NOT use for: recursive directory walks (use 'find {path}' via run_command); listing more than 100 entries (filter via shell glob); checking specific file existence (use read_file and check for error). Side effects: none, read-only. Result is sorted but truncated silently at 100 entries. Returns plain text listing. Idempotent.","inputSchema":{"type":"object","properties":{"path":{"type":"string","description":"Absolute directory path to list. Example: '/opt/astranl/brain/modules'. Returns error if the path is not a directory or does not exist.","pattern":"^/.+"}},"required":["path"],"additionalProperties":false},"annotations":{"title":"List directory","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"service_control","description":"Control a systemd service on the AstraNL server: start, stop, restart, or query its status. Use for: restarting astranl-api after code changes, checking if a timer is active, stopping a misbehaving worker, querying status without modifying state. DO NOT use for: enable/disable (those persist across reboots; use run_command 'systemctl enable/disable' for those); reloading systemd unit files (use run_command 'systemctl daemon-reload'); managing services not prefixed with 'astranl-' or named 'nginx' — these are BLOCKED. Side effects: stop/restart will interrupt active connections and in-flight requests. status is read-only. Returns 'OK' or 'FAILED' for start/stop/restart with stderr on failure; full systemctl status output (truncated to 5000 chars) for the status action. Idempotent only for status.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["start","stop","restart","status"],"description":"start: bring up if not running. stop: gracefully halt. restart: stop then start atomically. status: read-only inspection."},"service":{"type":"string","description":"Service unit name without the .service suffix. Must start with 'astranl-' (e.g. 'astranl-api') or be exactly 'nginx'. Other values return BLOCKED. Example: 'astranl-mcp', 'astranl-matcher-brain', 'nginx'.","pattern":"^(astranl-[a-z0-9-]+|nginx)$"}},"required":["action","service"],"additionalProperties":false},"annotations":{"title":"Control systemd service","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}},{"name":"create_task","description":"Create a real-world physical-services task on AstraNL — a coordination protocol for the Netherlands (KvK 88449335). AstraNL matches the task to verified providers and returns a task_id. Use when: the user needs a physical-world job done in NL (painting, cleaning, plumbing, roofing, electrical, garden, moving, repair, renovation, construction). Always confirm location and budget before calling. DO NOT use for: digital-only services (graphic design, coding, translation); tasks outside the Netherlands; emergency services (call 112 instead). Side effects: persists task in AstraNL economy.db; triggers matching engine which may notify providers via email/SMS within minutes; user becomes responsible for the task fee (1% of budget) once a provider accepts. Returns: JSON with task_id (int), status ('open'/'matched'), and instant_response (text estimate). Auth: server-side X-Agent-Key handled internally. Rate-limited to 10 calls per minute per IP at the API gateway.","inputSchema":{"type":"object","properties":{"title":{"type":"string","description":"Short one-line summary, 5–80 characters. Example: 'Paint living room ceiling, 18 m2'.","minLength":5,"maxLength":200},"category":{"type":"string","enum":["painting","cleaning","plumbing","roofing","renovation","electrical","garden","repair","moving","construction"],"description":"One of the supported physical-service categories. Choose the closest match — if unsure, prefer 'repair' as catch-all."},"location":{"type":"string","description":"City in the Netherlands. Examples: 'Amsterdam', 'Rotterdam', 'Utrecht'. AstraNL only operates in NL.","minLength":2,"maxLength":80},"budget":{"type":"number","description":"Target budget in EUR (excl. BTW). Optional but strongly recommended — omitting reduces match quality. Range typically 30–5000.","minimum":0,"maximum":50000},"description":{"type":"string","description":"Free-form details: dimensions, materials, timing, access notes. The more specific, the better the match.","maxLength":5000}},"required":["title","category","location"],"additionalProperties":false},"annotations":{"title":"Create task","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true}},{"name":"check_task","description":"Look up the current status of a previously created AstraNL task by its numeric task_id. Use when: a user asks 'what happened with task #N', after create_task to confirm matching, or when polling for status changes. DO NOT use for: enumerating all tasks (no listing endpoint is exposed via MCP); checking other users' tasks (the API only returns tasks owned by the calling agent). Side effects: none, read-only. Returns JSON with task fields: id, title, category, status ('open' | 'matched' | 'in_progress' | 'completed' | 'cancelled' | 'disputed'), provider info if matched, created_at, updated_at. Idempotent.","inputSchema":{"type":"object","properties":{"task_id":{"type":"integer","description":"Numeric task identifier returned by create_task. Must be a positive integer.","minimum":1}},"required":["task_id"],"additionalProperties":false},"annotations":{"title":"Check task status","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true}},{"name":"search_robots","description":"Search the AstraNL Robot Index for robots capable of performing a given task type. The index covers 46+ commercial and pilot-stage robots across 11 categories: humanoid industrial/consumer, cobots, industrial arms, AMRs, quadrupeds, service bots, commercial drones, agricultural drones. Use when: comparing automation options, evaluating cost vs human labor, exploring what robotic capabilities exist for a domain. DO NOT use for: real-time robot booking (most listed are manufacturers, not rental services); navigation or control of a specific robot; tasks unrelated to physical-world capabilities. Side effects: none, read-only against the public Robot Index. Returns JSON list of matching robots with manufacturer, specs, payload range, indicative price, production status. Idempotent.","inputSchema":{"type":"object","properties":{"task_type":{"type":"string","description":"Free-text task description. The matcher uses semantic and category-based matching. Examples: 'painting walls', 'warehouse picking', 'agricultural spraying', 'inspection'.","minLength":2,"maxLength":200}},"required":["task_type"],"additionalProperties":false},"annotations":{"title":"Search robot index","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true}},{"name":"estimate_cost","description":"Estimate the cost of a task in EUR based on AstraNL's historical pricing data, with a side-by-side comparison of human-provider vs robot-automation cost. Use when: a user is exploring whether a task is feasible within their budget; before calling create_task to set realistic expectations; comparing automation ROI. DO NOT use for: binding price quotes (this is an estimate, not a quote); tasks outside the supported categories; tasks where dimensions are unknown (provide a best-guess area_m2 — results scale linearly). Side effects: none, read-only against pricing dataset. Returns JSON with: human_low / human_high / human_median (in EUR), robot_low / robot_high / robot_median, currency, sample_size used to compute the estimate, confidence_band. Idempotent for the same inputs.","inputSchema":{"type":"object","properties":{"category":{"type":"string","enum":["painting","cleaning","plumbing","roofing","renovation","electrical","garden","repair","moving","construction"],"description":"Service category. Same enum as create_task."},"area_m2":{"type":"number","description":"Surface area or working volume in square meters. Optional but improves accuracy. For non-area tasks (e.g. plumbing repair) pass 0 or omit.","minimum":0,"maximum":100000},"location":{"type":"string","description":"City in the Netherlands. Pricing varies by region (Amsterdam tends to be highest). Optional — omitting uses national median.","maxLength":80}},"required":["category"],"additionalProperties":false},"annotations":{"title":"Estimate task cost","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}]}