Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"ionoscloud": {
"command": "ionoscloud-mcp",
"env": {
"IONOS_TOKEN": "${IONOS_TOKEN}"
"IONOS_TOKEN": "${IONOS_TOKEN}",
"IONOS_MCP_LOAD_MODE": "eager"
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Changelog

## Unreleased
## v1.0.1 — June 2026

### Added

- **`dynamic` load mode** (alias `search`): exposes only three meta-tools — `ionos_search_tools`, `ionos_describe_tools`, `ionos_call_tool` — through which the model discovers and invokes the full catalogue at runtime. The public tool list never changes (no `notifications/tools/list_changed` needed), so it fits clients with hard tool caps and no tool search of their own (Cursor ~40, Windsurf 100). Select with `--load-mode dynamic` or `IONOS_MCP_LOAD_MODE=dynamic`.

- **`--load-mode` flag**: selects the tool-registration strategy (`eager` | `lazy` | `dynamic`) from the command line. Takes precedence over `IONOS_MCP_LOAD_MODE`; the effective mode and its source are logged to stderr at startup.

- **Managed Kubernetes**: 8 new read-only tools as a dedicated product (eagerly loaded) — `list_k8s_clusters`, `get_k8s_cluster`, `list_k8s_nodepools`, `get_k8s_nodepool`, `list_k8s_nodepool_nodes`, `get_k8s_node`, `list_k8s_versions`, `get_k8s_default_version`. Covers the full cluster/node-pool/node hierarchy plus available version discovery.

## v1.0.0 — June 2026
Expand Down
20 changes: 11 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ Pass `VERSION=<tag>` to `make build` or `make docker` to override the version st
## Architecture

```
main.go # Entry point: all SDK clients init, MCP server, stdio transport
server_config.go # Version resolution (ldflags > go install > vcs), eagerLoad()
main.go # Entry point: arg parsing, load-mode resolution, SDK clients init, MCP server, stdio transport
server_config.go # Version resolution (ldflags > go install > vcs), resolveLoadMode()
resources.go # MCP resources (embedded docs served to LLM clients)
tools/
├── helpers.go # Shared helpers (TextResult)
├── inputs.go # Shared input structs with json/jsonschema tags
├── ionosclient/ # User-Agent string builder
├── loader/ # Lazy loaders for compute and object storage
├── dynamic/ # 'dynamic' load mode: catalog + search/describe/call meta-tools
├── compute/ # Compute Engine tools (servers, datacenters, volumes, NICs, etc.)
├── dns/ # DNS tools (zones, records, DNSSEC, quota)
├── billing/ # Billing tools (invoices, usage, utilization, traffic, EVN)
Expand All @@ -52,10 +53,11 @@ docs/
```

- **main.go**: Initializes all SDK clients (compute, DNS, billing, cert, object storage base + management, activity log), creates the MCP server, and runs over `mcp.StdioTransport`. All clients share a single `*http.Client` with the custom User-Agent `RoundTripper` installed.
- **server_config.go**: Resolves `serverVersion` from ldflags (release builds), `go install` module version, or VCS revision (local builds). Also contains `eagerLoad()` which reads `IONOS_MCP_EAGER_LOAD`.
- **server_config.go**: Resolves `serverVersion` from ldflags (release builds), `go install` module version, or VCS revision (local builds). Also contains `resolveLoadMode(flagVal, envVal)` — the pure precedence resolver (flag > env > default) for the load mode.
- **resources.go**: Registers MCP _resources_ (distinct from tools) — structured documents served to LLM clients. Currently exposes `ionos://billing/focus-v1.3` (the FOCUS v1.3 billing spec, embedded from `docs/billing/focus-v1.3.md`).
- **tools/ionosclient/**: Builds the User-Agent string for all outbound IONOS API calls, including product name, server version, SDK bundle version, transport mode, and Go OS/arch.
- **tools/loader/**: Registers `ionos_load_compute_tools` and `ionos_load_objectstorage_tools` — sentinel tools that dynamically register the full product tool set on first call. Used in lazy mode (default). Once called, the tool list is updated and MCP clients receive a `notifications/tools/list_changed` signal.
- **tools/loader/**: Registers `ionos_load_compute_tools` and `ionos_load_objectstorage_tools` — sentinel tools that dynamically register the full product tool set on first call. Used in `lazy` mode. Once called, the tool list is updated and MCP clients receive a `notifications/tools/list_changed` signal.
- **tools/dynamic/**: Implements `dynamic` load mode. Builds a private in-memory "catalog" server with every product's tools (reusing their `RegisterAll`), self-connects to snapshot the tool metadata, and registers three meta-tools on the public server — `ionos_search_tools` (keyword search over the catalog), `ionos_describe_tools` (full input schemas), `ionos_call_tool` (forwards an invocation to the catalog server). The public tool list never changes.

### Request Flow

Expand All @@ -71,17 +73,17 @@ Environment variables (read from the MCP server process — typically inherited

### Load modes

The server supports three tool-registration strategies via `IONOS_MCP_LOAD_MODE`:
The server supports three tool-registration strategies, selectable via the `--load-mode` flag or the `IONOS_MCP_LOAD_MODE` env var. Precedence: flag > env > default (`eager`). Resolution is a pure function `resolveLoadMode(flagVal, envVal)` in `server_config.go` (returns the mode + its source); `main.go` resolves once at startup and logs `load mode: <mode> (source: ...)` to stderr.

- **`eager`** (default): all tools register at startup. Optimal for Claude Code (ToolSearch defers schemas client-side, ~1–3k tokens for names only) and required for clients without `notifications/tools/list_changed` support (Claude Desktop, claude.ai connectors, Claude in Chrome, Smithery scanner).

- **`lazy`**: defer Compute and Object Storage behind `ionos_load_compute_tools` / `ionos_load_objectstorage_tools` sentinel tools. Calling either registers the full product set and emits `notifications/tools/list_changed`. Only useful for clients that honour the notification AND lack client-side schema deferral.
- **`lazy`**: defer Compute and Object Storage behind `ionos_load_compute_tools` / `ionos_load_objectstorage_tools` sentinel tools. Calling either registers the full product set and emits `notifications/tools/list_changed`. Only useful for clients that honour the notification AND lack client-side schema deferral. (Note: this runtime list-mutation pattern is the one the GitHub MCP server retired in 2026; `dynamic` is generally preferable for cap-limited clients.)

- **`router`** (reserved, not yet implemented): single `ionos_search_tools` + `ionos_invoke` pair. Designed for clients with hard tool caps (Cursor 40, Windsurf 100) or no schema deferral. Currently logs a warning and falls back to `eager`. Implementation tracked separately.
- **`dynamic`** (alias: `search`): exposes only three meta-tools — `ionos_search_tools`, `ionos_describe_tools`, `ionos_call_tool` (see `tools/dynamic/`). The full catalogue is registered onto a private in-memory "catalog" server (reusing each product's `RegisterAll` unchanged); the dynamic package self-connects over an in-memory transport, snapshots tool metadata at startup, and forwards `ionos_call_tool` to it. The public tool list never changes (no `list_changed` needed). For clients with hard tool caps and no tool search of their own (Cursor ~40, Windsurf 100). Not for Claude Code — keep it `eager`.

Parsing is case-insensitive. Unknown values fall back to `eager` with a stderr warning. Empty / unset env var = eager.
Parsing is case-insensitive. Any unknown value warns on stderr and falls back to `eager`. Empty / unset = eager.

All other products (DNS, Billing, Cert, Activity Log) are always registered eagerly.
In `eager` and `lazy` modes, the small products (DNS, Billing, Cert, Activity Log, k8s) always register eagerly. In `dynamic` mode every product — including those — is hidden behind the meta-tools. The product list is defined once as a `[]dynamic.Product` slice in `main.go` and shared across all three modes so they cannot drift.

### Adding New Tools

Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Apache 2.0](https://img.shields.io/github/license/ionos-cloud/ionoscloud-mcp)](LICENSE)
[![Go reference](https://pkg.go.dev/badge/github.com/ionos-cloud/ionoscloud-mcp.svg)](https://pkg.go.dev/github.com/ionos-cloud/ionoscloud-mcp)

A **read-only** [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that connects your IONOS CLOUD account to any MCP-compatible AI assistant or autonomous AI agent: Claude Desktop, Cursor, VS Code (GitHub Copilot), Windsurf, Cline, Continue, OpenCode, and 5+ others. **112 tools across 6 IONOS CLOUD products** — list, inspect, and audit your infrastructure through natural-language prompts or programmatic agentic loops.
A **read-only** [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that connects your IONOS CLOUD account to any MCP-compatible AI assistant or autonomous AI agent: Claude Desktop, Cursor, VS Code (GitHub Copilot), Windsurf, Cline, Continue, OpenCode, and 5+ others. **118 tools across 7 IONOS CLOUD products** — list, inspect, and audit your infrastructure through natural-language prompts or programmatic agentic loops.

Built and maintained by the IONOS Cloud team. The server runs as a local binary on your workstation, a CI runner, or inside a container. IONOS CLOUD API calls go directly to IONOS over HTTPS; no third-party AI provider sits in the data path.

Expand Down Expand Up @@ -55,7 +55,7 @@ This server is published across multiple MCP registries and IDE marketplaces:

## Supported products

All tools follow the `list_*`, `get_*`, and `head_*` naming convention. In `lazy` mode, two loader tools (`ionos_load_compute_tools`, `ionos_load_objectstorage_tools`) register the Compute and Object Storage catalogues on demand; in the default `eager` mode all tools register at startup. See [Tool loading mode](#tool-loading-mode).
All tools follow the `list_*`, `get_*`, and `head_*` naming convention. In the default `eager` mode all tools register at startup; `lazy` mode defers Compute and Object Storage behind loader tools; `dynamic` mode exposes only three search/describe/call meta-tools for clients with hard tool caps. See [Tool loading mode](#tool-loading-mode).

| Product | Tools | Capabilities |
|---|---|---|
Expand Down Expand Up @@ -162,29 +162,31 @@ Per-client setup guides for the 12 supported AI clients: [Connect to an AI Clien

## Tool loading mode

The `IONOS_MCP_LOAD_MODE` environment variable selects how tools are exposed:
The load mode selects how tools are exposed. Set it with either the `--load-mode` flag or the `IONOS_MCP_LOAD_MODE` environment variable; **the flag wins if both are set**, and otherwise the default is `eager`. Parsing is case-insensitive.

- **`eager`** (default): all tools register at startup. Recommended for Claude Code (which defers full schemas client-side via ToolSearch, paying ~1–3k tokens for names only) and the only working mode for clients that ignore `notifications/tools/list_changed` (Claude Desktop, claude.ai connectors, Claude in Chrome, Smithery scanner).

- **`lazy`**: Compute and Object Storage register only on demand. Two sentinel tools (`ionos_load_compute_tools`, `ionos_load_objectstorage_tools`) appear at startup; calling either registers the full product set and emits `notifications/tools/list_changed`. Use only if your MCP client honours that notification AND lacks client-side schema deferral — otherwise eager mode is cheaper.

Parsing is case-insensitive. Unknown values fall back to `eager`.
- **`dynamic`** (alias: `search`): the server exposes only **three** meta-tools — `ionos_search_tools`, `ionos_describe_tools` and `ionos_call_tool` — and the model discovers and invokes the full catalogue through them at runtime. The real tool list never changes, so unlike `lazy` this needs no `notifications/tools/list_changed` support. Intended for clients with **hard tool caps and no tool search of their own** (e.g. Cursor's ~40-tool cap, Windsurf's 100). Trade-off: the model must `search` → `describe` → `call` rather than seeing tools directly, costing extra round-trips, so prefer `eager` on Claude Code.

The server logs the effective mode and its source (flag / env / default) to stderr at startup, e.g. `load mode: dynamic (source: --load-mode flag)`.

```json
{
"mcpServers": {
"ionoscloud": {
"command": "/path/to/ionoscloud-mcp",
"args": ["--load-mode", "dynamic"],
"env": {
"IONOS_TOKEN": "your-api-token",
"IONOS_MCP_LOAD_MODE": "lazy"
"IONOS_TOKEN": "your-api-token"
}
}
}
}
```

**Tool-count limits:** Windsurf caps connected MCP servers at 100 tools combined. With the default eager mode the server exposes 112 tools and exceeds that limit. Use lazy loading on Windsurf. For more information, see [Selective Tool Loading](https://docs.ionos.com/cloud/ai/mcp-server/configuration/selective-tool-loading).
**Tool-count limits:** Windsurf caps connected MCP servers at 100 tools combined; Cursor caps at ~40 across all servers. With the default eager mode the server exceeds both. On Windsurf, `lazy` keeps the startup surface small enough; on Cursor (or any cap-limited client without its own tool search), use `dynamic` to present just three tools. For more information, see [Selective Tool Loading](https://docs.ionos.com/cloud/ai/mcp-server/configuration/selective-tool-loading).

## Demo

Expand Down
1 change: 1 addition & 0 deletions docs/compute/application-loadbalancer.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Lists all application load balancers (ALB) in a specific data center.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `datacenter_id` | string | Yes | The ID of the data center |
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand Down
4 changes: 3 additions & 1 deletion docs/compute/contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ Gets contract and resource limit information for your IONOS CLOUD account.

**Parameters:**

None.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `depth` | integer | No | Nesting depth of returned objects (0–5). |

**Example:**

Expand Down
6 changes: 5 additions & 1 deletion docs/compute/datacenter.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ description: |-

Lists all virtual data centers in your IONOS CLOUD account.

**Parameters:** None
**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). Depth 1 includes names and basic properties. |
Comment thread
cavramoniu-ionos marked this conversation as resolved.

**Example:**

Expand Down
2 changes: 2 additions & 0 deletions docs/compute/firewall-rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Lists all firewall rules on a specific network interface.
| `datacenter_id` | string | Yes | The ID of the data center |
| `server_id` | string | Yes | The ID of the server |
| `nic_id` | string | Yes | The ID of the network interface |
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). |

**Example:**

Expand Down Expand Up @@ -48,6 +49,7 @@ Gets detailed information about a specific firewall rule.
| `server_id` | string | Yes | The ID of the server |
| `nic_id` | string | Yes | The ID of the network interface |
| `firewallrule_id` | string | Yes | The ID of the firewall rule |
| `depth` | integer | No | Nesting depth of returned objects (0–5). |

**Example:**

Expand Down
6 changes: 5 additions & 1 deletion docs/compute/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ description: |-

Lists all available images (OS templates) in IONOS CLOUD.

**Parameters:** None
**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). Depth 1 includes names and basic properties. |

**Example:**

Expand Down
1 change: 1 addition & 0 deletions docs/compute/ip-block.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Gets detailed information about a specific reserved IP block.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `ipblock_id` | string | Yes | The ID of the IP block |
| `depth` | integer | No | Nesting depth of returned objects (0–5). |
Comment thread
cavramoniu-ionos marked this conversation as resolved.

**Example:**

Expand Down
1 change: 1 addition & 0 deletions docs/compute/lan.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Lists all LANs in a specific data center.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `datacenter_id` | string | Yes | The ID of the data center |
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand Down
1 change: 1 addition & 0 deletions docs/compute/loadbalancer.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Lists all load balancers in a specific data center.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `datacenter_id` | string | Yes | The ID of the data center |
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand Down
6 changes: 5 additions & 1 deletion docs/compute/location.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ description: |-

Lists all available locations (regions) in IONOS CLOUD.

**Parameters:** None
**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). Depth 1 includes names and basic properties. |

**Example:**

Expand Down
1 change: 1 addition & 0 deletions docs/compute/nat-gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Lists all NAT gateways in a specific data center.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `datacenter_id` | string | Yes | The ID of the data center |
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand Down
1 change: 1 addition & 0 deletions docs/compute/network-loadbalancer.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Lists all network load balancers (NLB) in a specific data center.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `datacenter_id` | string | Yes | The ID of the data center |
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand Down
1 change: 1 addition & 0 deletions docs/compute/nic.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Lists all network interfaces (NICs) attached to a specific server.
|------|------|----------|-------------|
| `datacenter_id` | string | Yes | The ID of the data center |
| `server_id` | string | Yes | The ID of the server |
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand Down
1 change: 1 addition & 0 deletions docs/compute/private-cross-connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Gets detailed information about a specific private cross-connect.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `pcc_id` | string | Yes | The ID of the private cross-connect |
| `depth` | integer | No | Nesting depth of returned objects (0–5). |
Comment thread
cavramoniu-ionos marked this conversation as resolved.

**Example:**

Expand Down
2 changes: 2 additions & 0 deletions docs/compute/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Gets detailed information about a specific API request.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `request_id` | string | Yes | The ID of the request |
| `depth` | integer | No | Nesting depth of returned objects (0–5). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand All @@ -62,6 +63,7 @@ Gets the status of a specific API request.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `request_id` | string | Yes | The ID of the request |
| `depth` | integer | No | Nesting depth of returned objects (0–5). |

**Example:**

Expand Down
2 changes: 2 additions & 0 deletions docs/compute/security-group.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Lists all security groups in a specific data center.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `datacenter_id` | string | Yes | The ID of the data center |
| `depth` | integer | No | Nesting depth of returned objects (0–5, default `1`). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand Down Expand Up @@ -97,6 +98,7 @@ Gets details of a specific security group rule.
| `datacenter_id` | string | Yes | The ID of the data center |
| `security_group_id` | string | Yes | The ID of the security group |
| `rule_id` | string | Yes | The ID of the security group rule |
| `depth` | integer | No | Nesting depth of returned objects (0–5). |

**Example:**

Expand Down
2 changes: 2 additions & 0 deletions docs/compute/server-subresources.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Gets details of a specific GPU attached to a server.
| `datacenter_id` | string | Yes | The ID of the data center |
| `server_id` | string | Yes | The ID of the server |
| `gpu_id` | string | Yes | The ID of the GPU |
| `depth` | integer | No | Nesting depth of returned objects (0–5). |

Comment thread
cavramoniu-ionos marked this conversation as resolved.
**Example:**

Expand Down Expand Up @@ -127,6 +128,7 @@ Gets the remote console URL for a specific server.
|------|------|----------|-------------|
| `datacenter_id` | string | Yes | The ID of the data center |
| `server_id` | string | Yes | The ID of the server |
| `depth` | integer | No | Nesting depth of returned objects (0–5). |

**Example:**

Expand Down
Loading