@sota-io/mcp 1.3.0 → 1.4.1
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/CLAUDE.md +13 -5
- package/README.md +82 -1
- package/dist/auth/disposable-check.d.ts +2 -0
- package/dist/auth/disposable-check.d.ts.map +1 -0
- package/dist/auth/disposable-check.js +30 -0
- package/dist/auth/disposable-check.js.map +1 -0
- package/dist/auth/token-bridge.d.ts +28 -0
- package/dist/auth/token-bridge.d.ts.map +1 -0
- package/dist/auth/token-bridge.js +152 -0
- package/dist/auth/token-bridge.js.map +1 -0
- package/dist/auth/verify-jwt.d.ts +13 -0
- package/dist/auth/verify-jwt.d.ts.map +1 -0
- package/dist/auth/verify-jwt.js +84 -0
- package/dist/auth/verify-jwt.js.map +1 -0
- package/dist/http.d.ts +21 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +259 -0
- package/dist/http.js.map +1 -0
- package/dist/tools/create-account.d.ts +23 -0
- package/dist/tools/create-account.d.ts.map +1 -0
- package/dist/tools/create-account.js +171 -0
- package/dist/tools/create-account.js.map +1 -0
- package/dist/tools/deploy.d.ts.map +1 -1
- package/dist/tools/deploy.js +2 -1
- package/dist/tools/deploy.js.map +1 -1
- package/dist/tools/domains.d.ts.map +1 -1
- package/dist/tools/domains.js +4 -0
- package/dist/tools/domains.js.map +1 -1
- package/dist/tools/env.d.ts.map +1 -1
- package/dist/tools/env.js +4 -2
- package/dist/tools/env.js.map +1 -1
- package/dist/tools/logs.d.ts.map +1 -1
- package/dist/tools/logs.js +1 -0
- package/dist/tools/logs.js.map +1 -1
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +3 -0
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/rollback.d.ts.map +1 -1
- package/dist/tools/rollback.js +1 -0
- package/dist/tools/rollback.js.map +1 -1
- package/dist/tools/status.d.ts.map +1 -1
- package/dist/tools/status.js +1 -0
- package/dist/tools/status.js.map +1 -1
- package/package.json +15 -8
package/CLAUDE.md
CHANGED
|
@@ -39,11 +39,17 @@ You do NOT need to:
|
|
|
39
39
|
- Configure connection strings
|
|
40
40
|
- Set up SSL for the database (internal network, sslmode=disable is safe)
|
|
41
41
|
|
|
42
|
-
If your app needs tables, run migrations on startup:
|
|
43
|
-
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
46
|
-
- **
|
|
42
|
+
If your app needs tables, run migrations on startup — there is no shell access to containers. Chain the migration command before your server start:
|
|
43
|
+
|
|
44
|
+
- **Prisma**: Use `npx prisma db push && node server.js` as your start script. Note: use `db push` (not `migrate deploy`) because PgBouncer transaction mode blocks advisory locks. Add `?pgbouncer=true` to DATABASE_URL in your Prisma schema.
|
|
45
|
+
- **Drizzle**: `npx drizzle-kit push && node server.js` — applies schema changes without migration files, works with PgBouncer.
|
|
46
|
+
- **TypeORM**: `npx typeorm migration:run -d dist/data-source.js && node dist/server.js`
|
|
47
|
+
- **Sequelize**: `npx sequelize-cli db:migrate && node server.js`
|
|
48
|
+
- **Knex**: `npx knex migrate:latest && node server.js`
|
|
49
|
+
- **Django**: `python manage.py migrate --noinput && gunicorn myproject.wsgi --bind 0.0.0.0:$PORT`
|
|
50
|
+
- **Alembic**: `alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port $PORT`
|
|
51
|
+
|
|
52
|
+
Full migration guide: https://sota.io/docs/guides/postgresql
|
|
47
53
|
|
|
48
54
|
## Environment Variables
|
|
49
55
|
|
|
@@ -115,6 +121,8 @@ module.exports = { output: 'standalone' }
|
|
|
115
121
|
| App not accessible | Use `get-status` — must show "running". Check health check (60s timeout). |
|
|
116
122
|
| Env vars not applied | Changing env vars requires redeployment. Call `deploy` again. |
|
|
117
123
|
| Next.js NEXT_PUBLIC_* empty | Set these BEFORE deploying (they're embedded at build time). |
|
|
124
|
+
| Migrations not running | Start script must chain migration before server. Example: `"start": "npx prisma db push && node server.js"` |
|
|
125
|
+
| `prepared statement already exists` | PgBouncer conflict — add `?pgbouncer=true` to DATABASE_URL in Prisma schema. |
|
|
118
126
|
|
|
119
127
|
## Links
|
|
120
128
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,33 @@ MCP server for [sota.io](https://sota.io) — deploy web apps via AI agents.
|
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
[](https://nodejs.org)
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Two transports
|
|
11
|
+
|
|
12
|
+
Since v1.4.0 this package ships **two transports**:
|
|
13
|
+
|
|
14
|
+
- **`sota-mcp`** (stdio, default) — for Claude Code, Cursor, Windsurf, and any
|
|
15
|
+
MCP client that spawns a local process. Pass your `SOTA_API_KEY` via env var.
|
|
16
|
+
- **`sota-mcp-http`** (Streamable HTTP) — for self-hosting the remote endpoint
|
|
17
|
+
that powers `mcp.sota.io` (used by Claude Desktop and Claude.ai web). Reads
|
|
18
|
+
`SUPABASE_JWT_SECRET`, `DATABASE_URL`, etc. — most users do not need this;
|
|
19
|
+
it's the same code that runs on `mcp.sota.io` if you want to host your own.
|
|
20
|
+
|
|
21
|
+
Most users want the stdio transport.
|
|
22
|
+
|
|
23
|
+
## One-click install for Claude Desktop / Claude.ai web
|
|
24
|
+
|
|
25
|
+
If you use Claude Desktop or Claude.ai (Pro / Max / Team / Enterprise plan),
|
|
26
|
+
the easiest install is **not** this npm package — it's the hosted remote
|
|
27
|
+
endpoint at `mcp.sota.io`. Click here:
|
|
28
|
+
|
|
29
|
+
[**Add to Claude →**](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=sota.io&connectorUrl=https%3A%2F%2Fmcp.sota.io%2Fmcp)
|
|
30
|
+
|
|
31
|
+
OAuth handles auth. New users can sign up entirely inside Claude via the
|
|
32
|
+
`create_account` tool — no browser tab switch.
|
|
33
|
+
|
|
34
|
+
See [https://sota.io/docs/integrations/claude](https://sota.io/docs/integrations/claude).
|
|
35
|
+
|
|
36
|
+
## Quick Start (stdio — Claude Code, Cursor, Windsurf, …)
|
|
11
37
|
|
|
12
38
|
1. Get an API key from [sota.io/dashboard/settings](https://sota.io/dashboard/settings)
|
|
13
39
|
2. [Configure your IDE](#configuration)
|
|
@@ -130,6 +156,10 @@ Edit `~/.codeium/windsurf/mcp_config.json`:
|
|
|
130
156
|
| `list-projects` | List all projects | *(none)* |
|
|
131
157
|
| `create-project` | Create a new project | `name` |
|
|
132
158
|
| `delete-project` | Delete a project permanently | `project_id` |
|
|
159
|
+
| `add-domain` | Add custom domain to project | `project_id`, `domain` |
|
|
160
|
+
| `list-domains` | List custom domains | `project_id` |
|
|
161
|
+
| `get-domain` | Get domain details and DNS status | `project_id`, `domain_id` |
|
|
162
|
+
| `remove-domain` | Remove custom domain | `project_id`, `domain_id` |
|
|
133
163
|
|
|
134
164
|
### `deploy`
|
|
135
165
|
|
|
@@ -241,6 +271,57 @@ Delete a project and all its deployments from sota.io. This action is permanent.
|
|
|
241
271
|
"Delete my sota.io project abc123"
|
|
242
272
|
```
|
|
243
273
|
|
|
274
|
+
### `add-domain`
|
|
275
|
+
|
|
276
|
+
Add a custom domain to a project. Returns DNS instructions for pointing the domain.
|
|
277
|
+
|
|
278
|
+
| Parameter | Type | Required | Description |
|
|
279
|
+
|-----------|------|----------|-------------|
|
|
280
|
+
| `project_id` | string | Yes | Project ID |
|
|
281
|
+
| `domain` | string | Yes | Domain name (e.g., "example.com" or "app.example.com") |
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
"Add example.com as a custom domain to my project"
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### `list-domains`
|
|
288
|
+
|
|
289
|
+
List all custom domains for a project.
|
|
290
|
+
|
|
291
|
+
| Parameter | Type | Required | Description |
|
|
292
|
+
|-----------|------|----------|-------------|
|
|
293
|
+
| `project_id` | string | Yes | Project ID |
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
"Show all custom domains for my project"
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### `get-domain`
|
|
300
|
+
|
|
301
|
+
Get domain details including DNS verification status and SSL state.
|
|
302
|
+
|
|
303
|
+
| Parameter | Type | Required | Description |
|
|
304
|
+
|-----------|------|----------|-------------|
|
|
305
|
+
| `project_id` | string | Yes | Project ID |
|
|
306
|
+
| `domain_id` | string | Yes | Domain ID |
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
"Check the DNS status of my custom domain"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### `remove-domain`
|
|
313
|
+
|
|
314
|
+
Remove a custom domain from a project.
|
|
315
|
+
|
|
316
|
+
| Parameter | Type | Required | Description |
|
|
317
|
+
|-----------|------|----------|-------------|
|
|
318
|
+
| `project_id` | string | Yes | Project ID |
|
|
319
|
+
| `domain_id` | string | Yes | Domain ID to remove |
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
"Remove the custom domain from my project"
|
|
323
|
+
```
|
|
324
|
+
|
|
244
325
|
## Environment Variables
|
|
245
326
|
|
|
246
327
|
| Variable | Required | Default | Description |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disposable-check.d.ts","sourceRoot":"","sources":["../../src/auth/disposable-check.ts"],"names":[],"mappings":"AAYA,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disposable-email domain check — server-side gate for create_account tool.
|
|
3
|
+
*
|
|
4
|
+
* The web /api/auth/check-email route uses a build-time-embedded TS module
|
|
5
|
+
* generated from the maintained github.com/disposable-email-domains list.
|
|
6
|
+
* Here in mcp-server we don't want to duplicate the 5500-domain list, so we
|
|
7
|
+
* just call the web's check endpoint (it's already public, no auth, returns
|
|
8
|
+
* 200/422). One round-trip per create_account Step 1.
|
|
9
|
+
*/
|
|
10
|
+
const CHECK_URL = process.env.DISPOSABLE_EMAIL_CHECK_URL ?? "https://sota.io/api/auth/check-email";
|
|
11
|
+
export async function isDisposableEmailDomain(email) {
|
|
12
|
+
try {
|
|
13
|
+
const r = await fetch(CHECK_URL, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: { "Content-Type": "application/json" },
|
|
16
|
+
body: JSON.stringify({ email }),
|
|
17
|
+
signal: AbortSignal.timeout(5_000),
|
|
18
|
+
});
|
|
19
|
+
if (r.status === 422)
|
|
20
|
+
return true;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Fail open on network error — we'd rather let a real user through
|
|
25
|
+
// than block them because our own /api/auth/check-email had a hiccup.
|
|
26
|
+
// Supabase OTP rate-limits will still catch volume abuse.
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=disposable-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disposable-check.js","sourceRoot":"","sources":["../../src/auth/disposable-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,sCAAsC,CAAC;AAEnF,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,KAAa;IACzD,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;YAC/B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;YAC/B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;QACH,IAAI,CAAC,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;QACnE,sEAAsE;QACtE,0DAA0D;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface BridgeResult {
|
|
2
|
+
/** The sota_xxx key plaintext to forward to sota-api on this request */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Whether a new key row was created (true on first sight of this user) */
|
|
5
|
+
created: boolean;
|
|
6
|
+
/** Whether the existing DB row was rotated (true on cold-start with pre-existing row) */
|
|
7
|
+
rotated: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Net-new user provisioning: called from the `create_account` tool after
|
|
11
|
+
* Supabase verified the OTP. Inserts the user_plans Free-tier row (if not
|
|
12
|
+
* already present) AND a `source='claude'` API key. Returns the key plaintext.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent on user_plans (ON CONFLICT DO NOTHING) — covers the case where
|
|
15
|
+
* the user already exists with a plan but no Claude key (e.g. they signed up
|
|
16
|
+
* via the web first, then called create_account from Claude).
|
|
17
|
+
*/
|
|
18
|
+
export declare function provisionPlanAndKey(userId: string): Promise<BridgeResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Look up or provision a `source='claude'` API key for the given user.
|
|
21
|
+
*
|
|
22
|
+
* On cold start (no in-memory plaintext), if a DB row already exists we have
|
|
23
|
+
* no plaintext recoverable. Solution: rotate — delete the old row, insert a
|
|
24
|
+
* fresh key, return the new plaintext. The Claude integration continues to
|
|
25
|
+
* work transparently because sota-mcp does the lookup on every request.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getOrProvisionApiKey(userId: string): Promise<BridgeResult>;
|
|
28
|
+
//# sourceMappingURL=token-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-bridge.d.ts","sourceRoot":"","sources":["../../src/auth/token-bridge.ts"],"names":[],"mappings":"AAgGA,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;IACjB,yFAAyF;IACzF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC,CAqBvB;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC,CAqCvB"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-to-Key bridge: maps a verified Claude JWT to an internal sota_xxx API key.
|
|
3
|
+
*
|
|
4
|
+
* Why: sota-api authenticates by `sota_xxx` keys (SHA-256 hashed in DB). Claude
|
|
5
|
+
* gives us a JWT. We don't pass the JWT through to sota-api — instead, on first
|
|
6
|
+
* sight of a JWT.sub, we provision a dedicated key tagged `source='claude'` and
|
|
7
|
+
* cache the mapping in-memory + persisted in DB.
|
|
8
|
+
*
|
|
9
|
+
* Benefits:
|
|
10
|
+
* - One revocation surface for Marx: "delete all source=claude keys for user X"
|
|
11
|
+
* immediately cuts off the Claude integration without touching the user's
|
|
12
|
+
* CLI/dashboard keys
|
|
13
|
+
* - Audit log can separate Claude-originated tool calls from CLI tool calls
|
|
14
|
+
* - Future plan-gating happens at the existing sota-api layer (no duplicate
|
|
15
|
+
* enforcement in sota-mcp)
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle:
|
|
18
|
+
* 1. JWT in (verified, sub = user_id)
|
|
19
|
+
* 2. SELECT id, key_prefix FROM api_keys WHERE user_id=$1 AND source='claude'
|
|
20
|
+
* LIMIT 1 -- one Claude key per user, reused across requests
|
|
21
|
+
* 3. If not found:
|
|
22
|
+
* a. Generate sota_ + 40 hex (20 bytes random)
|
|
23
|
+
* b. SHA-256 the full key
|
|
24
|
+
* c. INSERT INTO api_keys (user_id, name='Claude MCP', key_hash, key_prefix,
|
|
25
|
+
* source='claude')
|
|
26
|
+
* d. Hold the plaintext only in memory for this request
|
|
27
|
+
* 4. Cache user_id → plaintext key for the process lifetime (5-min TTL; new
|
|
28
|
+
* keys reuse the in-memory cache after restart by regenerating on first
|
|
29
|
+
* hit — that's wasteful only on cold start, acceptable)
|
|
30
|
+
*
|
|
31
|
+
* IMPORTANT: we never get the plaintext back from the DB on subsequent hits.
|
|
32
|
+
* The plaintext is generated ONCE per user, cached in-memory while the
|
|
33
|
+
* process lives. After a process restart, we have no plaintext for users
|
|
34
|
+
* whose key already exists in DB → solution: REGENERATE (delete old row,
|
|
35
|
+
* insert new). That's safe because each Claude session re-binds to whatever
|
|
36
|
+
* key is current.
|
|
37
|
+
*/
|
|
38
|
+
import crypto from "node:crypto";
|
|
39
|
+
import { Pool } from "pg";
|
|
40
|
+
const DATABASE_URL = process.env.DATABASE_URL ?? "";
|
|
41
|
+
let pool = null;
|
|
42
|
+
function getPool() {
|
|
43
|
+
if (!pool) {
|
|
44
|
+
if (!DATABASE_URL) {
|
|
45
|
+
throw new Error("DATABASE_URL not configured — sota-mcp cannot bridge tokens to API keys");
|
|
46
|
+
}
|
|
47
|
+
// Supabase pooler uses a CA chain pg can't verify by default. Strip
|
|
48
|
+
// ?sslmode= from the URL and force a permissive TLS config explicitly,
|
|
49
|
+
// matching what the rest of the sota.io stack does.
|
|
50
|
+
const cleanUrl = DATABASE_URL.replace(/[?&]sslmode=[^&]*/i, "");
|
|
51
|
+
pool = new Pool({
|
|
52
|
+
connectionString: cleanUrl,
|
|
53
|
+
max: 5,
|
|
54
|
+
idleTimeoutMillis: 30_000,
|
|
55
|
+
ssl: { rejectUnauthorized: false, require: true },
|
|
56
|
+
});
|
|
57
|
+
pool.on("error", (err) => {
|
|
58
|
+
// Best-effort logging; pool keeps working across single-conn errors
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.error("pg pool error:", err.message);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return pool;
|
|
64
|
+
}
|
|
65
|
+
// In-memory cache: userId → { plaintextKey, expiresAt }
|
|
66
|
+
// TTL is short (5 min) because we'd rather rotate-on-restart than ever leak.
|
|
67
|
+
const cache = new Map();
|
|
68
|
+
const CACHE_TTL_MS = 5 * 60_000;
|
|
69
|
+
function cacheGet(userId) {
|
|
70
|
+
const entry = cache.get(userId);
|
|
71
|
+
if (!entry)
|
|
72
|
+
return null;
|
|
73
|
+
if (entry.expiresAt < Date.now()) {
|
|
74
|
+
cache.delete(userId);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return entry.plaintext;
|
|
78
|
+
}
|
|
79
|
+
function cacheSet(userId, plaintext) {
|
|
80
|
+
cache.set(userId, { plaintext, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
81
|
+
}
|
|
82
|
+
function generateKey() {
|
|
83
|
+
const randBytes = crypto.randomBytes(20);
|
|
84
|
+
const plaintext = "sota_" + randBytes.toString("hex"); // sota_ + 40 hex
|
|
85
|
+
const hash = crypto.createHash("sha256").update(plaintext).digest("hex");
|
|
86
|
+
const prefix = plaintext.slice(0, 13); // "sota_" + first 8 hex
|
|
87
|
+
return { plaintext, hash, prefix };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Net-new user provisioning: called from the `create_account` tool after
|
|
91
|
+
* Supabase verified the OTP. Inserts the user_plans Free-tier row (if not
|
|
92
|
+
* already present) AND a `source='claude'` API key. Returns the key plaintext.
|
|
93
|
+
*
|
|
94
|
+
* Idempotent on user_plans (ON CONFLICT DO NOTHING) — covers the case where
|
|
95
|
+
* the user already exists with a plan but no Claude key (e.g. they signed up
|
|
96
|
+
* via the web first, then called create_account from Claude).
|
|
97
|
+
*/
|
|
98
|
+
export async function provisionPlanAndKey(userId) {
|
|
99
|
+
const client = await getPool().connect();
|
|
100
|
+
try {
|
|
101
|
+
// Free-tier defaults (mirrors api/internal/repository/user_plan.go defaults)
|
|
102
|
+
await client.query(`INSERT INTO user_plans (
|
|
103
|
+
user_id, plan, max_projects, max_memory_mb, max_cpu_millicores,
|
|
104
|
+
max_databases, max_custom_domains, max_builds_per_day,
|
|
105
|
+
source, onboarded_at
|
|
106
|
+
) VALUES ($1, 'free', 3, 256, 500, 1, 0, 10, 'claude', now())
|
|
107
|
+
ON CONFLICT (user_id) DO NOTHING`, [userId]);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
throw new Error(`user_plans provisioning failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
client.release();
|
|
114
|
+
}
|
|
115
|
+
return getOrProvisionApiKey(userId);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Look up or provision a `source='claude'` API key for the given user.
|
|
119
|
+
*
|
|
120
|
+
* On cold start (no in-memory plaintext), if a DB row already exists we have
|
|
121
|
+
* no plaintext recoverable. Solution: rotate — delete the old row, insert a
|
|
122
|
+
* fresh key, return the new plaintext. The Claude integration continues to
|
|
123
|
+
* work transparently because sota-mcp does the lookup on every request.
|
|
124
|
+
*/
|
|
125
|
+
export async function getOrProvisionApiKey(userId) {
|
|
126
|
+
const cached = cacheGet(userId);
|
|
127
|
+
if (cached)
|
|
128
|
+
return { apiKey: cached, created: false, rotated: false };
|
|
129
|
+
const client = await getPool().connect();
|
|
130
|
+
try {
|
|
131
|
+
const existing = await client.query(`SELECT id FROM api_keys WHERE user_id = $1 AND source = 'claude' LIMIT 1`, [userId]);
|
|
132
|
+
const { plaintext, hash, prefix } = generateKey();
|
|
133
|
+
if (existing.rowCount === 0) {
|
|
134
|
+
// First-time: simple INSERT
|
|
135
|
+
await client.query(`INSERT INTO api_keys (user_id, name, key_hash, key_prefix, source)
|
|
136
|
+
VALUES ($1, $2, $3, $4, 'claude')`, [userId, "Claude MCP", hash, prefix]);
|
|
137
|
+
cacheSet(userId, plaintext);
|
|
138
|
+
return { apiKey: plaintext, created: true, rotated: false };
|
|
139
|
+
}
|
|
140
|
+
// Cold-start rotation: replace the existing row's hash + prefix so the
|
|
141
|
+
// freshly minted plaintext becomes the active key for this user.
|
|
142
|
+
await client.query(`UPDATE api_keys
|
|
143
|
+
SET key_hash = $2, key_prefix = $3, last_used_at = now()
|
|
144
|
+
WHERE id = $1`, [existing.rows[0].id, hash, prefix]);
|
|
145
|
+
cacheSet(userId, plaintext);
|
|
146
|
+
return { apiKey: plaintext, created: false, rotated: true };
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
client.release();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=token-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-bridge.js","sourceRoot":"","sources":["../../src/auth/token-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface VerifiedJwt {
|
|
2
|
+
sub: string;
|
|
3
|
+
email?: string;
|
|
4
|
+
iss: string;
|
|
5
|
+
exp: number;
|
|
6
|
+
raw: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class JwtVerificationError extends Error {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
constructor(message: string, code: string);
|
|
11
|
+
}
|
|
12
|
+
export declare function verifyJwt(rawToken: string): Promise<VerifiedJwt>;
|
|
13
|
+
//# sourceMappingURL=verify-jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-jwt.d.ts","sourceRoot":"","sources":["../../src/auth/verify-jwt.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aACA,IAAI,EAAE,MAAM;gBAA7C,OAAO,EAAE,MAAM,EAAkB,IAAI,EAAE,MAAM;CAG1D;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAoDtE"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT validation for incoming Claude bearer tokens.
|
|
3
|
+
*
|
|
4
|
+
* Supabase OAuth Server (Phase 56) issues access tokens via HS256 — its JWKS
|
|
5
|
+
* endpoint is intentionally empty in May 2026 because tokens are signed with
|
|
6
|
+
* the project's shared JWT secret. We validate with that same secret.
|
|
7
|
+
*
|
|
8
|
+
* Acceptance criteria for a valid token (RFC 8707 audience binding partially):
|
|
9
|
+
* - signature verifies with the configured HS256 secret
|
|
10
|
+
* - exp not in the past, nbf not in the future
|
|
11
|
+
* - iss starts with the expected Supabase project issuer URL
|
|
12
|
+
* (we accept BOTH the project-ref URL and the custom-domain URL — Supabase
|
|
13
|
+
* Custom Domain doesn't rewrite the iss claim in May 2026, see Phase 56
|
|
14
|
+
* SUMMARY.md "Caveats" section)
|
|
15
|
+
* - sub is a non-empty UUID-shaped string
|
|
16
|
+
*
|
|
17
|
+
* NOT enforced in 57-02 (deferred to Phase 61 Pattern A migration where
|
|
18
|
+
* Anthropic-Held Credentials let us mint mcp.sota.io-audience tokens):
|
|
19
|
+
* - strict aud == "https://mcp.sota.io" check (Supabase tokens carry
|
|
20
|
+
* aud="authenticated", not our resource URL; rejecting on that would
|
|
21
|
+
* block every incoming token)
|
|
22
|
+
*/
|
|
23
|
+
import { jwtVerify, errors as joseErrors } from "jose";
|
|
24
|
+
const SUPABASE_JWT_SECRET = process.env.SUPABASE_JWT_SECRET ?? "";
|
|
25
|
+
const ACCEPTED_ISSUERS = [
|
|
26
|
+
"https://wnwsdtqhywfxxlnyqafk.supabase.co/auth/v1",
|
|
27
|
+
"https://auth.sota.io/auth/v1",
|
|
28
|
+
];
|
|
29
|
+
if (!SUPABASE_JWT_SECRET) {
|
|
30
|
+
// Server still boots, but every incoming token will fail validation.
|
|
31
|
+
// This keeps health checks alive while signalling a missing config.
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.warn("WARN: SUPABASE_JWT_SECRET is not set — every Bearer token will be rejected.");
|
|
34
|
+
}
|
|
35
|
+
const secretKey = SUPABASE_JWT_SECRET
|
|
36
|
+
? new TextEncoder().encode(SUPABASE_JWT_SECRET)
|
|
37
|
+
: null;
|
|
38
|
+
export class JwtVerificationError extends Error {
|
|
39
|
+
code;
|
|
40
|
+
constructor(message, code) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.code = code;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function verifyJwt(rawToken) {
|
|
46
|
+
if (!secretKey) {
|
|
47
|
+
throw new JwtVerificationError("Server JWT secret not configured", "server_misconfigured");
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const { payload } = await jwtVerify(rawToken, secretKey, {
|
|
51
|
+
algorithms: ["HS256"],
|
|
52
|
+
});
|
|
53
|
+
const iss = typeof payload.iss === "string" ? payload.iss : "";
|
|
54
|
+
if (!ACCEPTED_ISSUERS.includes(iss)) {
|
|
55
|
+
throw new JwtVerificationError(`Unexpected issuer: ${iss}`, "invalid_issuer");
|
|
56
|
+
}
|
|
57
|
+
const sub = typeof payload.sub === "string" ? payload.sub : "";
|
|
58
|
+
if (!sub) {
|
|
59
|
+
throw new JwtVerificationError("Token has no sub claim", "no_subject");
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
sub,
|
|
63
|
+
email: typeof payload.email === "string" ? payload.email : undefined,
|
|
64
|
+
iss,
|
|
65
|
+
exp: typeof payload.exp === "number" ? payload.exp : 0,
|
|
66
|
+
raw: rawToken,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (err instanceof JwtVerificationError)
|
|
71
|
+
throw err;
|
|
72
|
+
if (err instanceof joseErrors.JWTExpired) {
|
|
73
|
+
throw new JwtVerificationError("Token expired", "token_expired");
|
|
74
|
+
}
|
|
75
|
+
if (err instanceof joseErrors.JWTInvalid) {
|
|
76
|
+
throw new JwtVerificationError("Malformed JWT", "malformed_jwt");
|
|
77
|
+
}
|
|
78
|
+
if (err instanceof joseErrors.JWSSignatureVerificationFailed) {
|
|
79
|
+
throw new JwtVerificationError("JWT signature invalid", "invalid_signature");
|
|
80
|
+
}
|
|
81
|
+
throw new JwtVerificationError(err instanceof Error ? err.message : String(err), "unknown");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=verify-jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-jwt.js","sourceRoot":"","sources":["../../src/auth/verify-jwt.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sota.io MCP Server — HTTP Transport (remote)
|
|
4
|
+
*
|
|
5
|
+
* Streamable HTTP variant of the existing stdio MCP server. Same 13 tools,
|
|
6
|
+
* exposed via mcp.sota.io/mcp for one-click installation from Claude Desktop
|
|
7
|
+
* and Claude.ai.
|
|
8
|
+
*
|
|
9
|
+
* Phase 57-01 (this file): scaffold + transport + RFC 9728 metadata.
|
|
10
|
+
* Auth is currently a pass-through Bearer API key — clients send their
|
|
11
|
+
* existing sota_xxx key as `Authorization: Bearer <key>` and it gets
|
|
12
|
+
* forwarded to sota-api. No OAuth validation yet.
|
|
13
|
+
*
|
|
14
|
+
* Phase 57-02 (next): OAuth 2.1 + JWT validation against Supabase JWKS,
|
|
15
|
+
* token-to-API-key bridge with `source=claude` audit tagging.
|
|
16
|
+
*
|
|
17
|
+
* Phase 57-03 (next): `create_account` tool for net-new user signup from
|
|
18
|
+
* inside Claude + plan-based capability gating.
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;GAiBG"}
|