spm-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -0
- package/dist/src/client/cloud-functions.d.ts +5 -0
- package/dist/src/client/cloud-functions.js +25 -0
- package/dist/src/client/spm-api.d.ts +30 -0
- package/dist/src/client/spm-api.js +75 -0
- package/dist/src/client/sse-parser.d.ts +23 -0
- package/dist/src/client/sse-parser.js +85 -0
- package/dist/src/client/supabase.d.ts +46 -0
- package/dist/src/client/supabase.js +167 -0
- package/dist/src/config.d.ts +16 -0
- package/dist/src/config.js +23 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +185 -0
- package/dist/src/stdio.d.ts +19 -0
- package/dist/src/stdio.js +24 -0
- package/dist/src/tools/analyze.d.ts +32 -0
- package/dist/src/tools/analyze.js +96 -0
- package/dist/src/tools/clarify.d.ts +65 -0
- package/dist/src/tools/clarify.js +101 -0
- package/dist/src/tools/create-custom-nano-app.d.ts +15 -0
- package/dist/src/tools/create-custom-nano-app.js +9 -0
- package/dist/src/tools/evaluate.d.ts +21 -0
- package/dist/src/tools/evaluate.js +64 -0
- package/dist/src/tools/improve.d.ts +23 -0
- package/dist/src/tools/improve.js +55 -0
- package/dist/src/tools/list-nano-apps.d.ts +14 -0
- package/dist/src/tools/list-nano-apps.js +24 -0
- package/package.json +29 -0
- package/spm-mcp-0.1.0.tgz +0 -0
- package/src/client/cloud-functions.ts +30 -0
- package/src/client/spm-api.ts +103 -0
- package/src/client/sse-parser.ts +100 -0
- package/src/client/supabase.ts +210 -0
- package/src/config.ts +27 -0
- package/src/index.ts +242 -0
- package/src/stdio.ts +25 -0
- package/src/tools/analyze.ts +117 -0
- package/src/tools/clarify.ts +131 -0
- package/src/tools/create-custom-nano-app.ts +24 -0
- package/src/tools/evaluate.ts +85 -0
- package/src/tools/improve.ts +82 -0
- package/src/tools/list-nano-apps.ts +40 -0
- package/tsconfig.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# spm-mcp
|
|
2
|
+
|
|
3
|
+
**Cursor for product management** — expert document review as MCP tools.
|
|
4
|
+
|
|
5
|
+
AI made engineering 10x faster. But deciding *what* to build didn't get faster. SPM fixes the input — 30 domain-specific expert reviews that score your product documents against the standards senior PMs actually use.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx spm-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## The problem
|
|
12
|
+
|
|
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
|
+
|
|
15
|
+
## What SPM reviews
|
|
16
|
+
|
|
17
|
+
Not just PRDs. Every document in the PM lifecycle:
|
|
18
|
+
|
|
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
|
+
|
|
24
|
+
30 built-in expert reviews. Or create your own with `spm_create_custom_nano_app`.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
### 1. Get your API key
|
|
29
|
+
|
|
30
|
+
Sign in at [superproductmanager.ai](https://superproductmanager.ai) → Profile → Generate API Key
|
|
31
|
+
|
|
32
|
+
### 2. Set it
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export SPM_API_KEY=spm_k_your_key_here
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 3. Run
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx spm-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Tools
|
|
45
|
+
|
|
46
|
+
| Tool | What it does |
|
|
47
|
+
|------|-------------|
|
|
48
|
+
| `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–1.0 with evidence. |
|
|
50
|
+
| `spm_clarify` | Get decision-forcing questions for the weakest gaps. Questions escalate when you give vague answers. |
|
|
51
|
+
| `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 — not AI hallucination. |
|
|
53
|
+
| `spm_create_custom_nano_app` | Create a custom expert review for any document type not covered by the built-in 30. |
|
|
54
|
+
|
|
55
|
+
## How it works
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Your document
|
|
59
|
+
→ spm_analyze Scores every gap 0–1.0 against expert expectations
|
|
60
|
+
→ spm_clarify Asks the questions your stakeholder would ask
|
|
61
|
+
→ spm_evaluate Re-scores — did the gap close?
|
|
62
|
+
→ spm_improve Generates improvements from YOUR answers
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
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
|
+
|
|
67
|
+
## Example workflow
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
You: Analyze this PRD with prd_critique
|
|
71
|
+
SPM: 4 expectations scored. Problem Definition: 43/100. Success Metrics: 15/100.
|
|
72
|
+
|
|
73
|
+
You: Clarify the weakest gap
|
|
74
|
+
SPM: "Is operator churn or player registration drop-off your primary counter-metric?"
|
|
75
|
+
|
|
76
|
+
You: Operator churn — if >20% revert to WhatsApp, we've failed.
|
|
77
|
+
SPM: [re-evaluates] Counter-metric: 0 → 0.9. Now targeting: Instrumentation plan.
|
|
78
|
+
|
|
79
|
+
You: Improve the success metrics section
|
|
80
|
+
SPM: [generates paste-ready content grounded in your "operator churn" decision]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Average documents improve from 35% to 82% in three rounds.
|
|
84
|
+
|
|
85
|
+
## Setup for AI coding assistants
|
|
86
|
+
|
|
87
|
+
### Claude Code
|
|
88
|
+
|
|
89
|
+
Add to `.claude/settings.json`:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"mcpServers": {
|
|
94
|
+
"spm": {
|
|
95
|
+
"command": "npx",
|
|
96
|
+
"args": ["spm-mcp"],
|
|
97
|
+
"env": {
|
|
98
|
+
"SPM_API_KEY": "spm_k_your_key_here"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Cursor
|
|
106
|
+
|
|
107
|
+
Add to MCP settings:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"spm": {
|
|
112
|
+
"command": "npx",
|
|
113
|
+
"args": ["spm-mcp"],
|
|
114
|
+
"env": {
|
|
115
|
+
"SPM_API_KEY": "spm_k_your_key_here"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Windsurf
|
|
122
|
+
|
|
123
|
+
Add to MCP configuration with the same command and env structure.
|
|
124
|
+
|
|
125
|
+
## Environment variables
|
|
126
|
+
|
|
127
|
+
| Variable | Required | Default | Description |
|
|
128
|
+
|----------|----------|---------|-------------|
|
|
129
|
+
| `SPM_API_KEY` | Yes | — | Your API key from [superproductmanager.ai](https://superproductmanager.ai) → Profile |
|
|
130
|
+
| `SPM_SUPABASE_URL` | No | Production | Override for self-hosted or development |
|
|
131
|
+
| `SPM_SUPABASE_ANON_KEY` | No | Production | Override for self-hosted or development |
|
|
132
|
+
| `SPM_CLOUD_FUNCTIONS_BASE` | No | Production | Override Cloud Functions URL |
|
|
133
|
+
|
|
134
|
+
## Security
|
|
135
|
+
|
|
136
|
+
- **No filesystem access** — SPM never reads your local files, SSH keys, or credentials
|
|
137
|
+
- **No postinstall scripts** — nothing runs on `npm install`
|
|
138
|
+
- **4 specific env vars only** — reads `SPM_API_KEY`, `SPM_SUPABASE_URL`, `SPM_SUPABASE_ANON_KEY`, `SPM_CLOUD_FUNCTIONS_BASE`. No broad `process.env` access.
|
|
139
|
+
- **Network calls only when tools are invoked** — not on import, not on install
|
|
140
|
+
- **Source included** — TypeScript source ships alongside compiled JS for auditability
|
|
141
|
+
- **Zero known vulnerabilities** — `npm audit` clean
|
|
142
|
+
|
|
143
|
+
## Also available as
|
|
144
|
+
|
|
145
|
+
- **Chrome extension** — reviews documents inside Google Docs, Notion, ClickUp, Linear. [Install from Chrome Web Store](https://chromewebstore.google.com/detail/super-product-manager/ocpjfedoogmpbkhpojdkfdimkbiamihg)
|
|
146
|
+
- **Web app** — paste any document, full analysis in 30 seconds. [superproductmanager.ai](https://superproductmanager.ai)
|
|
147
|
+
|
|
148
|
+
## Links
|
|
149
|
+
|
|
150
|
+
- [Website](https://superproductmanager.ai)
|
|
151
|
+
- [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
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to call Cloud Functions that return plain JSON (not SSE).
|
|
3
|
+
* Used for nano app CRUD and discovery endpoints.
|
|
4
|
+
*/
|
|
5
|
+
import { config } from '../config.js';
|
|
6
|
+
export async function callJsonEndpoint(endpoint, body = {}, apiKey) {
|
|
7
|
+
const url = `${config.cloudFunctionsBase}/${endpoint}`;
|
|
8
|
+
const headers = {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
};
|
|
11
|
+
if (apiKey) {
|
|
12
|
+
headers['X-SPM-API-Key'] = apiKey;
|
|
13
|
+
}
|
|
14
|
+
const response = await fetch(url, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers,
|
|
17
|
+
body: JSON.stringify(body),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
const errorBody = await response.text();
|
|
21
|
+
throw new Error(`${endpoint} failed (${response.status}): ${errorBody}`);
|
|
22
|
+
}
|
|
23
|
+
return response.json();
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=cloud-functions.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for SPM Cloud Functions.
|
|
3
|
+
*
|
|
4
|
+
* Calls the same endpoints the Chrome extension uses, authenticated via
|
|
5
|
+
* API key (X-SPM-API-Key header) or Bearer token.
|
|
6
|
+
*/
|
|
7
|
+
declare const ENDPOINTS: {
|
|
8
|
+
readonly extensionAgentV3: "/extensionAgentV3";
|
|
9
|
+
readonly extensionClarificationAgentV3: "/extensionClarificationAgentV3";
|
|
10
|
+
readonly extensionGapEvaluatorV3: "/extensionGapEvaluatorV3";
|
|
11
|
+
readonly extensionImproviseAgentV3: "/extensionImproviseAgentV3";
|
|
12
|
+
readonly extensionReviewAgainAgentV3: "/extensionReviewAgainAgentV3";
|
|
13
|
+
readonly classifyIntent: "/classifyIntent";
|
|
14
|
+
};
|
|
15
|
+
type EndpointName = keyof typeof ENDPOINTS;
|
|
16
|
+
/**
|
|
17
|
+
* Call an SPM Cloud Function endpoint.
|
|
18
|
+
* Handles SSE stream parsing automatically.
|
|
19
|
+
*/
|
|
20
|
+
export declare function callEndpoint(endpoint: EndpointName, body: Record<string, unknown>, apiKey?: string): Promise<unknown>;
|
|
21
|
+
export declare class SpmApiError extends Error {
|
|
22
|
+
readonly code: 'AUTH_REQUIRED' | 'RATE_LIMITED' | 'UPSTREAM_ERROR' | 'INVALID_INPUT' | 'SESSION_NOT_FOUND' | 'INVALID_PHASE';
|
|
23
|
+
constructor(message: string, code: 'AUTH_REQUIRED' | 'RATE_LIMITED' | 'UPSTREAM_ERROR' | 'INVALID_INPUT' | 'SESSION_NOT_FOUND' | 'INVALID_PHASE');
|
|
24
|
+
}
|
|
25
|
+
export declare function callExpertAgent(body: Record<string, unknown>, apiKey?: string): Promise<unknown>;
|
|
26
|
+
export declare function callClarificationAgent(body: Record<string, unknown>, apiKey?: string): Promise<unknown>;
|
|
27
|
+
export declare function callGapEvaluator(body: Record<string, unknown>, apiKey?: string): Promise<unknown>;
|
|
28
|
+
export declare function callImproviser(body: Record<string, unknown>, apiKey?: string): Promise<unknown>;
|
|
29
|
+
export declare function callClassifyIntent(text: string, apiKey?: string): Promise<unknown>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for SPM Cloud Functions.
|
|
3
|
+
*
|
|
4
|
+
* Calls the same endpoints the Chrome extension uses, authenticated via
|
|
5
|
+
* API key (X-SPM-API-Key header) or Bearer token.
|
|
6
|
+
*/
|
|
7
|
+
import { config } from '../config.js';
|
|
8
|
+
import { parseSseResponse } from './sse-parser.js';
|
|
9
|
+
const ENDPOINTS = {
|
|
10
|
+
extensionAgentV3: '/extensionAgentV3',
|
|
11
|
+
extensionClarificationAgentV3: '/extensionClarificationAgentV3',
|
|
12
|
+
extensionGapEvaluatorV3: '/extensionGapEvaluatorV3',
|
|
13
|
+
extensionImproviseAgentV3: '/extensionImproviseAgentV3',
|
|
14
|
+
extensionReviewAgainAgentV3: '/extensionReviewAgainAgentV3',
|
|
15
|
+
classifyIntent: '/classifyIntent',
|
|
16
|
+
};
|
|
17
|
+
function getHeaders(apiKey) {
|
|
18
|
+
const key = apiKey || config.apiKey;
|
|
19
|
+
const headers = {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'Accept': 'text/event-stream, application/json',
|
|
22
|
+
};
|
|
23
|
+
if (key) {
|
|
24
|
+
headers['X-SPM-API-Key'] = key;
|
|
25
|
+
}
|
|
26
|
+
return headers;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Call an SPM Cloud Function endpoint.
|
|
30
|
+
* Handles SSE stream parsing automatically.
|
|
31
|
+
*/
|
|
32
|
+
export async function callEndpoint(endpoint, body, apiKey) {
|
|
33
|
+
const url = `${config.cloudFunctionsBase}${ENDPOINTS[endpoint]}`;
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: getHeaders(apiKey),
|
|
37
|
+
body: JSON.stringify(body),
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const text = await response.text().catch(() => '');
|
|
41
|
+
if (response.status === 401) {
|
|
42
|
+
throw new SpmApiError('Authentication required. Check your SPM_API_KEY.', 'AUTH_REQUIRED');
|
|
43
|
+
}
|
|
44
|
+
if (response.status === 429) {
|
|
45
|
+
throw new SpmApiError('Rate limited. Please slow down.', 'RATE_LIMITED');
|
|
46
|
+
}
|
|
47
|
+
throw new SpmApiError(`Cloud Function error (${response.status}): ${text.slice(0, 200)}`, 'UPSTREAM_ERROR');
|
|
48
|
+
}
|
|
49
|
+
return parseSseResponse(response);
|
|
50
|
+
}
|
|
51
|
+
export class SpmApiError extends Error {
|
|
52
|
+
code;
|
|
53
|
+
constructor(message, code) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.name = 'SpmApiError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Convenience wrappers
|
|
60
|
+
export async function callExpertAgent(body, apiKey) {
|
|
61
|
+
return callEndpoint('extensionAgentV3', body, apiKey);
|
|
62
|
+
}
|
|
63
|
+
export async function callClarificationAgent(body, apiKey) {
|
|
64
|
+
return callEndpoint('extensionClarificationAgentV3', body, apiKey);
|
|
65
|
+
}
|
|
66
|
+
export async function callGapEvaluator(body, apiKey) {
|
|
67
|
+
return callEndpoint('extensionGapEvaluatorV3', body, apiKey);
|
|
68
|
+
}
|
|
69
|
+
export async function callImproviser(body, apiKey) {
|
|
70
|
+
return callEndpoint('extensionImproviseAgentV3', body, apiKey);
|
|
71
|
+
}
|
|
72
|
+
export async function callClassifyIntent(text, apiKey) {
|
|
73
|
+
return callEndpoint('classifyIntent', { text }, apiKey);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=spm-api.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE stream parser for SPM Cloud Functions.
|
|
3
|
+
*
|
|
4
|
+
* All Cloud Functions respond with Server-Sent Events (text/event-stream).
|
|
5
|
+
* Frame format:
|
|
6
|
+
* data: {"type":"started"}\n\n
|
|
7
|
+
* data: {"type":"chunk","content":"...","fullContent":"..."}\n\n
|
|
8
|
+
* data: {"type":"complete","fullContent":"{...}"}\n\n
|
|
9
|
+
* data: {"type":"error","message":"..."}\n\n
|
|
10
|
+
* data: {"type":"status","message":"..."}\n\n
|
|
11
|
+
*/
|
|
12
|
+
export interface SseFrame {
|
|
13
|
+
type: 'started' | 'chunk' | 'complete' | 'error' | 'status';
|
|
14
|
+
content?: string;
|
|
15
|
+
fullContent?: string;
|
|
16
|
+
message?: string;
|
|
17
|
+
raw?: unknown;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Consumes an SSE response stream and returns the parsed final JSON result.
|
|
21
|
+
* Mirrors the parsing logic from src/lib/api/agent.ts.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseSseResponse(response: Response): Promise<unknown>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE stream parser for SPM Cloud Functions.
|
|
3
|
+
*
|
|
4
|
+
* All Cloud Functions respond with Server-Sent Events (text/event-stream).
|
|
5
|
+
* Frame format:
|
|
6
|
+
* data: {"type":"started"}\n\n
|
|
7
|
+
* data: {"type":"chunk","content":"...","fullContent":"..."}\n\n
|
|
8
|
+
* data: {"type":"complete","fullContent":"{...}"}\n\n
|
|
9
|
+
* data: {"type":"error","message":"..."}\n\n
|
|
10
|
+
* data: {"type":"status","message":"..."}\n\n
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Consumes an SSE response stream and returns the parsed final JSON result.
|
|
14
|
+
* Mirrors the parsing logic from src/lib/api/agent.ts.
|
|
15
|
+
*/
|
|
16
|
+
export async function parseSseResponse(response) {
|
|
17
|
+
const contentType = response.headers.get('content-type') || '';
|
|
18
|
+
// If not SSE, try plain JSON
|
|
19
|
+
if (!contentType.includes('text/event-stream')) {
|
|
20
|
+
const text = await response.text();
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(text);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error(`Unexpected response format: ${text.slice(0, 200)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const reader = response.body?.getReader();
|
|
29
|
+
if (!reader)
|
|
30
|
+
throw new Error('No response body');
|
|
31
|
+
const decoder = new TextDecoder();
|
|
32
|
+
let buffer = '';
|
|
33
|
+
let fullContent = '';
|
|
34
|
+
while (true) {
|
|
35
|
+
const { value, done } = await reader.read();
|
|
36
|
+
if (done)
|
|
37
|
+
break;
|
|
38
|
+
buffer += decoder.decode(value, { stream: true });
|
|
39
|
+
const frames = buffer.split('\n\n');
|
|
40
|
+
buffer = frames.pop() || '';
|
|
41
|
+
for (const frame of frames) {
|
|
42
|
+
const match = frame.match(/^data:\s*(.+)$/m);
|
|
43
|
+
if (!match)
|
|
44
|
+
continue;
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(match[1]);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
continue; // Skip malformed frames
|
|
51
|
+
}
|
|
52
|
+
switch (parsed.type) {
|
|
53
|
+
case 'chunk':
|
|
54
|
+
fullContent = parsed.fullContent || (fullContent + (parsed.content || ''));
|
|
55
|
+
break;
|
|
56
|
+
case 'complete':
|
|
57
|
+
fullContent = parsed.fullContent || fullContent;
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(fullContent);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return fullContent; // Return raw string if not JSON
|
|
63
|
+
}
|
|
64
|
+
case 'error':
|
|
65
|
+
throw new Error(parsed.message || 'Upstream agent error');
|
|
66
|
+
case 'status':
|
|
67
|
+
// Status updates (model fallback, etc.) — log but continue
|
|
68
|
+
break;
|
|
69
|
+
case 'started':
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Stream ended without a 'complete' frame — try parsing accumulated content
|
|
75
|
+
if (fullContent) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(fullContent);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return fullContent;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error('Stream ended without a complete response');
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=sse-parser.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase client for the MCP server.
|
|
3
|
+
* Uses the anon key (public) to read nano apps, expectations, and rules.
|
|
4
|
+
* No auth session needed — all reads use RLS SELECT policies with USING (true).
|
|
5
|
+
*/
|
|
6
|
+
export declare const supabase: import("@supabase/supabase-js").SupabaseClient<any, "public", "public", any, any>;
|
|
7
|
+
export interface NanoApp {
|
|
8
|
+
key: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string | null;
|
|
11
|
+
category: string | null;
|
|
12
|
+
flowType: string;
|
|
13
|
+
expectations: Expectation[];
|
|
14
|
+
}
|
|
15
|
+
export interface Expectation {
|
|
16
|
+
id: string;
|
|
17
|
+
slug: string;
|
|
18
|
+
name: string;
|
|
19
|
+
description: string | null;
|
|
20
|
+
orderIndex: number;
|
|
21
|
+
rules: Rule[];
|
|
22
|
+
}
|
|
23
|
+
export interface Rule {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
description: string | null;
|
|
27
|
+
spec: string;
|
|
28
|
+
isDefault: boolean;
|
|
29
|
+
orderIndex: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fetch all nano apps with their expectations and rules.
|
|
33
|
+
* Mirrors fetchSubapps() in src/lib/api/subapps.ts.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fetchNanoApps(): Promise<NanoApp[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Fetch a single nano app by key (slug).
|
|
38
|
+
* Returns null if not found.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getNanoAppByKey(key: string): Promise<NanoApp | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Build the `rules` string for the expert agent payload.
|
|
43
|
+
* Mirrors buildExpectationRulesText() from src/lib/expectationUtils.ts
|
|
44
|
+
* and the SubappPage.tsx expectations assembly (lines 778-807).
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildRulesPayload(nanoApp: NanoApp): string;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase client for the MCP server.
|
|
3
|
+
* Uses the anon key (public) to read nano apps, expectations, and rules.
|
|
4
|
+
* No auth session needed — all reads use RLS SELECT policies with USING (true).
|
|
5
|
+
*/
|
|
6
|
+
import { createClient } from '@supabase/supabase-js';
|
|
7
|
+
import { config } from '../config.js';
|
|
8
|
+
export const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
9
|
+
// ── Queries ──
|
|
10
|
+
/**
|
|
11
|
+
* Fetch all nano apps with their expectations and rules.
|
|
12
|
+
* Mirrors fetchSubapps() in src/lib/api/subapps.ts.
|
|
13
|
+
*/
|
|
14
|
+
export async function fetchNanoApps() {
|
|
15
|
+
const { data, error } = await supabase
|
|
16
|
+
.from('subapps')
|
|
17
|
+
.select(`
|
|
18
|
+
id, key, name, description, category, metadata, default_expectation_id,
|
|
19
|
+
expectations:expectations!subapp_id (
|
|
20
|
+
id, slug, name, description, order_index, metadata,
|
|
21
|
+
rules:rules!expectation_id (
|
|
22
|
+
id, name, description, spec, is_default, order_index
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
`)
|
|
26
|
+
.order('name', { ascending: true });
|
|
27
|
+
if (error)
|
|
28
|
+
throw new Error(`Failed to fetch nano apps: ${error.message}`);
|
|
29
|
+
return (data ?? []).map((s) => {
|
|
30
|
+
const expectationsArr = Array.isArray(s.expectations) ? s.expectations : [];
|
|
31
|
+
const expectations = expectationsArr.map((e) => ({
|
|
32
|
+
id: e.id,
|
|
33
|
+
slug: e.slug,
|
|
34
|
+
name: e.name,
|
|
35
|
+
description: e.description ?? null,
|
|
36
|
+
orderIndex: e.order_index ?? 0,
|
|
37
|
+
rules: (Array.isArray(e.rules) ? e.rules : []).map((r) => ({
|
|
38
|
+
id: r.id,
|
|
39
|
+
name: r.name,
|
|
40
|
+
description: r.description ?? null,
|
|
41
|
+
spec: typeof r.spec === 'string' ? r.spec : JSON.stringify(r.spec ?? ''),
|
|
42
|
+
isDefault: !!r.is_default,
|
|
43
|
+
orderIndex: r.order_index ?? 0,
|
|
44
|
+
})),
|
|
45
|
+
}));
|
|
46
|
+
const dbFlowType = s.metadata?.flow_type;
|
|
47
|
+
return {
|
|
48
|
+
key: s.key,
|
|
49
|
+
name: s.name,
|
|
50
|
+
description: s.description,
|
|
51
|
+
category: s.category,
|
|
52
|
+
flowType: (dbFlowType === 'direct' || dbFlowType === 'normal') ? dbFlowType : 'normal',
|
|
53
|
+
expectations,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Fetch a single nano app by key (slug).
|
|
59
|
+
* Returns null if not found.
|
|
60
|
+
*/
|
|
61
|
+
export async function getNanoAppByKey(key) {
|
|
62
|
+
const { data, error } = await supabase
|
|
63
|
+
.from('subapps')
|
|
64
|
+
.select(`
|
|
65
|
+
id, key, name, description, category, metadata, default_expectation_id,
|
|
66
|
+
expectations:expectations!subapp_id (
|
|
67
|
+
id, slug, name, description, order_index, metadata,
|
|
68
|
+
rules:rules!expectation_id (
|
|
69
|
+
id, name, description, spec, is_default, order_index
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
`)
|
|
73
|
+
.eq('key', key)
|
|
74
|
+
.maybeSingle();
|
|
75
|
+
if (error)
|
|
76
|
+
throw new Error(`Failed to fetch nano app '${key}': ${error.message}`);
|
|
77
|
+
if (!data)
|
|
78
|
+
return null;
|
|
79
|
+
const expectationsArr = Array.isArray(data.expectations) ? data.expectations : [];
|
|
80
|
+
const expectations = expectationsArr.map((e) => ({
|
|
81
|
+
id: e.id,
|
|
82
|
+
slug: e.slug,
|
|
83
|
+
name: e.name,
|
|
84
|
+
description: e.description ?? null,
|
|
85
|
+
orderIndex: e.order_index ?? 0,
|
|
86
|
+
rules: (Array.isArray(e.rules) ? e.rules : []).map((r) => ({
|
|
87
|
+
id: r.id,
|
|
88
|
+
name: r.name,
|
|
89
|
+
description: r.description ?? null,
|
|
90
|
+
spec: typeof r.spec === 'string' ? r.spec : JSON.stringify(r.spec ?? ''),
|
|
91
|
+
isDefault: !!r.is_default,
|
|
92
|
+
orderIndex: r.order_index ?? 0,
|
|
93
|
+
})),
|
|
94
|
+
}));
|
|
95
|
+
const dbFlowType = data.metadata?.flow_type;
|
|
96
|
+
return {
|
|
97
|
+
key: data.key,
|
|
98
|
+
name: data.name,
|
|
99
|
+
description: data.description,
|
|
100
|
+
category: data.category,
|
|
101
|
+
flowType: (dbFlowType === 'direct' || dbFlowType === 'normal') ? dbFlowType : 'normal',
|
|
102
|
+
expectations,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build the `rules` string for the expert agent payload.
|
|
107
|
+
* Mirrors buildExpectationRulesText() from src/lib/expectationUtils.ts
|
|
108
|
+
* and the SubappPage.tsx expectations assembly (lines 778-807).
|
|
109
|
+
*/
|
|
110
|
+
export function buildRulesPayload(nanoApp) {
|
|
111
|
+
const expectationsText = nanoApp.expectations
|
|
112
|
+
.sort((a, b) => a.orderIndex - b.orderIndex)
|
|
113
|
+
.reduce((acc, section, idx) => {
|
|
114
|
+
const key = String(idx + 1);
|
|
115
|
+
const { rulesBlock, aiInstructions } = buildExpectationRulesText(section.rules);
|
|
116
|
+
acc[key] = {
|
|
117
|
+
sectionTitle: section.name || '',
|
|
118
|
+
isEdited: false,
|
|
119
|
+
currentExpectation: [
|
|
120
|
+
`Full Expectation: ${section.description || ''}`.trim(),
|
|
121
|
+
'',
|
|
122
|
+
'Detailed Requirements:',
|
|
123
|
+
'',
|
|
124
|
+
rulesBlock.trim(),
|
|
125
|
+
aiInstructions,
|
|
126
|
+
].join('\n').trim(),
|
|
127
|
+
previousExpectation: '',
|
|
128
|
+
};
|
|
129
|
+
return acc;
|
|
130
|
+
}, {});
|
|
131
|
+
return JSON.stringify(expectationsText);
|
|
132
|
+
}
|
|
133
|
+
// Minimum content length for a rule to be included (matches extension logic)
|
|
134
|
+
const MIN_RULE_CONTENT_LENGTH = 10;
|
|
135
|
+
const MINIMAL_PATTERNS = [
|
|
136
|
+
/^\.+$/,
|
|
137
|
+
/^[a-z]{1,3}$/i,
|
|
138
|
+
/^\s*$/,
|
|
139
|
+
/^[^\w\s]+$/,
|
|
140
|
+
];
|
|
141
|
+
function buildExpectationRulesText(rules) {
|
|
142
|
+
const filtered = rules
|
|
143
|
+
.filter(r => r.name !== 'AI Analysis Instructions')
|
|
144
|
+
.sort((a, b) => a.orderIndex - b.orderIndex)
|
|
145
|
+
.filter(rule => {
|
|
146
|
+
const content = rule.spec.trim();
|
|
147
|
+
if (content.length < MIN_RULE_CONTENT_LENGTH)
|
|
148
|
+
return false;
|
|
149
|
+
return !MINIMAL_PATTERNS.some(p => p.test(content));
|
|
150
|
+
});
|
|
151
|
+
const rulesBlock = filtered
|
|
152
|
+
.map((rule, i) => {
|
|
153
|
+
let content = rule.spec;
|
|
154
|
+
// Strip "For Content Generation" sections
|
|
155
|
+
const idx = content.indexOf('## For Content Generation');
|
|
156
|
+
if (idx !== -1)
|
|
157
|
+
content = content.slice(0, idx).trimEnd();
|
|
158
|
+
return `${i + 1}. ${rule.name}:\n${content}\n`;
|
|
159
|
+
})
|
|
160
|
+
.join('\n');
|
|
161
|
+
const aiRule = rules.find(r => r.name === 'AI Analysis Instructions');
|
|
162
|
+
const aiInstructions = aiRule?.spec
|
|
163
|
+
? `\n\nAdditional AI Instructions:\n${aiRule.spec}`
|
|
164
|
+
: '';
|
|
165
|
+
return { rulesBlock, aiInstructions };
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=supabase.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPM MCP Server configuration.
|
|
3
|
+
* All credentials must be provided via environment variables.
|
|
4
|
+
*/
|
|
5
|
+
export declare const config: {
|
|
6
|
+
/** Supabase project URL */
|
|
7
|
+
readonly supabaseUrl: string;
|
|
8
|
+
/** Supabase anon key */
|
|
9
|
+
readonly supabaseAnonKey: string;
|
|
10
|
+
/** Base URL for Cloud Functions */
|
|
11
|
+
readonly cloudFunctionsBase: string;
|
|
12
|
+
/** User's SPM API key for authenticating with Cloud Functions */
|
|
13
|
+
readonly apiKey: string;
|
|
14
|
+
/** Port for hosted HTTP transport */
|
|
15
|
+
readonly port: number;
|
|
16
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPM MCP Server configuration.
|
|
3
|
+
* All credentials must be provided via environment variables.
|
|
4
|
+
*/
|
|
5
|
+
function requireEnv(name) {
|
|
6
|
+
const v = process.env[name];
|
|
7
|
+
if (!v)
|
|
8
|
+
throw new Error(`${name} environment variable is required`);
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
export const config = {
|
|
12
|
+
/** Supabase project URL */
|
|
13
|
+
supabaseUrl: requireEnv('SPM_SUPABASE_URL'),
|
|
14
|
+
/** Supabase anon key */
|
|
15
|
+
supabaseAnonKey: requireEnv('SPM_SUPABASE_ANON_KEY'),
|
|
16
|
+
/** Base URL for Cloud Functions */
|
|
17
|
+
cloudFunctionsBase: process.env.SPM_CLOUD_FUNCTIONS_BASE || 'https://us-central1-litethink.cloudfunctions.net',
|
|
18
|
+
/** User's SPM API key for authenticating with Cloud Functions */
|
|
19
|
+
apiKey: process.env.SPM_API_KEY || '',
|
|
20
|
+
/** Port for hosted HTTP transport */
|
|
21
|
+
port: parseInt(process.env.PORT || '8080', 10),
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPM MCP Server — Main entry point.
|
|
3
|
+
*
|
|
4
|
+
* Creates an MCP server with 6 stateless tools:
|
|
5
|
+
* - spm_list_nano_apps: Discover available analysis templates
|
|
6
|
+
* - spm_analyze: Expert analysis of a product document
|
|
7
|
+
* - spm_clarify: Clarification questions for identified gaps
|
|
8
|
+
* - spm_evaluate: Re-score gaps after clarification rounds
|
|
9
|
+
* - spm_improve: Generate improved content for gaps
|
|
10
|
+
* - spm_create_custom_nano_app: Create a custom nano app
|
|
11
|
+
*
|
|
12
|
+
* Used by both stdio (local NPM) and HTTP (Cloud Run) transports.
|
|
13
|
+
*/
|
|
14
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
|
+
export declare function createSpmMcpServer(options?: {
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
}): McpServer;
|
|
18
|
+
export { SpmApiError } from './client/spm-api.js';
|