@productbrain/mcp 0.0.1-beta.1 → 0.0.1-beta.3
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/.env.mcp.example +4 -4
- package/dist/cli/index.js +1 -1
- package/dist/index.js +1905 -204
- package/dist/index.js.map +1 -1
- package/dist/{setup-5UDBP4VE.js → setup-V6HIAYXL.js} +4 -4
- package/dist/setup-V6HIAYXL.js.map +1 -0
- package/package.json +6 -25
- package/README.md +0 -239
- package/dist/setup-5UDBP4VE.js.map +0 -1
|
@@ -84,7 +84,7 @@ async function writeClientConfig(client, apiKey) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// src/cli/setup.ts
|
|
87
|
-
var
|
|
87
|
+
var APP_URL = process.env.PRODUCTBRAIN_APP_URL ?? "http://localhost:5173";
|
|
88
88
|
function bold(s) {
|
|
89
89
|
return `\x1B[1m${s}\x1B[0m`;
|
|
90
90
|
}
|
|
@@ -144,8 +144,8 @@ async function runSetup() {
|
|
|
144
144
|
log("");
|
|
145
145
|
log(bold(` Product${orange("Brain")} Setup`));
|
|
146
146
|
log(dim(" Connect your AI assistant to your knowledge base\n"));
|
|
147
|
-
const apiKeysUrl = `${
|
|
148
|
-
log(` ${dim("1. Get your API key from
|
|
147
|
+
const apiKeysUrl = `${APP_URL}/settings/api-keys`;
|
|
148
|
+
log(` ${dim("1. Get your API key from Settings \u2192 API Keys")}`);
|
|
149
149
|
log(` ${dim(apiKeysUrl)}
|
|
150
150
|
`);
|
|
151
151
|
const openNow = await prompt(` Open this URL in your browser? [Y/n]: `);
|
|
@@ -224,4 +224,4 @@ function printConfigSnippet(apiKey) {
|
|
|
224
224
|
export {
|
|
225
225
|
runSetup
|
|
226
226
|
};
|
|
227
|
-
//# sourceMappingURL=setup-
|
|
227
|
+
//# sourceMappingURL=setup-V6HIAYXL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/setup.ts","../src/cli/config-writer.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * `npx @productbrain/mcp setup`\n *\n * Guided onboarding: get API key from the app, paste it, and write MCP config.\n */\n\nimport { createInterface } from \"node:readline\";\nimport { detectClients, writeClientConfig, type McpClientInfo } from \"./config-writer.js\";\n\nconst APP_URL =\n process.env.PRODUCTBRAIN_APP_URL ?? \"http://localhost:5173\";\n\n// ── Helpers ─────────────────────────────────────────────────────────────\n\nfunction bold(s: string) {\n return `\\x1b[1m${s}\\x1b[0m`;\n}\nfunction green(s: string) {\n return `\\x1b[32m${s}\\x1b[0m`;\n}\nfunction dim(s: string) {\n return `\\x1b[2m${s}\\x1b[0m`;\n}\nfunction orange(s: string) {\n return `\\x1b[33m${s}\\x1b[0m`;\n}\n\nfunction log(msg: string) {\n process.stdout.write(`${msg}\\n`);\n}\n\nfunction openBrowser(url: string) {\n const { execSync } = require(\"node:child_process\") as typeof import(\"node:child_process\");\n const platform = process.platform;\n try {\n if (platform === \"darwin\") execSync(`open \"${url}\"`);\n else if (platform === \"win32\") execSync(`start \"\" \"${url}\"`);\n else execSync(`xdg-open \"${url}\"`);\n } catch {\n log(dim(` Could not open browser automatically.`));\n log(` Open this URL manually: ${url}`);\n }\n}\n\nfunction prompt(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nfunction promptChoice(question: string, choices: string[]): Promise<number> {\n return new Promise((resolve) => {\n log(\"\");\n log(bold(question));\n choices.forEach((c, i) => log(` ${i + 1}) ${c}`));\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`\\n ${dim(\"Choice [1]:\")} `, (line) => {\n rl.close();\n const n = parseInt(line.trim(), 10);\n if (isNaN(n) || n < 1 || n > choices.length) {\n resolve(0);\n } else {\n resolve(n - 1);\n }\n });\n });\n}\n\n// ── Main ────────────────────────────────────────────────────────────────\n\nexport async function runSetup() {\n log(\"\");\n log(bold(` Product${orange(\"Brain\")} Setup`));\n log(dim(\" Connect your AI assistant to your knowledge base\\n\"));\n\n const apiKeysUrl = `${APP_URL}/settings/api-keys`;\n\n log(` ${dim(\"1. Get your API key from Settings → API Keys\")}`);\n log(` ${dim(apiKeysUrl)}\\n`);\n\n const openNow = await prompt(` Open this URL in your browser? [Y/n]: `);\n if (openNow.toLowerCase() !== \"n\" && openNow.toLowerCase() !== \"no\") {\n openBrowser(apiKeysUrl);\n }\n\n log(\"\");\n log(` ${dim(\"2. Generate a key (if you don't have one), then copy it.\\n\")}`);\n\n const apiKey = await prompt(` Paste your API key (pb_sk_...): `);\n\n if (!apiKey || !apiKey.startsWith(\"pb_sk_\")) {\n log(` ${orange(\"!\")} Invalid key format. Keys start with pb_sk_.`);\n log(` Get one at ${apiKeysUrl}\\n`);\n process.exit(1);\n }\n\n log(` ${green(\"✓\")} Key received\\n`);\n\n const clients = detectClients();\n\n if (clients.length === 0) {\n log(bold(\" No supported MCP clients detected.\\n\"));\n printConfigSnippet(apiKey);\n return;\n }\n\n const clientNames = clients.map((c) => c.name);\n const allOption =\n clients.length > 1\n ? [...clientNames, \"All of the above\", \"Just show me the config\"]\n : [...clientNames, \"Just show me the config\"];\n\n const choice = await promptChoice(\"Which AI assistant should I configure?\", allOption);\n\n if (choice === allOption.length - 1) {\n printConfigSnippet(apiKey);\n } else if (clients.length > 1 && choice === allOption.length - 2) {\n for (const client of clients) {\n await writeConfig(client, apiKey);\n }\n } else {\n await writeConfig(clients[choice], apiKey);\n }\n\n log(\"\");\n log(\n ` ${green(\"✓\")} Done! Restart your AI assistant and try: ${bold('\"Use the health tool\"')}`,\n );\n log(\"\");\n}\n\nasync function writeConfig(client: McpClientInfo, apiKey: string) {\n try {\n const wrote = await writeClientConfig(client, apiKey);\n if (wrote) {\n log(` ${green(\"✓\")} Wrote config to ${dim(client.configPath)}`);\n } else {\n log(` ${dim(\"ℹ\")} ${client.name} already configured — skipped`);\n }\n } catch (err: any) {\n log(` ${orange(\"!\")} Could not write ${client.name} config: ${err.message}`);\n printConfigSnippet(apiKey);\n }\n}\n\nfunction printConfigSnippet(apiKey: string) {\n log(\"\");\n log(bold(\" Add this to your MCP client config:\\n\"));\n const snippet = JSON.stringify(\n {\n mcpServers: {\n productbrain: {\n command: \"npx\",\n args: [\"-y\", \"@productbrain/mcp\"],\n env: { PRODUCTBRAIN_API_KEY: apiKey },\n },\n },\n },\n null,\n 2,\n );\n for (const line of snippet.split(\"\\n\")) {\n log(` ${line}`);\n }\n log(\"\");\n}\n","/**\n * Multi-client MCP config detection and writer.\n *\n * Supports:\n * - Cursor: .cursor/mcp.json in cwd (project-level)\n * - Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)\n * %APPDATA%/Claude/claude_desktop_config.json (Windows)\n *\n * The writer reads existing config, merges the new server entry (never\n * overwrites existing entries), and writes back. Falls back to printing\n * a snippet for unsupported OS or unknown formats.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\n\nexport interface McpClientInfo {\n name: string;\n configPath: string;\n}\n\nconst SERVER_ENTRY_KEY = \"productbrain\";\n\nfunction buildServerEntry(apiKey: string) {\n return {\n command: \"npx\",\n args: [\"-y\", \"@productbrain/mcp\"],\n env: { PRODUCTBRAIN_API_KEY: apiKey },\n };\n}\n\n// ── Detection ───────────────────────────────────────────────────────────\n\nfunction getCursorConfigPath(): string {\n return join(process.cwd(), \".cursor\", \"mcp.json\");\n}\n\nfunction getClaudeDesktopConfigPath(): string | null {\n const os = platform();\n if (os === \"darwin\") {\n return join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n }\n if (os === \"win32\") {\n const appData = process.env.APPDATA ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"Claude\", \"claude_desktop_config.json\");\n }\n // Linux: no official Claude Desktop location yet\n return null;\n}\n\nfunction isCursorProject(): boolean {\n return existsSync(join(process.cwd(), \".cursor\")) || existsSync(join(process.cwd(), \".cursorignore\"));\n}\n\nfunction isClaudeDesktopInstalled(): boolean {\n const configPath = getClaudeDesktopConfigPath();\n if (!configPath) return false;\n // Check if the Claude directory exists (config file may not exist yet)\n return existsSync(dirname(configPath));\n}\n\nexport function detectClients(): McpClientInfo[] {\n const clients: McpClientInfo[] = [];\n\n if (isCursorProject()) {\n clients.push({ name: \"Cursor\", configPath: getCursorConfigPath() });\n }\n\n if (isClaudeDesktopInstalled()) {\n clients.push({\n name: \"Claude Desktop\",\n configPath: getClaudeDesktopConfigPath()!,\n });\n }\n\n return clients;\n}\n\n// ── Writing ─────────────────────────────────────────────────────────────\n\nfunction readJsonSafe(path: string): Record<string, any> {\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\n/**\n * Write or merge the productbrain server entry into a client config file.\n * Returns true if the config was written, false if already present.\n */\nexport async function writeClientConfig(\n client: McpClientInfo,\n apiKey: string,\n): Promise<boolean> {\n const config = readJsonSafe(client.configPath);\n\n const serversKey = \"mcpServers\";\n if (!config[serversKey]) config[serversKey] = {};\n\n // Don't overwrite an existing productbrain entry\n if (config[serversKey][SERVER_ENTRY_KEY]) {\n return false;\n }\n\n config[serversKey][SERVER_ENTRY_KEY] = buildServerEntry(apiKey);\n\n const dir = dirname(client.configPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(client.configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n return true;\n}\n"],"mappings":"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","names":["platform"]}
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productbrain/mcp",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.1-beta.3",
|
|
4
|
+
"description": "Product Brain — MCP server for AI-assisted product knowledge management",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18"
|
|
8
|
+
},
|
|
6
9
|
"bin": {
|
|
7
10
|
"productbrain": "dist/cli/index.js",
|
|
11
|
+
"mcp": "dist/cli/index.js",
|
|
8
12
|
"synergyos-mcp": "dist/index.js"
|
|
9
13
|
},
|
|
10
14
|
"files": [
|
|
@@ -12,9 +16,6 @@
|
|
|
12
16
|
"README.md",
|
|
13
17
|
".env.mcp.example"
|
|
14
18
|
],
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=18.0.0"
|
|
17
|
-
},
|
|
18
19
|
"scripts": {
|
|
19
20
|
"build": "tsup",
|
|
20
21
|
"start": "node dist/index.js",
|
|
@@ -24,26 +25,6 @@
|
|
|
24
25
|
"publish:beta": "npm publish --tag=beta",
|
|
25
26
|
"version:prerelease": "npm version prerelease"
|
|
26
27
|
},
|
|
27
|
-
"keywords": [
|
|
28
|
-
"productbrain",
|
|
29
|
-
"synergyos",
|
|
30
|
-
"mcp",
|
|
31
|
-
"model-context-protocol",
|
|
32
|
-
"cursor",
|
|
33
|
-
"claude-desktop",
|
|
34
|
-
"product-management",
|
|
35
|
-
"knowledge-base",
|
|
36
|
-
"glossary",
|
|
37
|
-
"business-rules",
|
|
38
|
-
"terminology"
|
|
39
|
-
],
|
|
40
|
-
"author": "SynergyOS.ai",
|
|
41
|
-
"license": "MIT",
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/synergyai-os/productbrain.git"
|
|
45
|
-
},
|
|
46
|
-
"homepage": "https://github.com/synergyai-os/productbrain#readme",
|
|
47
28
|
"dependencies": {
|
|
48
29
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
49
30
|
"convex": "^1.32.0",
|
package/README.md
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
# ProductBrain
|
|
2
|
-
|
|
3
|
-
The single source of truth for product knowledge — glossary, business rules, tensions, decisions, labels, and relations — accessible as an MCP server in [Claude Desktop](https://claude.ai/download), [Cursor](https://cursor.com), and any MCP-compatible AI assistant.
|
|
4
|
-
|
|
5
|
-
ProductBrain connects your AI assistant to your team's knowledge base. Ask questions, capture decisions, and build a living knowledge graph without leaving your editor.
|
|
6
|
-
|
|
7
|
-
## Quick Start (Cloud)
|
|
8
|
-
|
|
9
|
-
### Option A: Guided setup (recommended)
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npx @productbrain/mcp setup
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
This opens SynergyOS → Settings → API Keys, prompts you to paste your key, and writes the config for Cursor or Claude Desktop.
|
|
16
|
-
|
|
17
|
-
### Option B: Manual config
|
|
18
|
-
|
|
19
|
-
**1. Get your API key**
|
|
20
|
-
|
|
21
|
-
Go to **SynergyOS → Settings → API Keys** and click **Generate Key**. Copy the `pb_sk_...` key.
|
|
22
|
-
|
|
23
|
-
**2. Configure your AI assistant**
|
|
24
|
-
|
|
25
|
-
**Claude Desktop** — edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
26
|
-
|
|
27
|
-
```json
|
|
28
|
-
{
|
|
29
|
-
"mcpServers": {
|
|
30
|
-
"productbrain": {
|
|
31
|
-
"command": "npx",
|
|
32
|
-
"args": ["-y", "@productbrain/mcp"],
|
|
33
|
-
"env": {
|
|
34
|
-
"PRODUCTBRAIN_API_KEY": "pb_sk_your_key_here"
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**Cursor** — edit `.cursor/mcp.json` in your project:
|
|
42
|
-
|
|
43
|
-
```json
|
|
44
|
-
{
|
|
45
|
-
"mcpServers": {
|
|
46
|
-
"productbrain": {
|
|
47
|
-
"command": "npx",
|
|
48
|
-
"args": ["-y", "@productbrain/mcp"],
|
|
49
|
-
"env": {
|
|
50
|
-
"PRODUCTBRAIN_API_KEY": "pb_sk_your_key_here"
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**3. Restart your assistant and verify**
|
|
58
|
-
|
|
59
|
-
Ask:
|
|
60
|
-
|
|
61
|
-
> "Use the health tool to check ProductBrain connectivity"
|
|
62
|
-
|
|
63
|
-
You should see your workspace ID, collection count, and latency.
|
|
64
|
-
|
|
65
|
-
## Self-Hosted Setup
|
|
66
|
-
|
|
67
|
-
If you're running your own Convex deployment, use the three-variable config:
|
|
68
|
-
|
|
69
|
-
```json
|
|
70
|
-
{
|
|
71
|
-
"mcpServers": {
|
|
72
|
-
"productbrain": {
|
|
73
|
-
"command": "npx",
|
|
74
|
-
"args": ["-y", "@productbrain/mcp"],
|
|
75
|
-
"env": {
|
|
76
|
-
"CONVEX_SITE_URL": "https://your-deployment.convex.site",
|
|
77
|
-
"MCP_API_KEY": "your-shared-api-key",
|
|
78
|
-
"WORKSPACE_SLUG": "your-workspace-slug"
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
| Variable | Where to find it |
|
|
86
|
-
|----------|-----------------|
|
|
87
|
-
| `CONVEX_SITE_URL` | Convex dashboard → Settings → URL (use `*.convex.site`, not `*.convex.cloud`) |
|
|
88
|
-
| `MCP_API_KEY` | Must match the `MCP_API_KEY` env var in your Convex deployment |
|
|
89
|
-
| `WORKSPACE_SLUG` | Your workspace slug from the SynergyOS URL |
|
|
90
|
-
|
|
91
|
-
## Dev vs Production
|
|
92
|
-
|
|
93
|
-
Set `PRODUCTBRAIN_URL` to switch between environments:
|
|
94
|
-
|
|
95
|
-
```json
|
|
96
|
-
{
|
|
97
|
-
"env": {
|
|
98
|
-
"PRODUCTBRAIN_API_KEY": "pb_sk_your_key_here",
|
|
99
|
-
"PRODUCTBRAIN_URL": "http://localhost:3210"
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Omit `PRODUCTBRAIN_URL` to default to production.
|
|
105
|
-
|
|
106
|
-
## What You Can Do
|
|
107
|
-
|
|
108
|
-
### Search and explore
|
|
109
|
-
|
|
110
|
-
- *"Search the glossary for 'tension'"*
|
|
111
|
-
- *"List all business rules in the Governance domain"*
|
|
112
|
-
- *"What is the canonical definition of 'supplier'?"*
|
|
113
|
-
|
|
114
|
-
### Capture knowledge
|
|
115
|
-
|
|
116
|
-
- *"Capture a tension: our MCP entry creation takes too many steps"*
|
|
117
|
-
- *"Draft a decision record for choosing Convex over Supabase"*
|
|
118
|
-
- *"Create a business rule about API key rotation"*
|
|
119
|
-
|
|
120
|
-
### Navigate the knowledge graph
|
|
121
|
-
|
|
122
|
-
- *"Gather full context around FEAT-001"*
|
|
123
|
-
- *"What entries are related to GT-019?"*
|
|
124
|
-
- *"Suggest links for this new tension"*
|
|
125
|
-
|
|
126
|
-
### Check quality
|
|
127
|
-
|
|
128
|
-
- *"Run a quality check on TEN-graph-db"*
|
|
129
|
-
- *"Review business rules for the AI & MCP Integration domain"*
|
|
130
|
-
- *"Verify the glossary against the codebase"*
|
|
131
|
-
|
|
132
|
-
## Tools (20+)
|
|
133
|
-
|
|
134
|
-
| Tool | What it does |
|
|
135
|
-
|------|-------------|
|
|
136
|
-
| `health` | Verify connectivity and get workspace stats |
|
|
137
|
-
| `kb-search` | Full-text search across all knowledge entries |
|
|
138
|
-
| `list-collections` | Browse all collection schemas |
|
|
139
|
-
| `list-entries` | Browse entries with optional filters |
|
|
140
|
-
| `get-entry` | Full record with data, labels, relations, history |
|
|
141
|
-
| `smart-capture` | One-call entry creation with auto-linking and quality scoring |
|
|
142
|
-
| `create-entry` | Create with full field control |
|
|
143
|
-
| `update-entry` | Partial update (merges with existing data) |
|
|
144
|
-
| `gather-context` | Multi-hop graph traversal around an entry |
|
|
145
|
-
| `suggest-links` | Discover potential connections |
|
|
146
|
-
| `relate-entries` | Create typed relations between entries |
|
|
147
|
-
| `find-related` | List direct relations for an entry |
|
|
148
|
-
| `quality-check` | Score an entry against collection-specific criteria |
|
|
149
|
-
| `review-rules` | Surface business rules for a domain |
|
|
150
|
-
| `verify` | Check knowledge entries against the actual codebase |
|
|
151
|
-
| `list-labels` | Browse workspace labels |
|
|
152
|
-
| `manage-labels` | Create, update, or delete labels |
|
|
153
|
-
| `label-entry` | Apply or remove labels from entries |
|
|
154
|
-
| `quick-capture` | Minimal-ceremony entry creation |
|
|
155
|
-
| `mcp-audit` | Session audit log with call statistics |
|
|
156
|
-
|
|
157
|
-
## Resources
|
|
158
|
-
|
|
159
|
-
| URI | Content |
|
|
160
|
-
|-----|---------|
|
|
161
|
-
| `productbrain://orientation` | System map: architecture, data model, rules, analytics |
|
|
162
|
-
| `productbrain://terminology` | Glossary + standards summary |
|
|
163
|
-
| `productbrain://collections` | All collection schemas with field definitions |
|
|
164
|
-
| `productbrain://{slug}/entries` | All entries in a given collection |
|
|
165
|
-
| `productbrain://labels` | Workspace labels with hierarchy |
|
|
166
|
-
|
|
167
|
-
## Prompts
|
|
168
|
-
|
|
169
|
-
| Prompt | Purpose |
|
|
170
|
-
|--------|---------|
|
|
171
|
-
| `review-against-rules` | Structured compliance review against business rules |
|
|
172
|
-
| `name-check` | Check variable/field names against the glossary |
|
|
173
|
-
| `draft-decision-record` | Draft a decision record from context |
|
|
174
|
-
| `draft-rule-from-context` | Draft a business rule from an observation |
|
|
175
|
-
|
|
176
|
-
## Security
|
|
177
|
-
|
|
178
|
-
- **Your data stays yours.** The MCP server connects only to your authenticated Convex deployment. No data is shared with third parties.
|
|
179
|
-
- **API key handling.** Cloud keys (`pb_sk_...`) are SHA-256 hashed before storage. Only the prefix is persisted for display. Keys are sent as Bearer tokens over HTTPS.
|
|
180
|
-
- **Workspace scoping.** Each API key is bound to a single workspace. No cross-workspace access is possible.
|
|
181
|
-
|
|
182
|
-
## Troubleshooting
|
|
183
|
-
|
|
184
|
-
### "Missing API key" or "Invalid API key"
|
|
185
|
-
|
|
186
|
-
Your `PRODUCTBRAIN_API_KEY` is missing or incorrect. Generate a new key from SynergyOS Settings → API Keys.
|
|
187
|
-
|
|
188
|
-
### "CONVEX_SITE_URL environment variable is required"
|
|
189
|
-
|
|
190
|
-
You're using self-hosted mode but missing the `env` block. Make sure all three variables are set.
|
|
191
|
-
|
|
192
|
-
### "Workspace not found"
|
|
193
|
-
|
|
194
|
-
For self-hosted: check your `WORKSPACE_SLUG`. For cloud: your API key may have been revoked.
|
|
195
|
-
|
|
196
|
-
### "MCP call network error"
|
|
197
|
-
|
|
198
|
-
The backend is unreachable. If using `PRODUCTBRAIN_URL`, verify the URL is correct and the server is running.
|
|
199
|
-
|
|
200
|
-
### Server doesn't appear in Claude Desktop / Cursor
|
|
201
|
-
|
|
202
|
-
Restart the application after editing the config file. In Cursor, check the MCP panel (Cmd+Shift+P → "MCP: Show Panel") for startup errors.
|
|
203
|
-
|
|
204
|
-
### Enable debug logging
|
|
205
|
-
|
|
206
|
-
Set `MCP_DEBUG=1` in your config's `env` block to see audit logs in stderr.
|
|
207
|
-
|
|
208
|
-
## Development
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
# Clone and install
|
|
212
|
-
git clone https://github.com/synergyai-os/productbrain.git
|
|
213
|
-
cd productbrain
|
|
214
|
-
# Or install: npm install @productbrain/mcp
|
|
215
|
-
npm install
|
|
216
|
-
|
|
217
|
-
# Copy env template and fill in your values
|
|
218
|
-
cp .env.mcp.example .env.mcp
|
|
219
|
-
|
|
220
|
-
# Run in dev mode (TypeScript, hot reload)
|
|
221
|
-
npm run dev
|
|
222
|
-
|
|
223
|
-
# Build for production
|
|
224
|
-
npm run build
|
|
225
|
-
|
|
226
|
-
# Run the built version
|
|
227
|
-
npm start
|
|
228
|
-
|
|
229
|
-
# Typecheck
|
|
230
|
-
npm run typecheck
|
|
231
|
-
|
|
232
|
-
# Publish beta
|
|
233
|
-
npm run publish:beta
|
|
234
|
-
# (Maintainers: set SYNERGYOS_POSTHOG_KEY=phc_... for usage tracking)
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## License
|
|
238
|
-
|
|
239
|
-
MIT
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/setup.ts","../src/cli/config-writer.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * `npx @productbrain/mcp setup`\n *\n * Guided onboarding: get API key from SynergyOS, paste it, and write MCP config.\n * No GitHub — keys come from SynergyOS → Settings → API Keys.\n */\n\nimport { createInterface } from \"node:readline\";\nimport { detectClients, writeClientConfig, type McpClientInfo } from \"./config-writer.js\";\n\nconst SYNERGYOS_APP_URL =\n process.env.SYNERGYOS_APP_URL ?? process.env.PRODUCTBRAIN_APP_URL ?? \"http://localhost:5173\";\n\n// ── Helpers ─────────────────────────────────────────────────────────────\n\nfunction bold(s: string) {\n return `\\x1b[1m${s}\\x1b[0m`;\n}\nfunction green(s: string) {\n return `\\x1b[32m${s}\\x1b[0m`;\n}\nfunction dim(s: string) {\n return `\\x1b[2m${s}\\x1b[0m`;\n}\nfunction orange(s: string) {\n return `\\x1b[33m${s}\\x1b[0m`;\n}\n\nfunction log(msg: string) {\n process.stdout.write(`${msg}\\n`);\n}\n\nfunction openBrowser(url: string) {\n const { execSync } = require(\"node:child_process\") as typeof import(\"node:child_process\");\n const platform = process.platform;\n try {\n if (platform === \"darwin\") execSync(`open \"${url}\"`);\n else if (platform === \"win32\") execSync(`start \"\" \"${url}\"`);\n else execSync(`xdg-open \"${url}\"`);\n } catch {\n log(dim(` Could not open browser automatically.`));\n log(` Open this URL manually: ${url}`);\n }\n}\n\nfunction prompt(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nfunction promptChoice(question: string, choices: string[]): Promise<number> {\n return new Promise((resolve) => {\n log(\"\");\n log(bold(question));\n choices.forEach((c, i) => log(` ${i + 1}) ${c}`));\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`\\n ${dim(\"Choice [1]:\")} `, (line) => {\n rl.close();\n const n = parseInt(line.trim(), 10);\n if (isNaN(n) || n < 1 || n > choices.length) {\n resolve(0);\n } else {\n resolve(n - 1);\n }\n });\n });\n}\n\n// ── Main ────────────────────────────────────────────────────────────────\n\nexport async function runSetup() {\n log(\"\");\n log(bold(` Product${orange(\"Brain\")} Setup`));\n log(dim(\" Connect your AI assistant to your knowledge base\\n\"));\n\n const apiKeysUrl = `${SYNERGYOS_APP_URL}/settings/api-keys`;\n\n log(` ${dim(\"1. Get your API key from SynergyOS\")}`);\n log(` ${dim(apiKeysUrl)}\\n`);\n\n const openNow = await prompt(` Open this URL in your browser? [Y/n]: `);\n if (openNow.toLowerCase() !== \"n\" && openNow.toLowerCase() !== \"no\") {\n openBrowser(apiKeysUrl);\n }\n\n log(\"\");\n log(` ${dim(\"2. Generate a key (if you don't have one), then copy it.\\n\")}`);\n\n const apiKey = await prompt(` Paste your API key (pb_sk_...): `);\n\n if (!apiKey || !apiKey.startsWith(\"pb_sk_\")) {\n log(` ${orange(\"!\")} Invalid key format. Keys start with pb_sk_.`);\n log(` Get one at ${apiKeysUrl}\\n`);\n process.exit(1);\n }\n\n log(` ${green(\"✓\")} Key received\\n`);\n\n // 3. Detect MCP clients and write config\n const clients = detectClients();\n\n if (clients.length === 0) {\n log(bold(\" No supported MCP clients detected.\\n\"));\n printConfigSnippet(apiKey);\n return;\n }\n\n const clientNames = clients.map((c) => c.name);\n const allOption =\n clients.length > 1\n ? [...clientNames, \"All of the above\", \"Just show me the config\"]\n : [...clientNames, \"Just show me the config\"];\n\n const choice = await promptChoice(\"Which AI assistant should I configure?\", allOption);\n\n if (choice === allOption.length - 1) {\n printConfigSnippet(apiKey);\n } else if (clients.length > 1 && choice === allOption.length - 2) {\n for (const client of clients) {\n await writeConfig(client, apiKey);\n }\n } else {\n await writeConfig(clients[choice], apiKey);\n }\n\n log(\"\");\n log(\n ` ${green(\"✓\")} Done! Restart your AI assistant and try: ${bold('\"Use the health tool\"')}`,\n );\n log(\"\");\n}\n\nasync function writeConfig(client: McpClientInfo, apiKey: string) {\n try {\n const wrote = await writeClientConfig(client, apiKey);\n if (wrote) {\n log(` ${green(\"✓\")} Wrote config to ${dim(client.configPath)}`);\n } else {\n log(` ${dim(\"ℹ\")} ${client.name} already configured — skipped`);\n }\n } catch (err: any) {\n log(` ${orange(\"!\")} Could not write ${client.name} config: ${err.message}`);\n printConfigSnippet(apiKey);\n }\n}\n\nfunction printConfigSnippet(apiKey: string) {\n log(\"\");\n log(bold(\" Add this to your MCP client config:\\n\"));\n const snippet = JSON.stringify(\n {\n mcpServers: {\n productbrain: {\n command: \"npx\",\n args: [\"-y\", \"@productbrain/mcp\"],\n env: { PRODUCTBRAIN_API_KEY: apiKey },\n },\n },\n },\n null,\n 2,\n );\n for (const line of snippet.split(\"\\n\")) {\n log(` ${line}`);\n }\n log(\"\");\n}\n","/**\n * Multi-client MCP config detection and writer.\n *\n * Supports:\n * - Cursor: .cursor/mcp.json in cwd (project-level)\n * - Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)\n * %APPDATA%/Claude/claude_desktop_config.json (Windows)\n *\n * The writer reads existing config, merges the new server entry (never\n * overwrites existing entries), and writes back. Falls back to printing\n * a snippet for unsupported OS or unknown formats.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\n\nexport interface McpClientInfo {\n name: string;\n configPath: string;\n}\n\nconst SERVER_ENTRY_KEY = \"productbrain\";\n\nfunction buildServerEntry(apiKey: string) {\n return {\n command: \"npx\",\n args: [\"-y\", \"@productbrain/mcp\"],\n env: { PRODUCTBRAIN_API_KEY: apiKey },\n };\n}\n\n// ── Detection ───────────────────────────────────────────────────────────\n\nfunction getCursorConfigPath(): string {\n return join(process.cwd(), \".cursor\", \"mcp.json\");\n}\n\nfunction getClaudeDesktopConfigPath(): string | null {\n const os = platform();\n if (os === \"darwin\") {\n return join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n }\n if (os === \"win32\") {\n const appData = process.env.APPDATA ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"Claude\", \"claude_desktop_config.json\");\n }\n // Linux: no official Claude Desktop location yet\n return null;\n}\n\nfunction isCursorProject(): boolean {\n return existsSync(join(process.cwd(), \".cursor\")) || existsSync(join(process.cwd(), \".cursorignore\"));\n}\n\nfunction isClaudeDesktopInstalled(): boolean {\n const configPath = getClaudeDesktopConfigPath();\n if (!configPath) return false;\n // Check if the Claude directory exists (config file may not exist yet)\n return existsSync(dirname(configPath));\n}\n\nexport function detectClients(): McpClientInfo[] {\n const clients: McpClientInfo[] = [];\n\n if (isCursorProject()) {\n clients.push({ name: \"Cursor\", configPath: getCursorConfigPath() });\n }\n\n if (isClaudeDesktopInstalled()) {\n clients.push({\n name: \"Claude Desktop\",\n configPath: getClaudeDesktopConfigPath()!,\n });\n }\n\n return clients;\n}\n\n// ── Writing ─────────────────────────────────────────────────────────────\n\nfunction readJsonSafe(path: string): Record<string, any> {\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\n/**\n * Write or merge the productbrain server entry into a client config file.\n * Returns true if the config was written, false if already present.\n */\nexport async function writeClientConfig(\n client: McpClientInfo,\n apiKey: string,\n): Promise<boolean> {\n const config = readJsonSafe(client.configPath);\n\n const serversKey = \"mcpServers\";\n if (!config[serversKey]) config[serversKey] = {};\n\n // Don't overwrite an existing productbrain entry\n if (config[serversKey][SERVER_ENTRY_KEY]) {\n return false;\n }\n\n config[serversKey][SERVER_ENTRY_KEY] = buildServerEntry(apiKey);\n\n const dir = dirname(client.configPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(client.configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n return true;\n}\n"],"mappings":";;;;;;AASA,SAAS,uBAAuB;;;ACIhC,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,MAAM,eAAe;AAC9B,SAAS,SAAS,gBAAgB;AAOlC,IAAM,mBAAmB;AAEzB,SAAS,iBAAiB,QAAgB;AACxC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAM,CAAC,MAAM,mBAAmB;AAAA,IAChC,KAAK,EAAE,sBAAsB,OAAO;AAAA,EACtC;AACF;AAIA,SAAS,sBAA8B;AACrC,SAAO,KAAK,QAAQ,IAAI,GAAG,WAAW,UAAU;AAClD;AAEA,SAAS,6BAA4C;AACnD,QAAM,KAAK,SAAS;AACpB,MAAI,OAAO,UAAU;AACnB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,SAAS;AAClB,UAAM,UAAU,QAAQ,IAAI,WAAW,KAAK,QAAQ,GAAG,WAAW,SAAS;AAC3E,WAAO,KAAK,SAAS,UAAU,4BAA4B;AAAA,EAC7D;AAEA,SAAO;AACT;AAEA,SAAS,kBAA2B;AAClC,SAAO,WAAW,KAAK,QAAQ,IAAI,GAAG,SAAS,CAAC,KAAK,WAAW,KAAK,QAAQ,IAAI,GAAG,eAAe,CAAC;AACtG;AAEA,SAAS,2BAAoC;AAC3C,QAAM,aAAa,2BAA2B;AAC9C,MAAI,CAAC,WAAY,QAAO;AAExB,SAAO,WAAW,QAAQ,UAAU,CAAC;AACvC;AAEO,SAAS,gBAAiC;AAC/C,QAAM,UAA2B,CAAC;AAElC,MAAI,gBAAgB,GAAG;AACrB,YAAQ,KAAK,EAAE,MAAM,UAAU,YAAY,oBAAoB,EAAE,CAAC;AAAA,EACpE;AAEA,MAAI,yBAAyB,GAAG;AAC9B,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,YAAY,2BAA2B;AAAA,IACzC,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAIA,SAAS,aAAa,MAAmC;AACvD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAMA,eAAsB,kBACpB,QACA,QACkB;AAClB,QAAM,SAAS,aAAa,OAAO,UAAU;AAE7C,QAAM,aAAa;AACnB,MAAI,CAAC,OAAO,UAAU,EAAG,QAAO,UAAU,IAAI,CAAC;AAG/C,MAAI,OAAO,UAAU,EAAE,gBAAgB,GAAG;AACxC,WAAO;AAAA,EACT;AAEA,SAAO,UAAU,EAAE,gBAAgB,IAAI,iBAAiB,MAAM;AAE9D,QAAM,MAAM,QAAQ,OAAO,UAAU;AACrC,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACpC;AAEA,gBAAc,OAAO,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO;AAChF,SAAO;AACT;;;AD/GA,IAAM,oBACJ,QAAQ,IAAI,qBAAqB,QAAQ,IAAI,wBAAwB;AAIvE,SAAS,KAAK,GAAW;AACvB,SAAO,UAAU,CAAC;AACpB;AACA,SAAS,MAAM,GAAW;AACxB,SAAO,WAAW,CAAC;AACrB;AACA,SAAS,IAAI,GAAW;AACtB,SAAO,UAAU,CAAC;AACpB;AACA,SAAS,OAAO,GAAW;AACzB,SAAO,WAAW,CAAC;AACrB;AAEA,SAAS,IAAI,KAAa;AACxB,UAAQ,OAAO,MAAM,GAAG,GAAG;AAAA,CAAI;AACjC;AAEA,SAAS,YAAY,KAAa;AAChC,QAAM,EAAE,SAAS,IAAI,UAAQ,eAAoB;AACjD,QAAMA,YAAW,QAAQ;AACzB,MAAI;AACF,QAAIA,cAAa,SAAU,UAAS,SAAS,GAAG,GAAG;AAAA,aAC1CA,cAAa,QAAS,UAAS,aAAa,GAAG,GAAG;AAAA,QACtD,UAAS,aAAa,GAAG,GAAG;AAAA,EACnC,QAAQ;AACN,QAAI,IAAI,yCAAyC,CAAC;AAClD,QAAI,6BAA6B,GAAG,EAAE;AAAA,EACxC;AACF;AAEA,SAAS,OAAO,UAAmC;AACjD,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,OAAG,SAAS,UAAU,CAAC,WAAW;AAChC,SAAG,MAAM;AACT,cAAQ,OAAO,KAAK,CAAC;AAAA,IACvB,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,aAAa,UAAkB,SAAoC;AAC1E,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,QAAI,EAAE;AACN,QAAI,KAAK,QAAQ,CAAC;AAClB,YAAQ,QAAQ,CAAC,GAAG,MAAM,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;AACjD,UAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,OAAG,SAAS;AAAA,IAAO,IAAI,aAAa,CAAC,KAAK,CAAC,SAAS;AAClD,SAAG,MAAM;AACT,YAAM,IAAI,SAAS,KAAK,KAAK,GAAG,EAAE;AAClC,UAAI,MAAM,CAAC,KAAK,IAAI,KAAK,IAAI,QAAQ,QAAQ;AAC3C,gBAAQ,CAAC;AAAA,MACX,OAAO;AACL,gBAAQ,IAAI,CAAC;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAIA,eAAsB,WAAW;AAC/B,MAAI,EAAE;AACN,MAAI,KAAK,YAAY,OAAO,OAAO,CAAC,QAAQ,CAAC;AAC7C,MAAI,IAAI,sDAAsD,CAAC;AAE/D,QAAM,aAAa,GAAG,iBAAiB;AAEvC,MAAI,KAAK,IAAI,oCAAoC,CAAC,EAAE;AACpD,MAAI,QAAQ,IAAI,UAAU,CAAC;AAAA,CAAI;AAE/B,QAAM,UAAU,MAAM,OAAO,0CAA0C;AACvE,MAAI,QAAQ,YAAY,MAAM,OAAO,QAAQ,YAAY,MAAM,MAAM;AACnE,gBAAY,UAAU;AAAA,EACxB;AAEA,MAAI,EAAE;AACN,MAAI,KAAK,IAAI,4DAA4D,CAAC,EAAE;AAE5E,QAAM,SAAS,MAAM,OAAO,oCAAoC;AAEhE,MAAI,CAAC,UAAU,CAAC,OAAO,WAAW,QAAQ,GAAG;AAC3C,QAAI,KAAK,OAAO,GAAG,CAAC,8CAA8C;AAClE,QAAI,gBAAgB,UAAU;AAAA,CAAI;AAClC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,KAAK,MAAM,QAAG,CAAC;AAAA,CAAiB;AAGpC,QAAM,UAAU,cAAc;AAE9B,MAAI,QAAQ,WAAW,GAAG;AACxB,QAAI,KAAK,wCAAwC,CAAC;AAClD,uBAAmB,MAAM;AACzB;AAAA,EACF;AAEA,QAAM,cAAc,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AAC7C,QAAM,YACJ,QAAQ,SAAS,IACb,CAAC,GAAG,aAAa,oBAAoB,yBAAyB,IAC9D,CAAC,GAAG,aAAa,yBAAyB;AAEhD,QAAM,SAAS,MAAM,aAAa,0CAA0C,SAAS;AAErF,MAAI,WAAW,UAAU,SAAS,GAAG;AACnC,uBAAmB,MAAM;AAAA,EAC3B,WAAW,QAAQ,SAAS,KAAK,WAAW,UAAU,SAAS,GAAG;AAChE,eAAW,UAAU,SAAS;AAC5B,YAAM,YAAY,QAAQ,MAAM;AAAA,IAClC;AAAA,EACF,OAAO;AACL,UAAM,YAAY,QAAQ,MAAM,GAAG,MAAM;AAAA,EAC3C;AAEA,MAAI,EAAE;AACN;AAAA,IACE,KAAK,MAAM,QAAG,CAAC,6CAA6C,KAAK,uBAAuB,CAAC;AAAA,EAC3F;AACA,MAAI,EAAE;AACR;AAEA,eAAe,YAAY,QAAuB,QAAgB;AAChE,MAAI;AACF,UAAM,QAAQ,MAAM,kBAAkB,QAAQ,MAAM;AACpD,QAAI,OAAO;AACT,UAAI,KAAK,MAAM,QAAG,CAAC,oBAAoB,IAAI,OAAO,UAAU,CAAC,EAAE;AAAA,IACjE,OAAO;AACL,UAAI,KAAK,IAAI,QAAG,CAAC,IAAI,OAAO,IAAI,oCAA+B;AAAA,IACjE;AAAA,EACF,SAAS,KAAU;AACjB,QAAI,KAAK,OAAO,GAAG,CAAC,oBAAoB,OAAO,IAAI,YAAY,IAAI,OAAO,EAAE;AAC5E,uBAAmB,MAAM;AAAA,EAC3B;AACF;AAEA,SAAS,mBAAmB,QAAgB;AAC1C,MAAI,EAAE;AACN,MAAI,KAAK,yCAAyC,CAAC;AACnD,QAAM,UAAU,KAAK;AAAA,IACnB;AAAA,MACE,YAAY;AAAA,QACV,cAAc;AAAA,UACZ,SAAS;AAAA,UACT,MAAM,CAAC,MAAM,mBAAmB;AAAA,UAChC,KAAK,EAAE,sBAAsB,OAAO;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,aAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,QAAI,OAAO,IAAI,EAAE;AAAA,EACnB;AACA,MAAI,EAAE;AACR;","names":["platform"]}
|