spm-mcp 0.2.0 → 0.3.1

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.
@@ -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,11 +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',
10
13
  'Origin': 'https://mcp.superproductmanager.ai',
14
+ 'X-SPM-Channel': _channel,
11
15
  };
12
16
  if (apiKey) {
13
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,12 +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',
22
25
  'Origin': 'https://mcp.superproductmanager.ai',
26
+ 'X-SPM-Channel': _channel,
23
27
  };
24
28
  if (key) {
25
29
  headers['X-SPM-API-Key'] = key;
package/dist/src/index.js CHANGED
@@ -44,7 +44,9 @@ export function createSpmMcpServer(options) {
44
44
  server.tool('spm_analyze', 'Analyze a product document against SPM expert expectations. ' +
45
45
  'Pass a document and a nano_app_id (e.g., "prd_critique", "user_story", "growth_strategy") ' +
46
46
  'to get an expert analysis with expectations, sub-expectations, scores, and evidence. ' +
47
- 'Call spm_list_nano_apps first if you need to discover available templates.', {
47
+ 'Call spm_list_nano_apps first if you need to discover available templates. ' +
48
+ 'IMPORTANT: Present results as a clear scorecard table (Expectation | Score | Verdict). ' +
49
+ 'Highlight critical gaps (score < 50%) and suggest running spm_clarify on the weakest gap next.', {
48
50
  document: z.string().describe('The product document text to analyze'),
49
51
  nano_app_id: z.string().describe('The nano app template key (e.g., "prd_critique"). Call spm_list_nano_apps to see available options.'),
50
52
  }, async (input) => {
@@ -61,7 +63,12 @@ export function createSpmMcpServer(options) {
61
63
  // Tool: spm_clarify
62
64
  server.tool('spm_clarify', 'Get clarification questions for gaps identified in an SPM analysis. ' +
63
65
  'Pass the original document, the expert analysis context, and a target gap. ' +
64
- 'Returns targeted questions with suggested answers to close document gaps.', {
66
+ 'Returns targeted questions with suggested answers to close document gaps. ' +
67
+ 'IMPORTANT: When presenting results to the user, minimize cognitive load. ' +
68
+ 'Show the question prominently, then present each answer option as a clear ' +
69
+ 'numbered choice the user can pick (1, 2, 3...). Mark the [recommended] option. ' +
70
+ 'If you have native interactive UI (buttons, selectable options, AskUserQuestion), use it. ' +
71
+ 'The user should be able to pick an option or type their own answer with minimal effort.', {
65
72
  document: z.string().describe('The original product document text'),
66
73
  nano_app_id: z.string().describe('The nano app template key used in the analysis'),
67
74
  main_expectation: z.string().describe('The main expectation name/text from the analysis result'),
@@ -71,6 +78,8 @@ export function createSpmMcpServer(options) {
71
78
  question: z.string(),
72
79
  answer: z.string(),
73
80
  })).optional().describe('Array of previous Q&A pairs from this clarification session. Empty for first question.'),
81
+ user_context: z.string().optional().describe('Context about the PM: role, experience level, company stage, research done, decisions made. Helps generate tailored questions.'),
82
+ about_company: z.string().optional().describe('Context about the company/product: stage, team size, market, existing users, business model.'),
74
83
  }, async (input) => {
75
84
  try {
76
85
  const result = await handleClarify(input, apiKey);
@@ -95,6 +104,8 @@ export function createSpmMcpServer(options) {
95
104
  question: z.string(),
96
105
  answer: z.string(),
97
106
  })).describe('All Q&A pairs accumulated from spm_clarify rounds. The evaluator uses these as evidence.'),
107
+ user_context: z.string().optional().describe('Context about the PM: role, experience level, company stage, research done, decisions made.'),
108
+ about_company: z.string().optional().describe('Context about the company/product: stage, team size, market, existing users, business model.'),
98
109
  }, async (input) => {
99
110
  try {
100
111
  const result = await handleEvaluate(input, apiKey);
@@ -110,7 +121,9 @@ export function createSpmMcpServer(options) {
110
121
  server.tool('spm_improve', 'Generate improved document content for a specific gap. ' +
111
122
  'Pass the document, the target sub-expectation, and all clarification Q&A pairs. ' +
112
123
  'Returns paste-ready content the PM can insert into their document. ' +
113
- 'Use after spm_evaluate confirms the gap is sufficiently covered by clarification answers.', {
124
+ 'Use after spm_evaluate confirms the gap is sufficiently covered by clarification answers. ' +
125
+ 'IMPORTANT: Present the generated content in clean markdown the user can copy-paste directly. ' +
126
+ 'If there are [ACTION NEEDED] markers, highlight them so the user knows what to fill in.', {
114
127
  document: z.string().describe('The original product document text'),
115
128
  nano_app_id: z.string().describe('The nano app template key used in the analysis'),
116
129
  main_expectation: z.string().describe('The main expectation name/text from the analysis result'),
@@ -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);
@@ -17,5 +17,7 @@ export interface EvaluateInput {
17
17
  question: string;
18
18
  answer: string;
19
19
  }>;
20
+ user_context?: string;
21
+ about_company?: string;
20
22
  }
21
23
  export declare function handleEvaluate(input: EvaluateInput, apiKey?: string): Promise<unknown>;
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "spm-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Super Product Manager MCP Server - AI-powered product document analysis for PRDs, roadmaps, and 30 PM document types",
5
5
  "author": "Super Product Manager <chiranjeevi.gunturi@superproductmanager.ai>",
6
6
  "homepage": "https://superproductmanager.ai",
@@ -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> = {},
@@ -14,6 +18,7 @@ export async function callJsonEndpoint(
14
18
  const headers: Record<string, string> = {
15
19
  'Content-Type': 'application/json',
16
20
  'Origin': 'https://mcp.superproductmanager.ai',
21
+ 'X-SPM-Channel': _channel,
17
22
  };
18
23
  if (apiKey) {
19
24
  headers['X-SPM-API-Key'] = apiKey;
@@ -19,12 +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',
27
31
  'Origin': 'https://mcp.superproductmanager.ai',
32
+ 'X-SPM-Channel': _channel,
28
33
  };
29
34
  if (key) {
30
35
  headers['X-SPM-API-Key'] = key;
package/src/index.ts CHANGED
@@ -55,7 +55,9 @@ export function createSpmMcpServer(options?: { apiKey?: string }): McpServer {
55
55
  'Analyze a product document against SPM expert expectations. ' +
56
56
  'Pass a document and a nano_app_id (e.g., "prd_critique", "user_story", "growth_strategy") ' +
57
57
  'to get an expert analysis with expectations, sub-expectations, scores, and evidence. ' +
58
- 'Call spm_list_nano_apps first if you need to discover available templates.',
58
+ 'Call spm_list_nano_apps first if you need to discover available templates. ' +
59
+ 'IMPORTANT: Present results as a clear scorecard table (Expectation | Score | Verdict). ' +
60
+ 'Highlight critical gaps (score < 50%) and suggest running spm_clarify on the weakest gap next.',
59
61
  {
60
62
  document: z.string().describe('The product document text to analyze'),
61
63
  nano_app_id: z.string().describe(
@@ -79,7 +81,12 @@ export function createSpmMcpServer(options?: { apiKey?: string }): McpServer {
79
81
  'spm_clarify',
80
82
  'Get clarification questions for gaps identified in an SPM analysis. ' +
81
83
  'Pass the original document, the expert analysis context, and a target gap. ' +
82
- 'Returns targeted questions with suggested answers to close document gaps.',
84
+ 'Returns targeted questions with suggested answers to close document gaps. ' +
85
+ 'IMPORTANT: When presenting results to the user, minimize cognitive load. ' +
86
+ 'Show the question prominently, then present each answer option as a clear ' +
87
+ 'numbered choice the user can pick (1, 2, 3...). Mark the [recommended] option. ' +
88
+ 'If you have native interactive UI (buttons, selectable options, AskUserQuestion), use it. ' +
89
+ 'The user should be able to pick an option or type their own answer with minimal effort.',
83
90
  {
84
91
  document: z.string().describe('The original product document text'),
85
92
  nano_app_id: z.string().describe('The nano app template key used in the analysis'),
@@ -96,6 +103,12 @@ export function createSpmMcpServer(options?: { apiKey?: string }): McpServer {
96
103
  })).optional().describe(
97
104
  'Array of previous Q&A pairs from this clarification session. Empty for first question.',
98
105
  ),
106
+ user_context: z.string().optional().describe(
107
+ 'Context about the PM: role, experience level, company stage, research done, decisions made. Helps generate tailored questions.',
108
+ ),
109
+ about_company: z.string().optional().describe(
110
+ 'Context about the company/product: stage, team size, market, existing users, business model.',
111
+ ),
99
112
  },
100
113
  async (input) => {
101
114
  try {
@@ -129,6 +142,12 @@ export function createSpmMcpServer(options?: { apiKey?: string }): McpServer {
129
142
  })).describe(
130
143
  'All Q&A pairs accumulated from spm_clarify rounds. The evaluator uses these as evidence.',
131
144
  ),
145
+ user_context: z.string().optional().describe(
146
+ 'Context about the PM: role, experience level, company stage, research done, decisions made.',
147
+ ),
148
+ about_company: z.string().optional().describe(
149
+ 'Context about the company/product: stage, team size, market, existing users, business model.',
150
+ ),
132
151
  },
133
152
  async (input) => {
134
153
  try {
@@ -148,7 +167,9 @@ export function createSpmMcpServer(options?: { apiKey?: string }): McpServer {
148
167
  'Generate improved document content for a specific gap. ' +
149
168
  'Pass the document, the target sub-expectation, and all clarification Q&A pairs. ' +
150
169
  'Returns paste-ready content the PM can insert into their document. ' +
151
- 'Use after spm_evaluate confirms the gap is sufficiently covered by clarification answers.',
170
+ 'Use after spm_evaluate confirms the gap is sufficiently covered by clarification answers. ' +
171
+ 'IMPORTANT: Present the generated content in clean markdown the user can copy-paste directly. ' +
172
+ 'If there are [ACTION NEEDED] markers, highlight them so the user knows what to fill in.',
152
173
  {
153
174
  document: z.string().describe('The original product document text'),
154
175
  nano_app_id: z.string().describe('The nano app template key used in the analysis'),
@@ -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
 
@@ -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