@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.
- package/Dockerfile +11 -0
- package/README.md +248 -0
- package/dist/auth/callback.d.ts +6 -0
- package/dist/auth/callback.js +97 -0
- package/dist/auth/keychain.d.ts +3 -0
- package/dist/auth/keychain.js +16 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +309 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.js +166 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +25 -0
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +38 -0
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.js +278 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +127 -0
- package/dist/commands/upgrade.d.ts +3 -0
- package/dist/commands/upgrade.js +112 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +64 -0
- package/dist/servers/chunk-DE7R7WKY.js +331 -0
- package/dist/servers/chunk-KMUSQOTJ.js +47 -0
- package/dist/servers/chunk-OP4EO6FV.js +454 -0
- package/dist/servers/chunk-UBBAYTW3.js +946 -0
- package/dist/servers/chunk-ZVWDXO6M.js +1063 -0
- package/dist/servers/cutline-server.js +10448 -0
- package/dist/servers/data-client-FPUZBUO3.js +160 -0
- package/dist/servers/exploration-server.js +930 -0
- package/dist/servers/graph-metrics-DCNR7JZN.js +12 -0
- package/dist/servers/integrations-server.js +107 -0
- package/dist/servers/output-server.js +107 -0
- package/dist/servers/premortem-server.js +971 -0
- package/dist/servers/tools-server.js +287 -0
- package/dist/utils/config-store.d.ts +8 -0
- package/dist/utils/config-store.js +35 -0
- package/dist/utils/config.d.ts +22 -0
- package/dist/utils/config.js +48 -0
- package/mcpb/manifest.json +77 -0
- package/package.json +76 -0
- package/server.json +42 -0
- 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
|
+
});
|