From 8f420c5a3dafae279e4ff1d73f77cb281b31745a Mon Sep 17 00:00:00 2001 From: Xuleileon <149945786+Xuleileon@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:17:11 +0800 Subject: [PATCH 1/3] fix(gateway): preserve structured tool results --- crates/mcpmux-gateway/src/mcp/handler.rs | 20 ++----- crates/mcpmux-gateway/src/pool/routing.rs | 72 +++++++++++++++++++---- 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/crates/mcpmux-gateway/src/mcp/handler.rs b/crates/mcpmux-gateway/src/mcp/handler.rs index 8258ab80..b270e743 100644 --- a/crates/mcpmux-gateway/src/mcp/handler.rs +++ b/crates/mcpmux-gateway/src/mcp/handler.rs @@ -860,15 +860,13 @@ impl ServerHandler for McpMuxGatewayHandler { .await .map_err(|e| McpError::internal_error(format!("Tool call failed: {}", e), None))?; - // Convert ToolCallResult to MCP CallToolResult - let content: Vec = tool_result - .content - .into_iter() - .filter_map(|v| serde_json::from_value(v).ok()) - .collect(); + // Convert ToolCallResult to MCP CallToolResult without dropping + // structuredContent or protocol-level _meta from the upstream server. + let result = tool_result.into_mcp_result(); // Log result summary - show content types and approximate sizes - let content_summary: Vec = content + let content_summary: Vec = result + .content .iter() .map(|c| { // Content is Annotated, serialize to inspect type @@ -907,17 +905,11 @@ impl ServerHandler for McpMuxGatewayHandler { .collect(); debug!( tool = %params.name, - is_error = tool_result.is_error, + is_error = result.is_error.unwrap_or(false), content = ?content_summary, "call_tool result" ); - let result = if tool_result.is_error { - CallToolResult::error(content) - } else { - CallToolResult::success(content) - }; - Ok(result) } diff --git a/crates/mcpmux-gateway/src/pool/routing.rs b/crates/mcpmux-gateway/src/pool/routing.rs index 7a081a33..42871b3b 100644 --- a/crates/mcpmux-gateway/src/pool/routing.rs +++ b/crates/mcpmux-gateway/src/pool/routing.rs @@ -12,7 +12,7 @@ use std::time::Duration; use anyhow::{anyhow, Result}; use mcpmux_core::{FeatureType, LogLevel, LogSource, ServerLog, ServerLogManager}; -use rmcp::model::CallToolRequestParams; +use rmcp::model::{CallToolRequestParams, CallToolResult, Content, Meta}; use serde_json::Value; use tracing::{debug, info, warn}; use uuid::Uuid; @@ -52,6 +52,39 @@ pub struct RoutedResource { pub struct ToolCallResult { pub content: Vec, pub is_error: bool, + pub structured_content: Option, + pub meta: Option, +} + +impl ToolCallResult { + fn from_mcp_result(result: CallToolResult) -> Self { + Self { + content: result + .content + .into_iter() + .map(|item| serde_json::to_value(item).unwrap_or(Value::Null)) + .collect(), + is_error: result.is_error.unwrap_or(false), + structured_content: result.structured_content, + meta: result.meta, + } + } + + pub(crate) fn into_mcp_result(self) -> CallToolResult { + let content: Vec = self + .content + .into_iter() + .filter_map(|item| serde_json::from_value(item).ok()) + .collect(); + let mut result = if self.is_error { + CallToolResult::error(content) + } else { + CallToolResult::success(content) + }; + result.structured_content = self.structured_content; + result.meta = self.meta; + result + } } /// Default timeout for MCP tool calls (60 seconds) @@ -284,16 +317,7 @@ impl RoutingService { .map_err(|_| anyhow!("Tool call timed out after {:?}", TOOL_CALL_TIMEOUT))? .map_err(|e| anyhow!("MCP call failed: {}", e))?; - let content: Vec = res - .content - .into_iter() - .map(|c| serde_json::to_value(c).unwrap_or(Value::Null)) - .collect(); - - Ok(ToolCallResult { - content, - is_error: res.is_error.unwrap_or(false), - }) + Ok(ToolCallResult::from_mcp_result(res)) } None => Err(anyhow!("Server instance has no active client")), } @@ -726,3 +750,29 @@ impl RoutingService { false } } + +#[cfg(test)] +mod tests { + use super::ToolCallResult; + use rmcp::model::{CallToolResult, Content, Meta}; + use serde_json::json; + + #[test] + fn tool_result_round_trip_preserves_structured_content_and_meta() { + let structured = json!({ "matches": [{ "message": "found" }] }); + let mut meta = Meta::new(); + meta.0.insert("traceId".to_string(), json!("trace-123")); + + let mut upstream = CallToolResult::structured(structured.clone()); + upstream.content = vec![Content::text("search completed")]; + upstream.meta = Some(meta.clone()); + + let routed = ToolCallResult::from_mcp_result(upstream); + let forwarded = routed.into_mcp_result(); + + assert_eq!(forwarded.content, vec![Content::text("search completed")]); + assert_eq!(forwarded.structured_content, Some(structured)); + assert_eq!(forwarded.meta, Some(meta)); + assert_eq!(forwarded.is_error, Some(false)); + } +} From a5bf1e86304a3fa139e7bff4bc0cd9e9bc18f57a Mon Sep 17 00:00:00 2001 From: its-mash Date: Wed, 15 Jul 2026 03:06:28 +0200 Subject: [PATCH 2/3] Add claude GitHub actions 1784077520436 (#207) --- .github/workflows/claude-code-review.yml | 44 +++++++++++++++++++++ .github/workflows/claude.yml | 50 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 .github/workflows/claude-code-review.yml create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 00000000..b5e8cfd4 --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,44 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 00000000..6b15fac7 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr *)' + From 04e4d513053c713176d186014b213a89e424139b Mon Sep 17 00:00:00 2001 From: Mohammod Al Amin Ashik Date: Wed, 15 Jul 2026 09:10:46 +0800 Subject: [PATCH 3/3] chore: add id-token write permission for codecov OIDC authentication --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0473a1b..6a4506ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ permissions: actions: read checks: write pull-requests: write + id-token: write env: CARGO_TERM_COLOR: always