@digitalocean/mcp 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/build/DOMcpServer.js +18 -0
- package/build/api.js +94 -0
- package/build/index.js +18 -0
- package/build/logger.js +42 -0
- package/build/server.js +26 -0
- package/build/specs/digitalocean-openapi.yaml.zod.js +10231 -0
- package/build/tools/app.js +631 -0
- package/build/version.js +5 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# DigitalOcean MCP Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that provides tools to interact with DigitalOcean's [App Platform](https://www.digitalocean.com/products/app-platform) services.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This MCP server exposes DigitalOcean App Platform functionality through standardized tools that can be used by any MCP client, including Claude Desktop and [Cursor](https://docs.cursor.com/context/model-context-protocol). It enables AI assistants to directly manage your DigitalOcean apps without needing to write code or remember complex API endpoints.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Manage apps (create, update, delete, restart)
|
|
12
|
+
- Work with deployments (list, create, get, cancel)
|
|
13
|
+
- Access logs for apps and deployments
|
|
14
|
+
- Retrieve execution URLs for components
|
|
15
|
+
- View instance sizes and regions
|
|
16
|
+
- Validate app specifications
|
|
17
|
+
- Manage app alerts
|
|
18
|
+
- Handle app rollbacks
|
|
19
|
+
- View bandwidth metrics
|
|
20
|
+
|
|
21
|
+
## Setting up with Claude Desktop
|
|
22
|
+
|
|
23
|
+
1. Open Claude Desktop
|
|
24
|
+
2. Go to Settings → Developer → Edit Config
|
|
25
|
+
3. This will open `claude_desktop_config.json`
|
|
26
|
+
4. Add or update the configuration with:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"digitalocean": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["@digitalocean/mcp"],
|
|
34
|
+
"env": {
|
|
35
|
+
"DIGITALOCEAN_API_TOKEN": "your_personal_access_token"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
5. Replace `your_personal_access_token` with your actual DigitalOcean API token
|
|
43
|
+
6. Save the file and restart Claude Desktop
|
|
44
|
+
|
|
45
|
+
## Setting up with Cursor
|
|
46
|
+
|
|
47
|
+
1. Go to Cursor Settings → MCP → Click on "Add a new global MCP server"
|
|
48
|
+
2. This will open `~/.cursor/mcp.json`
|
|
49
|
+
3. Add or update the configuration with the same JSON as used for Claude Desktop:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"digitalocean": {
|
|
55
|
+
"command": "npx",
|
|
56
|
+
"args": ["@digitalocean/mcp"],
|
|
57
|
+
"env": {
|
|
58
|
+
"DIGITALOCEAN_API_TOKEN": "your_personal_access_token"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
4. Replace `your_personal_access_token` with your actual DigitalOcean API token
|
|
66
|
+
5. Save the file and ensure the server is activated in Cursor Settings → MCP
|
|
67
|
+
|
|
68
|
+
## Using with Claude Desktop or Cursor
|
|
69
|
+
|
|
70
|
+
Once configured, you can start chatting with the AI assistant using natural language to manage your DigitalOcean App Platform resources. Here are some examples of what you can ask:
|
|
71
|
+
|
|
72
|
+
### Basic App Management
|
|
73
|
+
|
|
74
|
+
- "How many DigitalOcean apps do I have?"
|
|
75
|
+
- "List all my current apps on DigitalOcean"
|
|
76
|
+
- "Show me details for my app called 'customer-portal'"
|
|
77
|
+
- "Restart my app 'api-backend'"
|
|
78
|
+
- "Delete the app named 'test-environment'"
|
|
79
|
+
|
|
80
|
+
### Deployments
|
|
81
|
+
|
|
82
|
+
- "When was the last deployment for my 'production-website' app?"
|
|
83
|
+
- "Show me all deployments for my 'user-service' app"
|
|
84
|
+
- "Cancel the current deployment for my 'staging-env' app"
|
|
85
|
+
- "What's the status of the latest deployment for my 'data-processor' app?"
|
|
86
|
+
|
|
87
|
+
### Creating and Updating Apps
|
|
88
|
+
|
|
89
|
+
- "Create a new static site app on DigitalOcean that deploys from my GitHub repo yourusername/static-website"
|
|
90
|
+
- "Create a Flask app on DigitalOcean with the following specification: Python 3.9, 1 CPU, 1GB RAM, connected to my GitHub repo yourusername/flask-app"
|
|
91
|
+
- "Update my 'api-service' app to use 2GB of RAM instead of 1GB"
|
|
92
|
+
- "Set up a Node.js app that uses a managed PostgreSQL database"
|
|
93
|
+
|
|
94
|
+
### Logs and Debugging
|
|
95
|
+
|
|
96
|
+
- "Show me the logs for the 'web' component of my 'ecommerce' app"
|
|
97
|
+
- "Get the latest build logs for my 'backend-api' app"
|
|
98
|
+
- "What errors are showing up in my 'auth-service' deployment logs?"
|
|
99
|
+
|
|
100
|
+
### Infrastructure Information
|
|
101
|
+
|
|
102
|
+
- "What regions can I deploy my DigitalOcean apps to?"
|
|
103
|
+
- "List all available instance sizes for app components"
|
|
104
|
+
- "What's the smallest instance size I can use for a service component?"
|
|
105
|
+
- "Show me my bandwidth usage across all apps this month"
|
|
106
|
+
|
|
107
|
+
## Available Tools
|
|
108
|
+
|
|
109
|
+
Here's a quick overview of the available tools:
|
|
110
|
+
|
|
111
|
+
- `list_apps` - List all apps on your account
|
|
112
|
+
- `create_app` - Create a new app with an app specification
|
|
113
|
+
- `get_app` - Get details about a specific app
|
|
114
|
+
- `update_app` - Update an existing app
|
|
115
|
+
- `delete_app` - Delete an app
|
|
116
|
+
- `restart_app` - Restart an app
|
|
117
|
+
- `list_deployments` - List all deployments for an app
|
|
118
|
+
- `create_deployment` - Create a new deployment
|
|
119
|
+
- `get_deployment` - Get details about a specific deployment
|
|
120
|
+
- `cancel_deployment` - Cancel a deployment
|
|
121
|
+
- `retrieve_active_deployment_logs` - Get logs for a component of the active deployment
|
|
122
|
+
- `download_logs` - Download logs from a URL
|
|
123
|
+
- `list_app_regions` - List all regions supported by App Platform
|
|
124
|
+
- `list_instance_sizes` - List all instance sizes for components
|
|
125
|
+
- `validate_app_spec` - Validate an app specification
|
|
126
|
+
- `list_app_alerts` - List alerts for an app
|
|
127
|
+
- `update_app_alert_destinations` - Update alert destinations
|
|
128
|
+
- `rollback_app` - Rollback an app to a previous deployment
|
|
129
|
+
- `validate_app_rollback` - Validate an app rollback
|
|
130
|
+
- `commit_app_rollback` - Commit an app rollback
|
|
131
|
+
- `revert_app_rollback` - Revert an app rollback
|
|
132
|
+
- `get_app_bandwidth_daily_metrics` - Get bandwidth metrics for an app
|
|
133
|
+
- `get_all_app_bandwidth_daily_metrics` - Get bandwidth metrics for all apps
|
|
134
|
+
|
|
135
|
+
## Troubleshooting
|
|
136
|
+
|
|
137
|
+
### Common Issues
|
|
138
|
+
|
|
139
|
+
- **Authentication Problems**: Ensure your DigitalOcean API token is valid and has the necessary permissions
|
|
140
|
+
|
|
141
|
+
### If Tools Are Not Showing Up
|
|
142
|
+
|
|
143
|
+
1. Check that the MCP server icon appears in the UI
|
|
144
|
+
2. Verify your API token has the necessary permissions
|
|
145
|
+
3. Check the app logs for any error messages
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
[MIT License](LICENSE)
|
|
150
|
+
|
|
151
|
+
## Contributing
|
|
152
|
+
|
|
153
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DOMcpServer = void 0;
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
class DOMcpServer extends mcp_js_1.McpServer {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super(options);
|
|
8
|
+
}
|
|
9
|
+
registerTool({ name, description, parameters, cb, }) {
|
|
10
|
+
if (parameters) {
|
|
11
|
+
this.tool(name, description, parameters, cb);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
this.tool(name, description, cb);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.DOMcpServer = DOMcpServer;
|
package/build/api.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DigitalOceanApiError = void 0;
|
|
7
|
+
exports.createClient = createClient;
|
|
8
|
+
const digitalocean_openapi_yaml_zod_1 = require("./specs/digitalocean-openapi.yaml.zod");
|
|
9
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
10
|
+
const BASE_URL = "https://api.digitalocean.com";
|
|
11
|
+
function getToken() {
|
|
12
|
+
const token = process.env.DIGITALOCEAN_API_TOKEN;
|
|
13
|
+
if (!token) {
|
|
14
|
+
throw new Error("DIGITALOCEAN_API_TOKEN is not set");
|
|
15
|
+
}
|
|
16
|
+
return token;
|
|
17
|
+
}
|
|
18
|
+
function parsePath(path, pathParams = {}) {
|
|
19
|
+
// extract the path params from the path. Each params will be in the form {param_name}
|
|
20
|
+
const pathParts = path.split("/");
|
|
21
|
+
const newParts = pathParts.map((part) => {
|
|
22
|
+
if (part.startsWith("{") && part.endsWith("}")) {
|
|
23
|
+
const key = part.slice(1, -1);
|
|
24
|
+
return pathParams[key] || part;
|
|
25
|
+
}
|
|
26
|
+
return part;
|
|
27
|
+
});
|
|
28
|
+
return newParts.join("/");
|
|
29
|
+
}
|
|
30
|
+
function createClient(params = {}) {
|
|
31
|
+
const token = getToken();
|
|
32
|
+
const sharedHeaders = {
|
|
33
|
+
"User-Agent": "ModelContext/0.1.0",
|
|
34
|
+
Authorization: `Bearer ${token}`,
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
Accept: "application/json",
|
|
37
|
+
};
|
|
38
|
+
const client = (0, digitalocean_openapi_yaml_zod_1.createApiClient)(async (method, url, params) => {
|
|
39
|
+
const queryString = new URLSearchParams(params?.query).toString();
|
|
40
|
+
const _headers = {
|
|
41
|
+
...sharedHeaders,
|
|
42
|
+
...params?.header,
|
|
43
|
+
};
|
|
44
|
+
const _url = parsePath(url, params?.path);
|
|
45
|
+
const urlWithQuery = queryString ? `${_url}?${queryString}` : _url;
|
|
46
|
+
const body = params?.body && !["GET", "HEAD"].includes(method)
|
|
47
|
+
? JSON.stringify(params?.body)
|
|
48
|
+
: undefined;
|
|
49
|
+
const res = await fetch(urlWithQuery, {
|
|
50
|
+
method,
|
|
51
|
+
headers: _headers,
|
|
52
|
+
body,
|
|
53
|
+
});
|
|
54
|
+
if (res.status !== 200) {
|
|
55
|
+
const errorObj = (await res.json());
|
|
56
|
+
if (errorObj.id) {
|
|
57
|
+
const error = DigitalOceanApiError.fromResponse(errorObj);
|
|
58
|
+
logger_1.default.error(`API Error: ${method.toUpperCase()} ${urlWithQuery} ${error.message}`);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`${res.status} ${res.statusText}`);
|
|
62
|
+
}
|
|
63
|
+
const responseContentType = res.headers.get("content-type");
|
|
64
|
+
if (responseContentType?.includes("application/json")) {
|
|
65
|
+
return res.json();
|
|
66
|
+
}
|
|
67
|
+
else if (responseContentType?.includes("text/")) {
|
|
68
|
+
return res.text();
|
|
69
|
+
}
|
|
70
|
+
else if (responseContentType?.includes("application/octet-stream")) {
|
|
71
|
+
return res.blob();
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return res.text();
|
|
75
|
+
}
|
|
76
|
+
}, BASE_URL);
|
|
77
|
+
return { client };
|
|
78
|
+
}
|
|
79
|
+
class DigitalOceanApiError extends Error {
|
|
80
|
+
id;
|
|
81
|
+
message;
|
|
82
|
+
static fromResponse(response) {
|
|
83
|
+
return new DigitalOceanApiError(response.id, response.message);
|
|
84
|
+
}
|
|
85
|
+
static isDigitalOceanApiError(error) {
|
|
86
|
+
return error instanceof DigitalOceanApiError;
|
|
87
|
+
}
|
|
88
|
+
constructor(id, message) {
|
|
89
|
+
super(message);
|
|
90
|
+
this.id = id;
|
|
91
|
+
this.message = message;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.DigitalOceanApiError = DigitalOceanApiError;
|
package/build/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
8
|
+
const server_1 = require("./server");
|
|
9
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
10
|
+
dotenv_1.default.config();
|
|
11
|
+
async function main() {
|
|
12
|
+
const server = (0, server_1.createServer)();
|
|
13
|
+
(0, server_1.startStdioServer)(server);
|
|
14
|
+
}
|
|
15
|
+
main().catch((error) => {
|
|
16
|
+
logger_1.default.error("Error starting server:", error);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
});
|
package/build/logger.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
function toMessage(...args) {
|
|
4
|
+
return args
|
|
5
|
+
.map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : String(arg)))
|
|
6
|
+
.join(" ");
|
|
7
|
+
}
|
|
8
|
+
const logger = {
|
|
9
|
+
info: (...args) => {
|
|
10
|
+
const entry = {
|
|
11
|
+
level: "info",
|
|
12
|
+
message: toMessage(...args),
|
|
13
|
+
timestamp: new Date().toISOString(),
|
|
14
|
+
};
|
|
15
|
+
process.stdout.write(`${JSON.stringify(entry)}\n`);
|
|
16
|
+
},
|
|
17
|
+
error: (...args) => {
|
|
18
|
+
const entry = {
|
|
19
|
+
level: "error",
|
|
20
|
+
message: toMessage(...args),
|
|
21
|
+
timestamp: new Date().toISOString(),
|
|
22
|
+
};
|
|
23
|
+
process.stderr.write(`${JSON.stringify(entry)}\n`);
|
|
24
|
+
},
|
|
25
|
+
warn: (...args) => {
|
|
26
|
+
const entry = {
|
|
27
|
+
level: "warn",
|
|
28
|
+
message: toMessage(...args),
|
|
29
|
+
timestamp: new Date().toISOString(),
|
|
30
|
+
};
|
|
31
|
+
process.stderr.write(`${JSON.stringify(entry)}\n`);
|
|
32
|
+
},
|
|
33
|
+
debug: (...args) => {
|
|
34
|
+
const entry = {
|
|
35
|
+
level: "debug",
|
|
36
|
+
message: toMessage(...args),
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
};
|
|
39
|
+
process.stdout.write(`${JSON.stringify(entry)}\n`);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
exports.default = logger;
|
package/build/server.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createServer = createServer;
|
|
7
|
+
exports.startStdioServer = startStdioServer;
|
|
8
|
+
const app_1 = require("./tools/app");
|
|
9
|
+
const DOMcpServer_1 = require("./DOMcpServer");
|
|
10
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
11
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
12
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
13
|
+
function createServer() {
|
|
14
|
+
const server = new DOMcpServer_1.DOMcpServer({
|
|
15
|
+
name: "DigitalOcean MCP Server",
|
|
16
|
+
version: package_json_1.default.version,
|
|
17
|
+
});
|
|
18
|
+
(0, app_1.registerAppTools)(server);
|
|
19
|
+
return server;
|
|
20
|
+
}
|
|
21
|
+
function startStdioServer(server) {
|
|
22
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
23
|
+
return server.connect(transport).then(() => {
|
|
24
|
+
logger_1.default.info(`DigitalOcean MCP Server v${package_json_1.default.version} started`);
|
|
25
|
+
});
|
|
26
|
+
}
|