Pre-submission Checklist
Question Category
Your QuestionI'm building a simple arxiv tool with MCP interface. The raw tool has multiple functions: search;get by id;download pdf, etc. How would I describe the input schema of such a tool with multiple functions, each method/action as a tool, or use a sub-command/action dispatch pattern? |
Replies: 3 comments 1 reply
|
Breaking into separate tools would probably be the safest, since not every MCP implementation will handle the full JSON schema specification. You could also have each tool be a separate property, and the schema for that property would describe the input to that tool. Or use "oneOf". |
|
@jba Apologies for putting this here, but would appreciate your opinion. If I'm totally wrong in my approach then please tell me so. Arxiv test MCP tool:package main
import (
"github.com/modelcontextprotocol/go-sdk/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// Helper func: returns a pointer to any
func ptr(v any) *any {
return &v
}
// ArxivTool defines a single, consolidated MCP tool for interacting with the Arxiv service.
var ArxivTool = &mcp.Tool{
Name: "Arxiv",
Description: "A tool to interact with the Arxiv preprint server. You must choose a function ('search', 'get_by_id', or 'download_pdf_by_id') and provide its specific arguments.",
Meta: map[string]any{"service": "arxiv"},
InputSchema: &jsonschema.Schema{
Type: "object",
OneOf: []*jsonschema.Schema{
// Schema for the 'search' function
{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"action": {
Description: "Use this to search for papers on the Arxiv preprint server.",
Const: ptr("search"),
},
"args": {
Type: "object",
Properties: map[string]*jsonschema.Schema{
"query": {
Type: "string",
Description: "The search query to send to Arxiv.",
},
"max_results": {
Type: "integer",
Description: "Max results to return.",
},
},
Required: []string{"query"},
},
},
Required: []string{"action", "args"},
},
// Schema for the 'get_by_id' function
{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"action": {
Description: "Use this to get an Arxiv paper by its ID.",
Const: ptr("get_by_id"),
},
"args": {
Type: "object",
Properties: map[string]*jsonschema.Schema{
"id": {
Type: "string",
Description: "The ID of the paper.",
},
},
Required: []string{"id"},
},
},
Required: []string{"action", "args"},
},
// Schema for the 'download_pdf_by_id' function
{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"action": {
Description: "Use this to download an Arxiv paper as a PDF by its ID.",
Const: ptr("download_pdf_by_id"),
},
"args": {
Type: "object",
Properties: map[string]*jsonschema.Schema{
"id": {
Type: "string",
Description: "The ID of the paper to download.",
},
"filename": {
Type: "string",
Description: "The filename to use for the downloaded paper.",
},
"stream_content": {
Type: "boolean",
Description: "If true, the content of the PDF will be streamed back instead of saving to a file.",
},
},
Required: []string{"id"},
},
},
Required: []string{"action", "args"},
},
},
},
}Using this simple mcp client, it works OK.package main
import (
"context"
"github.com/joho/godotenv"
"github.com/modelcontextprotocol/go-sdk/mcp"
"log"
"log/slog"
"os"
"sync"
)
func main() {
// Lod environment vars
err := godotenv.Load()
if err != nil {
slog.Error("Error loading .env file", "error", err)
return
}
// Init logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
slog.SetDefault(logger)
ctx := context.Background()
var wg sync.WaitGroup
wg.Add(1)
// Create a new client
client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "0.0.1"}, &mcp.ClientOptions{
ProgressNotificationHandler: func(ctx context.Context, session *mcp.ClientSession, params *mcp.ProgressNotificationParams) {
slog.Info("Received progress notification", "params", params)
if params.Progress == 3 {
slog.Info("Final result received in progress notification")
wg.Done()
}
},
})
// Create the HTTP transport
httpTransport := mcp.NewStreamableClientTransport("http://localhost:8080/mcp", nil)
session, err := client.Connect(ctx, httpTransport)
if err != nil {
log.Fatal(err)
}
defer session.Close()
// Get a list of tools.
tools, err := session.ListTools(ctx, nil)
if err != nil {
return
}
slog.Debug("tools", "tools", tools)
// Call a tool on the server.
params := &mcp.CallToolParams{
Name: "Arxiv",
Arguments: map[string]any{
"action": "search",
"args": map[string]any{
"query": "Synthetic Imagination",
"max_results": 1,
},
},
Meta: map[string]interface{}{"progressToken": "TestClient01"},
}
res, err := session.CallTool(ctx, params)
if err != nil {
log.Fatalf("CallTool failed: %v", err)
}
slog.Info("CallTool result", "result", res)
}The test client above works OK. However, MCP Inspector v0.16.2 and Postman (MCP) do not see the actions/functions (see image)
|
UpdateThe code concept above is purely for evaluation and experimentation and should NOT be used in a production environment: |

Breaking into separate tools would probably be the safest, since not every MCP implementation will handle the full JSON schema specification. You could also have each tool be a separate property, and the schema for that property would describe the input to that tool. Or use "oneOf".