Skip to content

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

ToolPurpose
get_issueRead issue details
get_issue_statusQuick status check (slim version of get_issue)
update_issue_statusChange issue status
add_findingRecord a structured finding
add_learningRecord a reusable pattern
add_commentPost a comment on the issue
create_issueCreate a new issue
create_subtaskCreate a child issue
list_issuesList project issues
search_learningsSearch learnings across issues
create_prCreate a PR record with diff and gates
approve_prApprove the current PR
reject_prReject the current PR
configure_projectConfigure project settings
complete_phaseSubmit a phase completion claim
approve_phaseRecord judge approval of a phase artifact
reject_phaseRecord judge rejection with reason + fix instructions
record_coverageRecord research coverage for a file+concept
move_issueMove an issue to a different project
verify_workspaceVerify the agent is in the correct worktree
record_test_healthRecord test health metrics per agent run
search_knowledgeSearch knowledge documents by semantic similarity
vault_retrieveSemantic search over the Knowledge Vault
vault_updateRe-index a file in the Knowledge Vault
vault_deleteRemove a knowledge entry from the vault
query_run_eventsQuery agent run events for debugging
get_issue_statusCheck current issue status
update_memoryPersist agent memory across runs
query_run_eventsQuery structured agent stream events for traceability
get_issue_statusQuick status check with recent findings and runs

Tool Details

get_issue

Get details of an issue. Defaults to the currently assigned issue.

Parameters:

NameTypeRequiredDescription
issue_idstringnoIssue ID (defaults to current issue)

Example:

json
{}

Returns issue title, description, status, priority, findings, learnings, and comments.

update_issue_status

Change the status of the current issue.

Parameters:

NameTypeRequiredDescription
statusstringyesNew status: review, todo, or blocked
reasonstringnoReason for the change

Valid transitions from in_progress: review (done), todo (give up), blocked (need human input).

Example:

json
{ "status": "review", "reason": "Implementation complete, all tests pass" }

add_finding

Record a structured finding (test result, bug, pattern, gap).

Parameters:

NameTypeRequiredDescription
categorystringyestest_result, code_pattern, architecture, bug, or gap
summarystringyesShort scannable summary
detailsstringnoFull details
filesstring[]noRelated file paths

Example:

