@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.
Files changed (3) hide show
  1. package/README.md +58 -0
  2. package/dist/index.js +104 -0
  3. 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
+ }