Announcing CIMD support for MCP Client registration
Learn more

DigitalOcean MCP vs DigitalOcean API for AI Agents (2026)

Varun Krishnan
Senior Content Marketer

DigitalOcean gives an AI agent two ways into a customer's infrastructure: 25 hosted MCP servers plus an MCP Router that fronts nearly every product it sells, and a REST API with scoped tokens and a real OAuth flow. They overlap heavily on Droplets, databases, Kubernetes, and networking. They diverge on App Platform operations, on how much credential material lands in model context, and on which auth path survives a background agent running for months. This guide compares what each path lets an agent do, the grant each one puts you on, and how to run either across many customer teams in production.

TL;DR

  • DigitalOcean hosts 25 remote MCP servers, one per service, such as https://droplets.mcp.digitalocean.com/mcp and https://apps.mcp.digitalocean.com/mcp, plus an MCP Router at https://mcp.digitalocean.com/mcp that fronts every first-party service except Knowledge Bases through three meta-tools: find_tools, retrieve_schema, and invoke_tool. The coverage is broad and write-heavy: the Droplets server alone has 40 tools, including droplet-delete and tag-wide power actions, and the Kubernetes server hands back kubeconfigs and cluster credentials with doks-get-kubeconfig and doks-get-credentials.
  • The DigitalOcean API v2 at https://api.digitalocean.com/v2 fills the gaps the MCP servers leave in App Platform operations: cancelling a deployment, rolling restarts, validating a spec and its cost before applying it, and fetching build, deploy, and runtime log URLs for a specific deployment. It also has projects and tag management, which no MCP server covers. Rate limits are 5,000 requests per hour and 250 per minute per OAuth token.
  • DigitalOcean is unusual in one good way: credentials can actually be scoped. Personal access tokens come as Full Access (api:write), Read Only (api:read), or Custom Scopes built from resource:action pairs such as droplet:create and database:view_credentials, with an expiry you choose. The MCP servers accept those tokens as a Bearer header, and DigitalOcean now recommends OAuth with browser sign-in for its remote servers.
  • The OAuth app flow for the direct API issues 30-day access tokens to confidential clients and refresh tokens that can be used exactly once. A single-use refresh token is a concurrency problem in any agent that runs more than one worker per user.
  • Scalekit's digitalocean connector exposes 120 prebuilt tools over the DigitalOcean API, runs the OAuth flow through your own DigitalOcean OAuth app, and stores and refreshes each user's tokens in the token vault, so the MCP vs API decision doesn't change your auth infrastructure.

The decision your DigitalOcean agent forces first

Your agent needs to operate infrastructure in your customers' DigitalOcean accounts: figure out why an App Platform deploy failed, check whether a Droplet is out of memory, snapshot production before a migration, or add a DNS record for a new tenant. DigitalOcean ships official Model Context Protocol (MCP) servers, hosted, covering nearly every product it sells. It also has a mature REST API with scoped tokens and a real OAuth flow. They overlap heavily on Droplets, databases, Kubernetes, and networking. They diverge on App Platform operations, on how much credential material lands in model context, and on which auth path survives a background agent running for months. Here's how to pick.

What DigitalOcean MCP and DigitalOcean API actually are

DigitalOcean MCP

DigitalOcean released its MCP server on August 26, 2025 as a local package run with npx @digitalocean/mcp, authenticated with an API token in DIGITALOCEAN_API_TOKEN, and narrowed with a --services flag. On December 9, 2025, it added remote MCP for nine services over Streamable HTTP, each at its own hostname such as https://droplets.mcp.digitalocean.com/mcp and https://apps.mcp.digitalocean.com/mcp. The configuration docs now list 25 remote endpoints, from Droplets, App Platform, DBaaS, DOKS, Networking, Spaces, Volumes, Functions, and Container Registry to ten AI platform servers, plus a Documentation server that needs no token. The source is MIT-licensed at digitalocean-labs/mcp-digitalocean.

Two connection models exist. Individual endpoints expose each service's tools directly. The MCP Router at https://mcp.digitalocean.com/mcp exposes three tools instead: find_tools returns a ranked shortlist of up to 25 candidates for a natural-language query, retrieve_schema returns one tool's schema, and invoke_tool runs it by qualified name. Auth is OAuth with a browser sign-in, which DigitalOcean recommends, or an Authorization: Bearer header carrying a DigitalOcean API token. Neither the docs nor the launch posts label the remote servers as preview or GA.

DigitalOcean API

The DigitalOcean API is a REST API at https://api.digitalocean.com/v2. It covers Droplets and their actions, images and snapshots, volumes, App Platform, managed databases, Kubernetes, domains, firewalls, load balancers, reserved IPs, VPCs, projects, tags, monitoring, billing, and the AI platform. Every request carries an Authorization: Bearer header with either a personal access token or an access token from the OAuth API, which supports the Authorization Code flow with PKCE and refresh tokens.

Comparing them where it matters for agents

What your agent can actually do

Capability
DigitalOcean MCP (remote servers)
DigitalOcean API (Scalekit digitalocean tools)
Account, billing, action history
Yes, account-get-information, balance-get, invoice-list, action-list
Yes, digitalocean_account_get, digitalocean_billing_balance_get, digitalocean_invoices_list, digitalocean_actions_list
Droplet create, resize, power, delete
Yes, 40 tools: droplet-create, resize-droplet, power-off-droplet, droplet-delete
Yes, digitalocean_droplet_create, digitalocean_droplet_action_run, digitalocean_droplet_delete
Bulk actions by tag
Yes, power-off-droplets-tag, snapshot-droplets-tag, and seven more
Limited: filter with digitalocean_droplets_list tag_name, then act per Droplet
Droplet CPU, memory, bandwidth metrics
Limited: through the Insights server's insights-query family
Yes, digitalocean_droplet_metrics_get, digitalocean_droplet_bandwidth_metrics_get
App Platform deploys
Limited: create-app-from-spec, apps-update, apps-get-deployment-status; no cancel, restart, or spec validation
Yes, digitalocean_app_deployment_create, digitalocean_app_deployment_cancel, digitalocean_app_restart, digitalocean_app_spec_propose
App build and runtime logs
No log tool on the App Platform server
Yes, digitalocean_app_deployment_logs_get, digitalocean_app_logs_get (return log URLs)
Managed databases
Yes, 38 tools, including engine config, Kafka topics, and online migration
Limited: clusters, databases, users, firewall rules, and backups, such as digitalocean_database_cluster_create and digitalocean_database_firewall_rules_update
Kubernetes (DOKS)
Yes, 16 tools, including doks-upgrade-cluster, doks-get-kubeconfig, doks-get-credentials
Limited: cluster and node pool CRUD, such as digitalocean_kubernetes_node_pool_update; no kubeconfig tool
Domains, firewalls, load balancers, VPCs
Yes, 54 networking tools, including certificates, VPC peering, and BYOIP
Yes, digitalocean_domain_record_create, digitalocean_firewall_rules_add, digitalocean_load_balancer_droplets_add, digitalocean_vpc_create
Projects and tags
No Projects server; no tag create or assign
Yes, digitalocean_projects_list, digitalocean_project_resources_assign, digitalocean_tag_resources_assign
Spaces keys, Functions, Container Registry, uptime checks, AI platform
Yes, Dedicated servers, such as spaces-key-create and uptimecheck-create
Not in the Scalekit connector; available on the REST API
Event notifications
No
Limited: no general webhook API; alert policies notify email and Slack; digitalocean_monitoring_alert_policies_list reads them

The pattern is the reverse of most providers in this series. DigitalOcean's MCP servers are broad and mostly mirror the API, delete tools included. Where they fall short is App Platform operations, the area an agent touches most when something breaks, and resource organization through projects and tags. The Scalekit connector is narrower in total surface and deeper in the places a triage or operations agent spends its time.

What the MCP path makes easier

Diagnosing a failed App Platform deploy on the raw API is a chain: list apps to find the ID, list deployments to find the one in ERROR, fetch the deployment to see which step failed, then request a log URL for the right type and download it. Each step needs an ID from the step before.

The MCP path compresses discovery rather than that chain. A developer in Cursor or Claude Code connects one MCP Router URL and gets every service without writing a single schema. find_tools returns five candidates for "resize the staging database" instead of loading hundreds of tool definitions into context, which is DigitalOcean's own answer to tool bloat. invoke_tool also accepts a projection to trim output fields, which matters when a Droplet list response carries every network interface and kernel. Writing the schema is the hard part. Not the API call.

The tradeoff is that DigitalOcean controls the tool set, its names, and its behavior. The local and remote servers already differ: the npm README lists 21 services, the hosted docs list 25. The Insights server marks its v1 alert policy tools deprecated in favor of insights-alert-rule-* tools. An agent prompt that names a tool can break without a release on your side.

The auth path each one puts you on

On the MCP path, you have two options. OAuth is recommended: the client connects to the server URL with no Authorization header, opens a browser, and "obtains a short-lived access token through a browser-based sign-in." DigitalOcean's MCP docs do not say which scopes that sign-in grants, how long "short-lived" is, or how the user picks a team. The other option is a DigitalOcean API token in an Authorization: Bearer header, or in DIGITALOCEAN_API_TOKEN for the local server. That token sits in the client's config on the developer's machine; DigitalOcean's README notes that the Claude Code setup stores its configuration in ~/.claude.json, so the token value ends up in that file.

On the direct API path, you choose between a personal access token and an OAuth app, and both can be scoped. That is the part DigitalOcean gets right. A custom-scoped token is built from resource:action pairs, roughly 160 in DigitalOcean's table, such as droplet:create, account:read, database:view_credentials, and kubernetes:access_cluster. Scopes cannot be edited after creation, a missing scope returns 403, and a token can never exceed its owner's team role: if a team owner removes a permission from the role, existing tokens lose it too. You set an expiry when you create the token.

The OAuth API is what a multi-user agent should use. Users authorize at https://cloud.digitalocean.com/v1/oauth/authorize, your backend exchanges the code at /v1/oauth/token, and PKCE with S256 is required for public clients. Confidential clients receive access tokens with expires_in of 2,592,000 seconds, 30 days; public clients get one hour. The token response includes team_uuid and team_name, which tells you which team the grant is bound to.

What the user can't do, the agent can't do. On DigitalOcean that principle has teeth, because the token itself can be narrowed below the user's role. A triage agent can run on api:read and never hold droplet:delete, whatever the user's role allows.

Both paths still need per-user isolation

In a multi-tenant B2B agent, MCP gives you a credential per user and the direct API gives you a credential per user. In neither case does the path itself solve storage, rotation, or revocation; those are infrastructure problems regardless of which path you choose.

DigitalOcean adds a team dimension. A consultant who belongs to two of your customers' DigitalOcean teams can grant your agent access to either, and the team_uuid on the grant is what tells you which one. Tenant isolation on your side has to bind each connected account to one of your users and one customer, and refuse a grant whose team does not match the customer the user is acting for.

Recommended reading: OAuth vs API keys for AI agents

What you own in production

On the MCP path, DigitalOcean hosts the servers, owns the schemas, and decides what is exposed, including what credential material reaches the model. doks-get-kubeconfig and doks-get-credentials return cluster access data, and db-cluster-start-online-migration takes a source database password as tool input. On an MCP server, both land in the model's context and your conversation logs. You also own the credential for every user, either an OAuth session inside a client you may not control or a token in a config file, and you own the gap on events: there is no subscription mechanism, so an agent that must react to a failed deploy polls.

The MCP servers also have no read-only mode. DigitalOcean's README warns that using MCP against your account "can come with risks," and the only reduction mechanisms are which services you connect and which token scopes you grant. A token with api:write behind the Droplets server means the model can see droplet-delete and power-off-droplets-tag on every call.

On the API path, you own schemas, pagination, retries, and the token lifecycle. Rate limits are 5,000 requests per hour and 250 per minute per OAuth token, on a rolling window: each request counts for one hour from when it was made. Responses carry ratelimit-limit, ratelimit-remaining, and ratelimit-reset, and burst-limit 429 responses include retry-after. Some endpoints are stricter: GET /v2/account/keys allows 10 requests per 60 seconds, and CDN endpoints allow 5 per 10 seconds. Lists default to 20 items and accept per_page up to 200.

Most writes are asynchronous. A resize, snapshot, or power-off returns an action with status of in-progress, and the agent must poll GET /v2/actions/{id} until it reads completed or errored. An agent that reports "done" on the first response is reporting a request, not a result.

Eventing is the honest gap on both paths. The v2 API has no general webhook subscription. Monitoring alert policies notify an email list or a slack array of channel and webhook URL pairs, which reaches humans, not your agent. Everything else is polling, budgeted against 250 requests per minute.

When to use MCP, when to use the API

Use DigitalOcean MCP when:

  • A developer in Claude Code, Cursor, or VS Code asks "why is web-01 slow" and the model checks the Droplet with droplet-get and the Insights metrics tools in the same session, with a human reading every answer.
  • Someone is standing up a project from an editor: a Droplet, a managed Postgres cluster with db-cluster-create, a firewall, and a DNS record, created in one conversation through the MCP Router.
  • The task touches services outside the Scalekit connector, such as rotating a Spaces key with spaces-key-update, creating an uptime check, or exploring the GenAI platform.
  • You are prototyping an internal ops assistant for your own team on one person's read-only token.

Use the DigitalOcean API when:

  • The agent runs headless: a scheduled job finds App Platform deployments in ERROR, pulls the build log URL with digitalocean_app_deployment_logs_get, and posts a diagnosis, with no browser in the loop.
  • The agent changes running systems and must confirm the result: resizing with digitalocean_droplet_action_run, then polling digitalocean_action_get until the action reads completed.
  • The agent operates App Platform beyond create and update: cancelling a stuck deployment with digitalocean_app_deployment_cancel, a rolling restart with digitalocean_app_restart, or checking a spec's monthly cost with digitalocean_app_spec_propose before applying it.
  • You serve many customer teams, need per-user OAuth grants with a known team_uuid, and want credentials such as kubeconfigs and database passwords kept out of model context.

The credential problem that exists on both paths

Both paths hand you a DigitalOcean credential per user. Neither hands you a vault. A multi-tenant agent with 60 customers and 3 engineers each is 180 DigitalOcean grants. Each must be stored encrypted, kept out of logs and LLM context, refreshed, and revoked through /v1/oauth/revoke when a user disconnects or a customer churns.

DigitalOcean's refresh semantics make this harder than average. Each refresh token can be used exactly once; using it issues a new access token and a new refresh token and invalidates the old access token. Two agent workers that notice the same 30-day token is close to expiry and refresh at once produce one winner and one dead grant. The loser's refresh fails, and the user must authorize again. Refresh has to be serialized per connected account, and the new pair must be persisted before any worker uses it.

The tempting shortcut is one admin's personal access token per customer. That collapses 180 grants into 60 that act with the admin's full role, attribute every agent action to the admin, and stop working when the token's chosen expiry passes or the admin leaves. Fewer secrets, larger blast radius per secret.

Scalekit's digitalocean connector runs the OAuth flow, stores each user's tokens in the token vault, and refreshes them for both paths, so the MCP vs API decision doesn't change your auth infrastructure. Your agent passes an identifier; credentials never touch the agent runtime or the LLM context.

Recommended reading: how to handle token refresh for AI agents

How to connect a DigitalOcean agent with Scalekit

Scalekit's digitalocean connector is an API-path connector built by Scalekit with 120 tools: 63 read, 36 write, and 21 destructive. It covers the account, actions, billing and invoices, Droplets and their actions and metrics, images and snapshots, volumes, App Platform apps and deployments, managed databases, Kubernetes clusters and node pools, domains and records, firewalls, load balancers, reserved IPs, VPCs, projects, tags, SSH keys, regions, sizes, and monitoring alert policies. It authenticates with OAuth 2.0: each user signs in to DigitalOcean once, and Scalekit stores and refreshes their tokens. The connector docs list every tool and its inputs.

Prerequisites and the connection name

DigitalOcean connections use your own OAuth app. In the Scalekit dashboard, go to AgentKit > Connections, create a DigitalOcean connection, and copy its redirect URI. Register an OAuth application with DigitalOcean, add that redirect URI as the callback URL, and enter the app's Client ID and Client Secret on the Scalekit connection. Because the app is yours, the DigitalOcean consent screen shows your app's name to your users, and you control its lifecycle.

The name you give the connection is the connection_name your code passes, and the string must match the dashboard exactly. This is the most common integration error. Copy SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET from Developers > API Credentials into .env.

pip install scalekit-sdk-python python-dotenv langchain-openai requests

