Templates module (package init) and the runtime registry:
* TemplateRegistry
- .reload() rescans templates_dir and rebuilds the
{template_id: TemplateSpec} dict atomically
- .query(template_id, qid, args, graph) dispatches to the
graph backend via query_cypher()
- Duplicate template ids in the same dir raise
TemplateRegistryError with both source paths
- Every loaded query is re-validated through the Cypher
allowlist, so a bad body never reaches a backend
- Coerces string-typed optional params the same way the
core tools do ("null" / "None" / "" -> None)
* dynamic_tools.build_dynamic_tools(registry)
- One MCP ToolEntry per declared query: name = query.id,
description = query.description, inputSchema derived
from parameters (required = non-optional names).
- One list_template_tools discovery entry that returns
{templates: [{id, queries: [...]}, ...]} and accepts
an optional template_id filter.
- Same _make_adapter shape as the core tools so the
existing MCPServer dispatcher serves them with no
changes.
* 15 dedicated tests in test_template_registry.py cover:
empty dir, single template, query dispatch, duplicate-id
rejection, reload invalidates cache, dynamic tool schema
shape, list_template_tools (with and without filter),
merge into TOOL_REGISTRY visible in tools/list, end-to-end
tool call (with and without rows), missing required param
yields [], unknown template id / query id raise, and the
defining test: drop a new template file, reload(), see a
new tool appear with no code change.
Suite: 685 -> 700 (+15).
Co-Authored-By: Claude <noreply@anthropic.com>