spm-mcp 0.1.0 → 0.3.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.d.ts +1 -0
- package/dist/src/client/cloud-functions.js +5 -0
- package/dist/src/client/spm-api.d.ts +1 -0
- package/dist/src/client/spm-api.js +5 -0
- package/dist/src/config.d.ts +6 -3
- package/dist/src/config.js +33 -12
- package/dist/src/index.js +4 -0
- package/dist/src/stdio.d.ts +5 -12
- package/dist/src/stdio.js +99 -15
- package/dist/src/tools/clarify.d.ts +10 -0
- package/dist/src/tools/clarify.js +14 -1
- package/dist/src/tools/evaluate.d.ts +2 -0
- package/dist/src/tools/evaluate.js +3 -1
- package/package.json +12 -2
- package/spm-mcp-0.1.0.tgz +0 -0
- package/src/client/cloud-functions.ts +6 -0
- package/src/client/spm-api.ts +6 -0
- package/src/config.ts +40 -11
- package/src/index.ts +12 -0
- package/src/stdio.ts +109 -15
- package/src/tools/clarify.ts +19 -0
- package/src/tools/evaluate.ts +6 -0
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
|
|
|
@@ -2,4 +2,5 @@
|
|
|
2
2
|
* Helper to call Cloud Functions that return plain JSON (not SSE).
|
|
3
3
|
* Used for nano app CRUD and discovery endpoints.
|
|
4
4
|
*/
|
|
5
|
+
export declare function setChannel(ch: string): void;
|
|
5
6
|
export declare function callJsonEndpoint(endpoint: string, body?: Record<string, unknown>, apiKey?: string): Promise<unknown>;
|
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
* Used for nano app CRUD and discovery endpoints.
|
|
4
4
|
*/
|
|
5
5
|
import { config } from '../config.js';
|
|
6
|
+
// Channel identifier — overridden by hosted/cloud-run.ts
|
|
7
|
+
let _channel = 'mcp-npm';
|
|
8
|
+
export function setChannel(ch) { _channel = ch; }
|
|
6
9
|
export async function callJsonEndpoint(endpoint, body = {}, apiKey) {
|
|
7
10
|
const url = `${config.cloudFunctionsBase}/${endpoint}`;
|
|
8
11
|
const headers = {
|
|
9
12
|
'Content-Type': 'application/json',
|
|
13
|
+
'Origin': 'https://mcp.superproductmanager.ai',
|
|
14
|
+
'X-SPM-Channel': _channel,
|
|
10
15
|
};
|
|
11
16
|
if (apiKey) {
|
|
12
17
|
headers['X-SPM-API-Key'] = apiKey;
|
|
@@ -13,6 +13,7 @@ declare const ENDPOINTS: {
|
|
|
13
13
|
readonly classifyIntent: "/classifyIntent";
|
|
14
14
|
};
|
|
15
15
|
type EndpointName = keyof typeof ENDPOINTS;
|
|
16
|
+
export declare function setChannel(ch: string): void;
|
|
16
17
|
/**
|
|
17
18
|
* Call an SPM Cloud Function endpoint.
|
|
18
19
|
* Handles SSE stream parsing automatically.
|
|
@@ -14,11 +14,16 @@ const ENDPOINTS = {
|
|
|
14
14
|
extensionReviewAgainAgentV3: '/extensionReviewAgainAgentV3',
|
|
15
15
|
classifyIntent: '/classifyIntent',
|
|
16
16
|
};
|
|
17
|
+
// Channel identifier — shared with cloud-functions.ts
|
|
18
|
+
let _channel = 'mcp-npm';
|
|
19
|
+
export function setChannel(ch) { _channel = ch; }
|
|
17
20
|
function getHeaders(apiKey) {
|
|
18
21
|
const key = apiKey || config.apiKey;
|
|
19
22
|
const headers = {
|
|
20
23
|
'Content-Type': 'application/json',
|
|
21
24
|
'Accept': 'text/event-stream, application/json',
|
|
25
|
+
'Origin': 'https://mcp.superproductmanager.ai',
|
|
26
|
+
'X-SPM-Channel': _channel,
|
|
22
27
|
};
|
|
23
28
|
if (key) {
|
|
24
29
|
headers['X-SPM-API-Key'] = key;
|
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/index.js
CHANGED
|
@@ -71,6 +71,8 @@ export function createSpmMcpServer(options) {
|
|
|
71
71
|
question: z.string(),
|
|
72
72
|
answer: z.string(),
|
|
73
73
|
})).optional().describe('Array of previous Q&A pairs from this clarification session. Empty for first question.'),
|
|
74
|
+
user_context: z.string().optional().describe('Context about the PM: role, experience level, company stage, research done, decisions made. Helps generate tailored questions.'),
|
|
75
|
+
about_company: z.string().optional().describe('Context about the company/product: stage, team size, market, existing users, business model.'),
|
|
74
76
|
}, async (input) => {
|
|
75
77
|
try {
|
|
76
78
|
const result = await handleClarify(input, apiKey);
|
|
@@ -95,6 +97,8 @@ export function createSpmMcpServer(options) {
|
|
|
95
97
|
question: z.string(),
|
|
96
98
|
answer: z.string(),
|
|
97
99
|
})).describe('All Q&A pairs accumulated from spm_clarify rounds. The evaluator uses these as evidence.'),
|
|
100
|
+
user_context: z.string().optional().describe('Context about the PM: role, experience level, company stage, research done, decisions made.'),
|
|
101
|
+
about_company: z.string().optional().describe('Context about the company/product: stage, team size, market, existing users, business model.'),
|
|
98
102
|
}, async (input) => {
|
|
99
103
|
try {
|
|
100
104
|
const result = await handleEvaluate(input, apiKey);
|
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
|
|
@@ -47,6 +47,14 @@ export declare const clarifyDefinition: {
|
|
|
47
47
|
};
|
|
48
48
|
description: string;
|
|
49
49
|
};
|
|
50
|
+
user_context: {
|
|
51
|
+
type: string;
|
|
52
|
+
description: string;
|
|
53
|
+
};
|
|
54
|
+
about_company: {
|
|
55
|
+
type: string;
|
|
56
|
+
description: string;
|
|
57
|
+
};
|
|
50
58
|
};
|
|
51
59
|
required: string[];
|
|
52
60
|
};
|
|
@@ -61,5 +69,7 @@ export interface ClarifyInput {
|
|
|
61
69
|
question: string;
|
|
62
70
|
answer: string;
|
|
63
71
|
}>;
|
|
72
|
+
user_context?: string;
|
|
73
|
+
about_company?: string;
|
|
64
74
|
}
|
|
65
75
|
export declare function handleClarify(input: ClarifyInput, apiKey?: string): Promise<unknown>;
|
|
@@ -52,12 +52,23 @@ export const clarifyDefinition = {
|
|
|
52
52
|
description: 'Array of previous Q&A pairs from this clarification session. ' +
|
|
53
53
|
'Pass as [{question, answer}, ...]. Empty array for first question.',
|
|
54
54
|
},
|
|
55
|
+
user_context: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Context about the PM: their role, experience level, company stage, ' +
|
|
58
|
+
'what research they have done, what decisions they have already made. ' +
|
|
59
|
+
'This helps generate questions tailored to the specific user rather than generic ones.',
|
|
60
|
+
},
|
|
61
|
+
about_company: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'Context about the company/product: stage, team size, market, ' +
|
|
64
|
+
'existing users, business model. Helps ground questions in reality.',
|
|
65
|
+
},
|
|
55
66
|
},
|
|
56
67
|
required: ['document', 'nano_app_id', 'main_expectation', 'all_sub_expectations', 'target_sub_expectation'],
|
|
57
68
|
},
|
|
58
69
|
};
|
|
59
70
|
export async function handleClarify(input, apiKey) {
|
|
60
|
-
const { document, nano_app_id, main_expectation, all_sub_expectations, target_sub_expectation, previous_answers = [], } = input;
|
|
71
|
+
const { document, nano_app_id, main_expectation, all_sub_expectations, target_sub_expectation, previous_answers = [], user_context = '', about_company = '', } = input;
|
|
61
72
|
if (!document?.trim()) {
|
|
62
73
|
throw new SpmApiError('Document text is required', 'INVALID_INPUT');
|
|
63
74
|
}
|
|
@@ -88,6 +99,8 @@ export async function handleClarify(input, apiKey) {
|
|
|
88
99
|
recent_recommendation: '',
|
|
89
100
|
latest_review: '',
|
|
90
101
|
gap_coverage: '',
|
|
102
|
+
user_context,
|
|
103
|
+
about_company,
|
|
91
104
|
nano_app_id,
|
|
92
105
|
};
|
|
93
106
|
const result = await callClarificationAgent(payload, apiKey);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { callEndpoint, SpmApiError } from '../client/spm-api.js';
|
|
12
12
|
export async function handleEvaluate(input, apiKey) {
|
|
13
|
-
const { document, nano_app_id, main_expectation, all_sub_expectations, prior_clarifications = [], } = input;
|
|
13
|
+
const { document, nano_app_id, main_expectation, all_sub_expectations, prior_clarifications = [], user_context = '', about_company = '', } = input;
|
|
14
14
|
if (!document?.trim()) {
|
|
15
15
|
throw new SpmApiError('Document text is required', 'INVALID_INPUT');
|
|
16
16
|
}
|
|
@@ -51,6 +51,8 @@ export async function handleEvaluate(input, apiKey) {
|
|
|
51
51
|
all_sub_expectations: parsedSubExps,
|
|
52
52
|
requirement_document: document,
|
|
53
53
|
prior_clarifications: formattedClarifications,
|
|
54
|
+
user_context,
|
|
55
|
+
about_company,
|
|
54
56
|
nano_app_id,
|
|
55
57
|
};
|
|
56
58
|
const result = await callEndpoint('extensionGapEvaluatorV3', payload, apiKey);
|
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.3.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
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { config } from '../config.js';
|
|
7
7
|
|
|
8
|
+
// Channel identifier — overridden by hosted/cloud-run.ts
|
|
9
|
+
let _channel = 'mcp-npm';
|
|
10
|
+
export function setChannel(ch: string) { _channel = ch; }
|
|
11
|
+
|
|
8
12
|
export async function callJsonEndpoint(
|
|
9
13
|
endpoint: string,
|
|
10
14
|
body: Record<string, unknown> = {},
|
|
@@ -13,6 +17,8 @@ export async function callJsonEndpoint(
|
|
|
13
17
|
const url = `${config.cloudFunctionsBase}/${endpoint}`;
|
|
14
18
|
const headers: Record<string, string> = {
|
|
15
19
|
'Content-Type': 'application/json',
|
|
20
|
+
'Origin': 'https://mcp.superproductmanager.ai',
|
|
21
|
+
'X-SPM-Channel': _channel,
|
|
16
22
|
};
|
|
17
23
|
if (apiKey) {
|
|
18
24
|
headers['X-SPM-API-Key'] = apiKey;
|
package/src/client/spm-api.ts
CHANGED
|
@@ -19,11 +19,17 @@ const ENDPOINTS = {
|
|
|
19
19
|
|
|
20
20
|
type EndpointName = keyof typeof ENDPOINTS;
|
|
21
21
|
|
|
22
|
+
// Channel identifier — shared with cloud-functions.ts
|
|
23
|
+
let _channel = 'mcp-npm';
|
|
24
|
+
export function setChannel(ch: string) { _channel = ch; }
|
|
25
|
+
|
|
22
26
|
function getHeaders(apiKey?: string): Record<string, string> {
|
|
23
27
|
const key = apiKey || config.apiKey;
|
|
24
28
|
const headers: Record<string, string> = {
|
|
25
29
|
'Content-Type': 'application/json',
|
|
26
30
|
'Accept': 'text/event-stream, application/json',
|
|
31
|
+
'Origin': 'https://mcp.superproductmanager.ai',
|
|
32
|
+
'X-SPM-Channel': _channel,
|
|
27
33
|
};
|
|
28
34
|
if (key) {
|
|
29
35
|
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/index.ts
CHANGED
|
@@ -96,6 +96,12 @@ export function createSpmMcpServer(options?: { apiKey?: string }): McpServer {
|
|
|
96
96
|
})).optional().describe(
|
|
97
97
|
'Array of previous Q&A pairs from this clarification session. Empty for first question.',
|
|
98
98
|
),
|
|
99
|
+
user_context: z.string().optional().describe(
|
|
100
|
+
'Context about the PM: role, experience level, company stage, research done, decisions made. Helps generate tailored questions.',
|
|
101
|
+
),
|
|
102
|
+
about_company: z.string().optional().describe(
|
|
103
|
+
'Context about the company/product: stage, team size, market, existing users, business model.',
|
|
104
|
+
),
|
|
99
105
|
},
|
|
100
106
|
async (input) => {
|
|
101
107
|
try {
|
|
@@ -129,6 +135,12 @@ export function createSpmMcpServer(options?: { apiKey?: string }): McpServer {
|
|
|
129
135
|
})).describe(
|
|
130
136
|
'All Q&A pairs accumulated from spm_clarify rounds. The evaluator uses these as evidence.',
|
|
131
137
|
),
|
|
138
|
+
user_context: z.string().optional().describe(
|
|
139
|
+
'Context about the PM: role, experience level, company stage, research done, decisions made.',
|
|
140
|
+
),
|
|
141
|
+
about_company: z.string().optional().describe(
|
|
142
|
+
'Context about the company/product: stage, team size, market, existing users, business model.',
|
|
143
|
+
),
|
|
132
144
|
},
|
|
133
145
|
async (input) => {
|
|
134
146
|
try {
|
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
|
+
}
|
package/src/tools/clarify.ts
CHANGED
|
@@ -58,6 +58,19 @@ export const clarifyDefinition = {
|
|
|
58
58
|
'Array of previous Q&A pairs from this clarification session. ' +
|
|
59
59
|
'Pass as [{question, answer}, ...]. Empty array for first question.',
|
|
60
60
|
},
|
|
61
|
+
user_context: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description:
|
|
64
|
+
'Context about the PM: their role, experience level, company stage, ' +
|
|
65
|
+
'what research they have done, what decisions they have already made. ' +
|
|
66
|
+
'This helps generate questions tailored to the specific user rather than generic ones.',
|
|
67
|
+
},
|
|
68
|
+
about_company: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description:
|
|
71
|
+
'Context about the company/product: stage, team size, market, ' +
|
|
72
|
+
'existing users, business model. Helps ground questions in reality.',
|
|
73
|
+
},
|
|
61
74
|
},
|
|
62
75
|
required: ['document', 'nano_app_id', 'main_expectation', 'all_sub_expectations', 'target_sub_expectation'],
|
|
63
76
|
},
|
|
@@ -70,6 +83,8 @@ export interface ClarifyInput {
|
|
|
70
83
|
all_sub_expectations: string;
|
|
71
84
|
target_sub_expectation: string;
|
|
72
85
|
previous_answers?: Array<{ question: string; answer: string }>;
|
|
86
|
+
user_context?: string;
|
|
87
|
+
about_company?: string;
|
|
73
88
|
}
|
|
74
89
|
|
|
75
90
|
export async function handleClarify(input: ClarifyInput, apiKey?: string): Promise<unknown> {
|
|
@@ -80,6 +95,8 @@ export async function handleClarify(input: ClarifyInput, apiKey?: string): Promi
|
|
|
80
95
|
all_sub_expectations,
|
|
81
96
|
target_sub_expectation,
|
|
82
97
|
previous_answers = [],
|
|
98
|
+
user_context = '',
|
|
99
|
+
about_company = '',
|
|
83
100
|
} = input;
|
|
84
101
|
|
|
85
102
|
if (!document?.trim()) {
|
|
@@ -117,6 +134,8 @@ export async function handleClarify(input: ClarifyInput, apiKey?: string): Promi
|
|
|
117
134
|
recent_recommendation: '',
|
|
118
135
|
latest_review: '',
|
|
119
136
|
gap_coverage: '',
|
|
137
|
+
user_context,
|
|
138
|
+
about_company,
|
|
120
139
|
nano_app_id,
|
|
121
140
|
};
|
|
122
141
|
|
package/src/tools/evaluate.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface EvaluateInput {
|
|
|
17
17
|
main_expectation: string;
|
|
18
18
|
all_sub_expectations: string;
|
|
19
19
|
prior_clarifications: Array<{ question: string; answer: string }>;
|
|
20
|
+
user_context?: string;
|
|
21
|
+
about_company?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export async function handleEvaluate(input: EvaluateInput, apiKey?: string): Promise<unknown> {
|
|
@@ -26,6 +28,8 @@ export async function handleEvaluate(input: EvaluateInput, apiKey?: string): Pro
|
|
|
26
28
|
main_expectation,
|
|
27
29
|
all_sub_expectations,
|
|
28
30
|
prior_clarifications = [],
|
|
31
|
+
user_context = '',
|
|
32
|
+
about_company = '',
|
|
29
33
|
} = input;
|
|
30
34
|
|
|
31
35
|
if (!document?.trim()) {
|
|
@@ -71,6 +75,8 @@ export async function handleEvaluate(input: EvaluateInput, apiKey?: string): Pro
|
|
|
71
75
|
all_sub_expectations: parsedSubExps,
|
|
72
76
|
requirement_document: document,
|
|
73
77
|
prior_clarifications: formattedClarifications,
|
|
78
|
+
user_context,
|
|
79
|
+
about_company,
|
|
74
80
|
nano_app_id,
|
|
75
81
|
};
|
|
76
82
|
|