Linode OAuth MCP Server
Turns the Linode API v4 OpenAPI specification (Linode being Akamai's cloud computing platform, documented at techdocs.akamai.com/linode-api) into MCP tools, enabling management of Linode cloud resources such as Linodes (create, resize disks, boot/shutdown), volumes (detach and re-attach between Linodes), StackScripts, and LKE Kubernetes clusters (version upgrades with node recycling). Includes searchable tool discovery, per-operation OAuth scope requests with in-chat sign-in, X-Filter support for paginated list operations, and multi-step workflows for resize, volume move, and cluster upgrade.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Linode OAuth MCP Serverfind a tool to create a Linode and tell me what scopes it needs"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
Related MCP server: OpenAPI MCP Server
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 server takes the scopes a token actually has from the X-OAuth-Scopes header Linode sends on every response, so it knows when you granted fewer than were asked for. 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 trueThe 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-serverBoth 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 8000The 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-serverRun /mcp in Claude Code to confirm that linode is connected.
Codex CLI
codex mcp add linode -- uvx linode-oauth-mcp-serverIf 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-serverRun 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"]
}
}
}Workflows
Some changes take several API calls in a set order, with waits in between. The server exposes these as workflow_<workflowId> tools, defined in an Arazzo document (data/config/workflows.arazzo.yaml):
Tool | What it does |
| Shuts the Linode down if it is running, resizes the disk (unless it already has the new size), waits for the new size, and boots the Linode again if it was running and isn't already back up. |
| Detaches a volume, waits for the detach to finish, attaches it to another Linode (unless it's already there), and waits for the attachment. |
| Sets an LKE cluster's Kubernetes version, recycles its nodes (unless a recycle is already replacing them), and waits until every old node is replaced and all nodes are ready. |
Search finds workflow tools alongside the API tools, and get_tool_schema describes them. A workflow call waits for its steps to finish, reports progress per step, and returns its outputs with a list of the steps it ran. A call that would wait longer than its budget pauses instead (see Long-running workflows). Steps run through the server's own API tools, so a workflow uses the same credentials and requests the combined OAuth scopes of its steps in a single sign-in. If a step fails after an earlier step has changed something, the error says so, and the workflow is not retried automatically. A step that is rate limited even after the server's own retries (see Rate limits and retries) waits as long as Retry-After asks and sends the same request again; this doesn't count against the step's polling retries. Each step has a time limit: the polling time its retry action allows (retryAfter × retryLimit) plus two minutes for rate-limit waits. A step that runs past its limit stops the workflow.
A workflow can run again partway through its work, for example when a client re-sends a call whose connection dropped. So before each write that can't safely be repeated, a read step checks whether the write is already done and skips it if so. At startup the server checks every workflow for this and logs a warning for each problem it finds:
a write (
POST) that can be reached from the start, or from a read step, with no read before it that can skip it based on the response;a delete with no
onFailureaction for$statusCode == 404;a step that uses values read before an earlier delete. Create the replacement before deleting the original.
The checks look at the shape of a workflow, not what its conditions mean. A step that is safe to repeat for a reason they can't see can say why with x-replay-safe: <reason>. Run linode-oauth-mcp-server --check-workflows to print the findings and exit non-zero if there are any.
Long-running workflows
A disk resize or an LKE upgrade can take far longer than an MCP client waits for one call. So a workflow call that would wait more than max_wait_s seconds (default 45, or LINODE_MCP_WORKFLOW_WAIT) returns status: working instead. The result names the next step, gives retry_after_s (how long it was about to wait), shows the values it carried, and includes a resume token. Calling the same tool again with only resume continues from the next step. A call that comes back sooner than retry_after_s first waits out the rest of it (within its own max_wait_s), so polling keeps to each step's interval. The agent decides when to do that, and can do other work in between.
The token holds the whole state of the run, so the server keeps nothing between calls, and a run can continue after the server restarts, days later. It carries only what the rest of the run uses: the inputs, the outputs of earlier steps that later steps refer to, and how many polling retries have been used. It never carries credentials; each call uses the caller's own sign-in. The token is readable, and signed with HMAC-SHA256 so it can't be edited to skip a step or change a value. The signing key is created on first use and kept in the cache directory as resume.key, readable only by you. Set LINODE_MCP_RESUME_KEY to use your own. The server refuses a token:
that was edited, or was signed with another key;
that expired: tokens last a week, or
LINODE_MCP_RESUME_TTLseconds;whose remaining steps have changed since it was issued, for example after an upgrade. Changes to finished steps, or to descriptions, don't count.
A run pauses only before a read step from which it is safe to run the rest again (see the checks above). So a token that is used twice, or used after someone else has changed the resources, re-checks the real state before it writes anything.
An OAuth sign-in lasts about two hours, and Linode's public OAuth has no refresh token, so a long run can outlast it. When the sign-in expires partway through a call, or a poll gets a 401, the run pauses with a resume token at the next safe step and says the sign-in expired, rather than signing in mid-call, which can't show the client a prompt. The next call with resume asks you to sign in first, then continues.
The executor supports a subset of Arazzo 1.0: operationId steps, runtime expressions in parameters and request payloads, simple and regex criteria, end/goto/retry actions (retry with retryAfter and retryLimit is how a step polls), and step and workflow outputs. It rejects documents that use other features when it loads them. A workflow whose operations aren't in the cached spec is skipped at startup. To add workflows, list more Arazzo documents under workflows: in a config file; relative paths resolve against the config file's directory.
Rate limits and retries
The server retries Linode API requests that come back rate limited (429) or timed out (408), whatever the method, because the API turned those requests away before doing anything. Gateway errors (502, 503, 504) are retried only for GET, PUT, and DELETE: a POST that hits one may already have created something, so it is not sent again. Retries wait as long as the response's Retry-After header asks, or back off exponentially when it gives no value. Each wait is reported to the MCP client as progress. All the waits for one request share a 15-second budget, so a tool call returns before the client gives up on it. When retries run out, the tool error says whether the request was applied and when to try again. LINODE_MCP_RETRY_MAX sets the number of retries (default 5; 0 turns retries off), and LINODE_MCP_RETRY_WAIT sets the wait budget in seconds.
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 pytestLint, format, and type check the way CI does:
uv run --extra dev ruff check src tests
uv run --extra dev ruff format src tests
uv run --extra dev ty check srcTo 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.0Publishing 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-serverOwner:
displagueRepository name:
linode-oauth-mcp-serverWorkflow name:
release.ymlEnvironment name:
pypi
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
Available Tools
3 toolscall_toolCall ToolB
Call a tool by name with the given arguments.
Use this to execute tools discovered via search_tools.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | The name of the tool to call | |
| arguments | No | Arguments to pass to the tool |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations at all, the description carries the full burden for a generic proxy that can invoke arbitrary (potentially destructive) tools, yet says nothing about side effects, permissions, or reversibility. It also omits operational behavior an agent needs: what happens on an unknown tool name, whether arguments are validated against the target's schema, and whether the target tool's result is returned verbatim or wrapped.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, zero padding, with the action stated first and the routing hint second. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an unannotated, no-output-schema dispatcher, the description should cover argument conformance to the target schema, error semantics for bad names/arguments, and roughly what comes back. None of that is present, leaving real ambiguity for a tool whose behavior depends entirely on inputs.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so 'name' and 'arguments' are already documented and the baseline of 3 applies. The description adds no meaning beyond the schema — notably it never states that 'arguments' must conform to the target tool's input schema, which is the single most important semantic constraint for this parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a concrete verb+resource ('Call a tool by name with the given arguments') and implicitly distinguishes execution from discovery by pointing at search_tools. It never mentions get_tool_schema, the other obvious sibling, so an agent can't fully tell whether it needs to inspect a schema first.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"Use this to execute tools discovered via search_tools" gives a clear triggering condition tied to a named sibling, which is more than most definitions offer. It gives no guidance on the prerequisite step of fetching argument shapes via get_tool_schema, and no exclusions or failure conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_tool_schemaGet Tool SchemaARead-only
Return a Linode API tool's full description and input schema.
Search results summarize parameters in one line each. Use this before calling a tool whose nested parameters need more detail.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already establishes the safety profile, and the description adds useful context that search results are summaries and this tool provides fuller schema detail. It does not discuss name matching, errors, or rate limits, so it remains moderate rather than rich.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the purpose and followed by the usage condition. There is no redundant or filler language.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only lookup with one parameter and an output schema, the description covers purpose, usage, and the relationship to search. It leaves minor gaps around exact tool-name resolution and error behavior, which keeps it from full completeness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single `name` parameter has 0% schema description coverage; the description implies it is the Linode API tool name, but it does not explicitly define the parameter format, case sensitivity, or exact-match behavior. It provides some compensation but not enough for a high score.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Return') and resource ('a Linode API tool's full description and input schema'), and it distinguishes the tool from search results, which only summarize parameters in one line. An agent can tell this is the detail-retrieval sibling of search_tools and call_tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives a clear condition for use: before calling a tool whose nested parameters need more detail, because search summaries are only one line. It does not explicitly name the alternative tools or state when not to use this one, so it is clear but not fully prescriptive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_toolsSearch ToolsA
Search the Linode API tools in natural language. Returns a compact summary of each match: title, purpose, access level, required OAuth scopes, and parameters.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Natural language query to search for tools |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It does disclose the return contents (title, purpose, access level, OAuth scopes, parameters), which is real value, but it omits that search is read-only and side-effect free, plus any result ranking, limits, or permission requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, action and resource front-loaded, and the second sentence explains the result shape. Nothing is redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read-only discovery tool with no output schema, the description covers purpose, input style, and return contents well. Only safety/permission and result-limit behavior are unstated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is a single parameter with 100% schema description coverage, so the baseline is 3. 'Natural language' in the description mirrors the schema verbatim and adds no syntax, quoting, or format guidance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb (search) and resource (Linode API tools) and states it works in natural language, which tells an agent this is a discovery tool. The sibling names get_tool_schema and call_tool imply the distinction, but the description never explicitly contrasts itself with them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent can infer this is the step before get_tool_schema or call_tool. There is no stated when-to-use, when-not-to-use, or named alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
call_tool - First observed
get_tool_schema - First observed
search_tools
TDQS
Scored across 3 tools
The three tools have clearly distinct roles in a meta-tool workflow: search_tools discovers tools, get_tool_schema provides nested parameter detail, and call_tool executes a discovered tool. There is no meaningful overlap between them, and the descriptions reinforce when to use each.
All tool names follow a consistent snake_case verb_noun pattern: search_tools, get_tool_schema, and call_tool. The convention is predictable and readable across the entire set.
Three tools is minimal but exactly right for a discovery-and-execution gateway over a larger Linode API tool surface. Each tool earns its place by covering a necessary phase of the meta-workflow.
The surface covers discovery, schema inspection, and execution, which is the core lifecycle for a tool-gateway server. A minor gap is the absence of a browse/list-all operation, though natural-language search likely suffices for most agent workflows.
Maintenance
Related MCP Connectors
Discover, plan and run tools across your connected business systems through one OAuth MCP endpoint
One OAuth connector for your team's remote MCP servers, with Tool Packs, guardrails and logs.
Find, vet, and run MCP tools through a secure audited gateway with prompt-injection risk scoring
The OpenRouter for tools. One MCP connection gives any AI agent 254 hosted tools, pay per call.
Related MCP Servers
AlicenseCqualityBmaintenanceExposes the Centia API as MCP tools generated from its OpenAPI spec, enabling natural language interaction with Centia services.56230 npm1ISC- FlicenseNot gradedqualityDmaintenanceDynamically exposes any OpenAPI/Swagger API as tools for AI assistants, automatically generating MCP tools from OpenAPI specs with authentication support.-
- FlicenseAqualityDmaintenanceExposes two MCP tools (discover and execute) that enable agents to query an OpenAPI schema via natural language and execute matched API operations.2-
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to call any OpenAPI-defined API by automatically converting its operations into tools, with built-in support for authentication, rate limiting, and response handling.7Apache 2.0