idea.refine
Refine a SaaS idea through guided Q&A rounds, each turn returning a refined idea with readiness score and audience model until a threshold of 75 is reached, preparing the idea for lead search.
Instructions
Run one round of conversational refinement on a SaaS idea before searching for leads. Behavior: hits the same /refine endpoint the usegorilla.app site uses. Stateless on the server side; the MCP caller must carry history across turns. Does not write any DB rows and does NOT consume a credit. Idempotent. Usage: call this on the first turn with just {idea}, ask the returned question to the user, then call again with the same idea, the previous refined_idea as current_refined_idea, and the new {question, answer} appended to history. Stop when status is 'ready' (readiness_score crosses ~75) or after max_turns. Do NOT call idea.refine after leads.find has already run, the refinement is a pre-search step. Returns: status (ready or needs_answer), refined_idea (full text), readiness_score (0-100) with reason, missing_info list, audience_model, and one next question with suggested options (or null if ready).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| idea | Yes | The original raw idea text. Stays the same across turns. | |
| current_refined_idea | No | The latest refined_idea returned by a previous idea.refine call. Omit on the first turn. | |
| history | No | Prior turns: each entry is the question the server asked and the user's answer. Empty / omitted on the first turn. | |
| language | No | Output language. 'all' (default) auto-detects from the idea text. | |
| turn | No | 1-based turn number. Lets the server stop sooner if needed. | |
| max_turns | No | Maximum rounds before the server forces status='ready'. Default 5. |
Implementation Reference
- src/index.ts:298-393 (registration)Registration of the 'idea.refine' tool on the MCP server with its description, schema, and metadata.
server.tool( "idea.refine", "Run one round of conversational refinement on a SaaS idea before searching for leads. Behavior: hits the same /refine endpoint the usegorilla.app site uses. Stateless on the server side; the MCP caller must carry history across turns. Does not write any DB rows and does NOT consume a credit. Idempotent. Usage: call this on the first turn with just {idea}, ask the returned question to the user, then call again with the same idea, the previous refined_idea as current_refined_idea, and the new {question, answer} appended to history. Stop when status is 'ready' (readiness_score crosses ~75) or after max_turns. Do NOT call idea.refine after leads.find has already run, the refinement is a pre-search step. Returns: status (ready or needs_answer), refined_idea (full text), readiness_score (0-100) with reason, missing_info list, audience_model, and one next question with suggested options (or null if ready).", { idea: z.string().describe("The original raw idea text. Stays the same across turns."), current_refined_idea: z .string() .optional() .describe("The latest refined_idea returned by a previous idea.refine call. Omit on the first turn."), history: z .array( z.object({ question: z.string(), answer: z.string(), }), ) .optional() .describe("Prior turns: each entry is the question the server asked and the user's answer. Empty / omitted on the first turn."), language: z .enum(["en", "pt", "all"]) .optional() .describe("Output language. 'all' (default) auto-detects from the idea text."), turn: z .number() .int() .optional() .describe("1-based turn number. Lets the server stop sooner if needed."), max_turns: z .number() .int() .optional() .describe("Maximum rounds before the server forces status='ready'. Default 5."), }, { title: "Refine idea", readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, async ({ idea, current_refined_idea, history, language, turn, max_turns }) => { const err = requireKey(); if (err) return err; const body: Record<string, unknown> = { idea }; if (current_refined_idea) body.current_refined_idea = current_refined_idea; if (history && history.length) body.history = history; const effectiveLanguage = language ?? (GORILLA_DEFAULT_LANGUAGE === "en" || GORILLA_DEFAULT_LANGUAGE === "pt" || GORILLA_DEFAULT_LANGUAGE === "all" ? GORILLA_DEFAULT_LANGUAGE : undefined); if (effectiveLanguage) body.language = effectiveLanguage; if (typeof turn === "number") body.turn = turn; if (typeof max_turns === "number") body.max_turns = max_turns; const r = await call<{ status: "ready" | "needs_answer"; refined_idea: string; readiness_score: number; readiness_reason: string; missing_info: string[]; audience_model: unknown; question: { question: string; options?: string[] } | null; }>("POST", "refine", body); const lines: string[] = []; lines.push(`Status: ${r.status} (readiness ${r.readiness_score}/100)`); lines.push(""); lines.push(`Refined idea:\n ${r.refined_idea}`); if (r.missing_info.length) { lines.push(""); lines.push(`Still missing: ${r.missing_info.join("; ")}`); } if (r.status === "needs_answer" && r.question) { lines.push(""); lines.push(`Next question: ${r.question.question}`); if (r.question.options && r.question.options.length) { lines.push(`Suggested answers: ${r.question.options.join(" | ")}`); } lines.push(""); lines.push( "Call idea.refine again with the same `idea`, this question's `refined_idea` as `current_refined_idea`, and a `history` array including {question, answer}.", ); } else { lines.push(""); lines.push("Idea is ready. Pass `refined_idea` to leads.find."); } return { content: [{ type: "text" as const, text: lines.join("\n") }], }; }, ); - src/index.ts:301-330 (schema)Input schema for idea.refine: idea (required), current_refined_idea, history (array of {question, answer}), language (enum en/pt/all), turn, and max_turns.
{ idea: z.string().describe("The original raw idea text. Stays the same across turns."), current_refined_idea: z .string() .optional() .describe("The latest refined_idea returned by a previous idea.refine call. Omit on the first turn."), history: z .array( z.object({ question: z.string(), answer: z.string(), }), ) .optional() .describe("Prior turns: each entry is the question the server asked and the user's answer. Empty / omitted on the first turn."), language: z .enum(["en", "pt", "all"]) .optional() .describe("Output language. 'all' (default) auto-detects from the idea text."), turn: z .number() .int() .optional() .describe("1-based turn number. Lets the server stop sooner if needed."), max_turns: z .number() .int() .optional() .describe("Maximum rounds before the server forces status='ready'. Default 5."), }, - src/index.ts:338-393 (handler)Handler function for idea.refine: builds request body, calls POST /refine endpoint, formats and returns the response including status, readiness score, refined idea, missing info, and next question.
async ({ idea, current_refined_idea, history, language, turn, max_turns }) => { const err = requireKey(); if (err) return err; const body: Record<string, unknown> = { idea }; if (current_refined_idea) body.current_refined_idea = current_refined_idea; if (history && history.length) body.history = history; const effectiveLanguage = language ?? (GORILLA_DEFAULT_LANGUAGE === "en" || GORILLA_DEFAULT_LANGUAGE === "pt" || GORILLA_DEFAULT_LANGUAGE === "all" ? GORILLA_DEFAULT_LANGUAGE : undefined); if (effectiveLanguage) body.language = effectiveLanguage; if (typeof turn === "number") body.turn = turn; if (typeof max_turns === "number") body.max_turns = max_turns; const r = await call<{ status: "ready" | "needs_answer"; refined_idea: string; readiness_score: number; readiness_reason: string; missing_info: string[]; audience_model: unknown; question: { question: string; options?: string[] } | null; }>("POST", "refine", body); const lines: string[] = []; lines.push(`Status: ${r.status} (readiness ${r.readiness_score}/100)`); lines.push(""); lines.push(`Refined idea:\n ${r.refined_idea}`); if (r.missing_info.length) { lines.push(""); lines.push(`Still missing: ${r.missing_info.join("; ")}`); } if (r.status === "needs_answer" && r.question) { lines.push(""); lines.push(`Next question: ${r.question.question}`); if (r.question.options && r.question.options.length) { lines.push(`Suggested answers: ${r.question.options.join(" | ")}`); } lines.push(""); lines.push( "Call idea.refine again with the same `idea`, this question's `refined_idea` as `current_refined_idea`, and a `history` array including {question, answer}.", ); } else { lines.push(""); lines.push("Idea is ready. Pass `refined_idea` to leads.find."); } return { content: [{ type: "text" as const, text: lines.join("\n") }], }; }, );