feat: add mcp guides for various topic and stages for bot building (#380)

This commit is contained in:
Abhishek 2026-05-31 16:07:32 +05:30 committed by GitHub
parent 0962c4678f
commit 5c29b6ed94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1727 additions and 8 deletions

View file

@ -22,6 +22,18 @@ mistake the system has seen at least once.
DOGRAH_MCP_INSTRUCTIONS = """\
You build and edit Dograh voice-AI workflows by emitting TypeScript that uses the `@dograh/sdk` package. Workflows are stored as JSON; this server projects them to TypeScript for editing and parses them back on save.
## Stages
Every authoring session runs through three stages. Inject the right guidance at each by calling `get_voice_prompting_guide` before you write or revise prompts. Do not skip plan when creating; do not skip review when editing prompt-bearing fields.
1. **Plan** call `get_voice_prompting_guide` with `stage="plan"` first. Decide persona, ordered node list, edges, exit conditions, and tools/credentials needed. Enumerate available `list_node_types`, `list_tools`, `list_credentials`, `list_documents`, `list_recordings` as needed. Present a structured plan to the user and wait for confirmation before writing any code.
2. **Create** call `get_voice_prompting_guide` with `stage="create"` and (when applicable) `node_type=<type>` before writing each node type's prompts. Drill into specific topics via `get_voice_prompting_guide` with `topic=<id>` only when complexity warrants it. Then emit TypeScript and call `create_workflow` (new) or `save_workflow` (edit).
3. **Review** after a successful save, read any `tips[]` returned and surface them to the user with proposed fixes. Call `get_voice_prompting_guide` with `stage="review"` to enumerate review-time concerns (instruction collision, missing handoff cues, success-criteria gaps).
The guide tool is the authoritative source for prompt-authoring craft (turn-taking, persona, readback, disfluencies). Product-mechanics questions (how a node type works at runtime, what `template_variables` resolve to) belong in `search_docs` / `read_doc` instead don't conflate the two.
## Call order
### Reading documentation
@ -33,14 +45,17 @@ You build and edit Dograh voice-AI workflows by emitting TypeScript that uses th
1. `list_workflows` locate the target workflow.
2. `get_workflow_code` fetch the current source for that workflow.
3. (optional) `list_node_types` / `get_node_type` consult before adding or editing a node type whose fields aren't already visible in the current code.
4. Mutate the code in place. Preserve existing nodes, edges, and variable names unless the task requires removing or renaming them.
5. `save_workflow` persist as a new draft. The published version is untouched.
4. (optional) `get_voice_prompting_guide` with `stage="create"` and `node_type=<type>` call before revising any node's prompt field.
5. Mutate the code in place. Preserve existing nodes, edges, and variable names unless the task requires removing or renaming them.
6. `save_workflow` persist as a new draft. The published version is untouched.
### Creating a new workflow
1. Create a simple 1-node workflow with only `startCall`. The user can iteratively add complexity by editing it.
2. `list_node_types` / `get_node_type` consult to learn the fields available on the node types you intend to use.
3. Author SDK TypeScript from scratch. The `new Workflow({ name: "..." })` call is required `name` becomes the workflow's display name.
4. `create_workflow` persists a new workflow as version 1 (published). Returns the new `workflow_id`. For subsequent edits use `save_workflow` (which writes a draft).
1. Run the plan stage (see above) before any code.
2. Create a simple 1-node workflow with only `startCall` if the user just wants a starter. The user can iteratively add complexity by editing it.
3. `list_node_types` / `get_node_type` consult to learn the fields available on the node types you intend to use.
4. `get_voice_prompting_guide` with `stage="create"` and `node_type=<type>` call before writing each node's prompt.
5. Author SDK TypeScript from scratch. The `new Workflow({ name: "..." })` call is required `name` becomes the workflow's display name.
6. `create_workflow` persists a new workflow as version 1 (published). Returns the new `workflow_id`. For subsequent edits use `save_workflow` (which writes a draft).
## Allowed source shape

View file

@ -13,6 +13,7 @@ from api.mcp_server.tools.docs_search import list_docs, read_doc, search_docs
from api.mcp_server.tools.get_workflow_code import get_workflow_code
from api.mcp_server.tools.node_types import get_node_type, list_node_types
from api.mcp_server.tools.save_workflow import save_workflow
from api.mcp_server.tools.voice_prompting_guide import get_voice_prompting_guide
from api.mcp_server.tools.workflows import get_workflow, list_workflows
mcp = FastMCP("dograh", instructions=DOGRAH_MCP_INSTRUCTIONS)
@ -32,6 +33,15 @@ for _tool in (
):
mcp.tool(_tool)
_GUIDE_TOOL_ANNOTATIONS = ToolAnnotations(
readOnlyHint=True,
idempotentHint=True,
destructiveHint=False,
openWorldHint=False,
)
mcp.tool(get_voice_prompting_guide, annotations=_GUIDE_TOOL_ANNOTATIONS)
_DOCS_TOOL_ANNOTATIONS = ToolAnnotations(
readOnlyHint=True,
idempotentHint=True,

View file

@ -0,0 +1,105 @@
"""MCP tool that surfaces voice-prompting guidance to the workflow-authoring LLM.
The guide is split into stages (plan / create / review) and atoms
(topics). Stage calls return a tight briefing an intro plus a list of
relevant topics with one-line lenses. Topic calls return the full
reference content for one atom. No-arg calls return a flat index.
The LLM is expected to read the briefing for the current stage first,
then drill into specific topics only when complexity warrants it. The
authoritative guidance lives in `api.services.voice_prompting_guide`;
this tool is a thin MCP-facing projection.
"""
from __future__ import annotations
from typing import Any, Optional
from fastapi import HTTPException
from api.mcp_server.auth import authenticate_mcp_request
from api.mcp_server.tracing import traced_tool
from api.services.voice_prompting_guide import (
Stage,
build_briefing,
get_topic,
list_topic_index,
)
@traced_tool
async def get_voice_prompting_guide(
stage: Optional[str] = None,
topic: Optional[str] = None,
node_type: Optional[str] = None,
) -> dict[str, Any]:
"""Fetch staged voice-prompting guidance for authoring Dograh workflows.
Call this BEFORE composing or revising any prompt field on a node. The
guide is the authoritative source for prompt-authoring craft (turn-taking,
persona, readback rules, disfluencies); product-mechanics questions
(how a node type works at runtime) belong in `search_docs` / `read_doc`.
Args:
stage: "plan" | "create" | "review". Returns a stage briefing a
short intro plus the list of topics relevant at this stage,
each with a one-line lens. Combine with `node_type` during the
create stage to narrow to topics that apply to that node type's
prompts (e.g. `node_type="agent"`).
topic: A topic id from a prior briefing. Returns the full content
for that atom. Use after the briefing flags a topic worth
drilling into. Mutually exclusive with `stage`.
node_type: Optional filter. Most useful with `stage="create"`.
Returns:
- With `topic`: { id, title, severity, content, stages_relevant,
applies_to_node_types?, cross_refs? }.
- With `stage`: { stage, intro, topics: [{id, title, lens}],
drill_in, filtered_to_node_type? }.
- With no args: { topics: [{id, title}], next }.
Briefings are designed to be cheap read the lens, decide what to
drill into, then ask for full content for the 13 topics that matter
for the prompt you're about to write. Do not pull every topic.
"""
await authenticate_mcp_request()
if topic is not None and stage is not None:
raise ValueError(
"Pass either `topic` or `stage`, not both. Use `stage` for a "
"briefing index; use `topic` for full content of one atom."
)
if topic is not None:
atom = get_topic(topic)
if atom is None:
available = ", ".join(t["id"] for t in list_topic_index())
raise HTTPException(
status_code=404,
detail=(
f"Unknown voice-prompting topic: {topic!r}. "
f"Available topics: {available or '(none registered)'}."
),
)
return atom.to_deep_dict()
if stage is not None:
try:
stage_enum = Stage(stage)
except ValueError:
raise HTTPException(
status_code=400,
detail=(
f"Unknown stage: {stage!r}. "
f"Use one of: {', '.join(s.value for s in Stage)}."
),
)
return build_briefing(stage_enum, node_type=node_type)
return {
"topics": list_topic_index(),
"next": (
"Call with stage='plan'|'create'|'review' for a briefing, or "
"topic=<id> for the full content of one atom."
),
}