Skip to content

feat(mcp): route shell as built-in MCP tool (PR 1 of 2)#1424

Open
RohanSogani wants to merge 3 commits into
lightseekorg:mainfrom
RohanSogani:feat/shell-builtin-mcp-routing
Open

feat(mcp): route shell as built-in MCP tool (PR 1 of 2)#1424
RohanSogani wants to merge 3 commits into
lightseekorg:mainfrom
RohanSogani:feat/shell-builtin-mcp-routing

Conversation

@RohanSogani
Copy link
Copy Markdown
Contributor

@RohanSogani RohanSogani commented May 1, 2026

Description

Problem

SMG can route several OpenAI Responses built-in tools to MCP servers via builtin_type, including web_search_preview, code_interpreter, file_search, and image_generation.

The shell Responses tool was already represented in the protocol layer, but it was not available as a routable MCP built-in type. As a result, a request declaring {"type":"shell"} could not be matched
to a configured MCP server using builtin_type: shell.

Additionally, mapping shell to generic MCP passthrough would expose the routed call as an mcp_call, even though the Responses protocol has a native shell_call output item.

Solution

Add shell to the existing built-in MCP routing path, matching the pattern used for the other built-in tools.

BuiltinToolType::Shell maps to:

  • config value: shell
  • response format: shell_call
  • request tool: ResponseTool::Shell(_)

This keeps the first upstream slice focused on shell routing and native shell_call response-format alignment.

Full hosted-shell output parity is intentionally left for a follow-up. OpenAI documents shell runs as paired output items: shell_call for the requested commands and shell_call_output for command output
and exit outcomes. That follow-up will add the broader one-tool-result-to-multiple-output-items plumbing needed for shell_call_output.

Reference: https://platform.openai.com/docs/guides/tools-shell#shell-output-in-responses

Changes

  • Added BuiltinToolType::Shell.
  • Added serde/display coverage for the shell built-in type.
  • Added ResponseFormatConfig::ShellCall.
  • Added ResponseFormat::ShellCall.
  • Mapped BuiltinToolType::Shell to ResponseFormatConfig::ShellCall.
  • Transformed shell MCP results into ResponseOutputItem::ShellCall instead of generic passthrough.
  • Included ResponseTool::Shell(_) in built-in MCP routing and extraction.
  • Included BuiltinToolType::Shell in MCP session built-in binding detection.
  • Updated OpenAI/gRPC streaming helpers to classify shell as shell_call with sc_ item IDs instead of generic mcp_call.
  • Added unit coverage proving {"type":"shell"} routes to a configured MCP server with builtin_type: shell.
  • Added transformer coverage proving ResponseFormat::ShellCall produces ResponseOutputItem::ShellCall.

Test Plan

Repro from repo root:

cargo +nightly fmt --all -- --check
cargo check -p smg-mcp -p smg
cargo test -p smg-mcp test_shell_call_transform -- --nocapture
cargo test -p smg-mcp builtin_tool_type -- --nocapture
cargo test -p smg test_collect_builtin_routing_shell -- --nocapture

Verified behavior:

- BuiltinToolType::Shell serializes/deserializes as "shell".
- BuiltinToolType::Shell.response_format() returns ShellCall.
- ResponseFormatConfig::ShellCall serializes/deserializes as "shell_call".
- A Responses request with ResponseTool::Shell(_) is discovered as a built-in MCP-routable tool.
- A configured MCP server with builtin_type: shell is selected for shell tool routing.
- Shell tool results transform into ResponseOutputItem::ShellCall.
<details>
<summary>Checklist</summary>

- [x] cargo +nightly fmt passes
- [x] cargo clippy --all-targets --all-features -- -D warnings passes
- [ ] (Optional) Documentation updated
- [ ] (Optional) Please join us on Slack #sig-smg (https://slack.lightseek.org) to discuss, review, and merge PRs
</details>

Summary by CodeRabbit

  • New Features
    • Added end-to-end support for a new Shell call type: routing, transformation, streaming events, and tool-loop handling for shell calls (stable IDs, suppressed intermediate events).
  • Tests
    • Expanded test coverage for Shell call serialization, deserialization, response-format mapping, routing, transformation, and streaming behavior.

Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
@github-actions github-actions Bot added mcp MCP related changes model-gateway Model gateway crate changes labels May 1, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Walkthrough

Adds a new routed built-in tool type Shell / ShellCall across MCP config, session binding, transformer, and model-gateway routing/streaming, with tests and serialization mappings updated to include the shell variant.

Changes

