@purposebot/mcp 0.1.1 → 0.1.2
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 +7 -5
- package/dist/index.js +16 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Requires Node >= 18. Get an API key from your PurposeBot account.
|
|
|
29
29
|
"mcpServers": {
|
|
30
30
|
"purposebot": {
|
|
31
31
|
"command": "npx",
|
|
32
|
-
"args": ["-y", "@purposebot/mcp@0.1.
|
|
32
|
+
"args": ["-y", "@purposebot/mcp@0.1.2"],
|
|
33
33
|
"env": {
|
|
34
34
|
"PURPOSEBOT_API_KEY": "pb_xxx"
|
|
35
35
|
}
|
|
@@ -41,20 +41,22 @@ Requires Node >= 18. Get an API key from your PurposeBot account.
|
|
|
41
41
|
### Claude Code
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
claude mcp add purposebot --env PURPOSEBOT_API_KEY=pb_xxx -- npx -y @purposebot/mcp@0.1.
|
|
44
|
+
claude mcp add purposebot --env PURPOSEBOT_API_KEY=pb_xxx -- npx -y @purposebot/mcp@0.1.2
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
## Environment
|
|
48
48
|
|
|
49
49
|
| Variable | Required | Default | Notes |
|
|
50
50
|
|---|---|---|---|
|
|
51
|
-
| `PURPOSEBOT_API_KEY` |
|
|
51
|
+
| `PURPOSEBOT_API_KEY` | no | - | sent as `X-API-Key`; keyless mode supports first-key registration |
|
|
52
52
|
| `PURPOSEBOT_API_URL` | no | `https://purposebot.ai` | origin only; use `https://api.purposebot.ai` or sandbox/self-host origin |
|
|
53
53
|
| `PURPOSEBOT_ALLOW_CUSTOM_HOST` | no | unset | set to `1` for non-PurposeBot self-hosts |
|
|
54
54
|
|
|
55
|
-
The wrapper validates the target URL before sending
|
|
55
|
+
The wrapper validates the target URL before sending an API key. It only sends
|
|
56
56
|
keys to PurposeBot hosts by default, allows `http://localhost` for development,
|
|
57
|
-
rejects embedded credentials, and refuses HTTP redirects.
|
|
57
|
+
rejects embedded credentials, and refuses HTTP redirects. Without
|
|
58
|
+
`PURPOSEBOT_API_KEY`, the wrapper can still call keyless MCP tools such as
|
|
59
|
+
`begin_registration`; protected tools return the hosted PurposeBot auth error.
|
|
58
60
|
|
|
59
61
|
## Develop
|
|
60
62
|
|
package/dist/index.js
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* checks, so new tools appear automatically.
|
|
10
10
|
*
|
|
11
11
|
* Env:
|
|
12
|
-
* PURPOSEBOT_API_KEY (
|
|
12
|
+
* PURPOSEBOT_API_KEY (optional) - your PurposeBot API key (sent as X-API-Key)
|
|
13
13
|
* PURPOSEBOT_API_URL (optional) — defaults to https://purposebot.ai
|
|
14
14
|
* PURPOSEBOT_ALLOW_CUSTOM_HOST (optional) — set to 1 for self-hosts
|
|
15
15
|
*/
|
|
16
16
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
17
17
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
18
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
19
|
-
const API_KEY = process.env.PURPOSEBOT_API_KEY;
|
|
19
|
+
const API_KEY = process.env.PURPOSEBOT_API_KEY?.trim() || undefined;
|
|
20
20
|
const REQUEST_TIMEOUT_MS = 60_000;
|
|
21
21
|
const MAX_DIAGNOSTIC_BYTES = 2048;
|
|
22
22
|
const PURPOSEBOT_TOKEN_RE = /\bpb_[A-Za-z0-9_=-]{12,}\b/g;
|
|
@@ -36,10 +36,10 @@ function die(msg) {
|
|
|
36
36
|
console.error(`[purposebot-mcp] ${msg}`);
|
|
37
37
|
process.exit(1);
|
|
38
38
|
}
|
|
39
|
-
// The wrapper's trust boundary:
|
|
40
|
-
//
|
|
41
|
-
// explicit opt-in for anything else; never send the key over plain
|
|
42
|
-
// localhost dev); reject embedded credentials.
|
|
39
|
+
// The wrapper's trust boundary: if an API key is configured, it is sent to this
|
|
40
|
+
// host. Validate before any request. Default + allowlist PurposeBot hosts;
|
|
41
|
+
// require an explicit opt-in for anything else; never send the key over plain
|
|
42
|
+
// http (except localhost dev); reject embedded credentials.
|
|
43
43
|
const ALLOWED_HOSTS = new Set([
|
|
44
44
|
"api.purposebot.ai",
|
|
45
45
|
"api-sandbox.purposebot.ai",
|
|
@@ -71,9 +71,6 @@ function resolveApiBase(raw) {
|
|
|
71
71
|
}
|
|
72
72
|
return `${u.protocol}//${u.host}`;
|
|
73
73
|
}
|
|
74
|
-
if (!API_KEY) {
|
|
75
|
-
die("PURPOSEBOT_API_KEY is required. Set it in your MCP client config (env).");
|
|
76
|
-
}
|
|
77
74
|
const API_URL = resolveApiBase(process.env.PURPOSEBOT_API_URL ?? "https://purposebot.ai");
|
|
78
75
|
const MCP_ENDPOINT = `${API_URL}/mcp`;
|
|
79
76
|
let rpcId = 0;
|
|
@@ -111,13 +108,16 @@ async function rpc(method, params) {
|
|
|
111
108
|
const controller = new AbortController();
|
|
112
109
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
113
110
|
let res;
|
|
111
|
+
const headers = {
|
|
112
|
+
"content-type": "application/json",
|
|
113
|
+
};
|
|
114
|
+
if (API_KEY) {
|
|
115
|
+
headers["x-api-key"] = API_KEY;
|
|
116
|
+
}
|
|
114
117
|
try {
|
|
115
118
|
res = await fetch(MCP_ENDPOINT, {
|
|
116
119
|
method: "POST",
|
|
117
|
-
headers
|
|
118
|
-
"content-type": "application/json",
|
|
119
|
-
"x-api-key": API_KEY,
|
|
120
|
-
},
|
|
120
|
+
headers,
|
|
121
121
|
body: JSON.stringify({ jsonrpc: "2.0", id: ++rpcId, method, params }),
|
|
122
122
|
// Never follow redirects: undici keeps custom headers (X-API-Key) on
|
|
123
123
|
// cross-origin redirects, so a 30x from an allowlisted host could leak the
|
|
@@ -142,7 +142,7 @@ async function rpc(method, params) {
|
|
|
142
142
|
}
|
|
143
143
|
return body.result;
|
|
144
144
|
}
|
|
145
|
-
const server = new Server({ name: "purposebot", version: "0.1.
|
|
145
|
+
const server = new Server({ name: "purposebot", version: "0.1.2" }, { capabilities: { tools: {} } });
|
|
146
146
|
server.setRequestHandler(ListToolsRequestSchema, async (request) => {
|
|
147
147
|
const result = await rpc("tools/list", request.params);
|
|
148
148
|
return { ...(result ?? {}), tools: Array.isArray(result?.tools) ? result.tools : [] };
|
|
@@ -161,7 +161,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
161
161
|
});
|
|
162
162
|
async function main() {
|
|
163
163
|
await server.connect(new StdioServerTransport());
|
|
164
|
-
|
|
164
|
+
const authMode = API_KEY ? "authenticated" : "keyless";
|
|
165
|
+
console.error(`[purposebot-mcp] connected - proxying ${MCP_ENDPOINT} (${authMode})`);
|
|
165
166
|
}
|
|
166
167
|
main().catch((err) => {
|
|
167
168
|
console.error("[purposebot-mcp] fatal:", err);
|
package/package.json
CHANGED