spm-mcp 0.1.0 → 0.2.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 +57 -40
- package/dist/src/client/cloud-functions.js +1 -0
- package/dist/src/client/spm-api.js +1 -0
- package/dist/src/config.d.ts +6 -3
- package/dist/src/config.js +33 -12
- package/dist/src/stdio.d.ts +5 -12
- package/dist/src/stdio.js +99 -15
- package/package.json +12 -2
- package/spm-mcp-0.1.0.tgz +0 -0
- package/src/client/cloud-functions.ts +1 -0
- package/src/client/spm-api.ts +1 -0
- package/src/config.ts +40 -11
- package/src/stdio.ts +109 -15
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# spm-mcp
|
|
2
2
|
|
|
3
|
-
**Cursor for product management
|
|
3
|
+
**Cursor for product management.** Expert document review as MCP tools.
|
|
4
4
|
|
|
5
|
-
AI made engineering 10x faster. But deciding *what* to build didn't get faster. SPM fixes the input
|
|
5
|
+
AI made engineering 10x faster. But deciding *what* to build didn't get faster. SPM fixes the input with 30 domain-specific expert reviews that score your product documents against the standards senior PMs actually use.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx spm-mcp
|
|
@@ -10,59 +10,74 @@ npx spm-mcp
|
|
|
10
10
|
|
|
11
11
|
## The problem
|
|
12
12
|
|
|
13
|
-
AI agents build what you specify
|
|
13
|
+
AI agents build what you specify, including every blind spot. A vague spec used to waste a sprint. Now it ships to production in hours. SPM catches the blind spots before they become shipped code.
|
|
14
14
|
|
|
15
15
|
## What SPM reviews
|
|
16
16
|
|
|
17
17
|
Not just PRDs. Every document in the PM lifecycle:
|
|
18
18
|
|
|
19
|
-
- **Strategy
|
|
20
|
-
- **Analysis
|
|
21
|
-
- **Execution
|
|
22
|
-
- **Discovery
|
|
19
|
+
- **Strategy:** Product Roadmap, Growth Strategy, Go-to-Market, OKR Planning, Product Vision
|
|
20
|
+
- **Analysis:** Competitive Analysis, Market Research, Product Metrics, Stakeholder Management
|
|
21
|
+
- **Execution:** User Stories, Feature Spec, Sprint Planning, PRD to Jira, Release Notes, Test Cases
|
|
22
|
+
- **Discovery:** Problem Statement, Persona, Jobs-to-be-Done, Opportunity Assessment
|
|
23
23
|
|
|
24
24
|
30 built-in expert reviews. Or create your own with `spm_create_custom_nano_app`.
|
|
25
25
|
|
|
26
26
|
## Quick start
|
|
27
27
|
|
|
28
|
-
###
|
|
29
|
-
|
|
30
|
-
Sign in at [superproductmanager.ai](https://superproductmanager.ai) → Profile → Generate API Key
|
|
31
|
-
|
|
32
|
-
### 2. Set it
|
|
28
|
+
### Option A: Guided setup (recommended)
|
|
33
29
|
|
|
34
30
|
```bash
|
|
35
|
-
|
|
31
|
+
npx spm-mcp
|
|
36
32
|
```
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
First run auto-detects no API key and walks you through setup:
|
|
35
|
+
1. Opens your browser to sign in
|
|
36
|
+
2. You generate an API key on your Profile page
|
|
37
|
+
3. Paste it back in the terminal
|
|
38
|
+
4. Key saved to `~/.spm/config.json`. Done.
|
|
39
|
+
|
|
40
|
+
### Option B: Manual setup
|
|
39
41
|
|
|
40
42
|
```bash
|
|
43
|
+
# 1. Get your key at superproductmanager.ai > Profile > Generate API Key
|
|
44
|
+
# 2. Set it
|
|
45
|
+
export SPM_API_KEY=spm_k_your_key_here
|
|
46
|
+
# 3. Run
|
|
41
47
|
npx spm-mcp
|
|
42
48
|
```
|
|
43
49
|
|
|
50
|
+
### Option C: claude.ai (remote MCP)
|
|
51
|
+
|
|
52
|
+
For claude.ai web users, SPM is available as a remote MCP server:
|
|
53
|
+
|
|
54
|
+
1. Get your API key at [superproductmanager.ai](https://superproductmanager.ai) > Profile
|
|
55
|
+
2. In claude.ai, go to Settings > Integrations > Add MCP Server
|
|
56
|
+
3. URL: `https://spm-mcp.superproductmanager.ai/mcp`
|
|
57
|
+
4. Add header: `X-SPM-API-Key: spm_k_your_key_here`
|
|
58
|
+
|
|
44
59
|
## Tools
|
|
45
60
|
|
|
46
61
|
| Tool | What it does |
|
|
47
62
|
|------|-------------|
|
|
48
63
|
| `spm_list_nano_apps` | Discover available expert reviews (30 built-in + your custom ones) |
|
|
49
|
-
| `spm_analyze` | Score a document against domain-specific expert expectations. Every gap scored 0
|
|
64
|
+
| `spm_analyze` | Score a document against domain-specific expert expectations. Every gap scored 0-1.0 with evidence. |
|
|
50
65
|
| `spm_clarify` | Get decision-forcing questions for the weakest gaps. Questions escalate when you give vague answers. |
|
|
51
66
|
| `spm_evaluate` | Re-score gaps after clarification rounds. Tracks progress. Use after every 3 rounds of `spm_clarify`. |
|
|
52
|
-
| `spm_improve` | Generate paste-ready improvements grounded in your answers
|
|
67
|
+
| `spm_improve` | Generate paste-ready improvements grounded in your answers, not AI hallucination. |
|
|
53
68
|
| `spm_create_custom_nano_app` | Create a custom expert review for any document type not covered by the built-in 30. |
|
|
54
69
|
|
|
55
70
|
## How it works
|
|
56
71
|
|
|
57
72
|
```
|
|
58
73
|
Your document
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
> spm_analyze Scores every gap 0-1.0 against expert expectations
|
|
75
|
+
> spm_clarify Asks the questions your stakeholder would ask
|
|
76
|
+
> spm_evaluate Re-scores. Did the gap close?
|
|
77
|
+
> spm_improve Generates improvements from YOUR answers
|
|
63
78
|
```
|
|
64
79
|
|
|
65
|
-
The clarification questions are the product. They surface the assumptions you've been carrying without examining. When you dodge, they escalate
|
|
80
|
+
The clarification questions are the product. They surface the assumptions you've been carrying without examining. When you dodge, they escalate: evidence first, then action directives, then assumptions made on your behalf. Like a principal PM review, not a chatbot.
|
|
66
81
|
|
|
67
82
|
## Example workflow
|
|
68
83
|
|
|
@@ -73,8 +88,8 @@ SPM: 4 expectations scored. Problem Definition: 43/100. Success Metrics: 15/1
|
|
|
73
88
|
You: Clarify the weakest gap
|
|
74
89
|
SPM: "Is operator churn or player registration drop-off your primary counter-metric?"
|
|
75
90
|
|
|
76
|
-
You: Operator churn
|
|
77
|
-
SPM: [re-evaluates] Counter-metric: 0
|
|
91
|
+
You: Operator churn. If >20% revert to WhatsApp, we've failed.
|
|
92
|
+
SPM: [re-evaluates] Counter-metric: 0 > 0.9. Now targeting: Instrumentation plan.
|
|
78
93
|
|
|
79
94
|
You: Improve the success metrics section
|
|
80
95
|
SPM: [generates paste-ready content grounded in your "operator churn" decision]
|
|
@@ -86,17 +101,18 @@ Average documents improve from 35% to 82% in three rounds.
|
|
|
86
101
|
|
|
87
102
|
### Claude Code
|
|
88
103
|
|
|
89
|
-
|
|
104
|
+
```bash
|
|
105
|
+
claude mcp add spm -- npx spm-mcp
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Or add to `.claude/settings.json`. If you ran `npx spm-mcp` setup already, the key is in `~/.spm/config.json` and no env var is needed:
|
|
90
109
|
|
|
91
110
|
```json
|
|
92
111
|
{
|
|
93
112
|
"mcpServers": {
|
|
94
113
|
"spm": {
|
|
95
114
|
"command": "npx",
|
|
96
|
-
"args": ["spm-mcp"]
|
|
97
|
-
"env": {
|
|
98
|
-
"SPM_API_KEY": "spm_k_your_key_here"
|
|
99
|
-
}
|
|
115
|
+
"args": ["spm-mcp"]
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
}
|
|
@@ -126,30 +142,31 @@ Add to MCP configuration with the same command and env structure.
|
|
|
126
142
|
|
|
127
143
|
| Variable | Required | Default | Description |
|
|
128
144
|
|----------|----------|---------|-------------|
|
|
129
|
-
| `SPM_API_KEY` |
|
|
130
|
-
| `SPM_SUPABASE_URL` | No | Production | Override for
|
|
131
|
-
| `SPM_SUPABASE_ANON_KEY` | No | Production | Override for
|
|
145
|
+
| `SPM_API_KEY` | Only if no `~/.spm/config.json` | n/a | Your API key. Run `npx spm-mcp` for guided setup. |
|
|
146
|
+
| `SPM_SUPABASE_URL` | No | Production | Override for development |
|
|
147
|
+
| `SPM_SUPABASE_ANON_KEY` | No | Production | Override for development |
|
|
132
148
|
| `SPM_CLOUD_FUNCTIONS_BASE` | No | Production | Override Cloud Functions URL |
|
|
133
149
|
|
|
150
|
+
API key resolution order: `SPM_API_KEY` env var > `~/.spm/config.json` > setup prompt.
|
|
151
|
+
|
|
134
152
|
## Security
|
|
135
153
|
|
|
136
|
-
- **No filesystem access
|
|
137
|
-
- **No postinstall scripts
|
|
138
|
-
- **
|
|
139
|
-
- **Network calls only when tools are invoked
|
|
140
|
-
- **Source included
|
|
141
|
-
- **Zero known vulnerabilities
|
|
154
|
+
- **No filesystem access.** SPM never reads your local files, SSH keys, or credentials.
|
|
155
|
+
- **No postinstall scripts.** Nothing runs on `npm install`.
|
|
156
|
+
- **Minimal env access.** Reads 4 optional env vars and `~/.spm/config.json`. No broad `process.env` access.
|
|
157
|
+
- **Network calls only when tools are invoked.** Not on import, not on install.
|
|
158
|
+
- **Source included.** TypeScript source ships alongside compiled JS for auditability.
|
|
159
|
+
- **Zero known vulnerabilities.** `npm audit` clean.
|
|
142
160
|
|
|
143
161
|
## Also available as
|
|
144
162
|
|
|
145
|
-
- **Chrome extension
|
|
146
|
-
- **Web app
|
|
163
|
+
- **Chrome extension.** Reviews documents inside Google Docs, Notion, ClickUp, Linear. [Install from Chrome Web Store](https://chromewebstore.google.com/detail/super-product-manager/ocpjfedoogmpbkhpojdkfdimkbiamihg)
|
|
164
|
+
- **Web app.** Paste any document, full analysis in 30 seconds. [superproductmanager.ai](https://superproductmanager.ai)
|
|
147
165
|
|
|
148
166
|
## Links
|
|
149
167
|
|
|
150
168
|
- [Website](https://superproductmanager.ai)
|
|
151
169
|
- [Chrome Extension](https://chromewebstore.google.com/detail/super-product-manager/ocpjfedoogmpbkhpojdkfdimkbiamihg)
|
|
152
|
-
- [Y Combinator RFS: "Cursor for Product Management"](https://www.ycombinator.com/rfs)
|
|
153
170
|
|
|
154
171
|
## License
|
|
155
172
|
|
|
@@ -7,6 +7,7 @@ export async function callJsonEndpoint(endpoint, body = {}, apiKey) {
|
|
|
7
7
|
const url = `${config.cloudFunctionsBase}/${endpoint}`;
|
|
8
8
|
const headers = {
|
|
9
9
|
'Content-Type': 'application/json',
|
|
10
|
+
'Origin': 'https://mcp.superproductmanager.ai',
|
|
10
11
|
};
|
|
11
12
|
if (apiKey) {
|
|
12
13
|
headers['X-SPM-API-Key'] = apiKey;
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SPM MCP Server configuration.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Only SPM_API_KEY is required from the user.
|
|
5
|
+
* Supabase values are public (protected by RLS) and default to production.
|
|
6
|
+
* API key is the security boundary: user-specific, hashed, revocable.
|
|
4
7
|
*/
|
|
5
8
|
export declare const config: {
|
|
6
|
-
/** Supabase project URL */
|
|
9
|
+
/** Supabase project URL (public, RLS-protected) */
|
|
7
10
|
readonly supabaseUrl: string;
|
|
8
|
-
/** Supabase anon key */
|
|
11
|
+
/** Supabase anon key (public, RLS-protected) */
|
|
9
12
|
readonly supabaseAnonKey: string;
|
|
10
13
|
/** Base URL for Cloud Functions */
|
|
11
14
|
readonly cloudFunctionsBase: string;
|
package/dist/src/config.js
CHANGED
|
@@ -1,22 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SPM MCP Server configuration.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Only SPM_API_KEY is required from the user.
|
|
5
|
+
* Supabase values are public (protected by RLS) and default to production.
|
|
6
|
+
* API key is the security boundary: user-specific, hashed, revocable.
|
|
4
7
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
// ─── Public defaults (same values shipped in client-side bundle) ────
|
|
12
|
+
const DEFAULT_SUPABASE_URL = 'https://zrqngfkafphexqrteukm.supabase.co';
|
|
13
|
+
const DEFAULT_SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpycW5nZmthZnBoZXhxcnRldWttIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTYyNjc2MTIsImV4cCI6MjAzMTg0MzYxMn0.YNeVJnNwoTRKPRUi3JT0JQXq8XGXZ1GI6q1sqL9l0b4';
|
|
14
|
+
const DEFAULT_CLOUD_FUNCTIONS_BASE = 'https://us-central1-litethink.cloudfunctions.net';
|
|
15
|
+
// ─── Config file persistence (~/.spm/config.json) ───────────────────
|
|
16
|
+
function loadConfigFile() {
|
|
17
|
+
try {
|
|
18
|
+
const configPath = join(homedir(), '.spm', 'config.json');
|
|
19
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const savedConfig = loadConfigFile();
|
|
27
|
+
// ─── Resolve API key: env var > config file ─────────────────────────
|
|
28
|
+
function resolveApiKey() {
|
|
29
|
+
const key = process.env.SPM_API_KEY || savedConfig.apiKey || '';
|
|
30
|
+
return key;
|
|
10
31
|
}
|
|
11
32
|
export const config = {
|
|
12
|
-
/** Supabase project URL */
|
|
13
|
-
supabaseUrl:
|
|
14
|
-
/** Supabase anon key */
|
|
15
|
-
supabaseAnonKey:
|
|
33
|
+
/** Supabase project URL (public, RLS-protected) */
|
|
34
|
+
supabaseUrl: process.env.SPM_SUPABASE_URL || DEFAULT_SUPABASE_URL,
|
|
35
|
+
/** Supabase anon key (public, RLS-protected) */
|
|
36
|
+
supabaseAnonKey: process.env.SPM_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY,
|
|
16
37
|
/** Base URL for Cloud Functions */
|
|
17
|
-
cloudFunctionsBase: process.env.SPM_CLOUD_FUNCTIONS_BASE ||
|
|
38
|
+
cloudFunctionsBase: process.env.SPM_CLOUD_FUNCTIONS_BASE || DEFAULT_CLOUD_FUNCTIONS_BASE,
|
|
18
39
|
/** User's SPM API key for authenticating with Cloud Functions */
|
|
19
|
-
apiKey:
|
|
40
|
+
apiKey: resolveApiKey(),
|
|
20
41
|
/** Port for hosted HTTP transport */
|
|
21
42
|
port: parseInt(process.env.PORT || '8080', 10),
|
|
22
43
|
};
|
package/dist/src/stdio.d.ts
CHANGED
|
@@ -2,18 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Stdio transport entry point for the SPM MCP Server.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Two modes:
|
|
6
|
+
* 1. Setup (interactive): `npx spm-mcp --setup` or auto-detected on first run
|
|
7
|
+
* Opens browser for login, prompts for API key, saves to ~/.spm/config.json
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* "mcpServers": {
|
|
11
|
-
* "spm": {
|
|
12
|
-
* "command": "node",
|
|
13
|
-
* "args": ["/path/to/packages/mcp-server/dist/src/stdio.js"],
|
|
14
|
-
* "env": { "SPM_API_KEY": "spm_k_..." }
|
|
15
|
-
* }
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
9
|
+
* 2. Server (non-interactive): started by Claude Code / Cursor as MCP subprocess
|
|
10
|
+
* Reads API key from ~/.spm/config.json or SPM_API_KEY env var
|
|
18
11
|
*/
|
|
19
12
|
export {};
|
package/dist/src/stdio.js
CHANGED
|
@@ -2,23 +2,107 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Stdio transport entry point for the SPM MCP Server.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Two modes:
|
|
6
|
+
* 1. Setup (interactive): `npx spm-mcp --setup` or auto-detected on first run
|
|
7
|
+
* Opens browser for login, prompts for API key, saves to ~/.spm/config.json
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* "mcpServers": {
|
|
11
|
-
* "spm": {
|
|
12
|
-
* "command": "node",
|
|
13
|
-
* "args": ["/path/to/packages/mcp-server/dist/src/stdio.js"],
|
|
14
|
-
* "env": { "SPM_API_KEY": "spm_k_..." }
|
|
15
|
-
* }
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
9
|
+
* 2. Server (non-interactive): started by Claude Code / Cursor as MCP subprocess
|
|
10
|
+
* Reads API key from ~/.spm/config.json or SPM_API_KEY env var
|
|
18
11
|
*/
|
|
19
12
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
20
13
|
import { createSpmMcpServer } from './index.js';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
import { config } from './config.js';
|
|
15
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
import { homedir } from 'os';
|
|
18
|
+
import { createInterface } from 'readline';
|
|
19
|
+
const CONFIG_DIR = join(homedir(), '.spm');
|
|
20
|
+
const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
|
|
21
|
+
const SETUP_URL = 'https://superproductmanager.web.app/#/profile';
|
|
22
|
+
function isInteractive() {
|
|
23
|
+
return process.stdin.isTTY === true;
|
|
24
|
+
}
|
|
25
|
+
function hasApiKey() {
|
|
26
|
+
return config.apiKey !== '' && config.apiKey.startsWith('spm_k_');
|
|
27
|
+
}
|
|
28
|
+
async function prompt(question) {
|
|
29
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
rl.question(question, (answer) => {
|
|
32
|
+
rl.close();
|
|
33
|
+
resolve(answer.trim());
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function saveApiKey(key) {
|
|
38
|
+
let existing = {};
|
|
39
|
+
try {
|
|
40
|
+
existing = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch { /* no existing config */ }
|
|
43
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
44
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
writeFileSync(CONFIG_PATH, JSON.stringify({ ...existing, apiKey: key }, null, 2) + '\n');
|
|
47
|
+
}
|
|
48
|
+
async function runSetup() {
|
|
49
|
+
const log = (msg) => process.stderr.write(msg + '\n');
|
|
50
|
+
log('');
|
|
51
|
+
log(' Super Product Manager (SPM) MCP Server');
|
|
52
|
+
log(' ───────────────────────────────────────');
|
|
53
|
+
log('');
|
|
54
|
+
log(' No API key found. Let\'s set you up.');
|
|
55
|
+
log('');
|
|
56
|
+
log(` 1. Sign in at: ${SETUP_URL}`);
|
|
57
|
+
log(' 2. Go to Profile > Generate API Key');
|
|
58
|
+
log(' 3. Copy the key (starts with spm_k_)');
|
|
59
|
+
log('');
|
|
60
|
+
// Try to open browser
|
|
61
|
+
try {
|
|
62
|
+
const { exec } = await import('child_process');
|
|
63
|
+
const cmd = process.platform === 'darwin' ? 'open' :
|
|
64
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
65
|
+
exec(`${cmd} "${SETUP_URL}"`);
|
|
66
|
+
log(' Browser opened. Sign in and generate your key.');
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
log(` Open this URL in your browser: ${SETUP_URL}`);
|
|
70
|
+
}
|
|
71
|
+
log('');
|
|
72
|
+
const key = await prompt(' Paste your API key here: ');
|
|
73
|
+
if (!key.startsWith('spm_k_')) {
|
|
74
|
+
log('');
|
|
75
|
+
log(' Invalid key. API keys start with spm_k_');
|
|
76
|
+
log(' Run `npx spm-mcp --setup` to try again.');
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
saveApiKey(key);
|
|
80
|
+
log('');
|
|
81
|
+
log(` Key saved to ${CONFIG_PATH}`);
|
|
82
|
+
log('');
|
|
83
|
+
log(' You\'re all set! Add SPM to your AI tool:');
|
|
84
|
+
log('');
|
|
85
|
+
log(' Claude Code: claude mcp add spm -- npx spm-mcp');
|
|
86
|
+
log(' Cursor: Add to .cursor/mcp.json');
|
|
87
|
+
log('');
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
// ─── Main ───────────────────────────────────────────────────────────
|
|
91
|
+
const wantsSetup = process.argv.includes('--setup');
|
|
92
|
+
if (wantsSetup || (isInteractive() && !hasApiKey())) {
|
|
93
|
+
// Interactive setup mode
|
|
94
|
+
const ok = await runSetup();
|
|
95
|
+
process.exit(ok ? 0 : 1);
|
|
96
|
+
}
|
|
97
|
+
else if (!hasApiKey() && isInteractive()) {
|
|
98
|
+
// No key and interactive — guide the user
|
|
99
|
+
process.stderr.write('\n No SPM_API_KEY found. Run: npx spm-mcp --setup\n\n');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// MCP server mode (non-interactive, started by AI tool)
|
|
104
|
+
const server = createSpmMcpServer({ apiKey: config.apiKey });
|
|
105
|
+
const transport = new StdioServerTransport();
|
|
106
|
+
await server.connect(transport);
|
|
107
|
+
}
|
|
24
108
|
//# sourceMappingURL=stdio.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spm-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Super Product Manager MCP Server
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Super Product Manager MCP Server - AI-powered product document analysis for PRDs, roadmaps, and 30 PM document types",
|
|
5
|
+
"author": "Super Product Manager <chiranjeevi.gunturi@superproductmanager.ai>",
|
|
6
|
+
"homepage": "https://superproductmanager.ai",
|
|
5
7
|
"type": "module",
|
|
6
8
|
"main": "dist/index.js",
|
|
7
9
|
"bin": {
|
|
@@ -23,6 +25,14 @@
|
|
|
23
25
|
"@types/node": "^22.10.0",
|
|
24
26
|
"typescript": "~5.7.0"
|
|
25
27
|
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"mcp-server",
|
|
31
|
+
"model-context-protocol",
|
|
32
|
+
"product-management",
|
|
33
|
+
"document-review"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
26
36
|
"engines": {
|
|
27
37
|
"node": ">=20"
|
|
28
38
|
}
|
package/spm-mcp-0.1.0.tgz
CHANGED
|
Binary file
|
|
@@ -13,6 +13,7 @@ export async function callJsonEndpoint(
|
|
|
13
13
|
const url = `${config.cloudFunctionsBase}/${endpoint}`;
|
|
14
14
|
const headers: Record<string, string> = {
|
|
15
15
|
'Content-Type': 'application/json',
|
|
16
|
+
'Origin': 'https://mcp.superproductmanager.ai',
|
|
16
17
|
};
|
|
17
18
|
if (apiKey) {
|
|
18
19
|
headers['X-SPM-API-Key'] = apiKey;
|
package/src/client/spm-api.ts
CHANGED
|
@@ -24,6 +24,7 @@ function getHeaders(apiKey?: string): Record<string, string> {
|
|
|
24
24
|
const headers: Record<string, string> = {
|
|
25
25
|
'Content-Type': 'application/json',
|
|
26
26
|
'Accept': 'text/event-stream, application/json',
|
|
27
|
+
'Origin': 'https://mcp.superproductmanager.ai',
|
|
27
28
|
};
|
|
28
29
|
if (key) {
|
|
29
30
|
headers['X-SPM-API-Key'] = key;
|
package/src/config.ts
CHANGED
|
@@ -1,26 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SPM MCP Server configuration.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Only SPM_API_KEY is required from the user.
|
|
5
|
+
* Supabase values are public (protected by RLS) and default to production.
|
|
6
|
+
* API key is the security boundary: user-specific, hashed, revocable.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
|
|
13
|
+
// ─── Public defaults (same values shipped in client-side bundle) ────
|
|
14
|
+
|
|
15
|
+
const DEFAULT_SUPABASE_URL = 'https://zrqngfkafphexqrteukm.supabase.co';
|
|
16
|
+
const DEFAULT_SUPABASE_ANON_KEY =
|
|
17
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpycW5nZmthZnBoZXhxcnRldWttIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTYyNjc2MTIsImV4cCI6MjAzMTg0MzYxMn0.YNeVJnNwoTRKPRUi3JT0JQXq8XGXZ1GI6q1sqL9l0b4';
|
|
18
|
+
const DEFAULT_CLOUD_FUNCTIONS_BASE = 'https://us-central1-litethink.cloudfunctions.net';
|
|
19
|
+
|
|
20
|
+
// ─── Config file persistence (~/.spm/config.json) ───────────────────
|
|
21
|
+
|
|
22
|
+
function loadConfigFile(): Record<string, string> {
|
|
23
|
+
try {
|
|
24
|
+
const configPath = join(homedir(), '.spm', 'config.json');
|
|
25
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
26
|
+
return JSON.parse(raw);
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const savedConfig = loadConfigFile();
|
|
33
|
+
|
|
34
|
+
// ─── Resolve API key: env var > config file ─────────────────────────
|
|
35
|
+
|
|
36
|
+
function resolveApiKey(): string {
|
|
37
|
+
const key = process.env.SPM_API_KEY || savedConfig.apiKey || '';
|
|
38
|
+
return key;
|
|
10
39
|
}
|
|
11
40
|
|
|
12
41
|
export const config = {
|
|
13
|
-
/** Supabase project URL */
|
|
14
|
-
supabaseUrl:
|
|
42
|
+
/** Supabase project URL (public, RLS-protected) */
|
|
43
|
+
supabaseUrl: process.env.SPM_SUPABASE_URL || DEFAULT_SUPABASE_URL,
|
|
15
44
|
|
|
16
|
-
/** Supabase anon key */
|
|
17
|
-
supabaseAnonKey:
|
|
45
|
+
/** Supabase anon key (public, RLS-protected) */
|
|
46
|
+
supabaseAnonKey: process.env.SPM_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY,
|
|
18
47
|
|
|
19
48
|
/** Base URL for Cloud Functions */
|
|
20
|
-
cloudFunctionsBase: process.env.SPM_CLOUD_FUNCTIONS_BASE ||
|
|
49
|
+
cloudFunctionsBase: process.env.SPM_CLOUD_FUNCTIONS_BASE || DEFAULT_CLOUD_FUNCTIONS_BASE,
|
|
21
50
|
|
|
22
51
|
/** User's SPM API key for authenticating with Cloud Functions */
|
|
23
|
-
apiKey:
|
|
52
|
+
apiKey: resolveApiKey(),
|
|
24
53
|
|
|
25
54
|
/** Port for hosted HTTP transport */
|
|
26
55
|
port: parseInt(process.env.PORT || '8080', 10),
|
package/src/stdio.ts
CHANGED
|
@@ -2,24 +2,118 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Stdio transport entry point for the SPM MCP Server.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Two modes:
|
|
6
|
+
* 1. Setup (interactive): `npx spm-mcp --setup` or auto-detected on first run
|
|
7
|
+
* Opens browser for login, prompts for API key, saves to ~/.spm/config.json
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* "mcpServers": {
|
|
11
|
-
* "spm": {
|
|
12
|
-
* "command": "node",
|
|
13
|
-
* "args": ["/path/to/packages/mcp-server/dist/src/stdio.js"],
|
|
14
|
-
* "env": { "SPM_API_KEY": "spm_k_..." }
|
|
15
|
-
* }
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
9
|
+
* 2. Server (non-interactive): started by Claude Code / Cursor as MCP subprocess
|
|
10
|
+
* Reads API key from ~/.spm/config.json or SPM_API_KEY env var
|
|
18
11
|
*/
|
|
19
12
|
|
|
20
13
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
21
14
|
import { createSpmMcpServer } from './index.js';
|
|
15
|
+
import { config } from './config.js';
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
17
|
+
import { join } from 'path';
|
|
18
|
+
import { homedir } from 'os';
|
|
19
|
+
import { createInterface } from 'readline';
|
|
22
20
|
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
21
|
+
const CONFIG_DIR = join(homedir(), '.spm');
|
|
22
|
+
const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
|
|
23
|
+
const SETUP_URL = 'https://superproductmanager.web.app/#/profile';
|
|
24
|
+
|
|
25
|
+
function isInteractive(): boolean {
|
|
26
|
+
return process.stdin.isTTY === true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function hasApiKey(): boolean {
|
|
30
|
+
return config.apiKey !== '' && config.apiKey.startsWith('spm_k_');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function prompt(question: string): Promise<string> {
|
|
34
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
rl.question(question, (answer) => {
|
|
37
|
+
rl.close();
|
|
38
|
+
resolve(answer.trim());
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function saveApiKey(key: string): void {
|
|
44
|
+
let existing: Record<string, string> = {};
|
|
45
|
+
try {
|
|
46
|
+
existing = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
|
|
47
|
+
} catch { /* no existing config */ }
|
|
48
|
+
|
|
49
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
50
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
writeFileSync(CONFIG_PATH, JSON.stringify({ ...existing, apiKey: key }, null, 2) + '\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runSetup(): Promise<boolean> {
|
|
56
|
+
const log = (msg: string) => process.stderr.write(msg + '\n');
|
|
57
|
+
|
|
58
|
+
log('');
|
|
59
|
+
log(' Super Product Manager (SPM) MCP Server');
|
|
60
|
+
log(' ───────────────────────────────────────');
|
|
61
|
+
log('');
|
|
62
|
+
log(' No API key found. Let\'s set you up.');
|
|
63
|
+
log('');
|
|
64
|
+
log(` 1. Sign in at: ${SETUP_URL}`);
|
|
65
|
+
log(' 2. Go to Profile > Generate API Key');
|
|
66
|
+
log(' 3. Copy the key (starts with spm_k_)');
|
|
67
|
+
log('');
|
|
68
|
+
|
|
69
|
+
// Try to open browser
|
|
70
|
+
try {
|
|
71
|
+
const { exec } = await import('child_process');
|
|
72
|
+
const cmd = process.platform === 'darwin' ? 'open' :
|
|
73
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
74
|
+
exec(`${cmd} "${SETUP_URL}"`);
|
|
75
|
+
log(' Browser opened. Sign in and generate your key.');
|
|
76
|
+
} catch {
|
|
77
|
+
log(` Open this URL in your browser: ${SETUP_URL}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
log('');
|
|
81
|
+
const key = await prompt(' Paste your API key here: ');
|
|
82
|
+
|
|
83
|
+
if (!key.startsWith('spm_k_')) {
|
|
84
|
+
log('');
|
|
85
|
+
log(' Invalid key. API keys start with spm_k_');
|
|
86
|
+
log(' Run `npx spm-mcp --setup` to try again.');
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
saveApiKey(key);
|
|
91
|
+
log('');
|
|
92
|
+
log(` Key saved to ${CONFIG_PATH}`);
|
|
93
|
+
log('');
|
|
94
|
+
log(' You\'re all set! Add SPM to your AI tool:');
|
|
95
|
+
log('');
|
|
96
|
+
log(' Claude Code: claude mcp add spm -- npx spm-mcp');
|
|
97
|
+
log(' Cursor: Add to .cursor/mcp.json');
|
|
98
|
+
log('');
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── Main ───────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
const wantsSetup = process.argv.includes('--setup');
|
|
105
|
+
|
|
106
|
+
if (wantsSetup || (isInteractive() && !hasApiKey())) {
|
|
107
|
+
// Interactive setup mode
|
|
108
|
+
const ok = await runSetup();
|
|
109
|
+
process.exit(ok ? 0 : 1);
|
|
110
|
+
} else if (!hasApiKey() && isInteractive()) {
|
|
111
|
+
// No key and interactive — guide the user
|
|
112
|
+
process.stderr.write('\n No SPM_API_KEY found. Run: npx spm-mcp --setup\n\n');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
} else {
|
|
115
|
+
// MCP server mode (non-interactive, started by AI tool)
|
|
116
|
+
const server = createSpmMcpServer({ apiKey: config.apiKey });
|
|
117
|
+
const transport = new StdioServerTransport();
|
|
118
|
+
await server.connect(transport);
|
|
119
|
+
}
|