supersendtx-mcp 0.3.0 → 0.4.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/README.md +2 -2
- package/dist/index.js +77 -19
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,14 +10,14 @@ MCP server for [SuperSend TX](https://supersendtx.com) — send email and manage
|
|
|
10
10
|
|------|---------------|------|
|
|
11
11
|
| **stdio** (default) | `npx -y supersendtx-mcp` | `SUPERSENDTX_API_KEY=stx_…` |
|
|
12
12
|
| **HTTP** (local) | `npx -y supersendtx-mcp --http --port 3000` | `Authorization: Bearer stx_…` on each request |
|
|
13
|
-
| **HTTP** (hosted) | `https://mcp.supersendtx.com/mcp` | `Authorization: Bearer stx_…`
|
|
13
|
+
| **HTTP** (hosted) | `https://mcp.supersendtx.com/mcp` | **OAuth** (recommended) or `Authorization: Bearer stx_…` |
|
|
14
14
|
|
|
15
15
|
Optional: `SUPERSENDTX_API_URL` (e.g. `http://localhost:3003/api` for local API).
|
|
16
16
|
|
|
17
17
|
Local HTTP endpoint: `http://127.0.0.1:3000/mcp` · health: `GET /health`
|
|
18
18
|
Hosted health: `GET https://mcp.supersendtx.com/health`
|
|
19
19
|
|
|
20
|
-
OAuth
|
|
20
|
+
Hosted OAuth: add only `"url": "https://mcp.supersendtx.com/mcp"` — the client runs the browser consent flow. Bearer `stx_…` still works for advanced setups.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
package/dist/index.js
CHANGED
|
@@ -60,16 +60,16 @@ Environment:
|
|
|
60
60
|
SUPERSENDTX_API_URL Optional API base (default https://api.supersendtx.com)
|
|
61
61
|
|
|
62
62
|
HTTP auth:
|
|
63
|
-
Authorization: Bearer stx_\u2026
|
|
63
|
+
Authorization: Bearer stx_\u2026 (API key) or MCP OAuth access token
|
|
64
64
|
`);
|
|
65
65
|
}
|
|
66
|
-
function
|
|
66
|
+
function isSuperSendTxApiKey(token) {
|
|
67
|
+
return /^stx_[a-f0-9]{48}$/i.test(token.trim());
|
|
68
|
+
}
|
|
69
|
+
function parseBearerToken(authorization) {
|
|
67
70
|
if (!authorization) return null;
|
|
68
71
|
const match = /^Bearer\s+(\S+)/i.exec(authorization.trim());
|
|
69
|
-
|
|
70
|
-
const key = match[1];
|
|
71
|
-
if (!key.startsWith("stx_")) return null;
|
|
72
|
-
return key;
|
|
72
|
+
return match?.[1] || null;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// src/http.ts
|
|
@@ -543,23 +543,73 @@ async function runStdioServer() {
|
|
|
543
543
|
await server.connect(transport);
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
// src/oauth-metadata.ts
|
|
547
|
+
var MCP_HTTP_PATH = "/mcp";
|
|
548
|
+
function buildProtectedResourceMetadata(origin) {
|
|
549
|
+
const resource = `${origin.replace(/\/$/, "")}${MCP_HTTP_PATH}`;
|
|
550
|
+
const appOrigin = process.env.SUPERSENDTX_OAUTH_ISSUER?.trim() || process.env.SUPERSENDTX_APP_URL?.trim() || "https://app.supersendtx.com";
|
|
551
|
+
return {
|
|
552
|
+
resource,
|
|
553
|
+
authorization_servers: [appOrigin.replace(/\/$/, "")],
|
|
554
|
+
scopes_supported: ["full"],
|
|
555
|
+
bearer_methods_supported: ["header"]
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function buildWwwAuthenticateChallenge(origin) {
|
|
559
|
+
const metadataUrl = `${origin.replace(/\/$/, "")}/.well-known/oauth-protected-resource`;
|
|
560
|
+
return `Bearer error="invalid_token", error_description="OAuth authentication required", resource_metadata="${metadataUrl}"`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/oauth-resolve.ts
|
|
564
|
+
async function resolveOAuthAccessToken(accessToken, baseUrl) {
|
|
565
|
+
const apiBase = (baseUrl || process.env.SUPERSENDTX_API_URL || "https://api.supersendtx.com").replace(
|
|
566
|
+
/\/$/,
|
|
567
|
+
""
|
|
568
|
+
);
|
|
569
|
+
const resolveSecret = process.env.SUPERSENDTX_MCP_RESOLVE_SECRET?.trim() || process.env.SUPERSENDTX_CREDENTIALS_ENCRYPTION_KEY?.trim() || (process.env.NODE_ENV === "production" ? "" : "supersendtx-dev-mcp-resolve");
|
|
570
|
+
if (!resolveSecret) return null;
|
|
571
|
+
const response = await fetch(`${apiBase}/mcp/oauth/resolve`, {
|
|
572
|
+
method: "POST",
|
|
573
|
+
headers: {
|
|
574
|
+
authorization: `Bearer ${accessToken}`,
|
|
575
|
+
"content-type": "application/json",
|
|
576
|
+
"x-supersendtx-mcp-resolve": resolveSecret
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
if (!response.ok) return null;
|
|
580
|
+
const data = await response.json().catch(() => null);
|
|
581
|
+
const apiKey = data?.apiKey?.trim();
|
|
582
|
+
return apiKey || null;
|
|
583
|
+
}
|
|
584
|
+
|
|
546
585
|
// src/http.ts
|
|
547
|
-
function sendJson(res, status, body) {
|
|
586
|
+
function sendJson(res, status, body, headers = {}) {
|
|
548
587
|
const payload = JSON.stringify(body);
|
|
549
588
|
res.writeHead(status, {
|
|
550
589
|
"content-type": "application/json; charset=utf-8",
|
|
551
|
-
"content-length": Buffer.byteLength(payload)
|
|
590
|
+
"content-length": Buffer.byteLength(payload),
|
|
591
|
+
...headers
|
|
552
592
|
});
|
|
553
593
|
res.end(payload);
|
|
554
594
|
}
|
|
555
|
-
|
|
595
|
+
function getRequestOrigin(req) {
|
|
556
596
|
const hostHeader = req.headers.host || "127.0.0.1";
|
|
557
|
-
|
|
597
|
+
return `http://${hostHeader}`;
|
|
598
|
+
}
|
|
599
|
+
async function resolveApiKeyFromAuthorization(authorization, baseUrl) {
|
|
600
|
+
const bearer = parseBearerToken(authorization);
|
|
601
|
+
if (!bearer) return null;
|
|
602
|
+
if (isSuperSendTxApiKey(bearer)) return bearer;
|
|
603
|
+
return resolveOAuthAccessToken(bearer, baseUrl);
|
|
604
|
+
}
|
|
605
|
+
async function handleHttpRequest(req, res, options = {}) {
|
|
606
|
+
const origin = getRequestOrigin(req);
|
|
607
|
+
const url = new URL(req.url || "/", origin);
|
|
558
608
|
if (req.method === "OPTIONS") {
|
|
559
609
|
res.writeHead(204, {
|
|
560
610
|
"access-control-allow-origin": "*",
|
|
561
611
|
"access-control-allow-methods": "GET, POST, DELETE, OPTIONS",
|
|
562
|
-
"access-control-allow-headers": "Authorization, Content-Type, Accept, Mcp-Session-Id"
|
|
612
|
+
"access-control-allow-headers": "Authorization, Content-Type, Accept, Mcp-Session-Id, X-Supersendtx-Mcp-Resolve"
|
|
563
613
|
});
|
|
564
614
|
res.end();
|
|
565
615
|
return;
|
|
@@ -568,19 +618,27 @@ async function handleHttpRequest(req, res, options = {}) {
|
|
|
568
618
|
sendJson(res, 200, { ok: true, service: "supersendtx-mcp" });
|
|
569
619
|
return;
|
|
570
620
|
}
|
|
621
|
+
if (url.pathname === "/.well-known/oauth-protected-resource") {
|
|
622
|
+
sendJson(res, 200, buildProtectedResourceMetadata(origin), {
|
|
623
|
+
"cache-control": "public, max-age=300, s-maxage=3600"
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
571
627
|
if (url.pathname !== "/mcp" && url.pathname !== "/") {
|
|
572
628
|
sendJson(res, 404, { error: "Not found. MCP endpoint is POST/GET /mcp" });
|
|
573
629
|
return;
|
|
574
630
|
}
|
|
575
|
-
const apiKey =
|
|
576
|
-
typeof req.headers.authorization === "string" ? req.headers.authorization : void 0
|
|
631
|
+
const apiKey = await resolveApiKeyFromAuthorization(
|
|
632
|
+
typeof req.headers.authorization === "string" ? req.headers.authorization : void 0,
|
|
633
|
+
options.baseUrl
|
|
577
634
|
);
|
|
578
635
|
if (!apiKey) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
636
|
+
sendJson(
|
|
637
|
+
res,
|
|
638
|
+
401,
|
|
639
|
+
{ error: "Missing or invalid Authorization. Use Bearer stx_\u2026 or complete MCP OAuth." },
|
|
640
|
+
{ "www-authenticate": buildWwwAuthenticateChallenge(origin) }
|
|
641
|
+
);
|
|
584
642
|
return;
|
|
585
643
|
}
|
|
586
644
|
const server = createMcpServer(apiKey, options.baseUrl);
|
|
@@ -615,7 +673,7 @@ async function runHttpServer(options) {
|
|
|
615
673
|
httpServer.listen(port, host, () => resolve());
|
|
616
674
|
});
|
|
617
675
|
console.error(`SuperSend TX MCP HTTP listening on http://${host}:${port}/mcp`);
|
|
618
|
-
console.error("Authenticate with
|
|
676
|
+
console.error("Authenticate with Bearer stx_\u2026 or MCP OAuth access token");
|
|
619
677
|
}
|
|
620
678
|
|
|
621
679
|
// src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supersendtx-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "SuperSend TX MCP server — stdio or local HTTP (--http) for Cursor and Claude",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
43
|
-
"supersendtx": "0.8.
|
|
43
|
+
"supersendtx": "0.8.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"tsup": "^8.5.0",
|