Cohort / File(s) Summary
MCP Core Config & Session
crates/mcp/src/core/config.rs, crates/mcp/src/core/session.rs
Adds BuiltinToolType::Shell and ResponseFormatConfig::ShellCall, maps BuiltinToolType::ShellShellCall, updates Display/serde, and includes Shell in session builtin bindings.
MCP Transform & Types
crates/mcp/src/transform/transformer.rs, crates/mcp/src/transform/types.rs
Introduces ResponseFormat::ShellCall; parses shell-call action JSON into ShellCallAction, normalizes shell-call IDs, constructs ResponseOutputItem::ShellCall, and adds unit tests for parsing and id normalization.
Model Gateway — MCP utils & routing
model_gateway/src/routers/common/mcp_utils.rs
Maps ResponseTool::Shell(_)BuiltinToolType::Shell in discovery/routing and adds test verifying routing record and ResponseFormat::ShellCall.
Model Gateway — Streaming & Harmony/OpenAI handlers
model_gateway/src/routers/grpc/common/responses/streaming.rs, model_gateway/src/routers/grpc/harmony/streaming.rs, model_gateway/src/routers/openai/mcp/tool_loop.rs
Adds OutputItemType::ShellCall, registers "shell_call"/"sc" id prefix, treats shell calls as non-argument-streaming, suppresses intermediate SSE events for shell calls, and updates related tests.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant MG as ModelGateway
  participant TF as Transformer
  participant MCP as MCPServer
  participant Tool as ShellTool

  Client->>MG: request triggers tool call (shell_call)
  MG->>MCP: route to routed builtin (builtin_type = Shell)
  MCP->>TF: emit response with ResponseFormat::ShellCall
  TF->>TF: parse shell action JSON, normalize id
  TF->>MG: produce ResponseOutputItem::ShellCall (completed)
  MG->>Client: stream final CALL_COMPLETED event (type="shell_call", id prefixed "sc")
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

protocols, tests

Suggested reviewers

  • CatherineSue
  • key4ng
  • slin1237
  • zhoug9127
  • zhaowenzi

Poem

🐰 I found a shell in config land,
Routed neatly by a careful hand.
IDs now steady, calls behave,
Tests applaud the path we paved.
Hop, dispatch, a tidy strand! 🐚✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: adding shell as a built-in MCP tool with routing support, which aligns with the primary objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the Shell built-in tool type, updating the core configuration, session management, and routing utilities in the model gateway. Feedback suggests aligning the Shell response format with the protocol specification by using a dedicated variant instead of Passthrough. Additionally, several missing tool variants were identified: ImageGeneration should be added to the session's built-in tool list, and FileSearch needs to be included in the routing and extraction logic within mcp_utils.rs.

Comment thread crates/mcp/src/core/config.rs Outdated
Comment thread crates/mcp/src/core/session.rs
Comment thread model_gateway/src/routers/common/mcp_utils.rs
Comment thread model_gateway/src/routers/common/mcp_utils.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
model_gateway/src/routers/common/mcp_utils.rs (1)

187-193: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

file_search is still omitted from builtin routing/extraction match arms.

ResponseTool::FileSearch(_) is missing in both collect_builtin_routing and extract_builtin_types, so requests using {"type":"file_search"} won’t enter MCP builtin routing through these helpers.

🔧 Proposed fix
@@
         let builtin_type = match tool {
             ResponseTool::WebSearchPreview(_) => BuiltinToolType::WebSearchPreview,
             ResponseTool::CodeInterpreter(_) => BuiltinToolType::CodeInterpreter,
+            ResponseTool::FileSearch(_) => BuiltinToolType::FileSearch,
             ResponseTool::ImageGeneration(_) => BuiltinToolType::ImageGeneration,
             ResponseTool::Shell(_) => BuiltinToolType::Shell,
             _ => continue,
         };
@@
         .filter_map(|t| match t {
             ResponseTool::WebSearchPreview(_) => Some(BuiltinToolType::WebSearchPreview),
             ResponseTool::CodeInterpreter(_) => Some(BuiltinToolType::CodeInterpreter),
+            ResponseTool::FileSearch(_) => Some(BuiltinToolType::FileSearch),
             ResponseTool::ImageGeneration(_) => Some(BuiltinToolType::ImageGeneration),
             ResponseTool::Shell(_) => Some(BuiltinToolType::Shell),
             _ => None,
         })

