Repository navigation
Tag MCP tools for tool-rule matching #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b01a5fd
bf35d7b
5eb29c2
6c49bb8
e0a7341
17f6cbf
ae53dfb
59b8cc9
0dd5105
c510d6f
3457020
07b750b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ use serde_json::{Value, json}; | |
| use tinymcp_bus::{McpAuthConfig, McpCallError, McpCallOutcome}; | ||
| use tinytools::{PermissionLevel, Tool, ToolCallOptions, ToolResult}; | ||
|
|
||
| use super::naming::disambiguated_tool_name; | ||
| use super::scrub::SecretScrubber; | ||
| use crate::config_servers::{McpRegistrySource, McpServerRegistry}; | ||
|
|
||
|
|
@@ -295,6 +296,38 @@ impl Tool for McpCallTool { | |
| true | ||
| } | ||
|
|
||
| /// The per-server tool this call reaches, described exactly as | ||
| /// [`McpServerTool`](super::McpServerTool) describes it for a configured | ||
| /// server: the same name, family and `mcp.*` tags. A host's tool rules then | ||
| /// bind the operation whichever route the model takes, and the target is | ||
| /// judged on the remote tool's own `arguments`. | ||
| fn indirect_target(&self, args: &Value) -> Option<tinytools::IndirectCall> { | ||
| // The same normalization dispatch applies (trim, fences, trailing | ||
| // punctuation), so the rules judge the tool that will actually run. | ||
| let server = required_string_arg(args, "server").ok()?; | ||
| let tool = required_string_arg(args, "tool").ok()?; | ||
| let (server, tool) = (server.as_str(), tool.as_str()); | ||
| let mut target = | ||
| tinytools::ToolSubject::named(disambiguated_tool_name(server, server, tool)) | ||
| .with_family(server) | ||
| .with_tag(format!("mcp.server:{server}")) | ||
| .with_tag(format!("mcp.server_id:{server}")) | ||
| .with_tag(format!("mcp.tool:{tool}")) | ||
| .with_permission(PermissionLevel::Execute); | ||
| target.category = Some(tinytools::ToolCategory::Workflow); | ||
| let call = tinytools::IndirectCall::new(target); | ||
| Some( | ||
| match args | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not discard invalid arguments before evaluating rules When Additional
|
||
| .get("arguments") | ||
| .cloned() | ||
| .and_then(|arguments| tinymcp_bus::normalize_tool_arguments(arguments).ok()) | ||
| { | ||
| Some(arguments) => call.with_arguments(Value::Object(arguments)), | ||
| None => call, | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| async fn execute_with_options( | ||
| &self, | ||
| args: Value, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Build the indirect target from the real server id, not the label
indirect_targetpasses theserverargument (the configured label) as both the id and the label todisambiguated_tool_name(server, server, tool), and tags itmcp.server_id:{server}. The registered per-server tools are named with the server's actual id (disambiguated_tool_name(server_id, server_label, tool)innaming.rs), and theirmcp.server_id:tag carries that id. So wheneverserver_id != label, the digest differs — the indirect subject's name will not equal the registered tool's name — and a host rule targetingmcp.server_id:<real id>matches the direct route but notmcp_call_tool. That defeats the function's stated purpose ("bind the operation whichever route the model takes") and is a policy gap on the dispatcher path. The test masks this because its fixture's server id equals its label, and the name assertion calls the same function with the same arguments, so it cannot fail on this. Resolve the server record fromself.registryand use itsserver_idfor the digest and themcp.server_id:tag.[RULE] subject-identity-mismatch ·