@purposebot/mcp 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/README.md +58 -0
- package/dist/index.js +104 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @purposebot/mcp
|
|
2
|
+
|
|
3
|
+
A thin **stdio MCP server** that proxies to the hosted PurposeBot MCP
|
|
4
|
+
(`https://purposebot.ai/mcp`). Use it to plug PurposeBot — agent-native discovery,
|
|
5
|
+
commerce, API Trust, and trust/reputation tools — into any MCP client (Claude
|
|
6
|
+
Desktop, Cursor, Claude Code, etc.) via `npx`, without writing a custom HTTP
|
|
7
|
+
integration.
|
|
8
|
+
|
|
9
|
+
The hosted endpoint is the source of truth for the tool catalogue, so new tools
|
|
10
|
+
(`search_tools`, `submit_interaction_report`, `list_pending_feedback`,
|
|
11
|
+
`recommend_listings`, `create_api_trust_provider`, `request_api_trust_decision`,
|
|
12
|
+
`submit_api_trust_event`, …) appear automatically — this package just forwards
|
|
13
|
+
`tools/list` and `tools/call`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Requires Node ≥ 18. Get an API key from your PurposeBot account.
|
|
18
|
+
|
|
19
|
+
### Claude Desktop / Cursor (`mcpServers` config)
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"purposebot": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "@purposebot/mcp"],
|
|
27
|
+
"env": {
|
|
28
|
+
"PURPOSEBOT_API_KEY": "pb_xxx"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Claude Code
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
claude mcp add purposebot --env PURPOSEBOT_API_KEY=pb_xxx -- npx -y @purposebot/mcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Environment
|
|
42
|
+
|
|
43
|
+
| Variable | Required | Default | Notes |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| `PURPOSEBOT_API_KEY` | yes | — | sent as `X-API-Key` |
|
|
46
|
+
| `PURPOSEBOT_API_URL` | no | `https://purposebot.ai` | point at a sandbox/self-host |
|
|
47
|
+
|
|
48
|
+
## Develop
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
npm run build # tsc -> dist/
|
|
53
|
+
PURPOSEBOT_API_KEY=pb_xxx node dist/index.js # runs the stdio server
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The server is a transparent proxy: auth is the PurposeBot API key, and write/
|
|
57
|
+
trust-signal tools still enforce their own provenance and signed-proof
|
|
58
|
+
requirements server-side.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PurposeBot MCP server — a thin stdio proxy to the hosted PurposeBot MCP
|
|
4
|
+
* (JSON-RPC at <API_URL>/mcp). Lets agents in Claude Desktop, Cursor, etc. use
|
|
5
|
+
* PurposeBot via `npx @purposebot/mcp` without a custom HTTP integration.
|
|
6
|
+
*
|
|
7
|
+
* It forwards tools/list and tools/call straight through; the hosted endpoint is
|
|
8
|
+
* the source of truth for the tool catalogue, so new tools appear automatically.
|
|
9
|
+
*
|
|
10
|
+
* Env:
|
|
11
|
+
* PURPOSEBOT_API_KEY (required) — your PurposeBot API key (sent as X-API-Key)
|
|
12
|
+
* PURPOSEBOT_API_URL (optional) — defaults to https://purposebot.ai
|
|
13
|
+
*/
|
|
14
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
15
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
+
const API_KEY = process.env.PURPOSEBOT_API_KEY;
|
|
18
|
+
function die(msg) {
|
|
19
|
+
console.error(`[purposebot-mcp] ${msg}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
// The wrapper's trust boundary: we send X-API-Key to this host on every call, so
|
|
23
|
+
// validate it before any request. Default + allowlist PurposeBot hosts; require an
|
|
24
|
+
// explicit opt-in for anything else; never send the key over plain http (except
|
|
25
|
+
// localhost dev); reject embedded credentials.
|
|
26
|
+
const ALLOWED_HOSTS = new Set([
|
|
27
|
+
"purposebot.ai",
|
|
28
|
+
"www.purposebot.ai",
|
|
29
|
+
"purposebot-api-sandbox.fly.dev",
|
|
30
|
+
]);
|
|
31
|
+
function resolveApiBase(raw) {
|
|
32
|
+
let u;
|
|
33
|
+
try {
|
|
34
|
+
u = new URL(raw);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
die(`PURPOSEBOT_API_URL is not a valid URL: ${raw}`);
|
|
38
|
+
}
|
|
39
|
+
const isLocal = u.hostname === "localhost" || u.hostname === "127.0.0.1";
|
|
40
|
+
if (u.username || u.password)
|
|
41
|
+
die("PURPOSEBOT_API_URL must not contain embedded credentials");
|
|
42
|
+
if (u.protocol !== "https:" && !(u.protocol === "http:" && isLocal)) {
|
|
43
|
+
die(`PURPOSEBOT_API_URL must use https (http allowed only for localhost), got ${u.protocol}`);
|
|
44
|
+
}
|
|
45
|
+
if (!ALLOWED_HOSTS.has(u.hostname) && !isLocal && process.env.PURPOSEBOT_ALLOW_CUSTOM_HOST !== "1") {
|
|
46
|
+
die(`refusing to send your API key to non-PurposeBot host "${u.hostname}". ` +
|
|
47
|
+
`If this is intentional (self-host/sandbox), set PURPOSEBOT_ALLOW_CUSTOM_HOST=1.`);
|
|
48
|
+
}
|
|
49
|
+
return `${u.protocol}//${u.host}`;
|
|
50
|
+
}
|
|
51
|
+
if (!API_KEY) {
|
|
52
|
+
die("PURPOSEBOT_API_KEY is required. Set it in your MCP client config (env).");
|
|
53
|
+
}
|
|
54
|
+
const API_URL = resolveApiBase(process.env.PURPOSEBOT_API_URL ?? "https://purposebot.ai");
|
|
55
|
+
const MCP_ENDPOINT = `${API_URL}/mcp`;
|
|
56
|
+
let rpcId = 0;
|
|
57
|
+
async function rpc(method, params) {
|
|
58
|
+
const res = await fetch(MCP_ENDPOINT, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"content-type": "application/json",
|
|
62
|
+
"x-api-key": API_KEY,
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: ++rpcId, method, params }),
|
|
65
|
+
// Never follow redirects: undici keeps custom headers (X-API-Key) on
|
|
66
|
+
// cross-origin redirects, so a 30x from an allowlisted host could leak the
|
|
67
|
+
// key. Fail instead — the host allowlist only guards the INITIAL request.
|
|
68
|
+
redirect: "error",
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
const text = await res.text().catch(() => "");
|
|
72
|
+
throw new Error(`PurposeBot MCP ${method} -> HTTP ${res.status}: ${text}`);
|
|
73
|
+
}
|
|
74
|
+
const body = (await res.json());
|
|
75
|
+
if (body.error) {
|
|
76
|
+
throw new Error(`PurposeBot MCP ${method} error ${body.error.code}: ${body.error.message}`);
|
|
77
|
+
}
|
|
78
|
+
return body.result;
|
|
79
|
+
}
|
|
80
|
+
const server = new Server({ name: "purposebot", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
81
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
82
|
+
const result = await rpc("tools/list");
|
|
83
|
+
return { tools: Array.isArray(result?.tools) ? result.tools : [] };
|
|
84
|
+
});
|
|
85
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
86
|
+
const result = await rpc("tools/call", {
|
|
87
|
+
name: request.params.name,
|
|
88
|
+
arguments: request.params.arguments ?? {},
|
|
89
|
+
});
|
|
90
|
+
// The hosted endpoint already returns MCP-shaped { content, isError }; pass it
|
|
91
|
+
// through. Fall back to wrapping a raw result as text for forward-compat.
|
|
92
|
+
if (result && Array.isArray(result.content)) {
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
return { content: [{ type: "text", text: JSON.stringify(result ?? {}, null, 2) }] };
|
|
96
|
+
});
|
|
97
|
+
async function main() {
|
|
98
|
+
await server.connect(new StdioServerTransport());
|
|
99
|
+
console.error(`[purposebot-mcp] connected — proxying ${MCP_ENDPOINT}`);
|
|
100
|
+
}
|
|
101
|
+
main().catch((err) => {
|
|
102
|
+
console.error("[purposebot-mcp] fatal:", err);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@purposebot/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "PurposeBot MCP server — stdio proxy to the hosted PurposeBot MCP (https://purposebot.ai/mcp) so agents in Claude Desktop, Cursor, etc. can use it via npx.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"purposebot-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"purposebot",
|
|
25
|
+
"agent-commerce",
|
|
26
|
+
"trust"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
34
|
+
"typescript": "^5.4.0"
|
|
35
|
+
}
|
|
36
|
+
}
|