Skip to content

feat(mcp-tool-proxy): private MCP integration via per-tool SLXs#43

Open
theyashl wants to merge 19 commits into
mainfrom
feat/private-mcp-integration
Open

feat(mcp-tool-proxy): private MCP integration via per-tool SLXs#43
theyashl wants to merge 19 commits into
mainfrom
feat/private-mcp-integration

Conversation

@theyashl
Copy link
Copy Markdown
Contributor

Summary

  • New codebundles/mcp-tool-proxy/ that proxies a single MCP tool call: Python script does initialize + tools/call, Robot wrapper dynamically imports per-tool parameters from MCP_INPUT_SCHEMA.
  • Generation rule + SLX/Runbook templates render one SLX per discovered mcp_tool resource (mcp__{server}__{tool}, access: read-only, path: mcp/{server}).
  • Error policy split: transport / initialize failures exit 1; tools/call errors and result.isError=true are surfaced as task output (rc=0) so agentfarm can react to them.

Pairs with runwhen-contrib/runwhen-local#TBD (mcp_tools indexer that drives this codebundle from Helm-provided mcpConfig values).

Design spec: docs/superpowers/specs/2026-05-20-private-mcp-integration-design.md.

Test Plan

  • Python unit tests: cd codebundles/mcp-tool-proxy && PYTHONPATH=. .venv/bin/pytest tests/ -v → 18 pass + 1 skip
  • Robot file parses: from robot.api import get_model; get_model('runbook.robot') clean
  • Generation rule validates against runwhen-local's generation-rule-schema.json
  • SLX + Runbook templates render against a synthetic resource (assertions on runtime vars, required, defaults)
  • Local dry-run (./.test/dry-run.sh) — stub MCP server + script round-trip; bypasses Robot since RW.Core ships only in the runner image (documented in .test/README.md)
  • End-to-end inside the runner image (requires CI build)

🤖 Generated with Claude Code

theyashl and others added 16 commits May 25, 2026 14:53
…P integration

Spec covers four approaches (A multi-task SLX, B in-VPC gateway, C SLX-per-server,
D SLX-per-tool) and recommends D. Plan scopes to the codecollection mcp-tool-proxy
codebundle + the mcp_tools indexer in runwhen-local; papi DB/API/UI work is a
separate plan. Defaults locked for §10 open decisions in the plan header.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…p path, split error policy

- Discovery: D1 → D2 (no papi work needed for v1; reads MCP_CONFIG setting from
  Helm-provided mcpConfig values, mirroring CLOUD_CONFIG_SETTING pattern).
- SLX template: additionalContext gets path/hierarchy = "mcp/{server}"; access
  tag flipped to read-only as safe default until we can classify tools.
- Error policy split: tools/call errors and result.isError surface as task
  output (rc=0) so agentfarm can read and react; transport + initialize errors
  fail the task (rc=1). Reflected in invoke_tool (returns string vs raises) and
  main() exit codes.
- Tests rewritten accordingly; Phase 4 papi HTTP fetch replaced with
  Helm-config parsing + validation; Phase 5 E2E drops papi mock.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@theyashl theyashl requested a review from a team as a code owner May 25, 2026 10:28
theyashl added 3 commits May 25, 2026 16:12
…list

SLX YAMLs generated by workspace-builder don't carry configProvided
(that lives on the Runbook for the runner to read at exec time).
additionalContext.hierarchy is a list of tag keys the UI walks to build
the tree view, not a slash-path string. Use [source, mcp_server] so MCP
SLXs group by source → server → tool, and surface tool_name as its own
tag so the rendered alias isn't the only place it shows up.
RuntimeVarEntry in corestate-operator api/v1/common_types.go only
declares name/default/description/validation. The Runbook CRD validation
will reject envelopes with extra fields, so 'required' and 'type'
(carried over from MCP's JSON Schema) had to come out. The Robot wrapper
still receives the full input_schema via MCP_INPUT_SCHEMA so per-tool
required-arg enforcement happens at MCP call time, not at Runbook level.
Maps MCP JSON Schema property metadata onto RuntimeVarValidation:
  - properties[x].enum    -> validation.type=enum,  values=[...]
  - properties[x].pattern -> validation.type=regex, pattern=...
  - neither               -> validation.type=regex, pattern='.*'

CRD constrains validation.type to {enum, regex} (corestate-operator
common_types.go:53-63), so the catch-all fallback is a permissive regex
rather than 'optional / nothing'. Every emitted runtime var now carries
a validation block, which matches the CRD's expectation in practice.
@theyashl theyashl self-assigned this May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant