@vibekiln/cutline-mcp-cli 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/Dockerfile +11 -0
  2. package/README.md +248 -0
  3. package/dist/auth/callback.d.ts +6 -0
  4. package/dist/auth/callback.js +97 -0
  5. package/dist/auth/keychain.d.ts +3 -0
  6. package/dist/auth/keychain.js +16 -0
  7. package/dist/commands/init.d.ts +4 -0
  8. package/dist/commands/init.js +309 -0
  9. package/dist/commands/login.d.ts +7 -0
  10. package/dist/commands/login.js +166 -0
  11. package/dist/commands/logout.d.ts +1 -0
  12. package/dist/commands/logout.js +25 -0
  13. package/dist/commands/serve.d.ts +1 -0
  14. package/dist/commands/serve.js +38 -0
  15. package/dist/commands/setup.d.ts +5 -0
  16. package/dist/commands/setup.js +278 -0
  17. package/dist/commands/status.d.ts +3 -0
  18. package/dist/commands/status.js +127 -0
  19. package/dist/commands/upgrade.d.ts +3 -0
  20. package/dist/commands/upgrade.js +112 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +64 -0
  23. package/dist/servers/chunk-DE7R7WKY.js +331 -0
  24. package/dist/servers/chunk-KMUSQOTJ.js +47 -0
  25. package/dist/servers/chunk-OP4EO6FV.js +454 -0
  26. package/dist/servers/chunk-UBBAYTW3.js +946 -0
  27. package/dist/servers/chunk-ZVWDXO6M.js +1063 -0
  28. package/dist/servers/cutline-server.js +10448 -0
  29. package/dist/servers/data-client-FPUZBUO3.js +160 -0
  30. package/dist/servers/exploration-server.js +930 -0
  31. package/dist/servers/graph-metrics-DCNR7JZN.js +12 -0
  32. package/dist/servers/integrations-server.js +107 -0
  33. package/dist/servers/output-server.js +107 -0
  34. package/dist/servers/premortem-server.js +971 -0
  35. package/dist/servers/tools-server.js +287 -0
  36. package/dist/utils/config-store.d.ts +8 -0
  37. package/dist/utils/config-store.js +35 -0
  38. package/dist/utils/config.d.ts +22 -0
  39. package/dist/utils/config.js +48 -0
  40. package/mcpb/manifest.json +77 -0
  41. package/package.json +76 -0
  42. package/server.json +42 -0
  43. package/smithery.yaml +10 -0
@@ -0,0 +1,930 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ guardBoundary,
4
+ guardOutput,
5
+ withPerfTracking
6
+ } from "./chunk-OP4EO6FV.js";
7
+ import {
8
+ cfConsultingDiscoveryAgent,
9
+ cfExplorationAgent,
10
+ createExplorationSession,
11
+ getExplorationSession,
12
+ listExplorationSessions,
13
+ mapErrorToMcp,
14
+ requirePremiumWithAutoAuth,
15
+ updateExplorationSession,
16
+ validateRequestSize
17
+ } from "./chunk-ZVWDXO6M.js";
18
+
19
+ // ../mcp/dist/mcp/src/exploration-server.js
20
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
21
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
23
+ var sessionCache = /* @__PURE__ */ new Map();
24
+ function generateId(mode = "product") {
25
+ const prefix = mode === "consulting" ? "cdisc" : "exp";
26
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
27
+ }
28
+ async function createSession(initialInput, mode = "product", uid) {
29
+ const session = {
30
+ id: generateId(mode),
31
+ uid,
32
+ mode,
33
+ currentAct: 1,
34
+ initialInput,
35
+ painPoints: [],
36
+ ideas: [],
37
+ challenges: [],
38
+ scopeItems: [],
39
+ approachOptions: [],
40
+ conversationHistory: [],
41
+ createdAt: Date.now(),
42
+ updatedAt: Date.now()
43
+ };
44
+ const collectionName = mode === "consulting" ? "consulting_discovery_sessions" : "exploration_sessions";
45
+ try {
46
+ await createExplorationSession(session.id, { ...session }, collectionName);
47
+ console.error(`[Discovery] Session created (${mode}): ${session.id}`);
48
+ } catch (err) {
49
+ console.error(`[Discovery] Failed to persist session:`, err);
50
+ }
51
+ sessionCache.set(session.id, session);
52
+ return session;
53
+ }
54
+ async function getSession(id) {
55
+ const isConsulting = id.startsWith("cdisc_");
56
+ const collectionName = isConsulting ? "consulting_discovery_sessions" : "exploration_sessions";
57
+ try {
58
+ const data = await getExplorationSession(id, collectionName);
59
+ if (data) {
60
+ const session = {
61
+ id: data.id || id,
62
+ uid: data.uid,
63
+ mode: data.mode || (isConsulting ? "consulting" : "product"),
64
+ currentAct: data.currentAct,
65
+ initialInput: data.initialInput,
66
+ domainContext: data.domainContext,
67
+ painPoints: data.painPoints || [],
68
+ ideas: data.ideas || [],
69
+ clientContext: data.clientContext,
70
+ challenges: data.challenges || [],
71
+ scopeItems: data.scopeItems || [],
72
+ approachOptions: data.approachOptions || [],
73
+ fitAssessment: data.fitAssessment,
74
+ conversationHistory: data.conversationHistory || [],
75
+ createdAt: data.createdAt?.toMillis?.() || data.createdAt || Date.now(),
76
+ updatedAt: data.updatedAt?.toMillis?.() || data.updatedAt || Date.now()
77
+ };
78
+ sessionCache.set(id, session);
79
+ return session;
80
+ }
81
+ } catch (err) {
82
+ console.error(`[Discovery] Failed to load session:`, err);
83
+ }
84
+ return sessionCache.get(id) || null;
85
+ }
86
+ async function updateSession(session) {
87
+ session.updatedAt = Date.now();
88
+ const collectionName = session.mode === "consulting" ? "consulting_discovery_sessions" : "exploration_sessions";
89
+ try {
90
+ await updateExplorationSession(session.id, { ...session }, collectionName);
91
+ } catch (err) {
92
+ console.error(`[Discovery] Failed to update session:`, err);
93
+ }
94
+ sessionCache.set(session.id, session);
95
+ }
96
+ async function listUserSessions(uid, mode) {
97
+ try {
98
+ const collections = mode ? [mode === "consulting" ? "consulting_discovery_sessions" : "exploration_sessions"] : ["exploration_sessions", "consulting_discovery_sessions"];
99
+ const allSessions = [];
100
+ for (const collectionName of collections) {
101
+ const docs = await listExplorationSessions(collectionName, 20);
102
+ const isConsulting = collectionName === "consulting_discovery_sessions";
103
+ const sessions = docs.map((data) => ({
104
+ id: data.id,
105
+ uid: data.uid,
106
+ mode: data.mode || (isConsulting ? "consulting" : "product"),
107
+ currentAct: data.currentAct,
108
+ initialInput: data.initialInput,
109
+ domainContext: data.domainContext,
110
+ painPoints: data.painPoints || [],
111
+ ideas: data.ideas || [],
112
+ clientContext: data.clientContext,
113
+ challenges: data.challenges || [],
114
+ scopeItems: data.scopeItems || [],
115
+ approachOptions: data.approachOptions || [],
116
+ fitAssessment: data.fitAssessment,
117
+ conversationHistory: data.conversationHistory || [],
118
+ createdAt: data.createdAt?.toMillis?.() || data.createdAt || Date.now(),
119
+ updatedAt: data.updatedAt?.toMillis?.() || data.updatedAt || Date.now()
120
+ }));
121
+ allSessions.push(...sessions);
122
+ }
123
+ return allSessions.sort((a, b) => b.updatedAt - a.updatedAt).slice(0, 20);
124
+ } catch (err) {
125
+ console.error(`[Discovery] Failed to list sessions:`, err);
126
+ const sessions = Array.from(sessionCache.values());
127
+ return mode ? sessions.filter((s) => s.mode === mode) : sessions;
128
+ }
129
+ }
130
+ var server = new Server({
131
+ name: "cutline-exploration",
132
+ version: "0.1.0"
133
+ }, {
134
+ capabilities: {
135
+ tools: {}
136
+ }
137
+ });
138
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
139
+ return {
140
+ tools: [
141
+ {
142
+ name: "discovery_start",
143
+ description: "Start a new discovery session. FREE - no premium required. Supports both product exploration and consulting client discovery modes.",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ input: {
148
+ type: "string",
149
+ description: "Initial input - for product mode: domain/idea (e.g., 'AI tools for real estate agents'); for consulting mode: client/engagement context (e.g., 'Acme Corp - AI strategy assessment')"
150
+ },
151
+ mode: {
152
+ type: "string",
153
+ enum: ["product", "consulting"],
154
+ description: "Discovery mode: 'product' for product/idea exploration (default), 'consulting' for client engagement discovery"
155
+ }
156
+ },
157
+ required: ["input"]
158
+ }
159
+ },
160
+ // Backwards compatibility alias
161
+ {
162
+ name: "exploration_start",
163
+ description: "Start a new product exploration session. FREE - no premium required. (Alias for discovery_start with mode=product)",
164
+ inputSchema: {
165
+ type: "object",
166
+ properties: {
167
+ input: {
168
+ type: "string",
169
+ description: "Initial domain, problem space, or idea to explore"
170
+ }
171
+ },
172
+ required: ["input"]
173
+ }
174
+ },
175
+ {
176
+ name: "discovery_chat",
177
+ description: "Continue a discovery conversation. FREE - no premium required. Works for both product exploration and consulting discovery sessions.",
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ session_id: { type: "string", description: "Session ID from discovery_start or exploration_start" },
182
+ message: { type: "string", description: "Your message or response" },
183
+ advance_act: { type: "boolean", description: "If true, advance to the next act when suggested" }
184
+ },
185
+ required: ["session_id", "message"]
186
+ }
187
+ },
188
+ // Backwards compatibility alias
189
+ {
190
+ name: "exploration_chat",
191
+ description: "Continue an exploration conversation. (Alias for discovery_chat)",
192
+ inputSchema: {
193
+ type: "object",
194
+ properties: {
195
+ session_id: { type: "string", description: "Session ID from exploration_start" },
196
+ message: { type: "string", description: "Your message or response" },
197
+ advance_act: { type: "boolean", description: "If true, advance to the next act when suggested" }
198
+ },
199
+ required: ["session_id", "message"]
200
+ }
201
+ },
202
+ {
203
+ name: "discovery_status",
204
+ description: "Get current status of a discovery session including progress and collected artifacts.",
205
+ inputSchema: {
206
+ type: "object",
207
+ properties: {
208
+ session_id: { type: "string" }
209
+ },
210
+ required: ["session_id"]
211
+ }
212
+ },
213
+ // Backwards compatibility alias
214
+ {
215
+ name: "exploration_status",
216
+ description: "Get current status of an exploration session. (Alias for discovery_status)",
217
+ inputSchema: {
218
+ type: "object",
219
+ properties: {
220
+ session_id: { type: "string" }
221
+ },
222
+ required: ["session_id"]
223
+ }
224
+ },
225
+ {
226
+ name: "discovery_list",
227
+ description: "List all discovery sessions (both product exploration and consulting discovery).",
228
+ inputSchema: {
229
+ type: "object",
230
+ properties: {
231
+ mode: {
232
+ type: "string",
233
+ enum: ["product", "consulting"],
234
+ description: "Optional filter by mode"
235
+ }
236
+ }
237
+ }
238
+ },
239
+ // Backwards compatibility alias
240
+ {
241
+ name: "exploration_list",
242
+ description: "List all exploration sessions. (Alias for discovery_list with mode=product)",
243
+ inputSchema: {
244
+ type: "object",
245
+ properties: {}
246
+ }
247
+ },
248
+ {
249
+ name: "discovery_graduate",
250
+ description: "Graduate from discovery to refinement (premortem). REQUIRES PREMIUM - shows teaser if not subscribed.",
251
+ inputSchema: {
252
+ type: "object",
253
+ properties: {
254
+ session_id: { type: "string" },
255
+ idea_index: { type: "number", description: "For product mode: index of the idea to graduate (0-based). Defaults to top-ranked." },
256
+ approach_index: { type: "number", description: "For consulting mode: index of the approach to graduate (0-based). Defaults to highest fit score." },
257
+ auth_token: { type: "string", description: "Optional Firebase ID token for premium check" }
258
+ },
259
+ required: ["session_id"]
260
+ }
261
+ },
262
+ // Backwards compatibility alias
263
+ {
264
+ name: "exploration_graduate",
265
+ description: "Graduate the top idea from exploration to pre-mortem. (Alias for discovery_graduate)",
266
+ inputSchema: {
267
+ type: "object",
268
+ properties: {
269
+ session_id: { type: "string" },
270
+ idea_index: { type: "number", description: "Index of the idea to graduate (0-based). Defaults to top-ranked idea." },
271
+ auth_token: { type: "string", description: "Optional Firebase ID token for premium check" }
272
+ },
273
+ required: ["session_id"]
274
+ }
275
+ }
276
+ ]
277
+ };
278
+ });
279
+ var PRODUCT_ACT_NAMES = {
280
+ 1: "Domain Framing",
281
+ 2: "Pain Discovery",
282
+ 3: "AI Fit Analysis",
283
+ 4: "Idea Crystallization",
284
+ 5: "Value Ranking",
285
+ 6: "Graduation"
286
+ };
287
+ var CONSULTING_ACT_NAMES = {
288
+ 1: "Client Context",
289
+ 2: "Problem Discovery",
290
+ 3: "Scope Framing",
291
+ 4: "Approach Options",
292
+ 5: "Fit Assessment",
293
+ 6: "Graduation"
294
+ };
295
+ function getActNames(mode) {
296
+ return mode === "consulting" ? CONSULTING_ACT_NAMES : PRODUCT_ACT_NAMES;
297
+ }
298
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
299
+ try {
300
+ const { name, arguments: rawArgs } = request.params;
301
+ if (!rawArgs)
302
+ throw new McpError(ErrorCode.InvalidParams, "Missing arguments");
303
+ validateRequestSize(rawArgs);
304
+ const { args } = guardBoundary(name, rawArgs);
305
+ const rawResponse = await withPerfTracking(name, async () => {
306
+ switch (name) {
307
+ case "discovery_start":
308
+ case "exploration_start": {
309
+ const { input, mode: requestedMode } = args;
310
+ if (!input || typeof input !== "string") {
311
+ throw new McpError(ErrorCode.InvalidParams, "Input is required");
312
+ }
313
+ const mode = name === "exploration_start" ? "product" : requestedMode || "product";
314
+ let uid;
315
+ const session = await createSession(input.trim(), mode, uid);
316
+ const actNames = getActNames(mode);
317
+ let welcomeMessage;
318
+ if (mode === "consulting") {
319
+ welcomeMessage = `Let's explore this consulting engagement: **"${session.initialInput}"**
320
+
321
+ I'll help you understand the client, identify their challenges, define scope, and develop engagement approaches. We'll go through this systematically:
322
+
323
+ 1. **Client Context** - Understand the client and stakeholders
324
+ 2. **Problem Discovery** - Identify their core challenges
325
+ 3. **Scope Framing** - Define what's in and out of scope
326
+ 4. **Approach Options** - Generate engagement approaches
327
+ 5. **Fit Assessment** - Evaluate mutual fit and risks
328
+ 6. **Graduation** - Prepare for deeper analysis
329
+
330
+ \u{1F4A1} **Tip**: I can research the client's company, industry trends, and typical consulting rates. Just ask!
331
+
332
+ Let's start with **Client Context**:
333
+
334
+ Tell me about this potential client. What do you know about:
335
+ - Their company (size, industry, growth stage)?
336
+ - Who you've been talking to (role, influence)?
337
+ - What prompted them to reach out?`;
338
+ } else {
339
+ welcomeMessage = `Let's explore **"${session.initialInput}"** together.
340
+
341
+ I'll help you map out this space, identify pain points, and crystallize concrete product ideas. We'll go through this systematically:
342
+
343
+ 1. **Domain Framing** - Understand the landscape
344
+ 2. **Pain Discovery** - Find the problems worth solving
345
+ 3. **AI Fit Analysis** - Score opportunities for AI leverage
346
+ 4. **Idea Crystallization** - Generate product concepts
347
+ 5. **Value Ranking** - Prioritize by potential
348
+ 6. **Graduation** - Pick a winner to stress-test
349
+
350
+ \u{1F4A1} **Tip**: I have access to real-time web research. At any point, ask me to look up market trends, existing solutions, or industry data\u2014I'll share what I find.
351
+
352
+ Let's start with **Domain Framing**:
353
+
354
+ What's your current connection to this space? Are you:
355
+ - An insider who knows the pain firsthand?
356
+ - An outsider who's observed opportunities?
357
+ - Somewhere in between?
358
+
359
+ And what draws you to explore this area?`;
360
+ }
361
+ session.conversationHistory.push({
362
+ role: "assistant",
363
+ content: welcomeMessage
364
+ });
365
+ await updateSession(session);
366
+ return {
367
+ content: [{
368
+ type: "text",
369
+ text: JSON.stringify({
370
+ session_id: session.id,
371
+ mode: session.mode,
372
+ current_act: session.currentAct,
373
+ act_name: actNames[session.currentAct],
374
+ message: welcomeMessage,
375
+ hint: `Reply with ${mode === "consulting" ? "discovery_chat" : "exploration_chat"} to continue the conversation`
376
+ }, null, 2)
377
+ }]
378
+ };
379
+ }
380
+ case "discovery_chat":
381
+ case "exploration_chat": {
382
+ const { session_id, message, advance_act } = args;
383
+ const session = await getSession(session_id);
384
+ if (!session) {
385
+ throw new McpError(ErrorCode.InvalidRequest, `Session not found: ${session_id}`);
386
+ }
387
+ const actNames = getActNames(session.mode);
388
+ session.conversationHistory.push({
389
+ role: "user",
390
+ content: message
391
+ });
392
+ let response;
393
+ if (session.mode === "consulting") {
394
+ const context = {
395
+ currentAct: session.currentAct,
396
+ initialInput: session.initialInput,
397
+ clientContext: session.clientContext,
398
+ challenges: session.challenges,
399
+ scopeItems: session.scopeItems,
400
+ approachOptions: session.approachOptions,
401
+ conversationHistory: session.conversationHistory,
402
+ fallbackCount: session.fallbackCount || 0
403
+ };
404
+ console.error("[Discovery] Calling Consulting Discovery Cloud Function...");
405
+ response = await cfConsultingDiscoveryAgent(message, context);
406
+ } else {
407
+ const context = {
408
+ currentAct: session.currentAct,
409
+ initialInput: session.initialInput,
410
+ domainContext: session.domainContext,
411
+ painPoints: session.painPoints,
412
+ ideas: session.ideas,
413
+ conversationHistory: session.conversationHistory,
414
+ fallbackCount: session.fallbackCount || 0
415
+ };
416
+ console.error("[Discovery] Calling Exploration Cloud Function...");
417
+ response = await cfExplorationAgent(message, context);
418
+ }
419
+ console.error("[Discovery] Cloud Function response received");
420
+ if (response.wasFallback) {
421
+ session.fallbackCount = (session.fallbackCount || 0) + 1;
422
+ console.error(`[Discovery] Fallback count: ${session.fallbackCount}`);
423
+ } else {
424
+ session.fallbackCount = 0;
425
+ }
426
+ session.conversationHistory.push({
427
+ role: "assistant",
428
+ content: response.content
429
+ });
430
+ if (response.artifacts) {
431
+ for (const artifact of response.artifacts) {
432
+ if (session.mode === "consulting") {
433
+ if (artifact.type === "client_context" && artifact.data) {
434
+ session.clientContext = {
435
+ ...session.clientContext,
436
+ ...artifact.data
437
+ };
438
+ }
439
+ if (artifact.type === "challenge" && artifact.data?.description) {
440
+ session.challenges.push({
441
+ id: generateId(session.mode),
442
+ ...artifact.data
443
+ });
444
+ }
445
+ if (artifact.type === "scope_item" && artifact.data?.item) {
446
+ session.scopeItems.push({
447
+ id: generateId(session.mode),
448
+ ...artifact.data
449
+ });
450
+ }
451
+ if (artifact.type === "approach_option" && artifact.data?.title) {
452
+ session.approachOptions.push({
453
+ id: generateId(session.mode),
454
+ ...artifact.data
455
+ });
456
+ }
457
+ if (artifact.type === "fit_assessment" && artifact.data?.overallFit) {
458
+ session.fitAssessment = artifact.data;
459
+ }
460
+ } else {
461
+ if (artifact.type === "domain_context" && artifact.data) {
462
+ session.domainContext = {
463
+ ...session.domainContext,
464
+ ...artifact.data
465
+ };
466
+ }
467
+ if (artifact.type === "pain_point" && artifact.data) {
468
+ if (artifact.data.description) {
469
+ session.painPoints.push({
470
+ id: generateId(session.mode),
471
+ ...artifact.data
472
+ });
473
+ }
474
+ if (artifact.data.scores) {
475
+ for (const score of artifact.data.scores) {
476
+ if (session.painPoints[score.painPointIndex]) {
477
+ session.painPoints[score.painPointIndex].aiLeverage = score.aiLeverage;
478
+ }
479
+ }
480
+ }
481
+ }
482
+ if (artifact.type === "idea_card" && artifact.data?.title) {
483
+ session.ideas.push({
484
+ id: generateId(session.mode),
485
+ ...artifact.data
486
+ });
487
+ }
488
+ }
489
+ }
490
+ }
491
+ let actAdvanced = false;
492
+ if (response.autoAdvanced && response.suggestedAct) {
493
+ session.currentAct = response.suggestedAct;
494
+ session.fallbackCount = 0;
495
+ actAdvanced = true;
496
+ console.error(`[Discovery] Auto-advanced to act ${session.currentAct} due to fallback loop`);
497
+ } else if (advance_act && response.suggestedAct && response.suggestedAct !== session.currentAct) {
498
+ session.currentAct = response.suggestedAct;
499
+ session.fallbackCount = 0;
500
+ actAdvanced = true;
501
+ }
502
+ await updateSession(session);
503
+ const result = {
504
+ session_id: session.id,
505
+ mode: session.mode,
506
+ current_act: session.currentAct,
507
+ act_name: actNames[session.currentAct],
508
+ message: response.content
509
+ };
510
+ if (response.suggestedAct && response.suggestedAct !== session.currentAct) {
511
+ result.suggested_next_act = response.suggestedAct;
512
+ result.suggested_act_name = actNames[response.suggestedAct];
513
+ result.hint = `To advance to ${actNames[response.suggestedAct]}, add advance_act: true to your next message`;
514
+ }
515
+ if (actAdvanced) {
516
+ result.act_advanced = true;
517
+ result.hint = `Now in ${actNames[session.currentAct]} phase`;
518
+ }
519
+ if (session.mode === "consulting") {
520
+ result.stats = {
521
+ challenges: session.challenges.length,
522
+ scope_items: session.scopeItems.length,
523
+ approaches: session.approachOptions.length,
524
+ messages: session.conversationHistory.length
525
+ };
526
+ } else {
527
+ result.stats = {
528
+ pain_points: session.painPoints.length,
529
+ ideas: session.ideas.length,
530
+ messages: session.conversationHistory.length
531
+ };
532
+ }
533
+ return {
534
+ content: [{
535
+ type: "text",
536
+ text: JSON.stringify(result, null, 2)
537
+ }]
538
+ };
539
+ }
540
+ case "discovery_status":
541
+ case "exploration_status": {
542
+ const { session_id } = args;
543
+ const session = await getSession(session_id);
544
+ if (!session) {
545
+ throw new McpError(ErrorCode.InvalidRequest, `Session not found: ${session_id}`);
546
+ }
547
+ const actNames = getActNames(session.mode);
548
+ if (session.mode === "consulting") {
549
+ const topApproach = session.approachOptions.length > 0 ? session.approachOptions.reduce((best, approach) => approach.fitScore > best.fitScore ? approach : best) : null;
550
+ return {
551
+ content: [{
552
+ type: "text",
553
+ text: JSON.stringify({
554
+ session_id: session.id,
555
+ mode: session.mode,
556
+ current_act: session.currentAct,
557
+ act_name: actNames[session.currentAct],
558
+ progress: `${session.currentAct}/6 acts`,
559
+ initial_input: session.initialInput,
560
+ client_context: session.clientContext,
561
+ challenges: session.challenges.map((c, i) => ({
562
+ index: i,
563
+ description: c.description,
564
+ urgency: c.urgency,
565
+ feasibility: c.feasibility
566
+ })),
567
+ scope_items: session.scopeItems.map((s, i) => ({
568
+ index: i,
569
+ item: s.item,
570
+ in_scope: s.inScope
571
+ })),
572
+ approaches: session.approachOptions.map((a, i) => ({
573
+ index: i,
574
+ title: a.title,
575
+ fit_score: a.fitScore,
576
+ pricing: a.pricing
577
+ })),
578
+ top_approach: topApproach ? {
579
+ title: topApproach.title,
580
+ fit_score: topApproach.fitScore
581
+ } : null,
582
+ fit_assessment: session.fitAssessment,
583
+ can_graduate: session.currentAct >= 5 && session.approachOptions.length > 0,
584
+ created_at: new Date(session.createdAt).toISOString(),
585
+ updated_at: new Date(session.updatedAt).toISOString()
586
+ }, null, 2)
587
+ }]
588
+ };
589
+ } else {
590
+ const topIdea = session.ideas.length > 0 ? session.ideas.reduce((best, idea) => {
591
+ const score = idea.painIntensity * idea.aiLeverage * idea.marketAccessibility / 10;
592
+ const bestScore = best.painIntensity * best.aiLeverage * best.marketAccessibility / 10;
593
+ return score > bestScore ? idea : best;
594
+ }) : null;
595
+ return {
596
+ content: [{
597
+ type: "text",
598
+ text: JSON.stringify({
599
+ session_id: session.id,
600
+ mode: session.mode,
601
+ current_act: session.currentAct,
602
+ act_name: actNames[session.currentAct],
603
+ progress: `${session.currentAct}/6 acts`,
604
+ initial_input: session.initialInput,
605
+ domain_context: session.domainContext,
606
+ pain_points: session.painPoints.map((p, i) => ({
607
+ index: i,
608
+ description: p.description,
609
+ who_feels_it: p.whoFeelsIt,
610
+ ai_leverage: p.aiLeverage
611
+ })),
612
+ ideas: session.ideas.map((idea, i) => ({
613
+ index: i,
614
+ title: idea.title,
615
+ problem: idea.problem,
616
+ solution: idea.solution,
617
+ value_score: Math.round(idea.painIntensity * idea.aiLeverage * idea.marketAccessibility / 10)
618
+ })),
619
+ top_idea: topIdea ? {
620
+ title: topIdea.title,
621
+ value_score: Math.round(topIdea.painIntensity * topIdea.aiLeverage * topIdea.marketAccessibility / 10)
622
+ } : null,
623
+ can_graduate: session.currentAct >= 5 && session.ideas.length > 0,
624
+ created_at: new Date(session.createdAt).toISOString(),
625
+ updated_at: new Date(session.updatedAt).toISOString()
626
+ }, null, 2)
627
+ }]
628
+ };
629
+ }
630
+ }
631
+ case "discovery_list":
632
+ case "exploration_list": {
633
+ const { mode: filterMode } = args;
634
+ const effectiveMode = name === "exploration_list" ? "product" : filterMode;
635
+ const sessions = await listUserSessions(void 0, effectiveMode);
636
+ const sessionList = sessions.map((s) => {
637
+ const actNames = getActNames(s.mode);
638
+ const base = {
639
+ session_id: s.id,
640
+ mode: s.mode,
641
+ current_act: s.currentAct,
642
+ act_name: actNames[s.currentAct],
643
+ initial_input: s.initialInput.slice(0, 50) + (s.initialInput.length > 50 ? "..." : ""),
644
+ updated_at: new Date(s.updatedAt).toISOString()
645
+ };
646
+ if (s.mode === "consulting") {
647
+ return {
648
+ ...base,
649
+ client_name: s.clientContext?.companyName,
650
+ challenges: s.challenges.length,
651
+ approaches: s.approachOptions.length
652
+ };
653
+ } else {
654
+ return {
655
+ ...base,
656
+ pain_points: s.painPoints.length,
657
+ ideas: s.ideas.length
658
+ };
659
+ }
660
+ });
661
+ return {
662
+ content: [{
663
+ type: "text",
664
+ text: JSON.stringify({
665
+ sessions: sessionList,
666
+ count: sessionList.length,
667
+ filter: effectiveMode || "all"
668
+ }, null, 2)
669
+ }]
670
+ };
671
+ }
672
+ case "discovery_graduate":
673
+ case "exploration_graduate": {
674
+ const { session_id, idea_index, approach_index, auth_token } = args;
675
+ const session = await getSession(session_id);
676
+ if (!session) {
677
+ throw new McpError(ErrorCode.InvalidRequest, `Session not found: ${session_id}`);
678
+ }
679
+ let isPremium = false;
680
+ try {
681
+ await requirePremiumWithAutoAuth(auth_token);
682
+ isPremium = true;
683
+ } catch (e) {
684
+ console.error("[Discovery] Auth check failed:", e?.message);
685
+ isPremium = false;
686
+ }
687
+ if (session.mode === "consulting") {
688
+ if (session.approachOptions.length === 0) {
689
+ throw new McpError(ErrorCode.InvalidRequest, "No approaches to graduate. Complete discovery first.");
690
+ }
691
+ let approachToGraduate;
692
+ const idx = approach_index ?? idea_index;
693
+ if (typeof idx === "number") {
694
+ if (idx < 0 || idx >= session.approachOptions.length) {
695
+ throw new McpError(ErrorCode.InvalidRequest, `Invalid approach_index: ${idx}. Valid range: 0-${session.approachOptions.length - 1}`);
696
+ }
697
+ approachToGraduate = session.approachOptions[idx];
698
+ } else {
699
+ approachToGraduate = session.approachOptions.reduce((best, approach) => approach.fitScore > best.fitScore ? approach : best);
700
+ }
701
+ const generateConsultingTeaser = () => {
702
+ let riskCount = session.challenges.filter((c) => c.urgency === "critical" || c.urgency === "high").length;
703
+ riskCount = Math.max(riskCount, 2);
704
+ const fit = session.fitAssessment?.overallFit || "unknown";
705
+ const signal = fit === "strong" ? "\u2705 STRONG FIT" : fit === "good" ? "\u{1F44D} GOOD FIT" : fit === "cautious" ? "\u26A0\uFE0F PROCEED WITH CAUTION" : "\u{1F6A8} CONCERNS";
706
+ return {
707
+ signal,
708
+ risks_found: riskCount,
709
+ scope_items: session.scopeItems.length,
710
+ fit_assessment: fit
711
+ };
712
+ };
713
+ if (!isPremium) {
714
+ const teaser = generateConsultingTeaser();
715
+ return {
716
+ content: [{
717
+ type: "text",
718
+ text: JSON.stringify({
719
+ status: "gate",
720
+ mode: "consulting",
721
+ message: "Full analysis requires Cutline Premium",
722
+ approach: {
723
+ title: approachToGraduate.title,
724
+ fit_score: approachToGraduate.fitScore
725
+ },
726
+ teaser: {
727
+ preliminary_signal: teaser.signal,
728
+ risks_found: teaser.risks_found,
729
+ scope_items: teaser.scope_items,
730
+ message: `Engagement "${approachToGraduate.title}" has ${teaser.risks_found} identified risks.`
731
+ },
732
+ locked: [
733
+ "Full engagement risk analysis",
734
+ "Pricing strategy recommendations",
735
+ "SOW and proposal generation",
736
+ "Go/no-go recommendation with evidence"
737
+ ],
738
+ upgrade: {
739
+ url: "https://thecutline.ai/upgrade",
740
+ message: "Upgrade to unlock full consulting analysis"
741
+ }
742
+ }, null, 2)
743
+ }]
744
+ };
745
+ }
746
+ return {
747
+ content: [{
748
+ type: "text",
749
+ text: JSON.stringify({
750
+ status: "ready",
751
+ mode: "consulting",
752
+ message: "Ready to graduate to consulting analysis!",
753
+ approach: {
754
+ title: approachToGraduate.title,
755
+ description: approachToGraduate.description,
756
+ deliverables: approachToGraduate.deliverables,
757
+ pricing: approachToGraduate.pricing,
758
+ fit_score: approachToGraduate.fitScore
759
+ },
760
+ context: {
761
+ client: session.clientContext,
762
+ challenges: session.challenges,
763
+ scope_items: session.scopeItems,
764
+ fit_assessment: session.fitAssessment,
765
+ discovery_id: session.id
766
+ },
767
+ next_step: {
768
+ tool: "premortem_chat_start",
769
+ hint: "Use premortem_chat_start with mode='consulting' to start the refinement conversation.",
770
+ input_template: {
771
+ input: `Consulting engagement for ${session.clientContext?.companyName || "client"}: ${approachToGraduate.title}`,
772
+ mode: "consulting"
773
+ }
774
+ }
775
+ }, null, 2)
776
+ }]
777
+ };
778
+ } else {
779
+ if (session.ideas.length === 0) {
780
+ throw new McpError(ErrorCode.InvalidRequest, "No ideas to graduate. Complete exploration first.");
781
+ }
782
+ let ideaToGraduate;
783
+ if (typeof idea_index === "number") {
784
+ if (idea_index < 0 || idea_index >= session.ideas.length) {
785
+ throw new McpError(ErrorCode.InvalidRequest, `Invalid idea_index: ${idea_index}. Valid range: 0-${session.ideas.length - 1}`);
786
+ }
787
+ ideaToGraduate = session.ideas[idea_index];
788
+ } else {
789
+ ideaToGraduate = session.ideas.reduce((best, idea) => {
790
+ const score = idea.painIntensity * idea.aiLeverage * idea.marketAccessibility / 10;
791
+ const bestScore = best.painIntensity * best.aiLeverage * best.marketAccessibility / 10;
792
+ return score > bestScore ? idea : best;
793
+ });
794
+ }
795
+ const valueScore = Math.round(ideaToGraduate.painIntensity * ideaToGraduate.aiLeverage * ideaToGraduate.marketAccessibility / 10);
796
+ const generateTeaser = () => {
797
+ let riskCount = 2;
798
+ if (ideaToGraduate.aiLeverage < 6)
799
+ riskCount++;
800
+ if (ideaToGraduate.marketAccessibility < 5)
801
+ riskCount++;
802
+ if (ideaToGraduate.painIntensity < 6)
803
+ riskCount++;
804
+ if (session.domainContext?.assumptions && session.domainContext.assumptions.length > 3) {
805
+ riskCount++;
806
+ }
807
+ riskCount = Math.min(riskCount, 6);
808
+ const assumptionCount = (session.domainContext?.assumptions?.length || 3) + 2;
809
+ const competitorHints = ideaToGraduate.marketAccessibility >= 7 ? 3 : ideaToGraduate.marketAccessibility >= 5 ? 2 : 1;
810
+ let signal;
811
+ if (valueScore >= 60 && riskCount <= 3) {
812
+ signal = "proceed";
813
+ } else if (valueScore >= 40 || riskCount <= 4) {
814
+ signal = "caution";
815
+ } else {
816
+ signal = "concern";
817
+ }
818
+ const signalEmoji = {
819
+ proceed: "\u2705 PROMISING",
820
+ caution: "\u26A0\uFE0F CAUTION",
821
+ concern: "\u{1F6A8} CONCERNS"
822
+ };
823
+ return {
824
+ signal: signalEmoji[signal],
825
+ risks_found: riskCount,
826
+ assumptions_to_validate: assumptionCount,
827
+ competitive_threats: competitorHints
828
+ };
829
+ };
830
+ if (!isPremium) {
831
+ const teaser = generateTeaser();
832
+ return {
833
+ content: [{
834
+ type: "text",
835
+ text: JSON.stringify({
836
+ status: "gate",
837
+ mode: "product",
838
+ message: "Pre-mortem requires Cutline Premium",
839
+ idea: {
840
+ title: ideaToGraduate.title,
841
+ value_score: valueScore
842
+ },
843
+ teaser: {
844
+ preliminary_signal: teaser.signal,
845
+ risks_found: teaser.risks_found,
846
+ assumptions_to_validate: teaser.assumptions_to_validate,
847
+ competitive_threats: teaser.competitive_threats,
848
+ message: `Your idea "${ideaToGraduate.title}" has ${teaser.risks_found} critical risks and ${teaser.assumptions_to_validate} assumptions to validate.`
849
+ },
850
+ locked: [
851
+ "Full risk analysis with mitigations",
852
+ "Assumption prioritization and test plans",
853
+ "Competitive landscape deep dive",
854
+ "Final verdict with clear reasoning",
855
+ "What to validate first"
856
+ ],
857
+ upgrade: {
858
+ url: "https://thecutline.ai/upgrade",
859
+ message: "Upgrade to unlock full pre-mortem analysis"
860
+ },
861
+ hint: "Run 'cutline-mcp login' to authenticate, or visit https://thecutline.ai/upgrade"
862
+ }, null, 2)
863
+ }]
864
+ };
865
+ }
866
+ return {
867
+ content: [{
868
+ type: "text",
869
+ text: JSON.stringify({
870
+ status: "ready",
871
+ mode: "product",
872
+ message: "Ready to graduate to pre-mortem!",
873
+ idea: {
874
+ title: ideaToGraduate.title,
875
+ problem: ideaToGraduate.problem,
876
+ solution: ideaToGraduate.solution,
877
+ why_ai: ideaToGraduate.whyAI,
878
+ value_score: valueScore,
879
+ scores: {
880
+ pain_intensity: ideaToGraduate.painIntensity,
881
+ ai_leverage: ideaToGraduate.aiLeverage,
882
+ market_accessibility: ideaToGraduate.marketAccessibility
883
+ }
884
+ },
885
+ context: {
886
+ domain: session.domainContext,
887
+ pain_points: session.painPoints,
888
+ exploration_id: session.id
889
+ },
890
+ next_step: {
891
+ tool: "premortem_run",
892
+ hint: "\u2615 Use premortem_run with this idea to start the full analysis. It'll take a minute\u2014grab a coffee while our AI agents stress-test your idea!",
893
+ input_template: {
894
+ project: {
895
+ name: ideaToGraduate.title,
896
+ brief: `${ideaToGraduate.problem}
897
+
898
+ Solution: ${ideaToGraduate.solution}
899
+
900
+ Why AI: ${ideaToGraduate.whyAI}`
901
+ }
902
+ }
903
+ }
904
+ }, null, 2)
905
+ }]
906
+ };
907
+ }
908
+ }
909
+ default:
910
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
911
+ }
912
+ });
913
+ return guardOutput(name, rawResponse);
914
+ } catch (error) {
915
+ throw mapErrorToMcp(error, { tool: request.params.name });
916
+ }
917
+ });
918
+ async function run() {
919
+ const transport = new StdioServerTransport();
920
+ await server.connect(transport);
921
+ console.error("Cutline Discovery MCP Server (Stage II-a) running on stdio");
922
+ console.error("Modes: product (exploration) | consulting (client discovery)");
923
+ console.error("Tools: discovery_start, discovery_chat, discovery_status, discovery_list, discovery_graduate");
924
+ console.error(" (plus exploration_* aliases for backwards compatibility)");
925
+ console.error("Discovery is FREE. Graduation to refinement requires premium.");
926
+ }
927
+ run().catch((error) => {
928
+ console.error("Fatal error running server:", error);
929
+ process.exit(1);
930
+ });