Skip to content

Mcp#18

Merged
themartto merged 6 commits into
devfrom
mcp
May 3, 2026
Merged

Mcp#18
themartto merged 6 commits into
devfrom
mcp

Conversation

@themartto
Copy link
Copy Markdown
Contributor

@themartto themartto commented May 3, 2026

Summary by CodeRabbit

  • New Features
    • Added Model Context Protocol (MCP) server integration, enabling connection to external tool providers
    • Configure MCP servers in the configuration file with subprocess or HTTP transport options
    • Tools from configured MCP servers are automatically discovered and available for use

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.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/mcp/client.rs (1)

40-45: 💤 Low value

Silent url-over-command precedence when both fields are set

If a user accidentally specifies both url and command in a [mcp_servers.X] block, the url branch wins silently. A diagnostic warning or an explicit error would help surface misconfigured entries.

💡 Suggested improvement
+        } else if config.url.is_some() && config.command.is_some() {
+            return Err(Error::ConfigError(format!(
+                "MCP server '{}' has both 'command' and 'url' set; specify only one",
+                name
+            )));
         } else if let Some(ref command) = config.command {

Or, add it as a tracing::warn! if you want to keep the existing fallback behaviour.

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

In `@src/mcp/client.rs` around lines 40 - 45, The config parsing currently allows
both 'url' and 'command' to be set and silently prefers 'url'; update the branch
that chooses between url vs command to detect when both fields are present
(check the 'url' and 'command' fields for the selected server entry using the
existing variables like name, url, command) and either return an explicit
Error::ConfigError or emit a tracing::warn! indicating that both were set and
'url' will be used; implement the change adjacent to the existing match/if that
constructs the MCP client so the warning/error is produced before returning the
HTTP client branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Cargo.toml`:
- Line 30: The Cargo.toml dependency for rmcp references a non-published version
"1.6.0" which will cause Cargo to fail; update the rmcp dependency entry (the
rmcp package line) to a published crate version such as "1.5.0" or, if you need
the GitHub-only 1.6.0 changes, replace the version with a git dependency
pointing to the rmcp repository and a specific tag/commit (e.g., git =
"https://github.com/..." and rev = "...") so Cargo can resolve it reliably.

In `@src/mcp/client.rs`:
- Around line 48-56: The current list_tools method calls
peer().list_tools(Default::default()) once and thus only returns the first page;
replace that single-page call with the SDK's pagination helper by calling
peer().list_all_tools().await and map errors the same way (preserving the
Error::Other message including self.server_name), then collect and return the
tools as before; update the function body in the list_tools method in
src/mcp/client.rs to use list_all_tools instead of list_tools to ensure full
enumeration.
- Around line 85-95: The current extract_text_content function drops non-text
items and returns an empty String when no "text" fields are present; change it
so that after building the Vec<String> of extracted text, if that vec is empty
then produce a fallback JSON dump of the original content slice (by serializing
each item with serde_json::to_string or to_string_pretty and joining them) so
callers (e.g., call_tool) receive the tool's raw output instead of "". Update
extract_text_content to attempt the normal text extraction first, and if
result.is_empty() then return the serialized JSON fallback (handling
serialization errors by returning an appropriate string like a minimal JSON
error message).

In `@src/mcp/mod.rs`:
- Around line 38-40: The current prefix sanitization only replaces '-' and ' '
and should instead normalize all non-alphanumeric characters to underscores to
avoid invalid identifiers; change the prefix computation (the let prefix =
name.replace([...]) expression) to map any character not in [A-Za-z0-9] to '_'
(e.g., via a regex or iterator/filter) and trim/condense consecutive underscores
if desired, and also add collision detection where prefixes are registered (see
register() and the tracing::warn path) so duplicate prefixes are caught/rejected
(or logged as errors) rather than silently overwriting.
- Around line 15-28: Wrap each call to connect_server(name, config).await in a
tokio::time::timeout(...) and handle the timeout case by logging a warn with the
server name and skipping that server; also add timeouts inside
McpClient::connect (both HTTP and stdio paths) and around client.list_tools() so
those internal awaits return an explicit timeout error instead of blocking
forever. Ensure you import tokio::time::Duration/timeout, choose a sensible
Duration constant, translate a timeout error into an Err returned from
connect_server (or into the same logging path used for other connection errors)
so connect_server, McpClient::connect, and callers uniformly handle and report
timeouts.

---

Nitpick comments:
In `@src/mcp/client.rs`:
- Around line 40-45: The config parsing currently allows both 'url' and
'command' to be set and silently prefers 'url'; update the branch that chooses
between url vs command to detect when both fields are present (check the 'url'
and 'command' fields for the selected server entry using the existing variables
like name, url, command) and either return an explicit Error::ConfigError or
emit a tracing::warn! indicating that both were set and 'url' will be used;
implement the change adjacent to the existing match/if that constructs the MCP
client so the warning/error is produced before returning the HTTP client branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ea8e5a4-cd37-4613-b1ae-159a38245bc8

📥 Commits

Reviewing files that changed from the base of the PR and between 11fd4da and 40b284d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • Cargo.toml
  • src/api/mod.rs
  • src/cli/mod.rs
  • src/config/client.rs
  • src/config/config.toml.default
  • src/config/mod.rs
  • src/config/resolve.rs
  • src/config/types.rs
  • src/core/agent.rs
  • src/lib.rs
  • src/mcp/client.rs
  • src/mcp/mod.rs
  • src/mcp/tool_handler.rs
  • src/tools/mod.rs

Comment thread Cargo.toml
Comment thread src/mcp/client.rs
Comment thread src/mcp/client.rs Outdated
Comment thread src/mcp/mod.rs
Comment on lines +15 to +28
for (name, config) in configs {
match connect_server(name, config).await {
Ok(server_handlers) => {
tracing::info!(
server = %name,
count = server_handlers.len(),
"MCP server connected"
);
handlers.extend(server_handlers);
}
Err(e) => {
tracing::warn!(server = %name, error = %e, "MCP server failed to connect");
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "mod.rs" -path "*/mcp/*" -type f

Repository: weirdstuff-dev/openheim

Length of output: 84


🏁 Script executed:

find . -name "*.rs" | head -20

Repository: weirdstuff-dev/openheim

Length of output: 485


🏁 Script executed:

fd -e rs mcp | head -20

Repository: weirdstuff-dev/openheim

Length of output: 49


🏁 Script executed:

rg "connect_server" -A 5 -B 5

Repository: weirdstuff-dev/openheim

Length of output: 1234


🏁 Script executed:

rg "McpClient" -A 10 -B 2

Repository: weirdstuff-dev/openheim

Length of output: 4000


🏁 Script executed:

wc -l src/mcp/mod.rs

Repository: weirdstuff-dev/openheim

Length of output: 85


🏁 Script executed:

cat -n src/mcp/mod.rs

Repository: weirdstuff-dev/openheim

Length of output: 1954


🏁 Script executed:

wc -l src/mcp/client.rs

Repository: weirdstuff-dev/openheim

Length of output: 88


🏁 Script executed:

cat -n src/mcp/client.rs

Repository: weirdstuff-dev/openheim

Length of output: 4104


🏁 Script executed:

rg "timeout|Timeout" src/mcp/

Repository: weirdstuff-dev/openheim

Length of output: 49


Add a connection timeout to prevent MCP servers from blocking startup indefinitely.

McpClient::connect (both HTTP and stdio paths) and client.list_tools() have no timeout. A misconfigured or unresponsive MCP server will block startup indefinitely with no error output.

Proposed fix: wrap each `connect_server` call with `tokio::time::timeout`
+use std::time::Duration;
+
+const MCP_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);

 pub(crate) async fn load_mcp_tools(configs: &BTreeMap<String, McpServerConfig>) -> Vec<Box<dyn ToolHandler>> {
     let mut handlers: Vec<Box<dyn ToolHandler>> = Vec::new();

     for (name, config) in configs {
-        match connect_server(name, config).await {
-            Ok(server_handlers) => {
+        match tokio::time::timeout(MCP_CONNECT_TIMEOUT, connect_server(name, config)).await {
+            Ok(Ok(server_handlers)) => {
                 tracing::info!(
                     server = %name,
                     count = server_handlers.len(),
                     "MCP server connected"
                 );
                 handlers.extend(server_handlers);
             }
-            Err(e) => {
-                tracing::warn!(server = %name, error = %e, "MCP server failed to connect");
+            Ok(Err(e)) => {
+                tracing::warn!(server = %name, error = %e, "MCP server failed to connect");
+            }
+            Err(_) => {
+                tracing::warn!(server = %name, timeout_secs = MCP_CONNECT_TIMEOUT.as_secs(), "MCP server connection timed out");
             }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (name, config) in configs {
match connect_server(name, config).await {
Ok(server_handlers) => {
tracing::info!(
server = %name,
count = server_handlers.len(),
"MCP server connected"
);
handlers.extend(server_handlers);
}
Err(e) => {
tracing::warn!(server = %name, error = %e, "MCP server failed to connect");
}
}
use std::time::Duration;
const MCP_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub(crate) async fn load_mcp_tools(configs: &BTreeMap<String, McpServerConfig>) -> Vec<Box<dyn ToolHandler>> {
let mut handlers: Vec<Box<dyn ToolHandler>> = Vec::new();
for (name, config) in configs {
match tokio::time::timeout(MCP_CONNECT_TIMEOUT, connect_server(name, config)).await {
Ok(Ok(server_handlers)) => {
tracing::info!(
server = %name,
count = server_handlers.len(),
"MCP server connected"
);
handlers.extend(server_handlers);
}
Ok(Err(e)) => {
tracing::warn!(server = %name, error = %e, "MCP server failed to connect");
}
Err(_) => {
tracing::warn!(server = %name, timeout_secs = MCP_CONNECT_TIMEOUT.as_secs(), "MCP server connection timed out");
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mod.rs` around lines 15 - 28, Wrap each call to connect_server(name,
config).await in a tokio::time::timeout(...) and handle the timeout case by
logging a warn with the server name and skipping that server; also add timeouts
inside McpClient::connect (both HTTP and stdio paths) and around
client.list_tools() so those internal awaits return an explicit timeout error
instead of blocking forever. Ensure you import tokio::time::Duration/timeout,
choose a sensible Duration constant, translate a timeout error into an Err
returned from connect_server (or into the same logging path used for other
connection errors) so connect_server, McpClient::connect, and callers uniformly
handle and report timeouts.

Comment thread src/mcp/mod.rs Outdated
@weirdstuff-dev weirdstuff-dev deleted a comment from coderabbitai Bot May 3, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6700ed3a-cdf2-4636-8024-215aee36690b

📥 Commits

Reviewing files that changed from the base of the PR and between 40b284d and 3b968e1.

📒 Files selected for processing (2)
  • src/mcp/client.rs
  • src/mcp/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/mcp/client.rs

📝 Walkthrough

Walkthrough

This PR integrates MCP (Model Context Protocol) server support by adding the rmcp crate, implementing MCP client and tool handler adapters, refactoring SystemToolExecutor to dynamically load tools from configured MCP servers, and updating the application initialization to async-build the tool executor.

Changes

MCP Server Integration

Layer / File(s) Summary
Dependencies & Configuration
Cargo.toml, src/config/types.rs, src/config/mod.rs, src/config/config.toml.default
rmcp crate added with client, child-process, and reqwest features. AppConfig gains mcp_servers: BTreeMap<String, McpServerConfig> field. New McpServerConfig struct supports stdio/process (command, args, env) and HTTP (url) transports. Config file provides documented TOML examples.
MCP Protocol & Tool Handlers
src/mcp/client.rs, src/mcp/tool_handler.rs, src/mcp/mod.rs
McpClient wraps rmcp service and exposes list_tools() and call_tool() with transport selection (HTTP or subprocess). McpToolHandler adapts MCP tools to the application's ToolHandler trait, prefixing tool names as {server_name}__{tool_name}. load_mcp_tools() connects all configured servers and aggregates their handlers.
Tool Executor Refactoring
src/tools/mod.rs, src/lib.rs
ToolExecutor trait gains list_tools() method. SystemToolExecutor::build() is a new async constructor that registers built-ins and MCP-loaded handlers. Public register() and register_builtins() methods enable explicit tool management. Removes global executor helpers (get_available_tools, execute_tool). Adds pub mod mcp; to crate root.
Application Wiring & Async Initialization
src/cli/mod.rs, src/api/mod.rs, src/core/agent.rs
SessionContext::new() becomes async to await SystemToolExecutor::build(). Both run_agent_mode() and run_single_prompt() updated to .await session creation. run_agent_loop() now calls tool_executor.list_tools() instead of global get_available_tools().
Test & Configuration Fixtures
src/config/client.rs, src/config/resolve.rs
Test helper functions (sample_app_config(), sample_config()) and inline AppConfig literals updated to include mcp_servers: BTreeMap::new(). MockToolExecutor test impl adds list_tools() stub.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant SE as SystemToolExecutor
    participant MCP as MCP Module
    participant MC as MCP Client
    participant Server as MCP Server

    App->>SE: build(&mcp_servers).await
    SE->>MCP: load_mcp_tools(configs)
    MCP->>MCP: For each server config
    MCP->>MC: connect(name, config).await
    MC->>Server: Establish transport (HTTP or stdio)
    Server-->>MC: Connected
    MC->>Server: list_all_tools()
    Server-->>MC: [Tool 1, Tool 2, ...]
    MC-->>MCP: McpClient ready
    MCP->>MCP: Build McpToolHandler per tool
    MCP-->>SE: Vec<Box<dyn ToolHandler>>
    SE->>SE: register_builtins()
    SE->>SE: register(mcp_handlers)
    SE-->>App: ToolExecutor with all tools
    App->>SE: list_tools()
    SE-->>App: [all tool definitions]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The PR introduces a new protocol integration (MCP) across multiple new modules and refactors the tool executor, requiring review of protocol handling, async patterns, and integration points. The logic is moderately dense (connection handling, tool adaptation, transport selection) and distributed across multiple files, but changes follow a consistent pattern and the modifications to existing code are relatively localized. Test updates are straightforward.

Poem

A rabbit hops through protocols new,
MCP servers now in view,
Tools from afar, async and bright—
Building executors with dynamic might,
Configurable servers taking flight! 🐇✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The pull request title 'Mcp' is vague and does not clearly convey the scope and purpose of the changes, which involve integrating Model Context Protocol server support across multiple modules. Replace with a more descriptive title that summarizes the main change, such as 'Add Model Context Protocol (MCP) server integration' or 'Integrate MCP server support with tool execution system'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@themartto themartto marked this pull request as ready for review May 3, 2026 17:42
@themartto themartto merged commit 33a52ec into dev May 3, 2026
1 check passed
This was referenced May 3, 2026
Merged
@coderabbitai coderabbitai Bot mentioned this pull request May 12, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 31, 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