-
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? |
Beta Was this translation helpful? Give feedback.
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". |
Beta Was this translation helpful? Give feedback.
-
@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) ![]() |
Beta Was this translation helpful? Give feedback.
-
UpdateThe code concept above is purely for evaluation and experimentation and should NOT be used in a production environment: |
Beta Was this translation helpful? Give feedback.
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".