{"slug": "designing-mcp-tools-for-agents", "title": "Designing MCP tools for agents", "summary": "A developer advocates for designing MCP tools with separate, precise contracts per dataset rather than generic query tools, arguing that explicit tool names and schemas help AI agents select the correct operation. The post distinguishes between splitting tools for different contracts versus splitting servers for domain ownership, and recommends using MCP resources for frequently changing column definitions.", "body_md": "# Designing MCP tools for agents\n\nAn MCP server can expose one `query_data(dataset, filters)`\n\ntool and call it finished. The model sees a generic name, a loose `filters`\n\nobject and a dataset string. It cannot see that sales takes a date range and returns revenue while inventory takes a warehouse and returns stock levels.\n\nSeparate tools make those contracts visible:\n\n``` php\nquery_sales(start_date, end_date) -> SalesRows\nquery_inventory(warehouse, below_quantity) -> InventoryRows\n```\n\nThe model sees the tool’s name and description when the client calls `tools/list`\n\n. Its JSON schema describes the input, and it can publish an output schema for the client to validate. Those fields are part of the [MCP tool contract](https://modelcontextprotocol.io/specification/2025-11-25/server/tools), so the model gets useful context before it chooses a tool.\n\n## Tools and HTTP routes [#](#tools-and-http-routes)\n\nA remote MCP server usually has one Streamable HTTP endpoint. That endpoint exposes a catalog of tools. Creating a new tool does not require a new HTTP route, and splitting a server into separate endpoints solves a different problem.\n\nI split tools when the model needs a different contract to choose or call them correctly. Different permissions are another reason: `read_invoice`\n\nand `issue_refund`\n\nhave different blast radiuses even if the backing API puts both under `/billing`\n\n.\n\nI split servers when another team owns the domain or it needs its own authentication boundary. In [ acme-mcp](https://github.com/MattJColes/acme-mcp), orders and billing are mounted into one server because they belong to the same product. Analytics is shown as a separate proxied server that another team could deploy independently.\n\nOpenAPI can describe HTTP endpoints well. MCP gives agent clients a standard way to discover the subset they may use and invoke it through one protocol, which saves every client from building its own adapter.\n\n## Describe each dataset where it helps selection [#](#describe-each-dataset-where-it-helps-selection)\n\nA generic query tool works when the datasets share a real contract. It becomes vague once each dataset needs different fields and filters, especially when the business uses different words for the results. A `dataset=\"sales\"`\n\nparameter cannot change the static schema the model saw for the tool.\n\nFor a small set of important datasets, I prefer separate tools with precise descriptions:\n\n``` python\n@mcp.tool(tags={\"analytics\"})\ndef query_sales(start_date: date, end_date: date) -> SalesResult:\n    \"\"\"Return net sales grouped by day for the requested date range.\"\"\"\n    ...\n\n@mcp.tool(tags={\"analytics\"})\ndef query_inventory(warehouse: Warehouse, below_quantity: int) -> InventoryResult:\n    \"\"\"Return stock below a quantity threshold for one warehouse.\"\"\"\n    ...\n```\n\nThe function signatures advertise valid arguments and the return models describe different results. The docstrings use the same words as the people working with that data.\n\nColumn definitions change more often than the operation itself, and they can be too large for a tool description. MCP resources fit that data better:\n\n```\ndataset://sales/schema\ndataset://inventory/schema\n```\n\n| MCP primitive | Who selects it | What I put there |\n|---|---|---|\n| tool | model | an operation |\n| resource | application | data or context |\n| prompt | user | a reusable template |\n\nThat control split comes from the [MCP server concepts guide](https://modelcontextprotocol.io/docs/learn/server-concepts), and it is a useful test for deciding where something belongs.\n\n## Use discovery when the catalog gets large [#](#use-discovery-when-the-catalog-gets-large)\n\nSeparate tools stop helping when a warehouse has hundreds of datasets. Loading every contract gives the model a different problem: most of its starting context is tool definitions it will never use.\n\nThen I reduce the initial surface:\n\n```\nlist_datasets(query)\ndescribe_dataset(dataset_id)\nquery_dataset(dataset_id, query)\n```\n\nThe first call returns names and short descriptions. The second returns the selected schema and column definitions. The query tool runs only after the model has that context. This follows the MCP guidance on [progressive tool discovery](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#progressive-tool-discovery) while keeping each dataset’s shape explicit.\n\nI use separate tools for a small catalog where the datasets differ. Once the catalog is large, progressive discovery saves more context than static per-dataset contracts.\n\n## Serving skills with the tools [#](#serving-skills-with-the-tools)\n\nA schema describes the data. A skill explains the procedure around it: which resource to read first, how to interpret a company-specific field or what to do with a signed download link.\n\nThat skill can live in the user’s agent config or in a project repo. It can also live in the MCP server. FastMCP’s [Skills Provider](https://gofastmcp.com/servers/providers/skills) exposes `SKILL.md`\n\nand its supporting files as resources that a client can discover and fetch.\n\nI use that in `acme-mcp`\n\n: the server publishes `skill://handle-downloads/SKILL.md`\n\nbeside the report tool. The tool returns a signed URL, and the skill tells the client to show it to the user without reading the file into context. Access to both is governed by the same `reports`\n\ntag.\n\nI keep executable work in tools. The server can attach changing context and workflow instructions as resources, including a skill. I use a separate server when ownership or the security boundary changes.", "url": "https://wpnews.pro/news/designing-mcp-tools-for-agents", "canonical_source": "https://coles.codes/posts/designing-mcp-tools-for-agents/", "published_at": "2026-07-17 00:00:00+00:00", "updated_at": "2026-07-17 03:36:15.734456+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "artificial-intelligence"], "entities": ["MCP", "acme-mcp"], "alternates": {"html": "https://wpnews.pro/news/designing-mcp-tools-for-agents", "markdown": "https://wpnews.pro/news/designing-mcp-tools-for-agents.md", "text": "https://wpnews.pro/news/designing-mcp-tools-for-agents.txt", "jsonld": "https://wpnews.pro/news/designing-mcp-tools-for-agents.jsonld"}}