Skip to content

Commit 9c9f3d7

Browse files
committed
feat: ethora-run-recipe auto listing
1 parent dc4a92f commit 9c9f3d7

2 files changed

Lines changed: 24 additions & 7 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Use it from **Cursor**, **VS Code MCP**, **Claude Desktop**, or **Windsurf/Cline
3434
- MCP usage (`ETHORA_API_URL`, `ETHORA_APP_JWT`, `ETHORA_B2B_TOKEN`)
3535
- `ethora-generate-b2b-bootstrap-runbook` — minimal “call these MCP tools in order” runbook for B2B bootstrap
3636

37+
Tip: to list runnable recipes without calling `ethora-help`, call `ethora-run-recipe` with `goal: "auto"` and omit `recipeId`.
38+
3739
- **Session / Config**
3840
- `ethora-configure` — set API URL + App JWT (in-memory for this MCP session)
3941
- `ethora-status` — show configured API URL + whether auth tokens are present

src/tools.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ function runRecipeTool(server: McpServer) {
517517
{
518518
description: "Execute a built-in ethora-help recipe by id (sequential steps, no shell, no file writes).",
519519
inputSchema: {
520-
recipeId: z.string().min(1),
520+
recipeId: z.string().min(1).optional().describe("Recipe id. If omitted, lists runnable recipes for the selected goal."),
521521
goal: z.enum(["auto", "b2b-bootstrap-ai", "broadcast", "sources-ingest", "files-upload", "bot-manage", "user-login"]).optional()
522522
.describe("Optional goal scope to look up recipes; defaults to auto."),
523523
vars: z.record(z.any()).optional().describe("Variables to substitute (appId, appToken, b2bToken, appJwt, email, password, apiUrl, etc)."),
@@ -536,12 +536,15 @@ function runRecipeTool(server: McpServer) {
536536
// Minimal subset of recipes: these IDs match ethora-help.
537537
const out: any = { recipes: [] as any[] }
538538
const apiUrl = String(state.apiUrl || "https://api.ethoradev.com/v1")
539-
if (effectiveGoal === "b2b-bootstrap-ai") {
539+
const includeAll = effectiveGoal === "auto"
540+
541+
if (includeAll || effectiveGoal === "b2b-bootstrap-ai") {
540542
out.recipes.push(
541543
{
542544
id: "b2b-bootstrap-ai",
543545
title: "B2B bootstrap: create app → ingest → enable bot",
544546
description: "Best for partner automation and repeatable provisioning.",
547+
requiredVars: ["b2bToken"],
545548
steps: [
546549
{ tool: "ethora-configure", args: { apiUrl, b2bToken: "<B2B_TOKEN>" } },
547550
{ tool: "ethora-auth-use-b2b" },
@@ -552,6 +555,7 @@ function runRecipeTool(server: McpServer) {
552555
id: "b2b-create-app-only",
553556
title: "B2B: create app only",
554557
description: "Create an app via B2B token (no sources/bot).",
558+
requiredVars: ["b2bToken"],
555559
steps: [
556560
{ tool: "ethora-configure", args: { apiUrl, b2bToken: "<B2B_TOKEN>" } },
557561
{ tool: "ethora-auth-use-b2b" },
@@ -560,11 +564,12 @@ function runRecipeTool(server: McpServer) {
560564
}
561565
)
562566
}
563-
if (effectiveGoal === "broadcast") {
567+
if (includeAll || effectiveGoal === "broadcast") {
564568
out.recipes.push({
565569
id: "broadcast-v2",
566570
title: "Broadcast to chat rooms (v2 job)",
567571
description: "Select app + appToken, switch to app auth, enqueue broadcast, then poll for completion.",
572+
requiredVars: ["appId", "appToken"],
568573
steps: [
569574
{ tool: "ethora-app-select", args: { appId: "<APP_ID>", appToken: "<APP_TOKEN>" } },
570575
{ tool: "ethora-auth-use-app" },
@@ -573,12 +578,13 @@ function runRecipeTool(server: McpServer) {
573578
],
574579
})
575580
}
576-
if (effectiveGoal === "sources-ingest") {
581+
if (includeAll || effectiveGoal === "sources-ingest") {
577582
out.recipes.push(
578583
{
579584
id: "sources-site-crawl-v2",
580585
title: "Ingest website (Sources v2 crawl)",
581586
description: "Crawl a website for RAG ingestion using app-token auth.",
587+
requiredVars: ["appId", "appToken"],
582588
steps: [
583589
{ tool: "ethora-app-select", args: { appId: "<APP_ID>", appToken: "<APP_TOKEN>" } },
584590
{ tool: "ethora-auth-use-app" },
@@ -591,6 +597,7 @@ function runRecipeTool(server: McpServer) {
591597
id: "sources-docs-upload-v2",
592598
title: "Upload docs for ingestion (Sources v2 docs)",
593599
description: "Upload documents for parsing + embeddings using app-token auth.",
600+
requiredVars: ["appId", "appToken", "base64Content"],
594601
steps: [
595602
{ tool: "ethora-app-select", args: { appId: "<APP_ID>", appToken: "<APP_TOKEN>" } },
596603
{ tool: "ethora-auth-use-app" },
@@ -599,11 +606,12 @@ function runRecipeTool(server: McpServer) {
599606
}
600607
)
601608
}
602-
if (effectiveGoal === "bot-manage") {
609+
if (includeAll || effectiveGoal === "bot-manage") {
603610
out.recipes.push({
604611
id: "bot-enable-and-tune",
605612
title: "Enable bot + tune settings (v2)",
606613
description: "Enable a bot for an app and update its prompt/greeting.",
614+
requiredVars: ["appId", "appToken"],
607615
steps: [
608616
{ tool: "ethora-app-select", args: { appId: "<APP_ID>", appToken: "<APP_TOKEN>" } },
609617
{ tool: "ethora-auth-use-app" },
@@ -612,12 +620,13 @@ function runRecipeTool(server: McpServer) {
612620
],
613621
})
614622
}
615-
if (effectiveGoal === "user-login" || effectiveGoal === "files-upload") {
623+
if (includeAll || effectiveGoal === "user-login" || effectiveGoal === "files-upload") {
616624
out.recipes.push(
617625
{
618626
id: "user-login",
619627
title: "User login (for user-auth tools like files)",
620628
description: "Configure appJwt (if needed), switch to user auth, and login.",
629+
requiredVars: ["appJwt", "email", "password"],
621630
steps: [
622631
{ tool: "ethora-configure", args: { apiUrl, appJwt: "<APP_JWT>" } },
623632
{ tool: "ethora-auth-use-user" },
@@ -628,6 +637,7 @@ function runRecipeTool(server: McpServer) {
628637
id: "files-upload-v2",
629638
title: "Upload files (v2)",
630639
description: "Login a user, then call the v2 files upload tool.",
640+
requiredVars: ["email", "password", "base64Content"],
631641
steps: [
632642
{ tool: "ethora-auth-use-user" },
633643
{ tool: "ethora-user-login", args: { email: "<EMAIL>", password: "<PASSWORD>" } },
@@ -639,9 +649,14 @@ function runRecipeTool(server: McpServer) {
639649
return out
640650
})()
641651

652+
// List available runnable recipes when recipeId is omitted.
653+
if (!recipeId) {
654+
return asToolResult(ok({ goal: effectiveGoal, recipes: helpRes?.recipes || [] }, meta))
655+
}
656+
642657
const recipe = (helpRes?.recipes || []).find((r: any) => r.id === String(recipeId))
643658
if (!recipe) {
644-
return asToolResult(fail(new Error(`Unknown recipeId '${recipeId}' for goal '${effectiveGoal}'. Try ethora-help first.`), meta))
659+
return asToolResult(fail(new Error(`Unknown recipeId '${recipeId}' for goal '${effectiveGoal}'. Call ethora-run-recipe without recipeId to list available recipes.`), meta))
645660
}
646661

647662
const v = (vars && typeof vars === "object") ? (vars as any) : {}

0 commit comments

Comments
 (0)