@otwa/mcp-server 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — unreleased
4
+
5
+ Initial release.
6
+
7
+ - 20 tools covering account, catalog, servers (CRUD + power + reinstall + stats + SSO), networking PTR, billing (read-only), wallet (read-only) and webhooks.
8
+ - Bearer-key auth against `https://api.otwa.cloud/v1/*`.
9
+ - Idempotency-Key auto-generation for `create_server` and `reinstall_server`.
10
+ - Three-layer safety on destructive operations: `confirm` flag, `expectedLabel` typo-guard, backend scope enforcement.
11
+ - Stdio transport; remote HTTP transport planned for 0.2.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Otwa Cloud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # @otwa/mcp-server
2
+
3
+ Official Model Context Protocol server for [otwa.cloud](https://otwa.cloud).
4
+ Provision and manage servers from Claude Code, Cursor, Windsurf, Zed, or any
5
+ other MCP-capable AI tool — just by talking to it.
6
+
7
+ ```
8
+ > "Spin up a 4 vCPU Ubuntu 22 box in Frankfurt called prod-api-01"
9
+
10
+ Claude → calls otwa_list_products, otwa_list_os_templates, otwa_list_regions
11
+ → quotes the monthly price, asks you to confirm
12
+ → calls otwa_create_server
13
+ → returns the new server's IP and SSH credentials
14
+ ```
15
+
16
+ ## Two ways to use this
17
+
18
+ | Option | Best for | Install |
19
+ |---|---|---|
20
+ | **Hosted (no install)** | Anyone with an MCP client that supports remote HTTP | Just add `https://mcp.otwa.cloud/mcp` to your client config with `Authorization: Bearer otwa_…` |
21
+ | **Local stdio** | Older MCP clients, offline use, total control over the binary | `npx -y @otwa/mcp-server` — full instructions below |
22
+
23
+ ### Hosted endpoint (recommended)
24
+
25
+ ```
26
+ URL: https://mcp.otwa.cloud/mcp
27
+ Auth: Authorization: Bearer otwa_xxxxxxxxxxxxxxxxxxxx
28
+ Health: GET https://mcp.otwa.cloud/healthz
29
+ ```
30
+
31
+ The hosted server is stateless — every request is authenticated independently with your API key. No session state lives on `mcp.otwa.cloud`, so a leaked session id is useless.
32
+
33
+ ## Quick start (local stdio)
34
+
35
+ ### 1. Generate an API key
36
+
37
+ Go to [otwa.cloud/dashboard/settings/api-keys](https://otwa.cloud/dashboard/settings/api-keys)
38
+ and create a key with the scopes you want the AI to use.
39
+
40
+ Recommended starter scopes:
41
+
42
+ | Scope | Why |
43
+ |---|---|
44
+ | `account:read` | Read account info, product catalogue, regions |
45
+ | `servers:read` | List servers, view stats, view credentials |
46
+ | `servers:write` | Create servers, change power state, rename, reset password |
47
+ | `billing:read` | Read invoices and wallet balance |
48
+
49
+ Leave **`servers:destroy` unchecked** unless you really want the AI to be able
50
+ to permanently delete servers — even with confirmation guards, scope-level
51
+ denial is the strongest defence.
52
+
53
+ Copy the `otwa_…` key shown once on the dashboard; it cannot be re-displayed.
54
+
55
+ ### 2. Install in your AI client
56
+
57
+ #### Claude Code
58
+
59
+ ```bash
60
+ claude mcp add otwa npx -y @otwa/mcp-server
61
+ claude mcp env otwa OTWA_API_KEY=otwa_xxxxxxxxxxxxxxxxxxxx
62
+ ```
63
+
64
+ #### Cursor
65
+
66
+ Add to `~/.cursor/mcp.json`:
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "otwa": {
72
+ "command": "npx",
73
+ "args": ["-y", "@otwa/mcp-server"],
74
+ "env": {
75
+ "OTWA_API_KEY": "otwa_xxxxxxxxxxxxxxxxxxxx"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ #### Windsurf
83
+
84
+ Add to `~/.codeium/windsurf/mcp_config.json`:
85
+
86
+ ```json
87
+ {
88
+ "mcpServers": {
89
+ "otwa": {
90
+ "command": "npx",
91
+ "args": ["-y", "@otwa/mcp-server"],
92
+ "env": {
93
+ "OTWA_API_KEY": "otwa_xxxxxxxxxxxxxxxxxxxx"
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ #### Zed
101
+
102
+ Add to `~/.config/zed/settings.json`:
103
+
104
+ ```json
105
+ {
106
+ "context_servers": {
107
+ "otwa": {
108
+ "command": {
109
+ "path": "npx",
110
+ "args": ["-y", "@otwa/mcp-server"],
111
+ "env": { "OTWA_API_KEY": "otwa_xxxxxxxxxxxxxxxxxxxx" }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ #### Continue
119
+
120
+ Add to `~/.continue/config.json` under `experimental.modelContextProtocolServers`:
121
+
122
+ ```json
123
+ {
124
+ "experimental": {
125
+ "modelContextProtocolServers": [
126
+ {
127
+ "transport": {
128
+ "type": "stdio",
129
+ "command": "npx",
130
+ "args": ["-y", "@otwa/mcp-server"],
131
+ "env": { "OTWA_API_KEY": "otwa_xxxxxxxxxxxxxxxxxxxx" }
132
+ }
133
+ }
134
+ ]
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### 3. Try it
140
+
141
+ Restart your client and ask:
142
+
143
+ - *"What's on my otwa.cloud account?"*
144
+ - *"List my servers"*
145
+ - *"How much would a 4 vCPU plan cost per month?"*
146
+ - *"Create a new Ubuntu 22 server and call it test-box-1"*
147
+
148
+ ## Tools
149
+
150
+ 20 tools across six surfaces:
151
+
152
+ ### Account & catalogue (read-only, `account:read`)
153
+ - `otwa_account` — current account, balance, tier
154
+ - `otwa_list_products` — available plans with prices
155
+ - `otwa_list_regions` — deploy regions
156
+ - `otwa_list_os_templates` — OS images
157
+
158
+ ### Servers
159
+ - `otwa_list_servers` — list all servers *(servers:read)*
160
+ - `otwa_get_server` — full detail for one server *(servers:read)*
161
+ - `otwa_get_server_credentials` — SSH credentials *(servers:read, sensitive)*
162
+ - `otwa_get_server_stats` — live CPU/RAM/disk metrics *(servers:read)*
163
+ - `otwa_create_server` — provision new server *(servers:write)*
164
+ - `otwa_rename_server` — change label *(servers:write)*
165
+ - `otwa_power_server` — start/stop/reboot *(servers:write)*
166
+ - `otwa_reset_server_password` — rotate root password *(servers:write)*
167
+ - `otwa_reinstall_server` — wipe and reinstall OS *(servers:destroy)*
168
+ - `otwa_destroy_server` — permanently terminate *(servers:destroy)*
169
+ - `otwa_get_dashboard_sso` — 5-min SSO link to dashboard *(servers:read or account:read)*
170
+
171
+ ### Networking
172
+ - `otwa_list_server_ips` — IPs on a server *(servers:read)*
173
+ - `otwa_set_ptr` — set reverse DNS *(servers:write)*
174
+ - `otwa_delete_ptr` — clear reverse DNS *(servers:write)*
175
+
176
+ ### Billing (read-only)
177
+ - `otwa_list_invoices` *(billing:read)*
178
+ - `otwa_get_invoice` *(billing:read)*
179
+ - `otwa_list_transactions` *(billing:read)*
180
+ - `otwa_get_wallet_balance` *(billing:read)*
181
+
182
+ ### Webhooks
183
+ - `otwa_list_webhooks` *(webhooks:read)*
184
+ - `otwa_create_webhook` *(webhooks:write)*
185
+ - `otwa_delete_webhook` *(webhooks:write)*
186
+
187
+ ## Safety
188
+
189
+ Destructive tools (`otwa_destroy_server`, `otwa_reinstall_server`) require:
190
+
191
+ 1. **`confirm: true`** and **`iAcknowledgeDataLoss: true`** in the tool call
192
+ 2. **`expectedLabel`** matching the server's current label (typo-guard, only on destroy)
193
+ 3. **`servers:destroy` scope** on the API key, enforced server-side
194
+
195
+ Medium-risk tools (`otwa_power_server` for stop/reboot, `otwa_reset_server_password`,
196
+ `otwa_delete_webhook`) require **`confirm: true`** only.
197
+
198
+ `otwa_create_server` and `otwa_reinstall_server` use auto-generated idempotency
199
+ keys so transient LLM retries cannot double-provision or double-bill.
200
+
201
+ ## Environment variables
202
+
203
+ | Var | Required? | Default | Description |
204
+ |---|---|---|---|
205
+ | `OTWA_API_KEY` | yes | — | Your `otwa_…` API key |
206
+ | `OTWA_API_BASE` | no | `https://api.otwa.cloud` | API base URL (override for staging) |
207
+ | `OTWA_TELEMETRY` | no | off | Set to `1` to opt in to anonymous usage pings |
208
+
209
+ ## Troubleshooting
210
+
211
+ **"OTWA_API_KEY is not set"** — Restart your editor after setting the env var.
212
+ Some clients only read env on cold-start.
213
+
214
+ **"API key is missing required scope(s): servers:destroy"** — Your key wasn't
215
+ granted that scope. Revoke it at
216
+ [/dashboard/settings/api-keys](https://otwa.cloud/dashboard/settings/api-keys)
217
+ and create a new one.
218
+
219
+ **"Refusing to destroy server X: expectedLabel does not match"** — Safety guard
220
+ firing. Re-fetch the server label via `otwa_get_server`, confirm with the user,
221
+ and call again.
222
+
223
+ ## License
224
+
225
+ MIT — see [LICENSE](LICENSE). Source on GitHub:
226
+ [github.com/otwacloud/mcp-server](https://github.com/otwacloud/mcp-server).
227
+ The published npm tarball ships plain, unminified JavaScript that mirrors
228
+ the repo at the same tag.
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OtwaApiError = void 0;
4
+ exports.mapHttpError = mapHttpError;
5
+ class OtwaApiError extends Error {
6
+ status;
7
+ code;
8
+ body;
9
+ name = 'OtwaApiError';
10
+ constructor(message, status, code, body) {
11
+ super(message);
12
+ this.status = status;
13
+ this.code = code;
14
+ this.body = body;
15
+ }
16
+ }
17
+ exports.OtwaApiError = OtwaApiError;
18
+ /** Translate a raw HTTP response from otwa-cloud-api into a message the LLM
19
+ * can act on. The goal is always: tell the model exactly what to do next,
20
+ * not just what went wrong. */
21
+ function mapHttpError(status, body, requestId) {
22
+ const bodyMessage = extractMessage(body);
23
+ if (status === 401) {
24
+ return new OtwaApiError('OTWA_API_KEY is invalid, expired, or revoked. Ask the user to create a fresh key at ' +
25
+ 'https://otwa.cloud/dashboard/settings/api-keys, update the OTWA_API_KEY env var, and restart this MCP server.', status, 'unauthorized', body);
26
+ }
27
+ if (status === 403) {
28
+ return new OtwaApiError(bodyMessage ||
29
+ 'Forbidden — the API key is missing a required scope. The dashboard at ' +
30
+ 'https://otwa.cloud/dashboard/settings/api-keys can re-issue a key with the correct scopes.', status, 'forbidden', body);
31
+ }
32
+ if (status === 402) {
33
+ return new OtwaApiError('Account balance is too low to complete this operation. Tell the user to top up at ' +
34
+ 'https://otwa.cloud/dashboard/billing, or use otwa_get_wallet_balance to fetch a crypto deposit address.', status, 'payment_required', body);
35
+ }
36
+ if (status === 404) {
37
+ return new OtwaApiError(bodyMessage ||
38
+ 'Resource not found. If this is a server id, call otwa_list_servers to see what exists on this account.', status, 'not_found', body);
39
+ }
40
+ if (status === 409) {
41
+ return new OtwaApiError(bodyMessage || 'Conflict — the resource is already in the target state, or the operation is in progress.', status, 'conflict', body);
42
+ }
43
+ if (status === 422 || status === 400) {
44
+ return new OtwaApiError(bodyMessage || 'Validation failed. Check the field constraints and call again.', status, 'validation_failed', body);
45
+ }
46
+ if (status === 429) {
47
+ return new OtwaApiError('Rate-limited by otwa-cloud-api. Wait a few seconds before retrying.', status, 'rate_limited', body);
48
+ }
49
+ if (status >= 500) {
50
+ const trace = requestId ? ` Request id: ${requestId}.` : '';
51
+ return new OtwaApiError(`otwa-cloud-api returned ${status}. This is a server-side issue — retry once, then contact support@otwa.cloud if it persists.${trace}`, status, 'upstream_error', body);
52
+ }
53
+ return new OtwaApiError(bodyMessage || `Unexpected HTTP ${status} from otwa-cloud-api.`, status, 'unknown', body);
54
+ }
55
+ function extractMessage(body) {
56
+ if (!body || typeof body !== 'object')
57
+ return null;
58
+ const b = body;
59
+ if (typeof b.message === 'string')
60
+ return b.message;
61
+ if (Array.isArray(b.message))
62
+ return b.message.join('; ');
63
+ if (typeof b.error === 'string')
64
+ return b.error;
65
+ return null;
66
+ }
67
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":";;;AAeA,oCA+EC;AA9FD,MAAa,YAAa,SAAQ,KAAK;IAInB;IACA;IACA;IALA,IAAI,GAAG,cAAc,CAAC;IACxC,YACE,OAAe,EACC,MAAc,EACd,IAAY,EACZ,IAAc;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAU;IAGhC,CAAC;CACF;AAVD,oCAUC;AAED;;gCAEgC;AAChC,SAAgB,YAAY,CAAC,MAAc,EAAE,IAAa,EAAE,SAAkB;IAC5E,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAEzC,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,IAAI,YAAY,CACrB,sFAAsF;YACpF,+GAA+G,EACjH,MAAM,EACN,cAAc,EACd,IAAI,CACL,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,IAAI,YAAY,CACrB,WAAW;YACT,wEAAwE;gBACtE,4FAA4F,EAChG,MAAM,EACN,WAAW,EACX,IAAI,CACL,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,IAAI,YAAY,CACrB,oFAAoF;YAClF,yGAAyG,EAC3G,MAAM,EACN,kBAAkB,EAClB,IAAI,CACL,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,IAAI,YAAY,CACrB,WAAW;YACT,wGAAwG,EAC1G,MAAM,EACN,WAAW,EACX,IAAI,CACL,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,IAAI,YAAY,CACrB,WAAW,IAAI,0FAA0F,EACzG,MAAM,EACN,UAAU,EACV,IAAI,CACL,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACrC,OAAO,IAAI,YAAY,CACrB,WAAW,IAAI,gEAAgE,EAC/E,MAAM,EACN,mBAAmB,EACnB,IAAI,CACL,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,IAAI,YAAY,CACrB,qEAAqE,EACrE,MAAM,EACN,cAAc,EACd,IAAI,CACL,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,gBAAgB,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,IAAI,YAAY,CACrB,2BAA2B,MAAM,8FAA8F,KAAK,EAAE,EACtI,MAAM,EACN,gBAAgB,EAChB,IAAI,CACL,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,YAAY,CACrB,WAAW,IAAI,mBAAmB,MAAM,uBAAuB,EAC/D,MAAM,EACN,SAAS,EACT,IAAI,CACL,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,IAAa;IACnC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,CAAC,GAAG,IAA+B,CAAC;IAC1C,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,OAAO,CAAC;IACpD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,KAAK,CAAC;IAChD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OtwaClient = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const errors_1 = require("./errors");
6
+ const version_1 = require("../util/version");
7
+ class OtwaClient {
8
+ opts;
9
+ userAgent;
10
+ constructor(opts) {
11
+ this.opts = opts;
12
+ const pkg = (0, version_1.packageInfo)();
13
+ this.userAgent = `${pkg.name}/${pkg.version} (+https://otwa.cloud/docs/mcp)`;
14
+ }
15
+ async request(path, options = {}) {
16
+ const url = this.buildUrl(path, options.query);
17
+ const headers = {
18
+ 'Authorization': `Bearer ${this.opts.apiKey}`,
19
+ 'Accept': 'application/json',
20
+ 'User-Agent': this.userAgent,
21
+ };
22
+ if (options.body !== undefined) {
23
+ headers['Content-Type'] = 'application/json';
24
+ }
25
+ if (options.idempotent) {
26
+ headers['Idempotency-Key'] = `mcp-${(0, crypto_1.randomUUID)()}`;
27
+ }
28
+ const controller = new AbortController();
29
+ const timeoutMs = options.timeoutMs ?? this.opts.timeoutMs ?? 30_000;
30
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
31
+ let res;
32
+ try {
33
+ res = await fetch(url, {
34
+ method: options.method || 'GET',
35
+ headers,
36
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
37
+ signal: controller.signal,
38
+ });
39
+ }
40
+ catch (err) {
41
+ clearTimeout(timer);
42
+ const msg = err instanceof Error ? err.message : String(err);
43
+ if (msg.includes('aborted')) {
44
+ throw new errors_1.OtwaApiError(`Request to ${path} timed out after ${timeoutMs}ms. Retry, or check otwa.cloud status at https://status.otwa.cloud`, 0, 'timeout');
45
+ }
46
+ throw new errors_1.OtwaApiError(`Network error contacting otwa-cloud-api at ${this.opts.apiBase}: ${msg}`, 0, 'network_error');
47
+ }
48
+ clearTimeout(timer);
49
+ if (options.raw) {
50
+ if (!res.ok) {
51
+ const body = await safeReadJson(res);
52
+ throw (0, errors_1.mapHttpError)(res.status, body, res.headers.get('x-request-id') || undefined);
53
+ }
54
+ return res;
55
+ }
56
+ // 204 No Content: many DELETE endpoints return empty
57
+ if (res.status === 204) {
58
+ return { ok: true };
59
+ }
60
+ const body = await safeReadJson(res);
61
+ if (!res.ok) {
62
+ throw (0, errors_1.mapHttpError)(res.status, body, res.headers.get('x-request-id') || undefined);
63
+ }
64
+ return body;
65
+ }
66
+ buildUrl(path, query) {
67
+ const base = this.opts.apiBase;
68
+ const normalised = path.startsWith('/') ? path : `/${path}`;
69
+ const url = new URL(`${base}${normalised}`);
70
+ if (query) {
71
+ for (const [k, v] of Object.entries(query)) {
72
+ if (v === undefined || v === null || v === '')
73
+ continue;
74
+ url.searchParams.set(k, String(v));
75
+ }
76
+ }
77
+ return url.toString();
78
+ }
79
+ }
80
+ exports.OtwaClient = OtwaClient;
81
+ async function safeReadJson(res) {
82
+ const text = await res.text();
83
+ if (!text)
84
+ return null;
85
+ try {
86
+ return JSON.parse(text);
87
+ }
88
+ catch {
89
+ return { message: text };
90
+ }
91
+ }
92
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/client/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ // Hand-written response shapes that mirror what `/v1/*` actually returns.
3
+ // Kept loose on purpose — we only name fields we read; everything else passes
4
+ // through verbatim so a backend schema addition does not break the MCP.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":";AAAA,0EAA0E;AAC1E,8EAA8E;AAC9E,wEAAwE"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mediumConfirm = exports.destructiveConfirm = void 0;
4
+ const zod_1 = require("zod");
5
+ /** A guard schema fragment for destructive operations.
6
+ * Forces the LLM to set `confirm: true` AND acknowledge data loss, which
7
+ * makes the call surface visibly in the model's reasoning instead of being
8
+ * fired silently. Combined with backend `servers:destroy` scope enforcement
9
+ * this is three layers of defense for the worst case (terminating a VM). */
10
+ exports.destructiveConfirm = {
11
+ confirm: zod_1.z
12
+ .literal(true)
13
+ .describe('Must be exactly `true`. Confirms the user has been shown what is about to happen and explicitly approved it.'),
14
+ iAcknowledgeDataLoss: zod_1.z
15
+ .literal(true)
16
+ .describe('Must be exactly `true`. The operation will permanently destroy data on the disk.'),
17
+ };
18
+ /** Tag for medium-risk operations (stop a VM, reboot, reset root password).
19
+ * Cheaper guard — single confirm flag, no data-loss acknowledgement. */
20
+ exports.mediumConfirm = {
21
+ confirm: zod_1.z
22
+ .literal(true)
23
+ .describe('Must be exactly `true`. This operation interrupts the running workload — confirm with the user first.'),
24
+ };
25
+ //# sourceMappingURL=confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/guards/confirm.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB;;;;6EAI6E;AAChE,QAAA,kBAAkB,GAAG;IAChC,OAAO,EAAE,OAAC;SACP,OAAO,CAAC,IAAI,CAAC;SACb,QAAQ,CACP,8GAA8G,CAC/G;IACH,oBAAoB,EAAE,OAAC;SACpB,OAAO,CAAC,IAAI,CAAC;SACb,QAAQ,CAAC,kFAAkF,CAAC;CAChG,CAAC;AAEF;yEACyE;AAC5D,QAAA,aAAa,GAAG;IAC3B,OAAO,EAAE,OAAC;SACP,OAAO,CAAC,IAAI,CAAC;SACb,QAAQ,CACP,uGAAuG,CACxG;CACJ,CAAC"}
@@ -0,0 +1,138 @@
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 express_1 = __importDefault(require("express"));
8
+ const crypto_1 = require("crypto");
9
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
10
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
11
+ const http_1 = require("../client/http");
12
+ const _register_1 = require("../tools/_register");
13
+ const version_1 = require("../util/version");
14
+ const PORT = parseInt(process.env.PORT || '3210', 10);
15
+ const HOST = process.env.HOST || '127.0.0.1';
16
+ const API_BASE = (process.env.OTWA_API_BASE?.trim() || 'https://api.otwa.cloud').replace(/\/+$/, '');
17
+ const app = (0, express_1.default)();
18
+ app.disable('x-powered-by');
19
+ app.set('trust proxy', 'loopback');
20
+ app.use(express_1.default.json({ limit: '1mb' }));
21
+ const pkg = (0, version_1.packageInfo)();
22
+ app.get('/healthz', (_req, res) => {
23
+ res.json({ ok: true, service: pkg.name, version: pkg.version, apiBase: API_BASE });
24
+ });
25
+ app.get('/', (_req, res) => {
26
+ res.json({
27
+ name: pkg.name,
28
+ version: pkg.version,
29
+ transport: 'streamable-http',
30
+ docs: 'https://otwa.cloud/docs/mcp',
31
+ endpoints: {
32
+ mcp: 'POST /mcp',
33
+ health: 'GET /healthz',
34
+ },
35
+ auth: 'Bearer otwa_… (from https://otwa.cloud/dashboard/settings/api-keys)',
36
+ });
37
+ });
38
+ function extractBearer(req) {
39
+ const raw = req.header('authorization') || req.header('Authorization');
40
+ if (!raw)
41
+ return null;
42
+ const m = /^Bearer\s+(.+)$/.exec(raw.trim());
43
+ return m && m[1] ? m[1].trim() : null;
44
+ }
45
+ /** Stateless Streamable HTTP — every request gets a fresh McpServer + transport,
46
+ * scoped to the caller's API key. No session state lives on the server, so we
47
+ * scale horizontally without sticky sessions and a leaked session id is useless. */
48
+ app.post('/mcp', async (req, res) => {
49
+ const apiKey = extractBearer(req);
50
+ if (!apiKey) {
51
+ res.status(401).json({
52
+ jsonrpc: '2.0',
53
+ error: {
54
+ code: -32001,
55
+ message: 'Missing Authorization header. Set "Authorization: Bearer otwa_…" with an API key from ' +
56
+ 'https://otwa.cloud/dashboard/settings/api-keys',
57
+ },
58
+ id: null,
59
+ });
60
+ return;
61
+ }
62
+ if (!apiKey.startsWith('otwa_')) {
63
+ res.status(401).json({
64
+ jsonrpc: '2.0',
65
+ error: {
66
+ code: -32001,
67
+ message: 'Bearer token does not look like an otwa.cloud API key (expected prefix "otwa_").',
68
+ },
69
+ id: null,
70
+ });
71
+ return;
72
+ }
73
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
74
+ sessionIdGenerator: undefined,
75
+ enableJsonResponse: true,
76
+ });
77
+ const server = new mcp_js_1.McpServer({ name: pkg.name, version: pkg.version });
78
+ const client = new http_1.OtwaClient({ apiKey, apiBase: API_BASE });
79
+ (0, _register_1.registerAllTools)(server, client);
80
+ res.on('close', () => {
81
+ transport.close().catch(() => { });
82
+ server.close().catch(() => { });
83
+ });
84
+ try {
85
+ await server.connect(transport);
86
+ await transport.handleRequest(req, res, req.body);
87
+ }
88
+ catch (err) {
89
+ const msg = err instanceof Error ? err.message : String(err);
90
+ process.stderr.write(`[otwa-mcp-http] handler error: ${msg}\n`);
91
+ if (!res.headersSent) {
92
+ res.status(500).json({
93
+ jsonrpc: '2.0',
94
+ error: { code: -32603, message: `Internal error: ${msg}` },
95
+ id: null,
96
+ });
97
+ }
98
+ }
99
+ });
100
+ // GET /mcp and DELETE /mcp are part of the Streamable HTTP spec for stateful
101
+ // servers (session resumption + termination). We're stateless — refuse them
102
+ // with the spec-compliant error so clients know not to bother.
103
+ app.get('/mcp', (_req, res) => {
104
+ res.status(405).json({
105
+ jsonrpc: '2.0',
106
+ error: { code: -32000, message: 'Method Not Allowed. This server is stateless — use POST /mcp.' },
107
+ id: null,
108
+ });
109
+ });
110
+ app.delete('/mcp', (_req, res) => {
111
+ res.status(405).json({
112
+ jsonrpc: '2.0',
113
+ error: { code: -32000, message: 'Method Not Allowed. This server is stateless.' },
114
+ id: null,
115
+ });
116
+ });
117
+ app.use((req, res) => {
118
+ res.status(404).json({ error: 'not_found', path: req.path });
119
+ });
120
+ const reqId = () => (0, crypto_1.randomUUID)();
121
+ app.use((err, req, res, _next) => {
122
+ const id = reqId();
123
+ process.stderr.write(`[otwa-mcp-http] ${id} ${req.method} ${req.path}: ${err.stack || err.message}\n`);
124
+ if (!res.headersSent) {
125
+ res.status(500).json({ error: 'internal_error', requestId: id });
126
+ }
127
+ });
128
+ const httpServer = app.listen(PORT, HOST, () => {
129
+ process.stdout.write(`[otwa-mcp-http] ${pkg.name} ${pkg.version} listening on ${HOST}:${PORT} (API base: ${API_BASE})\n`);
130
+ });
131
+ const shutdown = (signal) => {
132
+ process.stderr.write(`[otwa-mcp-http] shutdown on ${signal}\n`);
133
+ httpServer.close(() => process.exit(0));
134
+ setTimeout(() => process.exit(1), 5_000).unref();
135
+ };
136
+ process.on('SIGINT', () => shutdown('SIGINT'));
137
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
138
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"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"}