--- name: mcsv description: Control a game server hosted on MCSV (mcsv.me) — Minecraft Java/Bedrock, Palworld, Rust, ARK, The Isle, Terraria, 7 Days to Die, Project Zomboid, The Forest, Sons of the Forest, SA-MP/open.mp — through its REST API or MCP server. Use when the user wants to manage, inspect, or automate their MCSV server (status, power, console, files, backups, databases, schedules, webhooks) or mentions mcsv.me, api.mcsv.me, or an mcsv_ API key. --- # MCSV Server API / MCP MCSV (mcsv.me) is a Thai game-server hosting platform — Minecraft (Java, Bedrock, proxies) plus Palworld, Rust, ARK, The Isle (Evrima), Terraria, 7 Days to Die, Project Zomboid, The Forest, Sons of the Forest, Hytale and GTA:SA (SA-MP / open.mp). Each hosted server can issue up to 10 API keys (format `mcsv_...`). One key = one specific server, with per-key permissions. The same key works on both surfaces: - REST: `https://api.mcsv.me/api/v1` - MCP: `https://api.mcsv.me/api/mcp` (Streamable HTTP, JSON-RPC 2.0, POST only, no SSE/GET stream) Human docs: https://mcsv.me/docs/api · Keys are created by the server owner at mcsv.me → server → "API / MCP" tab. The token is shown only once at creation. ## Auth Every request needs one of: - `Authorization: Bearer mcsv_xxx` (preferred) - `X-API-Key: mcsv_xxx` Get the key from the user or from the `MCSV_API_KEY` env var if present. Never print, echo, log, or commit the full key; refer to it by its prefix only. **OAuth (for clients that cannot set headers)**: the MCP endpoint is also an OAuth 2.1 protected resource. An unauthenticated request returns `401` with a `WWW-Authenticate` header pointing at `https://api.mcsv.me/.well-known/oauth-protected-resource/api/mcp`. Authorization server metadata: `https://api.mcsv.me/.well-known/oauth-authorization-server` (authorization code + PKCE S256 only, dynamic client registration and client-id-metadata-documents both supported, scopes `mcp:read` / `mcp:write` / `offline_access`). This is how ChatGPT and Claude web connectors attach — a static `mcsv_` key is simpler for any client that can send an `Authorization` header, so prefer it. ## Recommended flow 1. **Verify the key**: `GET /api/v1/me` → server `{id, name, game, server_type, game_version, status}`, key `{permission_mode, allowed_tool_count, total_tools}`, and rate limits. `permission_mode` is `full`, `read_only`, or `custom`. `game` tells you what kind of server this is — branch on it, most tools work on every game. 2. **Discover tools**: `GET /api/v1/tools` → every tool with `input_schema`, `description`, and `allowed` (whether this key may call it). Trust this live catalog over any memorized list. 3. **Invoke**: `POST /api/v1/tools/{name}` with the tool's arguments as the JSON body. No args → `{}` or empty body. A non-object body is a 400. Shortcut aliases (same behavior as the underlying tool): | Alias | Underlying tool | |---|---| | `GET /api/v1/server` | `server_overview` | | `POST /api/v1/power` body `{"action":"start\|stop\|restart\|kill"}` | `power_action` | | `POST /api/v1/command` body `{"command":"say hi"}` | `console_send` | | `GET /api/v1/logs?lines=200` (lines 1–5000, default 200) | `console_tail` | ## Responses and errors Success: `{"ok":true,"result":}` (`/me` and `/tools` return their own `{"ok":true,...}` shapes). Failure: `{"ok":false,"error":""}` with HTTP status: - **400** tool-level failure — blocked command/path, invalid arguments, backend error (also: non-object request body). Read the `error` message; it says what was wrong and what to do next. File-not-found failures additionally carry `listed_directory`, `entries` and `next_steps` — use them to fix the path instead of guessing again - **401** missing/invalid/revoked key - **403** key lacks permission for that tool (the server owner can change key permissions on the API / MCP page) - **404** unknown tool name → re-check `GET /api/v1/tools`. It can also mean the key's server no longer exists - **409** server is still installing → wait and retry later - **410** the key's server was deleted — the key is permanently dead. Stop retrying and ask the user for a key from a live server - **429** rate limit: **300 requests/min** and **6,000 requests/hour per key**. Over MCP this arrives as a JSON-RPC error (code `-32029`) with `data.retry_after_seconds`. Back off ~60s for the minute limit, much longer for the hour limit. Never retry-loop on 429; batch instead (`files_read_many`, `server_overview`). Over MCP, tool-level failures/denials are returned inside a successful JSON-RPC response: the result has `"isError": true` and the text content contains the error message — so always check tool results for `isError`/an `error` field instead of relying on HTTP status. ## Files: path rules (read this before any file tool) Wrong paths are the single most common failure mode. The rules: 1. **Paths start at the server volume root `/`** — `/server.properties`, `/logs/latest.log`, `/plugins/Essentials/config.yml`. There is no `/home/container` prefix inside the API (if you send one it is stripped for you, as are `~/`, `./`, missing leading slash and doubled slashes). 2. **Paths are case-sensitive.** `/plugins/geyser-spigot` and `/plugins/Geyser-Spigot` are different. When unsure of a name, call `files_list` on the parent first and copy the exact name from the response — never guess or normalise capitalisation yourself. 3. **When a path is not found, the error carries the fix.** The error body includes `listed_directory`, `entries` (the real names in the nearest existing folder), near-miss suggestions, and `next_steps`. Use it to correct the path in one retry. Do not guess a second path; if the name is not in `entries`, that thing does not exist on this server. 4. **Editing an existing file: use `files_edit`, not `files_write`.** `edits[{old_string, new_string}]` anchored on text copied verbatim from `files_read` — everything you did not anchor cannot go missing. An anchor matching zero or several places is refused with the nearest real lines: fix the anchor, never fall back to a whole-file write. `files_write` replaces the whole file (for creating one, or a genuine regeneration; it warns in the response when the result is much smaller). For `server.properties`, `server_properties_patch` is shortest. 5. **Mistaken write or delete? `files_undo`.** Every `files_write` / `files_edit` snapshots the previous bytes first (30 days, 10 versions per file, `files_undo_list` to browse). `files_delete` snapshots only the **first 10 files of the call** — the rest are unrecoverable and appear only as a count in `snapshot_skipped`, so delete in batches of ≤10 whenever you might need to undo. `files_undo` with the `path` puts the last version back — no whole-server backup restore needed. Directories and binaries are not snapshotted; the delete response lists those under `no_undo_for`. 6. **Creating a file in a folder that does not exist is refused**, as is writing a name that differs only by case from an existing file (the response tells you the real name). Both are guards against silently creating junk in the wrong place, because the underlying storage auto-creates parent directories. If you really do mean to create a new path, retry with `"force_new": true`. 7. **Argument style differs per tool** — read `input_schema` from `GET /api/v1/tools`, do not assume: - single full path: `files_read`, `files_write`, `files_edit`, `files_read_base64`, `files_upload_base64` → `path` (`files_edit` also takes `edits[]`) - directory: `files_list` → `directory` · `files_fetch_url` → `directory` + optional `filename` - base folder + relative names: `files_delete`/`files_compress` → `root` + `files[]` · `files_rename` → `root` + `renames[{from,to}]` · `files_decompress` → `root` + `file` · `files_mkdir` → `root` + `name` (a single folder name, not a path) - source file: `files_copy` → `location` (single file only; folders cannot be copied — compress then decompress instead). The response returns `copied_to` with the real destination name. - single full path for the download link: `files_download_url` → `file` Efficiency: - Reading several files: one `files_read_many` call (up to 25 paths) instead of many `files_read` calls — the main way to stay under the rate limit. Paths past the cap come back as explicit skip entries, not silently dropped. - Installing a plugin/mod: `search_mods` (filter by `loader` + `game_version`) returns a direct `download_url` for the top Modrinth matches — feed that straight into `files_fetch_url`. Never construct download URLs yourself. **Minecraft only** — Modrinth has nothing for the other games; there, edit the game's own config file with `files_read`/`files_edit` instead (`serverconfig.xml` for 7 Days to Die, `PalWorldSettings.ini` for Palworld, `config.json` for open.mp, and so on). - Size and truncation caps live in each tool's own description — read `input_schema` instead of memorising them. ## MCP connection Methods: `initialize` (protocolVersion `2025-03-26`), `tools/list` (filtered to the key's allowed tools), `tools/call`, `ping`. Cursor (`.cursor/mcp.json`) and most clients (`mcpServers` key): ```json { "mcpServers": { "mcsv": { "type": "http", "url": "https://api.mcsv.me/api/mcp", "headers": { "Authorization": "Bearer mcsv_xxx" } } } } ``` Claude Code: `claude mcp add --scope project --transport http mcsv https://api.mcsv.me/api/mcp --header "Authorization: Bearer mcsv_xxx"` VS Code Copilot (`.vscode/mcp.json`): same JSON but the top-level key is `servers`. Windsurf (`~/.codeium/windsurf/mcp_config.json`): same as Cursor but use `serverUrl` instead of `url`. If MCP is not available in the environment, use plain REST via curl/fetch — it exposes the same 61 tools. ## Tool catalog (61 tools · 15 groups) `RO` = read-only (safe, no mutation). Exact arguments per tool: always from `GET /api/v1/tools` (`input_schema`). - **info**: `server_overview` RO · `server_info` RO · `server_resources` RO · `activity_log_recent` RO · `players_online` RO · `players_sessions` RO · `domain_info` RO - **files_read**: `files_list` RO · `files_read` RO · `files_read_many` RO · `files_read_base64` RO · `files_undo_list` RO · `files_download_url` (in this group but NOT read-only) - **files_write**: `files_write` · `files_edit` · `files_undo` · `files_delete` · `files_mkdir` · `files_rename` · `files_copy` · `files_compress` · `files_decompress` · `files_upload_base64` · `files_fetch_url` - **console**: `console_tail` RO · `console_send` - **logs** (history + boot analysis, all read-only): `logs_list` · `logs_read` (decompresses rotated `.log.gz`) · `logs_search` (term/regex across files, newest first) · `logs_startup` — **call `logs_startup` FIRST for any "plugin not loading / command missing" issue**; a jar sitting in `/plugins` does not mean the plugin is running. `console_tail` only sees the current log; anything already rotated into `.log.gz` is reachable only through these. - **power**: `power_action` (start / stop / restart / kill) - **backups**: `backups_list` RO · `backups_create` · `backups_restore` · `backups_delete` - **settings**: `server_rename` · `eula_accept` · `server_properties_patch` - **plugins**: `plugins_list` RO (lists `/plugins`, or `/mods` on forge/fabric/neoforge/quilt — hybrid servers like mohist/arclight have both, check the other folder with `files_list` · pass `with_state: true` to cross-check each jar against the latest boot: enabled / disabled_during_boot / failed / not_seen_in_last_boot) · `security_scan` RO · `search_mods` RO (returns direct `download_url` for top Modrinth hits) - **databases**: `databases_list` RO · `databases_create` · `databases_delete` - **schedules**: `schedules_list` RO · `schedules_create` · `schedules_delete` · `schedules_toggle` - **webhooks**: `webhooks_list` RO · `webhooks_create` · `webhooks_delete` · `webhooks_test` - **members**: `members_list` RO · `members_remove` - **site** (the server's public website at `.mcsv.me` — its own path namespace `/`, `/rules`, `/style.css`, unrelated to server files): `site_pages_list` RO · `site_page_read` RO · **`site_page_edit`** (anchored `old_string`/`new_string` edit — the default way to change an existing page; bytes outside the anchor are never touched) · `site_page_write` (writes the **whole** page — for new pages, or a deliberate full rewrite) · `site_page_delete` · `site_set_enabled`. Page kinds, allowed imports, live-data variables and size limits are all in the tool descriptions — read them before writing a page. Both write tools save first and then return a `problems` array (TSX compile, `