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.
Files changed (43) hide show
  1. package/README.md +156 -0
  2. package/dist/src/client/cloud-functions.d.ts +5 -0
  3. package/dist/src/client/cloud-functions.js +25 -0
  4. package/dist/src/client/spm-api.d.ts +30 -0
  5. package/dist/src/client/spm-api.js +75 -0
  6. package/dist/src/client/sse-parser.d.ts +23 -0
  7. package/dist/src/client/sse-parser.js +85 -0
  8. package/dist/src/client/supabase.d.ts +46 -0
  9. package/dist/src/client/supabase.js +167 -0
  10. package/dist/src/config.d.ts +16 -0
  11. package/dist/src/config.js +23 -0
  12. package/dist/src/index.d.ts +18 -0
  13. package/dist/src/index.js +185 -0
  14. package/dist/src/stdio.d.ts +19 -0
  15. package/dist/src/stdio.js +24 -0
  16. package/dist/src/tools/analyze.d.ts +32 -0
  17. package/dist/src/tools/analyze.js +96 -0
  18. package/dist/src/tools/clarify.d.ts +65 -0
  19. package/dist/src/tools/clarify.js +101 -0
  20. package/dist/src/tools/create-custom-nano-app.d.ts +15 -0
  21. package/dist/src/tools/create-custom-nano-app.js +9 -0
  22. package/dist/src/tools/evaluate.d.ts +21 -0
  23. package/dist/src/tools/evaluate.js +64 -0
  24. package/dist/src/tools/improve.d.ts +23 -0
  25. package/dist/src/tools/improve.js +55 -0
  26. package/dist/src/tools/list-nano-apps.d.ts +14 -0
  27. package/dist/src/tools/list-nano-apps.js +24 -0
  28. package/package.json +29 -0
  29. package/spm-mcp-0.1.0.tgz +0 -0
  30. package/src/client/cloud-functions.ts +30 -0
  31. package/src/client/spm-api.ts +103 -0
  32. package/src/client/sse-parser.ts +100 -0
  33. package/src/client/supabase.ts +210 -0
  34. package/src/config.ts +27 -0
  35. package/src/index.ts +242 -0
  36. package/src/stdio.ts +25 -0
  37. package/src/tools/analyze.ts +117 -0
  38. package/src/tools/clarify.ts +131 -0
  39. package/src/tools/create-custom-nano-app.ts +24 -0
  40. package/src/tools/evaluate.ts +85 -0
  41. package/src/tools/improve.ts +82 -0
  42. package/src/tools/list-nano-apps.ts +40 -0
  43. 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,5 @@
1
+ /**
2
+ * Helper to call Cloud Functions that return plain JSON (not SSE).
3
+ * Used for nano app CRUD and discovery endpoints.
4
+ */
5
+ export declare function callJsonEndpoint(endpoint: string, body?: Record<string, unknown>, apiKey?: string): Promise<unknown>;
@@ -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';