SMR SDK + MCP Control Proposal (2026-02-19)
Goal
Add first-class Synth SDK and MCP support for controlling Synth Managed Research (SMR) projects and runs.
This proposal covers:
- a new
synth_ai.sdk.managed_research client,
- a new MCP server surface that wraps the SDK,
- and rollout sequencing to align backend/frontend/spec route contracts.
Current State (from code)
What is already implemented
- Backend SMR control-plane routes exist in
/Users/joshpurtell/Documents/Github/backend/app/api/v1/routes_smr.py.
- SMR data-plane MCP exists in
/Users/joshpurtell/Documents/Github/synth-managed-research/crates/smr-worker-host/src/main.rs and serves:
POST /mcp/orchestrator
POST /mcp/worker/:worker_id
- Orchestrator and worker MCP tool sets are defined in:
/Users/joshpurtell/Documents/Github/synth-managed-research/crates/smr-worker-host/src/smr_orchestrator_tools.rs
/Users/joshpurtell/Documents/Github/synth-managed-research/crates/smr-worker-host/src/smr_worker_tools.rs
Gaps relevant to SDK + MCP product surface
synth-ai currently has no dedicated SMR control client module under synth_ai/sdk/.
- Frontend and specs use project-scoped run routes (
/smr/projects/{project_id}/runs/...) while backend currently exposes canonical run routes mostly under /smr/runs/....
- Frontend SMR provider-key route currently prefers encrypted payloads (
encrypted_key_b64) while backend SMR provider-key schema expects plaintext api_key.
Proposed SDK Surface
Module layout
Add:
/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/managed_research/__init__.py
/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/managed_research/client.py
/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/managed_research/models.py
/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/managed_research/errors.py
Export from:
/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/__init__.py
Primary class
ManagedResearchClient
Constructor pattern should match existing SDK clients:
api_key: Optional[str] = None (fallback SYNTH_API_KEY)
backend_base: Optional[str] = None (fallback Synth backend resolver)
timeout_seconds: float = 30.0
Method groups
Project lifecycle:
create_project(...)
list_projects(include_archived=False)
get_project(project_id)
patch_project(project_id, ...)
get_project_status(project_id)
pause_project(project_id)
resume_project(project_id)
archive_project(project_id)
unarchive_project(project_id)
Onboarding + keys:
onboarding_start(project_id)
onboarding_complete_step(project_id, step, status, detail=None)
onboarding_dry_run(project_id)
onboarding_status(project_id)
set_provider_key(project_id, provider, funding_source, api_key=None, encrypted_key_b64=None, encrypt_before_send=False)
provider_key_status(project_id, provider, funding_source)
Run controls:
trigger_run(project_id, timebox_seconds=None)
list_runs(project_id)
list_active_runs(project_id)
get_run(run_id, project_id=None)
pause_run(run_id)
resume_run(run_id)
stop_run(run_id)
Human-in-loop:
list_project_questions(project_id, status_filter="pending")
list_run_questions(run_id, project_id=None)
respond_question(run_id, question_id, response_text, project_id=None)
list_project_approvals(project_id, status_filter="pending")
list_run_approvals(run_id, project_id=None)
approve(run_id, approval_id, comment=None, project_id=None)
deny(run_id, approval_id, comment=None, project_id=None)
Artifacts + observability:
list_run_artifacts(run_id, project_id=None)
get_artifact(artifact_id)
get_artifact_content_response(artifact_id, disposition="inline", follow_redirects=False)
get_usage(project_id)
get_ops_status(project_id, include_done_tasks=None)
search_victoria_logs(project_id, ...)
Compatibility rule
For all run-scoped read/write methods, use project-scoped route first and fallback to canonical /smr/runs/... if project-scoped returns 404.
This gives a stable SDK surface now while backend aliases are added.
Error model
Define ManagedResearchApiError including:
status_code
method
path
detail_snippet
This should mirror existing SDK error ergonomics (clear status + endpoint context).
Proposed MCP Surface (in synth-ai)
Why separate MCP
SMR already has internal orchestrator/worker MCP for data-plane runtime.
This proposal adds a customer/operator-facing MCP server that exposes control-plane actions via the new SDK client.
Transport
- Default:
stdio
- Optional: Streamable HTTP
Rationale: aligns with MCP architecture and transport guidance, where stdio is common local transport and Streamable HTTP is recommended for production deployments.
Tool namespace
Use explicit names to avoid collision with internal worker tools.
Project tools:
smr.project.create
smr.project.list
smr.project.get
smr.project.update
smr.project.status
smr.project.pause
smr.project.resume
smr.project.archive
Onboarding tools:
smr.onboarding.start
smr.onboarding.complete_step
smr.onboarding.dry_run
smr.onboarding.status
smr.provider_key.set
smr.provider_key.status
Run tools:
smr.run.trigger
smr.run.list
smr.run.list_active
smr.run.get
smr.run.pause
smr.run.resume
smr.run.stop
Approval/question tools:
smr.question.list_project
smr.question.list_run
smr.question.respond
smr.approval.list_project
smr.approval.list_run
smr.approval.approve
smr.approval.deny
Artifact/ops tools:
smr.artifact.list_run
smr.artifact.get
smr.artifact.content_link
smr.usage.get
smr.ops_status.get
smr.logs.search
Safety defaults
- No
/smr/internal/* tools in this MCP.
- Explicitly mark side-effecting tools in tool descriptions.
- Require required IDs (
project_id, run_id, etc.) and reject ambiguous calls.
- Return structured JSON only (no markdown blobs) for machine composability.
Packaging + CLI integration
Add optional dependency group in pyproject.toml:
Add CLI command group (flat style, per CLI AGENTS guidance):
/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/cli/commands/mcp/__init__.py
/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/cli/commands/mcp/smr.py
Suggested command:
synth-ai mcp smr --transport stdio
synth-ai mcp smr --transport streamable-http --host 0.0.0.0 --port 8765
Rollout Plan
- Backend contract alignment
- Add project-scoped run aliases in backend.
- Add
encrypted_key_b64 compatibility for SMR provider-key set.
- Keep canonical
/smr/runs/... routes for backward compatibility.
- SDK alpha (
synth_ai.sdk.managed_research)
- Implement methods listed above.
- Add fallback behavior and unit tests.
- Export from
synth_ai.sdk root.
- MCP alpha (
synth-ai mcp smr)
- Implement read-only tools first.
- Add side-effect tools after validation.
- Add tool contract tests (
tools/list, tools/call).
- GA hardening
- Add end-to-end tests against local backend.
- Publish docs and examples.
- Set stability tag policy in docs (Alpha -> Beta -> Stable).
Acceptance Criteria
- Users can control full SMR lifecycle from Python without manual REST wiring.
- MCP tools cover all core operator actions and return consistent typed JSON.
- SDK behavior is stable despite backend route transition (project-scoped + canonical).
- Key upload path is explicit and secure, with deterministic fallback behavior.
Primary external references
1---2name: 2962-smr-sdk-mcp-control-proposal-2026-02-19-e535a98c3description: SMR SDK + MCP Control Proposal (2026-02-19)4---5# SMR SDK + MCP Control Proposal (2026-02-19)67## Goal89Add first-class Synth SDK and MCP support for controlling Synth Managed Research (SMR) projects and runs.1011This proposal covers:1213- a new `synth_ai.sdk.managed_research` client,14- a new MCP server surface that wraps the SDK,15- and rollout sequencing to align backend/frontend/spec route contracts.1617## Current State (from code)1819### What is already implemented2021- Backend SMR control-plane routes exist in `/Users/joshpurtell/Documents/Github/backend/app/api/v1/routes_smr.py`.22- SMR data-plane MCP exists in `/Users/joshpurtell/Documents/Github/synth-managed-research/crates/smr-worker-host/src/main.rs` and serves:23 - `POST /mcp/orchestrator`24 - `POST /mcp/worker/:worker_id`25- Orchestrator and worker MCP tool sets are defined in:26 - `/Users/joshpurtell/Documents/Github/synth-managed-research/crates/smr-worker-host/src/smr_orchestrator_tools.rs`27 - `/Users/joshpurtell/Documents/Github/synth-managed-research/crates/smr-worker-host/src/smr_worker_tools.rs`2829### Gaps relevant to SDK + MCP product surface3031- `synth-ai` currently has no dedicated SMR control client module under `synth_ai/sdk/`.32- Frontend and specs use project-scoped run routes (`/smr/projects/{project_id}/runs/...`) while backend currently exposes canonical run routes mostly under `/smr/runs/...`.33- Frontend SMR provider-key route currently prefers encrypted payloads (`encrypted_key_b64`) while backend SMR provider-key schema expects plaintext `api_key`.3435## Proposed SDK Surface3637## Module layout3839Add:4041- `/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/managed_research/__init__.py`42- `/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/managed_research/client.py`43- `/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/managed_research/models.py`44- `/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/managed_research/errors.py`4546Export from:4748- `/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/sdk/__init__.py`4950### Primary class5152`ManagedResearchClient`5354Constructor pattern should match existing SDK clients:5556- `api_key: Optional[str] = None` (fallback `SYNTH_API_KEY`)57- `backend_base: Optional[str] = None` (fallback Synth backend resolver)58- `timeout_seconds: float = 30.0`5960### Method groups6162Project lifecycle:6364- `create_project(...)`65- `list_projects(include_archived=False)`66- `get_project(project_id)`67- `patch_project(project_id, ...)`68- `get_project_status(project_id)`69- `pause_project(project_id)`70- `resume_project(project_id)`71- `archive_project(project_id)`72- `unarchive_project(project_id)`7374Onboarding + keys:7576- `onboarding_start(project_id)`77- `onboarding_complete_step(project_id, step, status, detail=None)`78- `onboarding_dry_run(project_id)`79- `onboarding_status(project_id)`80- `set_provider_key(project_id, provider, funding_source, api_key=None, encrypted_key_b64=None, encrypt_before_send=False)`81- `provider_key_status(project_id, provider, funding_source)`8283Run controls:8485- `trigger_run(project_id, timebox_seconds=None)`86- `list_runs(project_id)`87- `list_active_runs(project_id)`88- `get_run(run_id, project_id=None)`89- `pause_run(run_id)`90- `resume_run(run_id)`91- `stop_run(run_id)`9293Human-in-loop:9495- `list_project_questions(project_id, status_filter="pending")`96- `list_run_questions(run_id, project_id=None)`97- `respond_question(run_id, question_id, response_text, project_id=None)`98- `list_project_approvals(project_id, status_filter="pending")`99- `list_run_approvals(run_id, project_id=None)`100- `approve(run_id, approval_id, comment=None, project_id=None)`101- `deny(run_id, approval_id, comment=None, project_id=None)`102103Artifacts + observability:104105- `list_run_artifacts(run_id, project_id=None)`106- `get_artifact(artifact_id)`107- `get_artifact_content_response(artifact_id, disposition="inline", follow_redirects=False)`108- `get_usage(project_id)`109- `get_ops_status(project_id, include_done_tasks=None)`110- `search_victoria_logs(project_id, ...)`111112### Compatibility rule113114For all run-scoped read/write methods, use project-scoped route first and fallback to canonical `/smr/runs/...` if project-scoped returns `404`.115116This gives a stable SDK surface now while backend aliases are added.117118### Error model119120Define `ManagedResearchApiError` including:121122- `status_code`123- `method`124- `path`125- `detail_snippet`126127This should mirror existing SDK error ergonomics (clear status + endpoint context).128129## Proposed MCP Surface (in `synth-ai`)130131## Why separate MCP132133SMR already has internal orchestrator/worker MCP for data-plane runtime.134This proposal adds a customer/operator-facing MCP server that exposes control-plane actions via the new SDK client.135136### Transport137138- Default: `stdio`139- Optional: Streamable HTTP140141Rationale: aligns with MCP architecture and transport guidance, where stdio is common local transport and Streamable HTTP is recommended for production deployments.142143### Tool namespace144145Use explicit names to avoid collision with internal worker tools.146147Project tools:148149- `smr.project.create`150- `smr.project.list`151- `smr.project.get`152- `smr.project.update`153- `smr.project.status`154- `smr.project.pause`155- `smr.project.resume`156- `smr.project.archive`157158Onboarding tools:159160- `smr.onboarding.start`161- `smr.onboarding.complete_step`162- `smr.onboarding.dry_run`163- `smr.onboarding.status`164- `smr.provider_key.set`165- `smr.provider_key.status`166167Run tools:168169- `smr.run.trigger`170- `smr.run.list`171- `smr.run.list_active`172- `smr.run.get`173- `smr.run.pause`174- `smr.run.resume`175- `smr.run.stop`176177Approval/question tools:178179- `smr.question.list_project`180- `smr.question.list_run`181- `smr.question.respond`182- `smr.approval.list_project`183- `smr.approval.list_run`184- `smr.approval.approve`185- `smr.approval.deny`186187Artifact/ops tools:188189- `smr.artifact.list_run`190- `smr.artifact.get`191- `smr.artifact.content_link`192- `smr.usage.get`193- `smr.ops_status.get`194- `smr.logs.search`195196### Safety defaults197198- No `/smr/internal/*` tools in this MCP.199- Explicitly mark side-effecting tools in tool descriptions.200- Require required IDs (`project_id`, `run_id`, etc.) and reject ambiguous calls.201- Return structured JSON only (no markdown blobs) for machine composability.202203## Packaging + CLI integration204205Add optional dependency group in `pyproject.toml`:206207- `mcp>=1.0.0`208209Add CLI command group (flat style, per CLI AGENTS guidance):210211- `/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/cli/commands/mcp/__init__.py`212- `/Users/joshpurtell/Documents/Github/synth-ai/synth_ai/cli/commands/mcp/smr.py`213214Suggested command:215216- `synth-ai mcp smr --transport stdio`217- `synth-ai mcp smr --transport streamable-http --host 0.0.0.0 --port 8765`218219## Rollout Plan2202211. Backend contract alignment222223- Add project-scoped run aliases in backend.224- Add `encrypted_key_b64` compatibility for SMR provider-key set.225- Keep canonical `/smr/runs/...` routes for backward compatibility.2262272. SDK alpha (`synth_ai.sdk.managed_research`)228229- Implement methods listed above.230- Add fallback behavior and unit tests.231- Export from `synth_ai.sdk` root.2322333. MCP alpha (`synth-ai mcp smr`)234235- Implement read-only tools first.236- Add side-effect tools after validation.237- Add tool contract tests (`tools/list`, `tools/call`).2382394. GA hardening240241- Add end-to-end tests against local backend.242- Publish docs and examples.243- Set stability tag policy in docs (Alpha -> Beta -> Stable).244245## Acceptance Criteria246247- Users can control full SMR lifecycle from Python without manual REST wiring.248- MCP tools cover all core operator actions and return consistent typed JSON.249- SDK behavior is stable despite backend route transition (project-scoped + canonical).250- Key upload path is explicit and secure, with deterministic fallback behavior.251252## Primary external references253254- MCP architecture: [https://modelcontextprotocol.io/docs/learn/architecture](https://modelcontextprotocol.io/docs/learn/architecture)255- MCP transports: [https://modelcontextprotocol.io/docs/concepts/transports](https://modelcontextprotocol.io/docs/concepts/transports)256- MCP tools: [https://modelcontextprotocol.io/docs/concepts/tools](https://modelcontextprotocol.io/docs/concepts/tools)257- MCP Python SDK: [https://github.com/modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk)258- Synth prompt optimization docs: [https://docs.usesynth.ai/prompt-optimization/gepa](https://docs.usesynth.ai/prompt-optimization/gepa)