The MCP Server Handler supports custom tools that allow you to create
sophisticated MCP
(Model Context Protocol) tools
using OpenAPI specifications and custom handler functions. This provides the
flexibility to build complex workflows that can invoke multiple API routes,
implement custom business logic, and provide rich responses to AI systems
without having to sacrifice the governance and power of an OpenAPI config.
Custom tools give you full programmatic control over tool behavior within an
MCP Server Handler. Define tools using standard OpenAPI
patterns with custom TypeScript handlers for complex multi-step workflows and
custom logic.
Key Features
OpenAPI Standard: Define tools using standard OpenAPI specifications
Custom Handlers: Implement complex logic using TypeScript functions
Complex Workflows: Chain multiple API calls, implement business logic, and
handle complex data transformations
Type Safety: Built-in JSON Schema validation for LLM tool arguments and
inputs
Runtime Integration: Access to context.invokeRoute(), logging, and other
Zuplo runtime features
Quick Start
1. Define Your API Specification
Create an OpenAPI specification that defines your tools:
POST routes with a requestBody and a defined schema are translated into an
MCP tool's parameters. When invoked, these are validated by the MCP server to
ensure the tool is being correctly used by the LLM.
Other methods like GET, DELETE, etc. work in a similar fashion in order to
support complex tools in the shape of your APIs.
2. Create Custom Handler Functions
Create handler modules for your tools that map to your routes:
# Test with MCP Inspectornpx @modelcontextprotocol/inspector# Or test with curlcurl https://your-gateway.zuplo.dev/mcp \ -X POST \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": "0", "method": "tools/list" }'
Advanced Usage
Complex Multi-Step Workflows
Create sophisticated workflows that chain multiple operations:
context.invokeRoutewill utilize the full inbound and outbound policy
pipeline. This means that policies you set on your MCP server route will be
invoked alongside policies that are associated with any calls made through
invokeRoute.
Testing Custom Tools
Using MCP Inspector
The MCP Inspector is ideal
for testing custom tools:
Code
npx @modelcontextprotocol/inspector
Set Transport Type to "Streamable HTTP"
Set URL to your MCP endpoint (e.g., https://your-gateway.zuplo.dev/mcp)
Connect and test your tools interactively
Using cURL
Test individual tools directly:
Code
# List available toolscurl https://your-gateway.zuplo.dev/mcp \ -X POST \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": "0", "method": "tools/list" }'# Call a specific toolcurl https://your-gateway.zuplo.dev/mcp \ -X POST \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": "1", "method": "tools/call", "params": { "name": "addNumbers", "arguments": { "a": 5, "b": 3 } } }'
Best Practices
Schema Design
Use descriptive and well-structured JSON schemas for your tools. This is used by
the server to validate MCP client inputs (i.e., JSON generated by an LLM).
Providing descriptive schemas ensures an MCP Client's LLM always has the
appropriate context on exactly what arguments to provide to tools and can
dramatically reduce invalid tool usage. This validation is done automatically.
Code
// Good! Uses descriptive names and specific types with limiters and formats.{ "type": "object", "required": ["userId"], "properties": { "userId": { "type": "string", "format": "uuid", "description": "Valid UUID for user ID" }, "amount": { "type": "number", "minimum": 0, "maximum": 10000, "description": "Amount in cents" } }}
Code
// Bad! Confusing. What is "a"? What is "b"? An LLM won't understand this.{ "type": "object", "required": ["userId"], "properties": { "a": { "type": "string" }, "b": { "type": "number" } }}
Tool Design
Clear Operation IDs: Use descriptive, action-oriented operation IDs
(addNumbers, processOrder)
Detailed Descriptions: Help AI systems understand what your tool does