Also applies to: 229-235

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/src/routers/common/mcp_utils.rs` around lines 187 - 193,
Builtin file_search is omitted from the mapping: update both
collect_builtin_routing and extract_builtin_types match expressions to include
ResponseTool::FileSearch(_) => BuiltinToolType::FileSearch so requests with
{"type":"file_search"} are handled; add the same arm in the other match (the one
around lines 229-235) to keep both helpers consistent and ensure the FileSearch
variant is routed/extracted like
WebSearchPreview/CodeInterpreter/ImageGeneration/Shell.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@model_gateway/src/routers/common/mcp_utils.rs`:
- Around line 187-193: Builtin file_search is omitted from the mapping: update
both collect_builtin_routing and extract_builtin_types match expressions to
include ResponseTool::FileSearch(_) => BuiltinToolType::FileSearch so requests
with {"type":"file_search"} are handled; add the same arm in the other match
(the one around lines 229-235) to keep both helpers consistent and ensure the
FileSearch variant is routed/extracted like
WebSearchPreview/CodeInterpreter/ImageGeneration/Shell.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 510a7055-521f-4d8c-88b8-acb38d7c0c21

📥 Commits

Reviewing files that changed from the base of the PR and between 04f9b2d and 2f1dd2e.

📒 Files selected for processing (3)
  • crates/mcp/src/core/config.rs
  • crates/mcp/src/core/session.rs
  • model_gateway/src/routers/common/mcp_utils.rs

Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
@github-actions github-actions Bot added grpc gRPC client and router changes openai OpenAI router changes labels May 1, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b9cdbe4f7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/mcp/src/transform/transformer.rs Outdated
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
model_gateway/src/routers/openai/mcp/tool_loop.rs (1)

624-635: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't strip the shell call_id in the non-streaming path.

Unlike the other hosted formats, shell_call keeps both an output-item id and the original call_id. By adding ResponseFormat::ShellCall to this normalization branch, build_transformed_mcp_call_item() now feeds to_shell_call() the stripped item-id source, so a normal upstream pair like id = "fc_123" / call_id = "call_123" is emitted as id = "sc_123" / call_id = "123". That breaks downstream correlation for follow-up shell items. Keep the raw call_id for shell and normalize only the outward-facing id.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/src/routers/openai/mcp/tool_loop.rs` around lines 624 - 635,
The function non_streaming_tool_item_id_source is incorrectly stripping prefixes
for ResponseFormat::ShellCall; remove ShellCall from the branch that normalizes
item IDs so shell call_ids remain raw. Update non_streaming_tool_item_id_source
to treat ResponseFormat::ShellCall (and any shell-specific path) as a
passthrough that returns item_id.to_string(), leaving the existing stripping
behavior for WebSearchCall, CodeInterpreterCall, FileSearchCall,
ImageGenerationCall, and ShellCall removed from that normalization branch; this
preserves call_id when build_transformed_mcp_call_item() later calls
to_shell_call().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@model_gateway/src/routers/openai/mcp/tool_loop.rs`:
- Around line 624-635: The function non_streaming_tool_item_id_source is
incorrectly stripping prefixes for ResponseFormat::ShellCall; remove ShellCall
from the branch that normalizes item IDs so shell call_ids remain raw. Update
non_streaming_tool_item_id_source to treat ResponseFormat::ShellCall (and any
shell-specific path) as a passthrough that returns item_id.to_string(), leaving
the existing stripping behavior for WebSearchCall, CodeInterpreterCall,
FileSearchCall, ImageGenerationCall, and ShellCall removed from that
normalization branch; this preserves call_id when
build_transformed_mcp_call_item() later calls to_shell_call().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0edb13b8-745f-4662-8c53-21781250d316

📥 Commits

Reviewing files that changed from the base of the PR and between 2f1dd2e and cf8cbf9.

📒 Files selected for processing (7)
  • crates/mcp/src/core/config.rs
  • crates/mcp/src/transform/transformer.rs
  • crates/mcp/src/transform/types.rs
  • model_gateway/src/routers/common/mcp_utils.rs
  • model_gateway/src/routers/grpc/common/responses/streaming.rs
  • model_gateway/src/routers/grpc/harmony/streaming.rs
  • model_gateway/src/routers/openai/mcp/tool_loop.rs

@RohanSogani RohanSogani changed the title feat(mcp): route shell as built-in MCP tool feat(mcp): route shell as built-in MCP tool (PR 1 out 2) May 1, 2026
@RohanSogani RohanSogani changed the title feat(mcp): route shell as built-in MCP tool (PR 1 out 2) feat(mcp): route shell as built-in MCP tool (PR 1 of 2) May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

grpc gRPC client and router changes mcp MCP related changes model-gateway Model gateway crate changes openai OpenAI router changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant