Linode OAuth MCP Server
An experimental MCP server that turns the Linode API v4 OpenAPI specification into tools. Each tool requests only the OAuth scopes its operation needs, and the sign-in link appears in the MCP client's chat. The API spec is fetched from Linode's official OpenAPI distribution, parsed, cached on disk, and used to build the MCP tool catalog.
Scope and discovery
The server exposes Linode API operations as linode_<operationId> tools. By default, the server lists only three tools and keeps the rest of the catalog out of the initial list: search_tools finds operations, call_tool invokes them, and get_tool_schema returns one tool's full input schema. Search ranks tools by their name, title, and summary, and returns a compact entry for each of up to 10 matches: its title, first description paragraph, access level, required OAuth scopes, and a one-line summary per parameter. Results run about 5K characters, where full tool definitions would be up to 250K. Responses are rendered as YAML, including calls made through call_tool. --tool-catalog full lists every generated tool directly. Every paginated list tool accepts an X-Filter argument, a JSON string sent as Linode's filter header (for example, {"mine": true}). The upstream spec declares that header on only a few operations.
The server fetches the spec on first launch and stores it in the user's cache directory (LINODE_MCP_CACHE_DIR overrides the default). Later starts load the cached JSON copy, which takes about a second. After startup, the server checks the upstream spec in the background with a conditional GET using the cached ETag. An unchanged spec costs one small 304 Not Modified request. If the spec has changed, the server fetches and caches it, and the new spec takes effect the next time the server starts. Run linode-oauth-mcp-server --update-specs to force a fresh fetch.
Authentication
Credentials are checked in this order:
LINODE_API_TOKENorLINODE_OAUTH_TOKEN.LINODE_CLI_TOKEN, then thetokenin~/.config/linode-cli(orLINODE_CLI_CONFIG).- A Linode public OAuth app, when
LINODE_OAUTH_CLIENT_IDis configured.
When only the public client is configured, the first authenticated tool call opens Linode sign-in in a browser. The user grants access and the server receives the short-lived bearer token through a loopback callback. The server saves the token, its expiry, and its granted scopes to oauth-token.json in the cache directory. The file is readable only by the user (mode 0600). A restarted server, for example after an MCP reconnect, reuses that token until it expires; Linode's public OAuth tokens last 2 hours. Expired tokens, and tokens saved for a different OAuth client ID, are discarded. Delete the file to sign out. Tokens from LINODE_API_TOKEN or linode-cli take precedence over a saved sign-in. A personal access token or an existing CLI credential is simpler for personal use. The Linode CLI can set up its own browser-based sign-in with linode-cli configure.
The first sign-in requests read-only access to Linodes and StackScripts, plus whatever scopes the first tool call needs. Each operation's required scopes come from its security entry in the OpenAPI spec. When a later call needs a scope the current token lacks (for example, linodes:read_write to create a Linode), the server asks for sign-in again. That request asks for the scopes already granted plus the new ones. MCP clients that support URL-mode elicitation show the sign-in link in the chat. On MCP 2026-07-28, the server returns an input-required result carrying the link, and the client retries the call after the user opens it. On earlier protocol versions, the server sends the elicitation request during the call. Clients without URL elicitation get the link opened in the system browser. Each tool's required scopes are listed in its linode/oauthScopes metadata. The call waits until the user approves, then continues with the new token. Operations the spec marks as public, such as listing regions or Linode types, never prompt for sign-in. Set LINODE_OAUTH_SCOPES (comma- or space-separated) to change the initial scope set, for example to * to grant everything up front. Tokens from LINODE_API_TOKEN or linode-cli are used as-is, without scope checks.
The repo includes a shared public OAuth client ID. To register a replacement client, use a Linode CLI identity permitted to create OAuth clients:
linode-cli account client-create \
--label "Linode OAuth MCP Server" \
--redirect_uri http://localhost:8765/oauth/callback \
--public true
The returned id is public and safe to commit; do not publish a private client's secret. Users can share this public client and do not need to register their own. The callback URL must exactly match the configured http://localhost:8765/oauth/callback. Public OAuth returns a short-lived access token in the redirect URL and does not issue a refresh token, so users sign in again when it expires. For personal use, a Linode CLI credential or personal access token is simpler. See Linode authentication docs and Create an OAuth client.
Install and run
Requires Python 3.13+. The package is published on PyPI as linode-oauth-mcp-server. Run it without installing, using either tool:
uvx linode-oauth-mcp-server
pipx run linode-oauth-mcp-server
Both fetch the latest release on first use and cache it. To pick up a newer release, run uvx linode-oauth-mcp-server@latest, or pin a version with uvx linode-oauth-mcp-server@0.2.0. To install the command permanently, run uv tool install linode-oauth-mcp-server or pipx install linode-oauth-mcp-server.
This starts the default stdio server for MCP clients that launch local processes. For ChatGPT, which connects to remote MCP endpoints, run the server over local Streamable HTTP and connect through a Secure MCP Tunnel:
uvx linode-oauth-mcp-server --transport http --host 127.0.0.1 --port 8000
The local MCP endpoint is http://127.0.0.1:8000/mcp. In ChatGPT, create a custom MCP connection and select an available Secure MCP Tunnel for this endpoint; use the resulting tunnel URL in the connection. Keep the local server running while testing. The HTTP endpoint has no separate MCP bearer authentication, so use it only locally behind a private tunnel; do not expose it directly to the public internet.
The initial spec fetch requires network access. Later starts use the cached specification.
MCP client examples
The server signs in through Linode's shared public OAuth client by default, so no token needs to be copied into client configuration. It also uses LINODE_API_TOKEN or an existing Linode CLI credential when one is present.
Claude Code
claude mcp add linode -- uvx linode-oauth-mcp-server
Run /mcp in Claude Code to confirm that linode is connected.
Codex CLI
codex mcp add linode -- uvx linode-oauth-mcp-server
If your Codex sandbox restricts writes to your home cache directory or blocks network access, use local caches and fetch the spec once from your normal shell before adding the server:
UV_CACHE_DIR="$PWD/.uv-cache" LINODE_MCP_CACHE_DIR="$PWD/.linode-mcp-cache" \
uvx linode-oauth-mcp-server --update-specs
codex mcp add linode \
--env UV_CACHE_DIR="$PWD/.uv-cache" \
--env LINODE_MCP_CACHE_DIR="$PWD/.linode-mcp-cache" \
-- uvx linode-oauth-mcp-server
Run codex mcp list to confirm the registration, then restart Codex so the server is loaded.
Other stdio clients
{
"mcpServers": {
"linode": {
"command": "uvx",
"args": ["linode-oauth-mcp-server"]
}
}
}
API spec updates
The default source is https://raw.githubusercontent.com/linode/linode-api-openapi/main/openapi.json. To change it, pass a YAML config:
apis:
linode:
description: "Linode API v4"
tags: [cloud, compute, networking]
service_name: linode
specs:
- url: "https://raw.githubusercontent.com/linode/linode-api-openapi/main/openapi.json"
Then run linode-oauth-mcp-server --config path/to/apis.yaml --update-specs.
Safety and limits
The upstream OpenAPI document includes read and write operations, including operations that create billable resources or delete resources. Review the operation description and parameters before invoking a write. Grant the token only the scopes required for the work. This project is experimental; API behavior and account-level permissions still need testing against a Linode account.
Development
Run the server and tests from a checkout:
uv run linode-oauth-mcp-server
uv run --extra dev pytest
To have an MCP client run your checkout instead of the PyPI release, register uv run --project /path/to/checkout linode-oauth-mcp-server as the command.
Releasing
Versions come from git tags through setuptools-scm. Pushing a tag like v0.2.0, or publishing a GitHub release that creates one, runs .github/workflows/release.yml. The workflow tests, builds, and publishes 0.2.0 to PyPI:
git tag v0.2.0
git push origin v0.2.0
Publishing uses PyPI trusted publishing, which is based on GitHub's OpenID Connect tokens, so the repository stores no PyPI API token. One-time setup:
- On PyPI, open Your account → Publishing → Add a new pending publisher and enter:
- PyPI project name:
linode-oauth-mcp-server - Owner:
displague - Repository name:
linode-oauth-mcp-server - Workflow name:
release.yml - Environment name:
pypi
- PyPI project name:
- On GitHub, create an environment named
pypiunder Settings → Environments. Optionally, limit it tov*tags or require a reviewer.
The first tagged release creates the PyPI project.
References
Metadata
Release files for linode-oauth-mcp-server 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| linode_oauth_mcp_server-0.1.0.tar.gz | 49.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| linode_oauth_mcp_server-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 89.3 kB
Release files / linode_oauth_mcp_server-0.1.0.tar.gz
| Download URL | linode_oauth_mcp_server-0.1.0.tar.gz |
|---|---|
| Size | 49.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
15ec69dd4c4d9438c75a6b58e78287ff00c34fb6213ae2eb213d937c491ae0e9
|
|
BLAKE2b-256 checksum How to use checksums |
17e5a428fc7eda9f477d77464fef1754f13eef71eeb72f31e9c70e8035b342a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / linode_oauth_mcp_server-0.1.0-py3-none-any.whl
| Download URL | linode_oauth_mcp_server-0.1.0-py3-none-any.whl |
|---|---|
| Size | 39.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4ce8fb13ebc7bb098dc194f2b2e9e2e3e2d83617068b100231997b33671d8b5f
|
|
BLAKE2b-256 checksum How to use checksums |
01db2b169f9c68491e0aa17397bc2773a21a6829a632df70ad59417861781ebd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency log