MCP Tools
Symphony provides MCP (Model Context Protocol) tools to agents over stdio. Each agent session gets an ephemeral MCP server initialized with the agent's issueId and projectId.
Overview
| Tool | Purpose |
|---|---|
get_issue | Read issue details |
get_issue_status | Quick status check (slim version of get_issue) |
update_issue_status | Change issue status |
add_finding | Record a structured finding |
add_learning | Record a reusable pattern |
add_comment | Post a comment on the issue |
create_issue | Create a new issue |
create_subtask | Create a child issue |
list_issues | List project issues |
search_learnings | Search learnings across issues |
create_pr | Create a PR record with diff and gates |
approve_pr | Approve the current PR |
reject_pr | Reject the current PR |
configure_project | Configure project settings |
complete_phase | Submit a phase completion claim |
approve_phase | Record judge approval of a phase artifact |
reject_phase | Record judge rejection with reason + fix instructions |
record_coverage | Record research coverage for a file+concept |
move_issue | Move an issue to a different project |
verify_workspace | Verify the agent is in the correct worktree |
record_test_health | Record test health metrics per agent run |
search_knowledge | Search knowledge documents by semantic similarity |
vault_retrieve | Semantic search over the Knowledge Vault |
vault_update | Re-index a file in the Knowledge Vault |
vault_delete | Remove a knowledge entry from the vault |
query_run_events | Query agent run events for debugging |
get_issue_status | Check current issue status |
update_memory | Persist agent memory across runs |
query_run_events | Query structured agent stream events for traceability |
get_issue_status | Quick status check with recent findings and runs |
Tool Details
get_issue
Get details of an issue. Defaults to the currently assigned issue.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
issue_id | string | no | Issue ID (defaults to current issue) |
Example:
{}Returns issue title, description, status, priority, findings, learnings, and comments.
update_issue_status
Change the status of the current issue.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
status | string | yes | New status: review, todo, or blocked |
reason | string | no | Reason for the change |
Valid transitions from in_progress: review (done), todo (give up), blocked (need human input).
Example:
{ "status": "review", "reason": "Implementation complete, all tests pass" }add_finding
Record a structured finding (test result, bug, pattern, gap).
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
category | string | yes | test_result, code_pattern, architecture, bug, or gap |
summary | string | yes | Short scannable summary |
details | string | no | Full details |
files | string[] | no | Related file paths |
Example:
{
"category": "bug",
"summary": "Race condition in worktree cleanup",
"details": "When two agents exit simultaneously, rmSync can fail on shared lock files",
"files": ["server/orchestrator/workspaceManager.ts"]
}add_learning
Record a reusable pattern or gotcha for future agents. Includes quality gates for deduplication and minimum detail requirements.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
pattern | string | yes | Descriptive pattern (min 50 chars). Start with the subject. |
context | string | no | When/where this applies (min 100 chars if provided) |
applies_to | string[] | no | Directory/file prefixes for relevance matching |
learning_type | string | no | convention, gotcha, or pattern |
Quality gates:
- Pattern must be at least 50 characters (rejects vague "use TDD" style learnings)
- Context must be at least 100 characters when provided
- Duplicate patterns within the same issue are rejected (fuzzy matching via normalization)
- New learnings start with
quality_score: 50
Example:
{
"pattern": "Nuxt auto-imports files in app/utils/ — don't add explicit imports or you'll get already-defined errors",
"context": "Nuxt 4 convention applies to all files in app/utils/, app/composables/, and app/components/ directories. The auto-import system scans these dirs at build time.",
"applies_to": ["app/utils/", "app/composables/"]
}add_comment
Post a comment on the current issue.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
content | string | yes | Comment text |
Example:
{ "content": "Found the root cause — the migration was missing a NOT NULL constraint" }create_issue
Create a new issue on the project board.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
title | string | yes | Issue title |
description | string | no | Issue description |
priority | number | no | 1=urgent, 2=high, 3=medium (default), 4=low |
labels | string[] | no | Labels |
depends_on | string[] | no | Issue IDs this depends on |
Example:
{
"title": "Add input validation to POST /api/projects/:id/issues",
"description": "Title should be max 500 chars, priority 1-4, status must be valid enum",
"priority": 2,
"labels": ["validation"]
}create_subtask
Create a sub-task of the current issue. Sets parent to blocked until the sub-task is done.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
title | string | yes | Sub-task title |
description | string | no | Description |
priority | number | no | 1-4 (default: 3) |
labels | string[] | no | Labels |
Example:
{
"title": "Add unit tests for dispatcher priority sorting",
"description": "Test that judges dispatch before workers, and workers before planners",
"priority": 2
}list_issues
List issues in the current project.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
status | string | no | Filter by status |
labels | string | no | Filter by label |
limit | number | no | Max results (default 50, max 100) |
Example:
{ "status": "backlog", "limit": 20 }search_learnings
Search learnings across all issues in the project using FTS5 with BM25 ranking. Results are ranked by combined relevance (50% BM25 + 30% field priority + 20% quality score).
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
query | string | yes | Search query (supports prefix matching and stemming) |
limit | number | no | Max results (default 50, max 100) |
cross_project | boolean | no | Search other projects instead of current (default false) |
min_quality_score | number | no | Filter out learnings below this quality threshold (0-100). Verified learnings always pass. |
Example:
{ "query": "auto-import", "min_quality_score": 40 }create_pr
Create a local PR record with diff snapshot, file stats, gate results, and risk score. Moves issue to review.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
diff | string | yes | Full git diff output |
branch_name | string | yes | Source branch name |
base_branch | string | yes | Target branch name |
gate_results | array | no | Quality gate results |
Each gate result object:
| Field | Type | Required | Description |
|---|---|---|---|
command | string | yes | Command that was run |
label | string | yes | Human-readable label |
exit_code | number | yes | Exit code (0 = pass) |
output | string | yes | Command output |
required | boolean | no | Whether this gate is required |
Example:
{
"diff": "diff --git a/server/utils/...",
"branch_name": "symphony/SYM-001",
"base_branch": "main",
"gate_results": [
{ "command": "npx vitest run", "label": "Tests", "exit_code": 0, "output": "42 passed", "required": true },
{ "command": "npx nuxt build", "label": "Build", "exit_code": 0, "output": "Build succeeded", "required": true }
]
}approve_pr
Approve the current PR. Sets PR status to approved. Issue stays in review for human merge.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
reason | string | no | Approval reason |
Example:
{ "reason": "Code changes match requirements, all gates pass, good test coverage" }reject_pr
Reject the current PR. Sets PR status to rejected, moves issue to todo for rework.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
reason | string | no | Rejection reason |
fix_instructions | string | no | What the worker should fix |
Example:
{
"reason": "Missing error handling in the new API endpoint",
"fix_instructions": "Add try/catch in server/api/projects/[projectId]/issues/index.post.ts around the DB insert. Return 400 for validation errors, 500 for unexpected errors."
}configure_project
Configure project settings. Used primarily by the setup wizard.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
gates | array | no | Quality gate commands |
sensitive_paths | string[] | no | Glob patterns for sensitive files |
risk_thresholds | object | no | Risk scoring thresholds |
worker_instructions | string | no | Custom instructions for workers |
judge_instructions | string | no | Custom instructions for judges |
Each gate object:
| Field | Type | Required | Description |
|---|---|---|---|
command | string | yes | Command to run |
label | string | yes | Human-readable label |
required | boolean | yes | Whether this gate must pass |
Example:
{
"gates": [
{ "command": "npx vitest run", "label": "Tests", "required": true },
{ "command": "npx nuxt build", "label": "Build", "required": true }
],
"sensitive_paths": ["server/database/*", ".env*"],
"risk_thresholds": { "lowLines": 50, "highLines": 500, "lowFiles": 3, "highFiles": 15 }
}complete_phase
Submit a phase completion claim. Validates artifact content structure before recording the claim as a phase_completion finding. The orchestrator performs the transition on the next tick.
Content Validation (pre-ready phases only):
Before recording the claim, the tool validates the artifact content to give agents immediate feedback:
- Minimum length: Artifact must be at least 100 characters (trimmed)
- Markdown format: Artifact must contain at least one Markdown heading (
#or##) - Required sections: If the phase contract is available, checks that all
required_sectionsare present as##headings
If validation fails, the tool returns an error with actionable details so the agent can fix the artifact in the same turn — avoiding wasted retries from the orchestrator rejecting the claim after the agent exits.
The orchestrator still validates independently as defense-in-depth. Content validation is skipped for phase:ready (the "artifact" is committed code).
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
completed_phase | string | yes | Phase being completed: research, architecture, grooming, or ready |
contract_version | number | yes* | Must match the contract's version (currently 1). *Not required for ready. |
artifact_path | string | yes* | Relative path to the artifact (must be inside docs/tickets/). *Not required for ready. |
artifact_hash | string | yes* | SHA-256 hash of the artifact content. *Not required for ready. |
summary | string | yes | Brief summary of the artifact |
open_questions | string[] | no | Unresolved questions |
confidence | string | no | high, medium, or low (default: medium) |
promote_to | string | no | Target path for promotion into project docs tree (must be inside docs/) |
recommended_next_phase | string | yes* | Suggested next phase. *Not required for ready. |
project_path | string | yes | Absolute path to the project root |
contracts_dir | string | no | Path to phase contracts directory (defaults to prompts/phases/) |
Example:
{
"completed_phase": "architecture",
"contract_version": 1,
"artifact_path": "docs/tickets/SYM-033/design.md",
"artifact_hash": "abc123...",
"summary": "Design proposes token counting middleware with 3 budget tiers",
"recommended_next_phase": "grooming",
"project_path": "/path/to/project"
}Validation errors return details like:
Artifact content too short (42 chars, minimum 100)Artifact does not appear to be Markdown — no headings foundArtifact missing required sections: risks, recommendation
Used by: researcher (research, architecture phases), planner (grooming phase), worker (ready phase).
approve_phase
Record a judge approval verdict on a phase artifact. Does NOT perform the transition — the orchestrator reads the verdict and advances the phase.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
phase | string | yes | Phase being approved |
artifact_hash | string | yes | Hash of the artifact being approved |
reason | string | no | Approval reason |
Example:
{
"phase": "architecture",
"artifact_hash": "abc123...",
"reason": "Design is coherent, all required sections present"
}Used by: judge (phase review only, not PR review).
reject_phase
Record a judge rejection verdict on a phase artifact. Adds a comment with rejection details and fix instructions.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
phase | string | yes | Phase being rejected |
artifact_hash | string | yes | Hash of the artifact being rejected |
reason | string | yes | Rejection reason |
fix_instructions | string | no | What the agent should fix |
Example:
{
"phase": "architecture",
"artifact_hash": "abc123...",
"reason": "Missing rollback strategy",
"fix_instructions": "Add a rollout section covering how to revert if the migration fails"
}Used by: judge (phase review only, not PR review).
record_coverage
Record research coverage for a file+concept combination. Call this for every file examined during a research session. Used by the scanner to track what has been reviewed and avoid duplicate work.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
file_path | string | yes | Relative file path examined |
concept | string | yes | Research concept (e.g., test_coverage, error_handling, type_safety) |
status | string | yes | covered = no issues found, has_issues = a ticket was created |
ticket_id | string | no | ID of the ticket created (if status is has_issues) |
notes | string | no | Brief observation about the file for this concept |
Example:
{
"file_path": "server/orchestrator/dispatcher.ts",
"concept": "error_handling",
"status": "has_issues",
"ticket_id": "SYM-042",
"notes": "Unhandled promise rejection on DB timeout"
}Used by: scanner (background research agent).
move_issue
Move an issue to a different project. Copies artifact files, re-prefixes the identifier, and resets the issue status to todo. Preserves the current phase label if compatible with the target project's pipeline, or resets to the target's first phase if incompatible. Adds moved-from:<old-identifier> labels for traceability and skip:<phase> labels for phases with existing artifacts.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
target_project_id | string | yes | ID of the destination project |
reason | string | yes | Why the issue is being moved |
Example:
{
"target_project_id": "proj_abc123",
"reason": "Implementation requires changes to podium.api, not symphony"
}Constraints: Issue must have a phase:* label or a cross-project/multi-repo label. Cannot move issues in done or cancelled status.
verify_workspace
Verify that the agent is operating in the correct worktree. Agents should call this as step 0 before starting work. Returns workspace metadata on success, or an error with recovery instructions if the agent has escaped its assigned worktree.
Parameters: None.
Returns (success):
{
"valid": true,
"path": "/path/to/worktree",
"issue": "SYM-001",
"branch": "symphony/SYM-001",
"message": "Workspace verified. You are in the correct worktree."
}Returns (failure):
{
"error": "Worktree marker file not found at /path/.symphony-worktree. Recovery: run `cd /path/to/worktree` to return to your worktree."
}The tool reads a .symphony-worktree marker file written by the orchestrator when creating/reusing worktrees. Critical MCP tools (complete_phase, create_pr) also validate the worktree automatically before executing.
Used by: all agents operating in worktrees (worker, researcher, planner, architect).
record_test_health
Record test health metrics after running tests. Call this after each test run to track test count trends and detect test bloat.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
test_count | number | yes | Total number of test cases after this run |
test_files_changed | number | no | Number of test files modified in this run |
tests_added | number | no | Number of new test cases added |
tests_removed | number | no | Number of test cases removed or consolidated |
execution_time_ms | number | no | Test suite execution duration in milliseconds |
Example:
{
"test_count": 150,
"tests_added": 5,
"tests_removed": 1,
"test_files_changed": 3,
"execution_time_ms": 30000
}Used by: worker (after running test suite).
get_issue_status
Get the essential state of an issue: status, labels, last 5 finding summaries, and last 3 run outcomes. Use this for quick state checks at the start of a run. Much smaller than get_issue — use get_issue only when you need the full description, all findings details, or complete run outputs.
search_knowledge
Search uploaded knowledge documents by semantic similarity. Returns ranked text snippets with source attribution and similarity scores. Requires the project to have a vault configuration section.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
query | string | yes | Natural language search query to find relevant knowledge chunks |
top_k | number | no | Maximum number of results to return (default 5, max 20) |
min_score | number | no | Minimum similarity score threshold (default 0.3) |
Example:
{ "query": "retry pattern with exponential backoff", "top_k": 10, "min_score": 0.4 }Used by: all profiles.
vault_retrieve
Semantic search over the Knowledge Vault using cosine similarity on embeddings. Returns ranked results with text, path, score, and metadata.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
query | string | yes | Natural language search query for semantic similarity matching |
top_k | number | no | Number of results to return (default 5, max 50) |
Example:
{ "query": "retry pattern with exponential backoff", "top_k": 10 }Used by: all profiles.
vault_update
Re-read, re-extract, and re-embed a file in the Knowledge Vault. Use when a file has changed and the vault entry is stale.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
path | string | yes | Path to the file to re-index (relative to project root) |
Example:
{ "path": "docs/patterns/retry-with-backoff.md" }Used by: worker, researcher.
vault_delete
Remove a knowledge entry and all associated vectors from the Knowledge Vault. Use when content is obsolete or incorrect.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
path | string | yes | Path of the knowledge entry to remove from the vault |
Example:
{ "path": "docs/deprecated/old-pattern.md" }Used by: worker only.
query_run_events
Query agent run events for debugging and investigation.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
run_id | string | no | Filter by specific agent run ID |
limit | number | no | Max results (default 50) |
Used by: judge, researcher, architect.
get_issue_status
Check the current status of an issue. Lightweight alternative to get_issue when only the status is needed.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
issue_id | string | no | Issue ID (defaults to current issue) |
Used by: worker, researcher, judge, architect, planner.
update_memory
Persist knowledge for the agent's future runs. Unlike add_learning (issue-scoped, shared with other agents), memory is private to the agent's profile type and always injected into the system prompt on every future run. Returns: id, identifier, title, status, labels, priority, size, recentFindings (last 5 summaries), recentRuns (last 3 with status/error/cost), updatedAt.
Example:
{}Used by: worker, researcher, judge, architect, planner.
query_run_events
Query structured agent stream events for reasoning traceability. Use to diagnose context management issues, capabilities issues, and system issues. Filter by run_id for a single run trace, or issue_id for all runs on an issue.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
scope | string | yes | project (specific to this project) or global (applies across all projects) |
filename | string | yes | Memory file name (e.g., codebase-notes.md). Must end in .md. Alphanumeric, hyphens, and underscores only. |
content | string | yes | Full content to write. Overwrites existing file. Max 10,000 characters. |
Constraints:
- Requires
--agent-type,--symphony-dir, and--project-slug(for project scope) CLI args on the MCP server - Filename is validated against
/^[a-zA-Z0-9_-]+\.md$/to prevent path traversal - Atomic write (write to temp file, then rename) prevents partial reads
- Memory files are stored at
data/memory/<projectSlug>/<profileType>/(project) ordata/memory/_global/<profileType>/(global) |issue_id| string | no | Return events across all runs for this issue | |run_id| string | no | Return events for a specific agent run | |event_type| string | no | Filter by event type:system,assistant,user,result,rate_limit_event| |event_subtype| string | no | Filter by subtype:init,thinking,text,tool_use,tool_result| |limit| number | no | Max results (default 100, max 500) | |offset| number | no | Pagination offset |
Note: Either run_id or issue_id is required.
Example:
{
"scope": "project",
"filename": "codebase-notes.md",
"content": "# Codebase Notes\n\n- Use WAL mode for SQLite\n- Tests use in-memory DB via createTestDb()"
}Used by: all agent profiles.
Tool Scope by Profile
Not all tools are available to every agent type. The following matrix shows which tools each profile can use:
| Tool | Worker | Researcher | Judge | Scanner | Architect | Planner | Intake |
|---|---|---|---|---|---|---|---|
get_issue | ✅ | ✅ | ✅ | — | ✅ | ✅ | — |
get_issue_status | ✅ | ✅ | ✅ | — | ✅ | ✅ | — |
update_issue_status | ✅ | — | ✅ | — | — | ✅ | — |
add_finding | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
add_learning | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — |
add_comment | ✅ | ✅ | ✅ | — | ✅ | ✅ | — |
create_issue | ✅ | — | — | ✅ | ✅ | — | ✅ |
create_subtask | ✅ | — | — | — | — | ✅ | — |
list_issues | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ |
search_learnings | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — |
create_pr | ⚠️ | — | — | — | — | — | — |
approve_pr | — | — | ✅ | — | — | — | — |
reject_pr | — | — | ✅ | — | — | — | — |
configure_project | — | — | — | — | — | — | — |
complete_phase | ✅ | ✅ | — | — | ✅ | ✅ | — |
approve_phase | — | — | ✅ | — | — | — | — |
reject_phase | — | — | ✅ | — | — | — | — |
record_coverage | — | — | — | ✅ | — | — | — |
move_issue | — | — | — | — | — | ✅ | — |
verify_workspace | ✅ | ✅ | — | — | ✅ | ✅ | — |
record_test_health | ✅ | — | — | — | — | — | — |
search_knowledge | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
vault_retrieve | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
vault_update | ✅ | ✅ | — | — | — | — | — |
vault_delete | ✅ | — | — | — | — | — | — |
query_run_events | — | ✅ | ✅ | — | ✅ | — | — |
get_issue_status | ✅ | ✅ | ✅ | — | ✅ | ✅ | — |
update_memory | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
query_run_events | — | ✅ | ✅ | — | ✅ | — | — |
Legend: ✅ = available, — = not available, ⚠️ = deprecated (use complete_phase instead)
Notes:
create_pris deprecated for all profiles — the orchestrator auto-creates PRs from committed changes when workers callcomplete_phaseconfigure_projectis used by the setup wizard, not by any agent profile directlyrecord_coverageis exclusively for the scanner to track which files/concepts have been reviewedmove_issueis used by the planner for cross-project routing during the grooming phaseverify_workspaceis available to all worktree-based agents — verifies the agent is in the correct worktree via the.symphony-worktreemarker filevault_retrieveis available to all profiles for reading from the global Knowledge Vault.vault_updateis restricted to worker and researcher.vault_deleteis worker-only. Vault tools are global (not issue-scoped) and depend on SYM-103 vector store integration.
For detailed tool parameters and examples, see the Tool Details section above.
MCP Server Architecture
The MCP server is created per-agent session in server/mcp/server.ts:
- The server registers all tools at startup via
server/mcp/tools/registry.ts - Each tool receives a
ToolContextwith{ db, issueId, projectId } - Tool handlers are synchronous (SQLite is synchronous via
better-sqlite3) - Errors are returned as MCP tool errors, not thrown
Server Initialization
The MCP server is launched as:
node server/mcp/index.ts --issue-id <id> --db <path> [--project-id <id>] [--worktree-path <path>] [--agent-type <type>] [--project-slug <slug>] [--symphony-dir <path>]Initialization sequence:
- Parse
--issue-id,--db, optional--project-id,--worktree-path,--agent-type,--tool-scope-file,--project-slug,--symphony-dir - Open SQLite with WAL mode,
busy_timeout=5000, foreign key constraints - Resolve
projectId— use explicit arg or look up from the issue in the DB - If
--tool-scope-fileis provided, read it as a JSON string array of tool names - Create MCP server with
StdioServerTransport, applying tool scope filtering - Block on stdio, processing tool calls until the agent exits
Tool Scope Filtering
The MCP server applies two-tier enforcement with three levels of precedence:
--tool-scope-file(highest priority): When provided, the MCP server reads the JSON file containing an array of tool names computed at dispatch time. Only these tools are exposed. This file is generated bycomputeEffectiveTools()based on the agent profile'sallowed_toolsanddisallowed_toolscolumns.--agent-type(fallback): When--tool-scope-fileis absent but--agent-typeis provided, filtering uses the hardcodedTOOL_REGISTRYallowedProfiles (legacy behavior).Neither (backward compat): All tools exposed (e.g., setup wizard).
Both list-time and call-time enforcement apply:
List-time filtering: The
ListToolsRequesthandler only returns tools in the effective scope. This saves context window tokens by not exposing irrelevant tool schemas.Call-time enforcement: The
CallToolRequesthandler rejects calls to out-of-scope tools with aPERMISSION_DENIEDerror, even if the agent somehow attempts them. This is a security boundary.
Configurable Tool Scope (SYM-114)
Tool scope is now configurable per agent profile via two JSON columns on agent_profiles:
allowed_tools(JSON string[] | null): Explicit allowlist.null= inherit all registry defaults for this profile type.disallowed_tools(JSON string[] | null): Explicit denylist.null= no removals.
The effective tool set is computed at dispatch time by computeEffectiveTools():
- Start with registry defaults for the profile type
- If
allowed_toolsis non-null, intersect with the base set - If
disallowed_toolsis non-null, subtract from the effective set - Warn (not block) if critical tools for the profile type are missing
The computed tool list is written to a JSON file (tools-<issueId>.json) in .tmp/ and passed to the MCP server via --tool-scope-file.
UI: The settings page includes a Tool Scope editor per agent profile with checkbox toggles for each tool, grouped by category (Read, Write, Phase, Review, System). Warning badges appear when critical tools are denied.
Valid agent types: worker, researcher, judge, scanner, architect, planner, intake.
Tool Context and Identity Binding
Every tool receives a ToolContext that is bound at startup:
interface ToolContext {
db: AppDatabase // Drizzle ORM connection
issueId: string // Current issue — baked in, not passed per-call
projectId: string // Resolved project — baked in
worktreePath?: string // Agent's worktree path (for workspace verification)
agentProfileType?: string // Agent profile type — for memory path resolution
projectSlug?: string // Project slug — for memory path resolution
symphonyDir?: string // Symphony root directory — resolves data/memory/ root
}This prevents agents from accidentally modifying the wrong issue. An agent can still query other issues with get_issue (which accepts an optional issue_id), but mutations default to the bound issue.
Findings Data Structures
All findings are append-only — immutable once written. This provides an audit trail and prevents race conditions when multiple agents or the orchestrator read the same issue.
PhaseCompletionFinding (created by complete_phase):
{
id: string // 'pc-' prefix
category: 'phase_completion'
completed_phase: Phase // 'research' | 'architecture' | 'grooming' | 'ready'
contract_version: number // Must match the phase contract
artifact_path: string // docs/tickets/{id}/{phase}.md
artifact_hash: string // SHA-256 of content
summary: string
open_questions: string[]
confidence: 'high' | 'medium' | 'low'
promote_to: string | null // Optional path for artifact promotion into docs/
recommended_next_phase: string
processed: false // Orchestrator sets true after processing
created_at: ISO8601
}PhaseVerdictFinding (created by approve_phase / reject_phase):
{
id: string // 'pv-' prefix
category: 'phase_verdict'
phase: Phase
artifact_hash: string // Must match the completion claim's hash
verdict: 'approved' | 'rejected'
reason: string
fix_instructions?: string // Only for rejections
processed: false // Orchestrator sets true after advancing phase
created_at: ISO8601
}The processed: false flag is how agents signal the orchestrator to act. The orchestrator picks up unprocessed findings on each 5-second tick and validates/transitions accordingly.
Tool Argument Validation
Every tool validates its arguments with Zod schemas at the tool boundary. If validation fails, the tool returns a structured error result (not a thrown exception) with a clear message so the agent can retry with corrected arguments. The Zod schema is also the source of truth for the MCP inputSchema advertised to agents.