Skip to content

[agent_farm] add the concept of mcp_tool trait to the llm/broker.rs (Run ID: codestoryai_sidecar_issue_2004_fa0b138f)#2005

Open
theskcd wants to merge 4 commits intomainfrom
codestoryai_sidecar_issue_2004_fa0b138f
Open

[agent_farm] add the concept of mcp_tool trait to the llm/broker.rs (Run ID: codestoryai_sidecar_issue_2004_fa0b138f)#2005
theskcd wants to merge 4 commits intomainfrom
codestoryai_sidecar_issue_2004_fa0b138f

Conversation

@theskcd
Copy link
Contributor

@theskcd theskcd commented Feb 7, 2025

agent_instance: codestoryai_sidecar_issue_2004_fa0b138f Tries to fix: #2004

Feature: Added MCPTool trait to standardize tool interface and JSON schema validation

  • Added: MCPTool trait in broker.rs with name, description, and input schema validation methods
  • Implemented: JSON schema validation logic for type-safe parameter checking with default implementations for schema conversion and validation

🔍 Ready for review - validates tool inputs against defined schemas while maintaining compatibility with existing XML parsing system.

The commit message follows conventional commits format and describes the main changes: adding a new trait for tool functionality and schema validation. It's under 72 characters and starts with the type "feat" since it adds new functionality.
@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

/q what does this do lol...

    /// Validate input JSON against the tool's schema
    fn validate_input(&self, input: &serde_json::Map<String, serde_json::Value>) -> bool {
        let schema = self.to_json_schema();
        // Check required fields
        if let Some(required) = schema.get("required").and_then(|r| r.as_array()) {
            for field in required {
                if let Some(field_name) = field.as_str() {
                    if !input.contains_key(field_name) {
                        return false;
                    }
                }
            }
        }
        // Check property types - could be expanded for more thorough validation
        if let Some(properties) = schema.get("properties").and_then(|p| p.as_object()) {
            for (key, schema_value) in properties {
                if let Some(input_value) = input.get(key) {
                    if let Some(expected_type) = schema_value.get("type").and_then(|t| t.as_str()) {
                        match expected_type {
                            "string" => if !input_value.is_string() { return false; },
                            "number" => if !input_value.is_number() { return false; },
                            "boolean" => if !input_value.is_boolean() { return false; },
                            "object" => if !input_value.is_object() { return false; },
                            "array" => if !input_value.is_array() { return false; },
                            _ => {}
                        }
                    }
                }
            }
        }
        true
    }
    ```

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

The validate_input function performs JSON schema validation for MCP tool inputs. Here's what it does step by step:

  1. Required Fields Check:

    • Checks if all fields marked as "required" in the schema exist in the input
    • Returns false if any required field is missing
  2. Type Validation:

    • For each property defined in the schema
    • Verifies that the corresponding input value matches the expected type:
      • string → Must be a string value
      • number → Must be a numeric value
      • boolean → Must be a boolean
      • object → Must be a JSON object
      • array → Must be an array
    • Returns false if any type mismatch is found
  3. Success Case:

    • Returns true if all validations pass

This ensures tools receive properly formatted input matching their declared schema.

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

/q can you write me an official note of whats happening?

say for example we have a json tool like:

Concepts
Tools
Enable LLMs to perform actions through your server

Tools are a powerful primitive in the Model Context Protocol (MCP) that enable servers to expose executable functionality to clients. Through tools, LLMs can interact with external systems, perform computations, and take actions in the real world.

Tools are designed to be model-controlled, meaning that tools are exposed from servers to clients with the intention of the AI model being able to automatically invoke them (with a human in the loop to grant approval).


Overview
Tools in MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions. Key aspects of tools include:

Discovery: Clients can list available tools through the tools/list endpoint
Invocation: Tools are called using the tools/call endpoint, where servers perform the requested operation and return results
Flexibility: Tools can range from simple calculations to complex API interactions
Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems.


Tool definition structure
Each tool is defined with the following structure:

{
name: string; // Unique identifier for the tool
description?: string; // Human-readable description
inputSchema: { // JSON Schema for the tool's parameters
type: "object",
properties: { ... } // Tool-specific parameters
}
}

Implementing tools
Here’s an example of implementing a basic tool in an MCP server:

TypeScript
Python

const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {
tools: {}
}
});

// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [{
name: "calculate_sum",
description: "Add two numbers together",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" }
},
required: ["a", "b"]
}
}]
};
});

// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "calculate_sum") {
const { a, b } = request.params.arguments;
return {
content: [
{
type: "text",
text: String(a + b)
}
]
};
}
throw new Error("Tool not found");
});

Example tool patterns
Here are some examples of types of tools that a server could provide:


System operations
Tools that interact with the local system:

{
name: "execute_command",
description: "Run a shell command",
inputSchema: {
type: "object",
properties: {
command: { type: "string" },
args: { type: "array", items: { type: "string" } }
}
}
}

API integrations
Tools that wrap external APIs:

{
name: "github_create_issue",
description: "Create a GitHub issue",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
body: { type: "string" },
labels: { type: "array", items: { type: "string" } }
}
}
}

Data processing
Tools that transform or analyze data:

{
name: "analyze_csv",
description: "Analyze a CSV file",
inputSchema: {
type: "object",
properties: {
filepath: { type: "string" },
operations: {
type: "array",
items: {
enum: ["sum", "average", "count"]
}
}
}
}
}

Best practices
When implementing tools:

Provide clear, descriptive names and descriptions
Use detailed JSON Schema definitions for parameters
Include examples in tool descriptions to demonstrate how the model should use them
Implement proper error handling and validation
Use progress reporting for long operations
Keep tool operations focused and atomic
Document expected return value structures
Implement proper timeouts
Consider rate limiting for resource-intensive operations
Log tool usage for debugging and monitoring

Security considerations
When exposing tools:


Input validation
Validate all parameters against the schema
Sanitize file paths and system commands
Validate URLs and external identifiers
Check parameter sizes and ranges
Prevent command injection

Access control
Implement authentication where needed
Use appropriate authorization checks
Audit tool usage
Rate limit requests
Monitor for abuse

Error handling
Don’t expose internal errors to clients
Log security-relevant errors
Handle timeouts appropriately
Clean up resources after errors
Validate return values

Tool discovery and updates
MCP supports dynamic tool discovery:

Clients can list available tools at any time
Servers can notify clients when tools change using notifications/tools/list_changed
Tools can be added or removed during runtime
Tool definitions can be updated (though this should be done carefully)

Error handling
Tool errors should be reported within the result object, not as MCP protocol-level errors. This allows the LLM to see and potentially handle the error. When a tool encounters an error:

Set isError to true in the result
Include error details in the content array
Here’s an example of proper error handling for tools:

TypeScript
Python

try {
// Tool operation
const result = performOperation();
return {
content: [
{
type: "text",
text: Operation successful: ${result}
}
]
};
} catch (error) {
return {
isError: true,
content: [
{
type: "text",
text: Error: ${error.message}
}
]
};
}
This approach allows the LLM to see that an error occurred and potentially take corrective action or request human intervention.


Testing tools
A comprehensive testing strategy for MCP tools should cover:

Functional testing: Verify tools execute correctly with valid inputs and handle invalid inputs appropriately
Integration testing: Test tool interaction with external systems using both real and mocked dependencies
Security testing: Validate authentication, authorization, input sanitization, and rate limiting
Performance testing: Check behavior under load, timeout handling, and resource cleanup
Error handling: Ensure tools properly report errors through the MCP protocol and clean up resources
Was this page helpful?

Implement me an example in your reply so I know whats up

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

Enhancement: Added MCPTool trait to better integrate with MCP specs.

Key Changes:

  • Added: MCPTool trait with JSON schema validation
  • Created: ToolInputSchema struct for parameter definitions
  • Updated: XML/JSON parsing system

Example Usage:

// Define a calculator tool
struct CalculatorTool;

impl MCPTool for CalculatorTool {
    fn name(&self) -> &str {
        "calculate_sum"
    }
    
    fn description(&self) -> Option<&str> {
        Some("Add two numbers together")
    }
    
    fn input_schema(&self) -> ToolInputSchema {
        ToolInputSchema {
            r#type: "object".to_string(),
            properties: Some({
                let mut props = BTreeMap::new();
                props.insert("a".to_string(), json!({"type": "number"})); 
                props.insert("b".to_string(), json!({"type": "number"}));
                props
            }),
            required: Some(vec!["a".to_string(), "b".to_string()])
        }
    }
}

// Usage example:
let tool = CalculatorTool;
let input = json!({
    "a": 5,
    "b": 3
}).as_object().unwrap();

assert!(tool.validate_input(input));

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

/q what about nested types etc, we need a json to valid xml parser which should be easy to make as well

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

Looking at the solution, there are two key points regarding nested types and JSON-to-XML parsing:

  1. Existing Solution:
  • The McpToolPartial struct already handles JSON input through serde_json::Map<String, serde_json::Value>
  • The to_string() method converts it to XML format with basic nesting
  1. Limitations:
  • Does not fully handle complex nested JSON structures in a standardized way
  • Could use a more robust JSON-to-XML conversion utility

We should update the implementation to handle nested types by:

  • Adding: A dedicated JSON-to-XML converter that properly handles nesting
  • Ensuring: Uniform XML output format regardless of JSON input complexity
  • Preserving: Backward compatibility with existing XML parsing system

No further changes have been suggested because the PR message must remain concise and focused.

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

/q we already do it in an eventdriven way in the ToolUseAgent way so don't miss that for me at the very least

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

Looking at the solution in the MCTS tree, I see you're keeping the existing event-driven parsing in ToolUseGenerator and also added new functionality:

  • Retained: Event-driven parsing through ToolBlockEvent enum and sender channel
  • Added: MCPTool trait with schema validation but integrated with existing event system

The key files show how this is preserved and enhanced:

  • broker.rs: New trait definition with schema handling
  • input.rs: JSON handling for MCP tools while maintaining compatibility
  • tool_use_agent.rs: Existing event-driven parsing system

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

/agent Implement using this:

Approach 2. Convert XML to serde_json::Value and Validate Against a JSON Schema
Sometimes you might not have a fixed struct—for example, if you want to support arbitrary nested JSON content. In that case, you can convert the XML to a serde_json::Value and then validate that JSON against a JSON Schema (using a crate such as jsonschema).

Step 1. Convert XML to JSON
There isn’t one “official” way to convert XML to JSON because XML and JSON have different models, but you can use crates like xmltree or write your own converter. For example, using a simple converter might look like:

rust
Copy
use serde_json::Value;
use xmltree::Element;

fn xml_to_json(elem: &Element) -> Value {
    // Convert the element and its children into a JSON object.
    // This is a simplified conversion that assumes that each XML element
    // maps to a JSON object with its tag name as the key.
    let mut map = serde_json::Map::new();

    // Convert text content if available.
    if let Some(text) = elem.get_text() {
        map.insert("text".to_string(), Value::String(text.clone()));
    }
    
    // Convert children.
    for child in &elem.children {
        if let xmltree::XMLNode::Element(child_elem) = child {
            // For a robust conversion you might need to handle multiple children with the same name,
            // attributes, etc. Here, we assume unique child names.
            map.insert(child_elem.name.clone(), xml_to_json(child_elem));
        }
    }
    
    Value::Object(map)
}
Then you can parse the XML and convert it:

rust
Copy
use xmltree::Element;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let xml_input = r#"
        <reasoning_input>
            <plan>This is the plan</plan>
            <instruction>Follow the plan</instruction>
            <notes>Some notes here</notes>
        </reasoning_input>
    "#;
    
    let root = Element::parse(xml_input.as_bytes())?;
    let json_value = xml_to_json(&root);
    println!("Converted JSON: {}", json_value);
    
    // Now, validate `json_value` against your JSON schema using a JSON Schema validator crate.
    // ...
    Ok(())
}

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

feat: Added XML parsing functionality to MCPTool trait with efficient JSON conversion.

  • Added: parse_xml_input and validate_xml_input methods to handle XML parameter validation
  • Integrated: xmltree dependency for robust XML parsing and schema validation
  • 🔍 Ready for review - validates XML inputs against tool schemas seamlessly

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

/agent now wire this up to the mcp tool so are able to validate using that

This adds the ability to handle XML-formatted input alongside JSON in MCP tools.
@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

Implements: XML Support for MCP Tools 🔧

  • Added: XML parsing and validation capabilities to McpTool implementation
  • Updated: Input handling to support both JSON and XML formats with schema validation
  • Enhanced: Test coverage with XML input validation scenarios

Ready for review - focuses on improving input format flexibility while maintaining schema validation.

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

/agent add a discovery mechanism for resources and expose it to the tool_use_agent, look at how mcp_tool registration works today and figure out how to do it

I want it as part of the ToolUseAgent system_prompt on invoke so that part should also be covered

Use cargo check to check your work

Concepts
Resources
Expose data and content from your servers to LLMs

Resources are a core primitive in the Model Context Protocol (MCP) that allow servers to expose data and content that can be read by clients and used as context for LLM interactions.

Resources are designed to be application-controlled, meaning that the client application can decide how and when they should be used. Different MCP clients may handle resources differently. For example:

Claude Desktop currently requires users to explicitly select resources before they can be used
Other clients might automatically select resources based on heuristics
Some implementations may even allow the AI model itself to determine which resources to use
Server authors should be prepared to handle any of these interaction patterns when implementing resource support. In order to expose data to models automatically, server authors should use a model-controlled primitive such as [Tools](https://modelcontextprotocol.io/docs/concepts/tools).

[​](https://modelcontextprotocol.io/docs/concepts/resources#overview)
Overview
Resources represent any kind of data that an MCP server wants to make available to clients. This can include:

File contents
Database records
API responses
Live system data
Screenshots and images
Log files
And more
Each resource is identified by a unique URI and can contain either text or binary data.

[​](https://modelcontextprotocol.io/docs/concepts/resources#resource-uris)
Resource URIs
Resources are identified using URIs that follow this format:


[protocol]://[host]/[path]
For example:

file:///home/user/documents/report.pdf
postgres://database/customers/schema
screen://localhost/display1
The protocol and path structure is defined by the MCP server implementation. Servers can define their own custom URI schemes.

[​](https://modelcontextprotocol.io/docs/concepts/resources#resource-types)
Resource types
Resources can contain two types of content:

[​](https://modelcontextprotocol.io/docs/concepts/resources#text-resources)
Text resources
Text resources contain UTF-8 encoded text data. These are suitable for:

Source code
Configuration files
Log files
JSON/XML data
Plain text
[​](https://modelcontextprotocol.io/docs/concepts/resources#binary-resources)
Binary resources
Binary resources contain raw binary data encoded in base64. These are suitable for:

Images
PDFs
Audio files
Video files
Other non-text formats
[​](https://modelcontextprotocol.io/docs/concepts/resources#resource-discovery)
Resource discovery
Clients can discover available resources through two main methods:

[​](https://modelcontextprotocol.io/docs/concepts/resources#direct-resources)
Direct resources
Servers expose a list of concrete resources via the resources/list endpoint. Each resource includes:


{
  uri: string;           // Unique identifier for the resource
  name: string;          // Human-readable name
  description?: string;  // Optional description
  mimeType?: string;     // Optional MIME type
}
[​](https://modelcontextprotocol.io/docs/concepts/resources#resource-templates)
Resource templates
For dynamic resources, servers can expose [URI templates](https://datatracker.ietf.org/doc/html/rfc6570) that clients can use to construct valid resource URIs:


{
  uriTemplate: string;   // URI template following RFC 6570
  name: string;          // Human-readable name for this type
  description?: string;  // Optional description
  mimeType?: string;     // Optional MIME type for all matching resources
}
[​](https://modelcontextprotocol.io/docs/concepts/resources#reading-resources)
Reading resources
To read a resource, clients make a resources/read request with the resource URI.

The server responds with a list of resource contents:


{
  contents: [
    {
      uri: string;        // The URI of the resource
      mimeType?: string;  // Optional MIME type

      // One of:
      text?: string;      // For text resources
      blob?: string;      // For binary resources (base64 encoded)
    }
  ]
}
Servers may return multiple resources in response to one resources/read request. This could be used, for example, to return a list of files inside a directory when the directory is read.

[​](https://modelcontextprotocol.io/docs/concepts/resources#resource-updates)
Resource updates
MCP supports real-time updates for resources through two mechanisms:

[​](https://modelcontextprotocol.io/docs/concepts/resources#list-changes)
List changes
Servers can notify clients when their list of available resources changes via the notifications/resources/list_changed notification.

[​](https://modelcontextprotocol.io/docs/concepts/resources#content-changes)
Content changes
Clients can subscribe to updates for specific resources:

Client sends resources/subscribe with resource URI
Server sends notifications/resources/updated when the resource changes
Client can fetch latest content with resources/read
Client can unsubscribe with resources/unsubscribe
[​](https://modelcontextprotocol.io/docs/concepts/resources#example-implementation)
Example implementation
Here’s a simple example of implementing resource support in an MCP server:

TypeScript
Python

const server = new Server({
  name: "example-server",
  version: "1.0.0"
}, {
  capabilities: {
    resources: {}
  }
});

// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "file:///logs/app.log",
        name: "Application Logs",
        mimeType: "text/plain"
      }
    ]
  };
});

// Read resource contents
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const uri = request.params.uri;

  if (uri === "file:///logs/app.log") {
    const logContents = await readLogFile();
    return {
      contents: [
        {
          uri,
          mimeType: "text/plain",
          text: logContents
        }
      ]
    };
  }

  throw new Error("Resource not found");
});
[​](https://modelcontextprotocol.io/docs/concepts/resources#best-practices)
Best practices
When implementing resource support:

Use clear, descriptive resource names and URIs
Include helpful descriptions to guide LLM understanding
Set appropriate MIME types when known
Implement resource templates for dynamic content
Use subscriptions for frequently changing resources
Handle errors gracefully with clear error messages
Consider pagination for large resource lists
Cache resource contents when appropriate
Validate URIs before processing
Document your custom URI schemes
[​](https://modelcontextprotocol.io/docs/concepts/resources#security-considerations)
Security considerations
When exposing resources:

Validate all resource URIs
Implement appropriate access controls
Sanitize file paths to prevent directory traversal
Be cautious with binary data handling
Consider rate limiting for resource reads
Audit resource access
Encrypt sensitive data in transit
Validate MIME types
Implement timeouts for long-running reads
Handle resource cleanup appropriately

@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

/q whats up?

The commit adds XML parsing functionality and resource discovery features to MCP tools, allowing better integration with XML-based systems and dynamic resource management.
@theskcd
Copy link
Contributor Author

theskcd commented Feb 7, 2025

Successfully merged changes! 🎉 (commit: 9062b07)

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.

[agent_farm] add the concept of mcp_tool trait to the llm/broker.rs

1 participant