mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-25 08:48:13 +02:00
feat: add mcp guides for various topic and stages for bot building (#380)
This commit is contained in:
parent
0962c4678f
commit
5c29b6ed94
22 changed files with 1727 additions and 8 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
105
api/mcp_server/tools/voice_prompting_guide.py
Normal file
105
api/mcp_server/tools/voice_prompting_guide.py
Normal 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 1–3 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."
|
||||
),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue