Skip to content

feat(OMN-12841): surface generated COMPUTE nodes as MCP tools via an orchestrator wrapper surface - #2113

Merged
jonahgabriel merged 1 commit into
devfrom
jonah/omn-12841-mcp-orchestrator-wrapper
Jun 26, 2026
Merged

jonahgabriel merged 1 commit into
devfrom
jonah/omn-12841-mcp-orchestrator-wrapper

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Jun 26, 2026 •

Copy link
Copy Markdown
Collaborator

OMN-12841 — Surface generated COMPUTE nodes as MCP tools via an orchestrator wrapper surface

Implements Option B (contract-native) from the ticket: a generated COMPUTE node never surfaces as an MCP tool because MCP exposure is gated orchestrator-only in two source-verified places (services/mcp/service_mcp_tool_discovery.py:197-212 discovery filter; services/mcp/service_mcp_tool_sync.py exposure rule). Rather than relaxing the gate (Option A — rejected by the ticket as a "flip the literal" smell that forks the MCP gate logic), this emits, alongside the generated COMPUTE node, a thin declarative ORCHESTRATOR wrapper (CONTRACT + handler_routing only, no custom node logic) whose contract declares mcp.expose=true + a tool_name and routes the inbound MCP invocation to the generated COMPUTE handler. The wrapper is an ORCHESTRATOR, so it satisfies the existing gate without modifying it.

What landed

  • ServiceMCPWrapperContractEmitter (pure, deterministic, no I/O): given a ModelGeneratedComputeNodeSpec, returns a ModelMCPWrapperEmission carrying both the generated COMPUTE contract (verbatim) and the ORCHESTRATOR wrapper contract YAML.
  • ModelGeneratedComputeNodeSpec / ModelMCPWrapperEmission — strongly-typed, frozen, extra="forbid", fail-fast on blanks.
  • Canonical per-tool topic-suffix builders in topics/platform_topic_suffixes.py (build_generated_tool_invoke_suffix / build_generated_tool_result_suffix), validated via validate_topic_suffix, so no hardcoded topic literals live in the emitter (no-hardcoded-topics gate preserved; no allowlist).
  • contracts/OMN-12841.yaml.

Gates explicitly NOT modified (DoD #3)

  • service_mcp_tool_sync.py exposure rule — unchanged.
  • service_mcp_tool_discovery.py:197-212 orchestrator-only filter — unchanged.

TDD (failing-first)

  1. Generation layer (RED→green): tests/unit/services/mcp/test_service_mcp_wrapper_contract_emitter.py — written failing first (ModuleNotFoundError), drives the emitter; asserts the pipeline emits BOTH a *_compute contract AND a *_orchestrator wrapper contract (mcp.expose=true + tool_name, payload_type_match route to the COMPUTE handler). 7 cases.
  2. Discovery layer (proves the gate): tests/integration/services/mcp/test_service_mcp_tool_discovery.py::TestMCPWrapperOrchestratorSatisfiesGate — wrapper-shaped orchestrator projection → non-None ModelMCPToolDefinition; bare compute_generic projection → None (rule preserved, not relaxed).
  3. Topic-builder tests in tests/unit/topics/test_platform_topic_suffixes.py::TestGeneratedToolTopicBuilders.

Verification

  • uv run pytest tests/unit/services/mcp/ tests/unit/topics/ tests/integration/services/mcp/test_service_mcp_tool_discovery.py -q → 322 passed.
  • mypy --strict clean on new sources; ruff format/check clean; pre-commit on changed files green (patterns gate exemptions added for literal name identifiers — same precedent as model_skill_mapping.py; no-hardcoded-topics PASS).
  • Full uv run pytest tests/ -v (no -k) runs in CI as the authoritative gate.

CI-unblocking dependency (documented)

This PR also vendors docker/migrations/forward/nodes/node_projection_event_chain/0001_create_event_chain.sql via the canonical scripts/sync-node-migrations.sh. OMN-13620 merged this node migration to omnimarket dev (11:52 UTC) after the last infra PR #2112 merged (10:16 UTC), so the node-migration-sync CI gate is now RED on infra@dev for this and every subsequent infra PR until the migration is vendored. The vendored file matches omnimarket origin/dev byte-for-byte. This is the root-fix path the gate itself mandates ("Run scripts/sync-node-migrations.sh then commit the diff").

Scope notes (honest)

  • The live node-generation pipeline lives in SEA / omnimarket (node_generate_node_effect), not omnibase_infra. The wrapper-emission logic is correctly placed in omnibase_infra (the repo that owns MCP-exposure semantics and the orchestrator-only gate); the generation pipeline calls this emitter. Out of scope (per ticket): true hot-load / runtime dynamic dispatch of generated nodes; the June-11 demo loop (sandbox-invoke path); changing the sandbox-invoke path.

Evidence-Source: OCC#3183
Evidence-Ticket: OMN-12841

Refs OMN-12841. Paired OCC receipt PR: OCC#3183.

…orchestrator wrapper surface

Contract-native Option B: emit, alongside a generated COMPUTE node, a thin
declarative ORCHESTRATOR wrapper (CONTRACT + handler_routing only) that is the
MCP-exposed entity. The wrapper declares mcp.expose=true + a tool_name and
routes the inbound MCP invocation envelope to the generated COMPUTE handler. It
satisfies the existing orchestrator-only MCP gate WITHOUT relaxing it.

- ServiceMCPWrapperContractEmitter (pure, deterministic): given a
  ModelGeneratedComputeNodeSpec, emits ModelMCPWrapperEmission carrying BOTH the
  generated COMPUTE contract (verbatim) and the ORCHESTRATOR wrapper contract.
- The two orchestrator-only gates (service_mcp_tool_sync.py /
  service_mcp_tool_discovery.py:197-212) are NOT modified. Option A (relaxing the
  gate for node-type:compute) was rejected by the ticket as a "flip the literal"
  smell that forks the MCP gate logic permanently.
- Topic suffixes for the per-tool invoke/result are composed + validated by the
  canonical topics module (build_generated_tool_invoke_suffix /
  build_generated_tool_result_suffix) so no hardcoded topic literals live in the
  emitter (no-hardcoded-topics gate preserved, no allowlist).

TDD: discovery-layer test (wrapper-shaped orchestrator projection converts to a
tool; bare compute projection returns None) + generation-layer test (emitter
emits BOTH compute + orchestrator wrapper contracts) written first.

Also vendors the omnimarket node_projection_event_chain migration (OMN-13620,
merged to omnimarket dev after the last infra PR) into the infra forward tree via
scripts/sync-node-migrations.sh, required to keep the node-migration-sync CI gate
green for this and all subsequent infra PRs.

Evidence-Source: 725d2967b031d7065efd41d0751f903638202cc1
Evidence-Ticket: OMN-12841
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jonahgabriel, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 50 minutes and 35 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f9720e2-937a-4ea0-9887-797ce01a0a25

📥 Commits

Reviewing files that changed from the base of the PR and between 22128bd and 1e77d4b.

📒 Files selected for processing (13)
  • contracts/OMN-12841.yaml
  • docker/migrations/forward/nodes/node_projection_event_chain/0001_create_event_chain.sql
  • src/omnibase_infra/models/mcp/__init__.py
  • src/omnibase_infra/models/mcp/model_generated_compute_node_spec.py
  • src/omnibase_infra/models/mcp/model_mcp_wrapper_emission.py
  • src/omnibase_infra/services/mcp/__init__.py
  • src/omnibase_infra/services/mcp/service_mcp_wrapper_contract_emitter.py
  • src/omnibase_infra/topics/__init__.py
  • src/omnibase_infra/topics/platform_topic_suffixes.py
  • src/omnibase_infra/validation/validation_exemptions.yaml
  • tests/integration/services/mcp/test_service_mcp_tool_discovery.py
  • tests/unit/services/mcp/test_service_mcp_wrapper_contract_emitter.py
  • tests/unit/topics/test_platform_topic_suffixes.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonah/omn-12841-mcp-orchestrator-wrapper

Comment @coderabbitai help to get the list of available commands.

@jonahgabriel
jonahgabriel enabled auto-merge June 26, 2026 12:37
@jonahgabriel
jonahgabriel added this pull request to the merge queue Jun 26, 2026
Merged via the queue into dev with commit f73760b Jun 26, 2026
93 of 99 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-12841-mcp-orchestrator-wrapper branch June 26, 2026 13:11
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