mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
feat: refactor node spec and add mcp tools (#244)
* refactor: carve out extraction panel * refactor: create spec versions for node types * refactor: create a GenericNode and remove custom nodes * feat: add python and typescript sdk * add dograh sdk * fix: fetch draft workflow definition over published one * fix: fix routes of SDKs to use code gen * chore: remove doclink dependency to reduce image size * chore: format files * chore: bump pipecat * feat: let mcp fetch archived workflows on demand * chore: fix tests * feat: add sdk documentation * chore: change banner and add badge
This commit is contained in:
parent
0a61ef295f
commit
00a1a22b74
162 changed files with 14355 additions and 3554 deletions
124
api/mcp_server/instructions.py
Normal file
124
api/mcp_server/instructions.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""Top-level orchestration guide surfaced to every MCP session.
|
||||
|
||||
Sent to the client via `FastMCP(instructions=...)` — the client bakes
|
||||
this into its system prompt, so every LLM session sees it before the
|
||||
first tool call. Prefer procedural orchestration here (call order, error
|
||||
handling, hard constraints). Design-level per-field guidance belongs in
|
||||
each `PropertySpec.llm_hint`; it flows out through `get_node_type` and
|
||||
doesn't need to be repeated here.
|
||||
|
||||
Extend based on real LLM failures — every bullet below ideally maps to a
|
||||
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.
|
||||
|
||||
## Call order
|
||||
|
||||
1. `list_workflows` — locate the target workflow.
|
||||
2. `get_workflow_code(workflow_id)` — fetch the current source (draft if
|
||||
one exists, otherwise published).
|
||||
3. (optional) `list_node_types` / `get_node_type(name)` — 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(workflow_id, code)` — persist as a new draft. The
|
||||
published version is untouched.
|
||||
|
||||
## Allowed source shape
|
||||
|
||||
The parser is AST-only and rejects anything outside this grammar. At the
|
||||
top level, only three statement forms are accepted:
|
||||
|
||||
import ... from "..."; // any import
|
||||
const <var> = <initializer>; // bindings (see below)
|
||||
wf.edge(<src>, <tgt>, { label, condition }); // bare edge calls
|
||||
|
||||
`<initializer>` is one of:
|
||||
new Workflow({ name: "..." })
|
||||
wf.addTyped(<factory>({ ...fields }) [, { position: [x, y] }])
|
||||
wf.add({ type: "<nodeType>", ...fields [, position: [x, y]] })
|
||||
|
||||
No functions, arrow fns, loops, conditionals, ternaries, spreads,
|
||||
destructuring, template interpolation, `export`, or `.map`/`.forEach`.
|
||||
Data-position values must be plain literals (strings, numbers, booleans,
|
||||
null, arrays/objects of same). A single `new Workflow(...)` per file —
|
||||
the `name` you pass there is the workflow's display name and is applied
|
||||
on save (renames propagate immediately; definition changes go to draft).
|
||||
|
||||
## Adding edges — explicit syntax
|
||||
|
||||
wf.edge(source, target, { label: "...", condition: "..." });
|
||||
|
||||
Rules:
|
||||
- `source` and `target` are the **bare variable identifiers** bound by
|
||||
`wf.addTyped(...)` / `wf.add(...)` — not strings, not `.id`, not inline
|
||||
factories. Both must be declared earlier in the file.
|
||||
- `label` is a short tag (≤4 words) shown in call logs to identify the
|
||||
branch: `"qualified"`, `"wrap up"`, `"retry"`.
|
||||
- `condition` is a full natural-language predicate the runtime evaluates
|
||||
against the live conversation: `"caller confirmed interest in a demo"`,
|
||||
not `"interested"`. Condition clarity determines routing accuracy.
|
||||
- Both fields are required and must be non-empty strings.
|
||||
- Edges are directional; emit one `wf.edge(...)` per outgoing branch.
|
||||
- Place all edges after all node bindings; group by source node.
|
||||
|
||||
Example:
|
||||
|
||||
const greet = wf.addTyped(startCall({ name: "Greet", prompt: "Hi!" }));
|
||||
const done = wf.addTyped(endCall({ name: "Done", prompt: "Bye." }));
|
||||
wf.edge(greet, done, {
|
||||
label: "wrap up",
|
||||
condition: "user acknowledged the greeting and is ready to end"
|
||||
});
|
||||
|
||||
## Hard graph constraints
|
||||
|
||||
- Exactly one `startCall` node per workflow; no incoming edges.
|
||||
- `endCall` nodes have no outgoing edges.
|
||||
- `globalNode` has no incoming or outgoing edges; its prompt is prepended
|
||||
to every other node's prompt at runtime when that node sets
|
||||
`add_global_prompt=true`.
|
||||
- Every non-global node must be reachable from `startCall`.
|
||||
|
||||
## Iterating on errors
|
||||
|
||||
`save_workflow` returns one of:
|
||||
- `parse_error` Disallowed construct (see grammar above) or
|
||||
malformed TypeScript.
|
||||
- `validation_error` Node data failed spec validation (unknown field,
|
||||
missing required, wrong type, bad `options` value).
|
||||
- `graph_validation` Structural rule broken (missing startCall,
|
||||
unreachable node, edge to/from wrong node type).
|
||||
- `bridge_error` Internal — retry once, then surface to the user.
|
||||
|
||||
Every error carries `line` and `column`. Fix at that location and
|
||||
resubmit the **complete source** — this tool does not accept patches.
|
||||
|
||||
## Field conventions
|
||||
|
||||
- `data.name` is the canonical identifier. Pick a descriptive name
|
||||
(`"Qualify Budget"`, not `"Node1"`) — the generated code uses it as
|
||||
the variable name and call logs reference it.
|
||||
- Reference fields take UUIDs, not human names:
|
||||
`tool_refs`, `document_refs` → from `list_tools`, `list_documents`
|
||||
`credential_ref` → from `list_credentials`
|
||||
`recording_ref` → from `list_recordings`
|
||||
- `mention_textarea` fields (prompts, greetings, etc.) accept
|
||||
`{{template_variables}}` — values resolved at runtime from
|
||||
`pre_call_fetch`, caller context, or earlier extraction passes.
|
||||
|
||||
## Style
|
||||
|
||||
- Prefer `wf.addTyped(factory({ ... }))` over `wf.add({ type, ... })`.
|
||||
- Only include fields whose values differ from the spec default — the
|
||||
parser re-applies defaults on save, so extras are noise.
|
||||
- Omit `position`; the server reconciles positions against the previous
|
||||
saved workflow and lays out new nodes automatically.
|
||||
- Add nodes in call-flow order (start → intermediate → end) so the
|
||||
generated code reads top-to-bottom, with all edges after all nodes.
|
||||
"""
|
||||
Loading…
Add table
Add a link
Reference in a new issue