Authorize each user once

get_or_create_connected_account returns the user's connected account. If it is not ACTIVE, get_authorization_link returns a link that sends the user through DigitalOcean's OAuth consent screen for your app. DigitalOcean redirects back to Scalekit, which exchanges the code and stores the tokens. The agent never sees them.

import os from dotenv import load_dotenv from scalekit import ScalekitClient load_dotenv() scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ) actions = scalekit_client.actions CONNECTION_NAME = "digitalocean" # must match the connection name in AgentKit > Connections identifier = "user_123" # your app's stable ID for this user account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=identifier, ) print("Connect DigitalOcean:", link.link) input("Press Enter after approving access in DigitalOcean...") account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": raise RuntimeError(f"DigitalOcean is {account.connected_account.status}, not ACTIVE") who = actions.execute_tool( tool_name="digitalocean_account_get", tool_input={}, connection_name=CONNECTION_NAME, identifier=identifier, ) print(who.data)

The last call is worth keeping in production. digitalocean_account_get returns the account's email, status, team, and limits such as droplet_limit, so you can confirm the grant belongs to the team you expected before the agent does anything else.

Retrieve the DigitalOcean tools this user can call

This is not a catalog load. The agent asks for the tools this user's connected account is authorized to call, then narrows them to the seven an App Platform triage agent needs. Loading all 120 DigitalOcean tools would put digitalocean_app_delete, digitalocean_droplet_delete, and digitalocean_database_cluster_delete in front of a model whose job is to read deployments and explain a failure. None of the seven below can change a running app.

tools = actions.langchain.get_tools( identifier=identifier, connection_names=[CONNECTION_NAME], tool_names=[ "digitalocean_apps_list", "digitalocean_app_get", "digitalocean_app_health_get", "digitalocean_app_deployments_list", "digitalocean_app_deployment_get", "digitalocean_app_deployment_logs_get", "digitalocean_app_logs_get", ], page_size=100, ) tool_map = {t.name: t for t in tools} print(sorted(tool_map))

Run the agent loop

The loop runs until the model stops requesting tools. Every call executes with this user's DigitalOcean grant, so the agent sees only the apps that grant can read.

from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [ SystemMessage( "You diagnose DigitalOcean App Platform deploy failures. Use " "digitalocean_apps_list to find the app by name, then " "digitalocean_app_deployments_list to find the newest deployment in phase ERROR. " "Use digitalocean_app_deployment_get to see which progress step failed, and " "digitalocean_app_deployment_logs_get with type BUILD or DEPLOY for its log URLs. " "Use digitalocean_app_health_get only for the app that is serving now. " "Never guess IDs." ), HumanMessage( "The latest deploy of the 'storefront' app failed. Find the failing deployment, " "tell me which step broke, and point me to the build log." ), ] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for tc in response.tool_calls: tool = tool_map.get(tc["name"]) if tool: result = tool.invoke(tc["args"]) else: result = f"Unknown tool: {tc['name']}" messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

Deterministic calls with execute_tool

For pipelines, skip the model. execute_tool runs one tool with a fixed input and returns data plus an execution_id you can log. DigitalOcean has no webhook for a failed deployment, so this job runs on a schedule. It sweeps every app the user can read, finds apps whose newest deployment is in ERROR, downloads the tail of the build log, and redeploys with a forced rebuild only when the failure step looks transient.

import requests TRANSIENT_MARKERS = ("timed out", "connection reset", "temporary failure", "429") def items(data, key): """DigitalOcean list responses wrap records in a named array, such as {"apps": [...]}.""" if isinstance(data, dict): return data.get(key, []) return data or [] def run(tool_name, tool_input, identifier): result = actions.execute_tool( tool_name=tool_name, tool_input=tool_input, connection_name=CONNECTION_NAME, identifier=identifier, ) print(tool_name, result.execution_id) return result.data def sweep_failed_deploys(identifier: str) -> None: apps = items(run("digitalocean_apps_list", {"per_page": 200}, identifier), "apps") for app in apps: app_id = app["id"] app_name = app.get("spec", {}).get("name", app_id) deployments = items( run( "digitalocean_app_deployments_list", {"app_id": app_id, "per_page": 1}, identifier, ), "deployments", ) if not deployments or deployments[0].get("phase") != "ERROR": continue deployment_id = deployments[0]["id"] logs = run( "digitalocean_app_deployment_logs_get", {"app_id": app_id, "deployment_id": deployment_id, "type": "BUILD"}, identifier, ) log_text = "" for url in logs.get("historic_urls", []): log_text += requests.get(url, timeout=30).text tail = log_text[-4000:] print(f"{app_name} deployment {deployment_id} failed:\n{tail}") if any(marker in tail.lower() for marker in TRANSIENT_MARKERS): redeploy = run( "digitalocean_app_deployment_create", {"app_id": app_id, "force_build": True}, identifier, ) print(f"Redeployed {app_name}:", redeploy.get("deployment", {}).get("id")) sweep_failed_deploys(identifier)

Watch the budget. One sweep costs one call for the app list plus one per app, before logs, against a 250 per minute burst limit that is per token. A user with 80 apps on a one-minute schedule spends a third of it on polling alone. Run the sweep every five to ten minutes, or only for apps tagged as production.

Droplet writes need the same discipline. digitalocean_droplet_action_run takes a droplet_id and a type such as snapshot, resize, or power_off, and returns an action that is usually still in-progress. Poll digitalocean_action_get with the returned ID until its status reads completed or errored before the pipeline moves on. A resize with disk set to true is permanent, because disks cannot shrink, and belongs behind a human approval step.

Multi-tool, multi-tenant DigitalOcean agents on a virtual MCP server

The digitalocean connector has 120 tools. At roughly 200 tokens each, that is about 24,000 tokens of tool definitions before the agent does any work, and 21 of them are destructive. A triage agent that also holds digitalocean_droplet_delete is one bad inference away from deleting a customer's server. DigitalOcean's own MCP Router addresses the token cost with find_tools, but it searches everything the token can reach; it does not take tools away. The fix is not better prompting. It is surface reduction. A Scalekit virtual MCP server declares exactly which tools an agent role can see, and session tokens keep each run bound to one user's connected accounts. Docs: set up a virtual MCP server.

Create the server once per agent role

from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping vmcp = actions.mcp.create_config( name="digitalocean-deploy-triage", description="Reads App Platform deployments and logs and posts a failure summary to Slack", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="digitalocean", tools=[ "digitalocean_apps_list", "digitalocean_app_deployments_list", "digitalocean_app_deployment_get", "digitalocean_app_deployment_logs_get", ], ), McpConfigConnectionToolMapping( connection_name="slack", tools=["slack_send_message"], ), ], ) print(vmcp.config.id, vmcp.config.mcp_server_url)

Mint a session token per user, per run

One server definition serves every user. Before each run, check that the user's accounts are active and mint a short-lived session token scoped to them. Any MCP client that speaks Streamable HTTP can connect; this example uses langchain-mcp-adapters (pip install langchain-mcp-adapters) and passes the token as a Bearer header.

import asyncio from datetime import timedelta from langchain_mcp_adapters.client import MultiServerMCPClient config_id = vmcp.config.id mcp_server_url = vmcp.config.mcp_server_url user_id = "user_123" accounts = actions.mcp.list_mcp_connected_accounts( config_id=config_id, identifier=user_id, include_auth_link=True, ) not_ready = [ a for a in accounts.connected_accounts if str(a.connected_account_status).upper() != "ACTIVE" ] for a in not_ready: print(f"{a.connection_name} is {a.connected_account_status}: {a.authentication_link}") if not_ready: raise RuntimeError("Connect the accounts above before starting this run") session_token = actions.mcp.create_session_token( mcp_config_id=config_id, identifier=user_id, expiry=timedelta(minutes=30), ).token async def load_tools(): client = MultiServerMCPClient( { "scalekit": { "transport": "streamable_http", "url": mcp_server_url, "headers": {"Authorization": f"Bearer {session_token}"}, } } ) return await client.get_tools() mcp_tools = asyncio.run(load_tools()) print([t.name for t in mcp_tools])

The agent sees five tools instead of 120. The endpoint is static; the identity is per-user. Unlike DigitalOcean's own MCP servers, this endpoint can include tools they don't have, such as digitalocean_app_deployment_logs_get and digitalocean_app_deployment_cancel, for the one agent role that should hold them, and leave every delete tool out of every role that shouldn't.

Observability: every DigitalOcean action tied to a user

Every execute_tool call returns an execution_id, and every execution is attributed to a connected account, which maps to one of your users and one customer team. When a customer asks which agent run powered off db-replica-2 at 03:40 or forced a rebuild of storefront six times in an hour, the answer is a lookup, not an investigation. DigitalOcean's own action history, readable with digitalocean_actions_list, records each power event, resize, and attach with its resource_id and timestamps, and your execution log shows which agent run and which prompt led there.

A shared credential breaks that. If every user's agent runs on one admin's personal access token, DigitalOcean records every action under that admin, every agent reaches every resource the admin's role allows, and the token's expiry becomes an outage for every customer at once. Shared credentials are a single-user solution. They do not survive a second user.

Recommended reading: audit trails for agent auth

Which one to build against

If your agent is a developer's assistant in Claude Code, Cursor, or VS Code, provisioning Droplets and databases, editing firewall rules, or touching Spaces, Functions, and the AI platform with a human watching, DigitalOcean's remote MCP servers are a good fit: they are hosted, they cover more products than any connector, and the MCP Router keeps context small. If your agent runs unattended, sweeps App Platform for failed deploys, waits on Droplet actions to complete, organizes resources into projects, or serves many customer teams, build against the DigitalOcean API with per-user OAuth grants and a deliberately small tool set. Either way, you hold one DigitalOcean grant per user, bound to a team, refreshed with a token that works exactly once, and that's what needs production-grade infrastructure.

FAQs

Does DigitalOcean have an official MCP server?

Yes, 25 of them. DigitalOcean hosts one remote MCP server per service, such as https://droplets.mcp.digitalocean.com/mcp and https://apps.mcp.digitalocean.com/mcp, plus an MCP Router at https://mcp.digitalocean.com/mcp that fronts every first-party service except Knowledge Bases through three meta-tools: find_tools, retrieve_schema, and invoke_tool. The source is MIT-licensed, and DigitalOcean recommends OAuth with a browser sign-in for the remote servers.

What can an agent not do through DigitalOcean MCP?

App Platform operations beyond create and update: there is no tool to cancel a deployment, restart a service, validate a spec and its cost, or fetch build and runtime log URLs. There is no Projects server and no tag create or assign. There are no event triggers, no read-only mode, and tools such as doks-get-kubeconfig and db-cluster-start-online-migration put cluster credentials and database passwords into model context.

Can DigitalOcean tokens be scoped?

Yes, and that is unusual among cloud providers. Personal access tokens come as Full Access, Read Only, or Custom Scopes built from roughly 160 resource:action pairs such as droplet:create and database:view_credentials, with an expiry you choose. A token can never exceed its owner's team role, and a missing scope returns 403. A triage agent can run on api:read and never hold droplet:delete.

Why are DigitalOcean refresh tokens a concurrency problem?

Each OAuth refresh token can be used exactly once; using it issues a new access token and refresh token and invalidates the old access token. Two agent workers that refresh the same 30-day token at once produce one winner and one dead grant, and the user must authorize again. Refresh has to be serialized per connected account, and the new pair persisted before any worker uses it.

When should an agent use the DigitalOcean API instead of MCP?

When it runs headless on a schedule sweeping App Platform for deployments in ERROR, when it must confirm results by polling actions until they read completed, when it cancels stuck deployments, restarts services, or checks a spec's cost before applying it, or when it serves many customer teams with per-user OAuth grants bound to a known team_uuid and credentials kept out of model context.

How does Scalekit connect an AI agent to DigitalOcean?

The digitalocean connector exposes 120 tools over the DigitalOcean API, runs the OAuth flow through your own DigitalOcean OAuth app, and stores and refreshes each user's tokens in the token vault, serializing the single-use refresh. A virtual MCP server narrows the tools per agent role so a triage agent never holds a delete tool, and every execution returns an execution_id attributed to that user and customer team.

Build your DigitalOcean agent with Scalekit

Browse the Scalekit DigitalOcean connector and the DigitalOcean connector docs with the full tool list. See Scalekit pricing, or talk to us.

No items found.
On this page
Share this article
Scalekit
Team-bound grants

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.