Skip to main content
Glama
README.md
# gigahost-mcp

A tiny [MCP](https://modelcontextprotocol.io) server exposing the
[Gigahost](https://gigahost.no) API (`https://api.gigahost.no/api/v0`) to any
MCP-compatible client (Claude Code/Desktop, Cursor, Windsurf, Cline, or your own).

The Gigahost API is uniform REST, so the whole surface (~150 endpoints: DNS
zones/records, `.no` domains, servers, web hosting, BGP, DynDNS) is wrapped by a
single passthrough tool rather than one tool per endpoint. The model drives paths
directly.

## Tool

`gigahost_request(method, path, body=None, query=None, files=None)`

- `method` — `GET` / `POST` / `PUT` / `DELETE`
- `path` — e.g. `/dns/zones`, `/servers/123/reboot`
- `body` — JSON body for `POST`/`PUT`
- `query` — querystring params
- `files` — `{form_field: local_file_path}` for multipart uploads (e.g.
  `POST /webhosting/{id}/files/upload`). When set, `body` is sent as multipart form
  fields instead of JSON.

Returns `{"status": <http_status>, "data": <json|text>}`, or `{"error": ...}` on a
network/file failure.

This covers the full Gigahost REST surface: all JSON endpoints plus multipart file
uploads. The only bound on what works is the API key's permission scope.

Endpoint reference: <https://gigahost.no/en/api-dokumentasjon>

### Examples

Calls below are shown as the tool arguments the model produces.

List DNS zones:

```json
{ "method": "GET", "path": "/dns/zones" }
```

Add an A record to zone 4040:

```json
{
  "method": "POST",
  "path": "/dns/zones/4040/records",
  "body": { "record_name": "@", "record_type": "A", "record_value": "203.0.113.10", "record_ttl": 3600 }
}
```

Reboot a server:

```json
{ "method": "GET", "path": "/servers/123/reboot" }
```

Upload a file to a web-hosting account (multipart):

```json
{
  "method": "POST",
  "path": "/webhosting/55/files/upload",
  "files": { "file": "/local/path/index.html" }
}
```

## Prerequisites

- **Python 3.11+**
- A **Gigahost API key** (`flux_live_<hex>`), created in the Flux dashboard under
  **Konto → API keys**. Scope it to only the permissions you need (e.g. DNS /
  domain). It is revocable and shown only once at creation.
- The key is read from the `GIGAHOST_API_KEY` environment variable.

## Install

Pick whichever you already use.

### Option A — uv (zero install)

Dependencies are declared inline (PEP 723), so nothing to install:

```sh
GIGAHOST_API_KEY=flux_live_xxxxxxxx uv run /path/to/server.py
```

### Option B — pip / venv

```sh
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
GIGAHOST_API_KEY=flux_live_xxxxxxxx python server.py
```

### Self-check (offline, no key, no network)

```sh
uv run server.py --selfcheck      # or: python server.py --selfcheck
# prints: ok
```

## Configure your MCP client

Most clients read a JSON config with an `mcpServers` map. Add one of the following,
using an **absolute path** to `server.py` and your key in `env`.

With uv:

```json
{
  "mcpServers": {
    "gigahost": {
      "command": "uv",
      "args": ["run", "/absolute/path/to/server.py"],
      "env": { "GIGAHOST_API_KEY": "flux_live_xxxxxxxx" }
    }
  }
}
```

With a venv / system Python:

```json
{
  "mcpServers": {
    "gigahost": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"],
      "env": { "GIGAHOST_API_KEY": "flux_live_xxxxxxxx" }
    }
  }
}
```

Config file locations vary by client, e.g.:

- **Claude Desktop** — `claude_desktop_config.json` (Settings → Developer → Edit Config)
- **Cursor** — `~/.cursor/mcp.json`
- **Windsurf / Cline / others** — their MCP settings JSON

### Claude Code (CLI shortcut)

```sh
claude mcp add -s user gigahost -e GIGAHOST_API_KEY=flux_live_xxxxxxxx \
  -- uv run /absolute/path/to/server.py
```

After configuring, the client should list a `gigahost` server with the
`gigahost_request` tool. Smoke-test by asking it to `GET /dns/zones`.

## Key permissions (scope)

When you create the Flux API key you set an access level **per area**. This bounds
what `gigahost_request` can do, independent of the code:

- **Ingen** — no access, calls return `403`.
- **Les** — read only, `GET` works, writes (`POST`/`PUT`/`DELETE`) return `403`.
- **Les/skriv** — full read + write.

Areas map to API path prefixes:

| Flux area        | Path prefix              | Usable with an API key?   |
|------------------|--------------------------|---------------------------|
| DNS og domener   | `/dns/...`               | yes                       |
| Servere          | `/servers/...`           | yes                       |
| Webhotell        | `/webhosting/...`        | yes                       |
| Deploy           | `/deploy/...`, `/reinstall/...` | yes                |
| Rack             | `/bgp/...`               | yes                       |
| Fakturering      | `/my/invoices`           | **no, see below**         |
| Min konto        | `/account/...`, `/my/account` | `/account` yes, `/my/account` no |

Three traps, all found by probing the live API rather than reading the docs:

**`403` is the catch-all.** A bogus path returns the same
`"You do not have permission for this operation."` as a genuine denial, so a 403
proves neither that the path exists nor that a scope is missing.

**A key cannot audit itself.** Every endpoint marked `(admin only)` needs a
session token and always 403s to a key, including all of `/account/apikeys`. So
the only way to know what a key can do is to look in the Flux web UI.

**Scope does not gate everything.** `/account` returns full account data even
for a key scoped to `dns` alone, and `/servers` / `/webhosting` / `/bgp` return
`200` with empty collections when you own none of those. Neither a `200` nor an
empty list tells you a scope is granted. `/my/invoices` 403s under every scope
tried, including after granting `Fakturering`, for reasons that cannot be
resolved from the API. Read invoices in the Flux UI.

Grant the minimum each area needs, not blanket `Les/skriv`. But note that
`DNS og domener` must be `Les/skriv` for `POST /dns/domains/register`, and must
not be pinned to specific zone IDs, since a newly registered domain is by
definition not in that list. See [API.md](API.md) for the full endpoint map.

## Security

- The API key is read from `GIGAHOST_API_KEY` only. It is never written to this repo.
- Putting the key in a client config file (or `~/.claude.json` via `claude mcp add
  -e`) stores it in plaintext. Acceptable for a personal scoped key on your own
  machine. Revoke and rotate in Flux if it leaks.
- Prefer a narrowly-scoped key over your account password.

## License

MIT