json
{
  "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:

NameTypeRequiredDescription
patternstringyesDescriptive pattern (min 50 chars). Start with the subject.
contextstringnoWhen/where this applies (min 100 chars if provided)
applies_tostring[]noDirectory/file prefixes for relevance matching
learning_typestringnoconvention, 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:

json
{
  "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:

NameTypeRequiredDescription
contentstringyesComment text

Example:

json
{ "content": "Found the root cause — the migration was missing a NOT NULL constraint" }

create_issue

Create a new issue on the project board.

Parameters:

NameTypeRequiredDescription
titlestringyesIssue title
descriptionstringnoIssue description
prioritynumberno1=urgent, 2=high, 3=medium (default), 4=low
labelsstring[]noLabels
depends_onstring[]noIssue IDs this depends on

Example:

json
{
  "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:

NameTypeRequiredDescription
titlestringyesSub-task title
descriptionstringnoDescription
prioritynumberno1-4 (default: 3)
labelsstring[]noLabels

Example:

json
{
  "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:

NameTypeRequiredDescription
statusstringnoFilter by status
labelsstringnoFilter by label
limitnumbernoMax results (default 50, max 100)

Example:

json
{ "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:

NameTypeRequiredDescription
querystringyesSearch query (supports prefix matching and stemming)
limitnumbernoMax results (default 50, max 100)
cross_projectbooleannoSearch other projects instead of current (default false)
min_quality_scorenumbernoFilter out learnings below this quality threshold (0-100). Verified learnings always pass.

Example:

json
{ "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:

NameTypeRequiredDescription
diffstringyesFull git diff output
branch_namestringyesSource branch name
base_branchstringyesTarget branch name
gate_resultsarraynoQuality gate results

Each gate result object:

FieldTypeRequiredDescription
commandstringyesCommand that was run
labelstringyesHuman-readable label
exit_codenumberyesExit code (0 = pass)
outputstringyesCommand output
requiredbooleannoWhether this gate is required

Example:

json
{
  "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:

NameTypeRequiredDescription
reasonstringnoApproval reason

Example:

json
{ "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:

NameTypeRequiredDescription
reasonstringnoRejection reason
fix_instructionsstringnoWhat the worker should fix

Example:

json
{
  "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:

NameTypeRequiredDescription
gatesarraynoQuality gate commands
sensitive_pathsstring[]noGlob patterns for sensitive files
risk_thresholdsobjectnoRisk scoring thresholds
worker_instructionsstringnoCustom instructions for workers
judge_instructionsstringnoCustom instructions for judges

Each gate object:

FieldTypeRequiredDescription
commandstringyesCommand to run
labelstringyesHuman-readable label
requiredbooleanyesWhether this gate must pass

Example:

json
{
  "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:

  1. Minimum length: Artifact must be at least 100 characters (trimmed)
  2. Markdown format: Artifact must contain at least one Markdown heading (# or ##)
  3. Required sections: If the phase contract is available, checks that all required_sections are 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:

NameTypeRequiredDescription
completed_phasestringyesPhase being completed: research, architecture, grooming, or ready
contract_versionnumberyes*Must match the contract's version (currently 1). *Not required for ready.
artifact_pathstringyes*Relative path to the artifact (must be inside docs/tickets/). *Not required for ready.
artifact_hashstringyes*SHA-256 hash of the artifact content. *Not required for ready.
summarystringyesBrief summary of the artifact
open_questionsstring[]noUnresolved questions
confidencestringnohigh, medium, or low (default: medium)
promote_tostringnoTarget path for promotion into project docs tree (must be inside docs/)
recommended_next_phasestringyes*Suggested next phase. *Not required for ready.
project_pathstringyesAbsolute path to the project root
contracts_dirstringnoPath to phase contracts directory (defaults to prompts/phases/)

Example:

json
{
  "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 found
  • Artifact 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:

NameTypeRequiredDescription
phasestringyesPhase being approved
artifact_hashstringyesHash of the artifact being approved
reasonstringnoApproval reason

Example:

json
{
  "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:

NameTypeRequiredDescription
phasestringyesPhase being rejected
artifact_hashstringyesHash of the artifact being rejected
reasonstringyesRejection reason
fix_instructionsstringnoWhat the agent should fix

Example:

json
{
  "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:

NameTypeRequiredDescription
file_pathstringyesRelative file path examined
conceptstringyesResearch concept (e.g., test_coverage, error_handling, type_safety)
statusstringyescovered = no issues found, has_issues = a ticket was created
ticket_idstringnoID of the ticket created (if status is has_issues)
notesstringnoBrief observation about the file for this concept

Example:

json
{
  "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:

NameTypeRequiredDescription
target_project_idstringyesID of the destination project
reasonstringyesWhy the issue is being moved

Example:

json
{
  "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):

json
{
  "valid": true,
  "path": "/path/to/worktree",
  "issue": "SYM-001",
  "branch": "symphony/SYM-001",
  "message": "Workspace verified. You are in the correct worktree."
}

Returns (failure):

json
{
  "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:

NameTypeRequiredDescription
test_countnumberyesTotal number of test cases after this run
test_files_changednumbernoNumber of test files modified in this run
tests_addednumbernoNumber of new test cases added
tests_removednumbernoNumber of test cases removed or consolidated
execution_time_msnumbernoTest suite execution duration in milliseconds

Example:

json
{
  "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:

NameTypeRequiredDescription
querystringyesNatural language search query to find relevant knowledge chunks
top_knumbernoMaximum number of results to return (default 5, max 20)
min_scorenumbernoMinimum similarity score threshold (default 0.3)

Example:

json
{ "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:

NameTypeRequiredDescription
querystringyesNatural language search query for semantic similarity matching
top_knumbernoNumber of results to return (default 5, max 50)

Example:

json
{ "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:

NameTypeRequiredDescription
pathstringyesPath to the file to re-index (relative to project root)

Example:

json
{ "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:

NameTypeRequiredDescription
pathstringyesPath of the knowledge entry to remove from the vault

Example:

json
{ "path": "docs/deprecated/old-pattern.md" }

Used by: worker only.

query_run_events

Query agent run events for debugging and investigation.

Parameters:

NameTypeRequiredDescription
run_idstringnoFilter by specific agent run ID
limitnumbernoMax 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:

NameTypeRequiredDescription
issue_idstringnoIssue 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:

json
{}

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:

NameTypeRequiredDescription
scopestringyesproject (specific to this project) or global (applies across all projects)
filenamestringyesMemory file name (e.g., codebase-notes.md). Must end in .md. Alphanumeric, hyphens, and underscores only.
contentstringyesFull 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) or data/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:

json
{
  "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:

ToolWorkerResearcherJudgeScannerArchitectPlannerIntake
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_pr is deprecated for all profiles — the orchestrator auto-creates PRs from committed changes when workers call complete_phase
  • configure_project is used by the setup wizard, not by any agent profile directly
  • record_coverage is exclusively for the scanner to track which files/concepts have been reviewed
  • move_issue is used by the planner for cross-project routing during the grooming phase
  • verify_workspace is available to all worktree-based agents — verifies the agent is in the correct worktree via the .symphony-worktree marker file
  • vault_retrieve is available to all profiles for reading from the global Knowledge Vault. vault_update is restricted to worker and researcher. vault_delete is 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 ToolContext with { 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:

bash
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:

  1. Parse --issue-id, --db, optional --project-id, --worktree-path, --agent-type, --tool-scope-file, --project-slug, --symphony-dir
  2. Open SQLite with WAL mode, busy_timeout=5000, foreign key constraints
  3. Resolve projectId — use explicit arg or look up from the issue in the DB
  4. If --tool-scope-file is provided, read it as a JSON string array of tool names
  5. Create MCP server with StdioServerTransport, applying tool scope filtering
  6. 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:

  1. --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 by computeEffectiveTools() based on the agent profile's allowed_tools and disallowed_tools columns.

  2. --agent-type (fallback): When --tool-scope-file is absent but --agent-type is provided, filtering uses the hardcoded TOOL_REGISTRY allowedProfiles (legacy behavior).

  3. Neither (backward compat): All tools exposed (e.g., setup wizard).

Both list-time and call-time enforcement apply:

  • List-time filtering: The ListToolsRequest handler only returns tools in the effective scope. This saves context window tokens by not exposing irrelevant tool schemas.

  • Call-time enforcement: The CallToolRequest handler rejects calls to out-of-scope tools with a PERMISSION_DENIED error, 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():

  1. Start with registry defaults for the profile type
  2. If allowed_tools is non-null, intersect with the base set
  3. If disallowed_tools is non-null, subtract from the effective set
  4. 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:

typescript
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):

typescript
{
  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):

typescript
{
  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.