latentSource

MCP Servers: Building Tool-Using AI with Model Context Protocol

The Model Context Protocol standardizes how AI models discover and use tools. Here's how MCP servers work and why they matter for the agentic future.

·6 min read
Share
MCP Servers: Building Tool-Using AI with Model Context Protocol

Every AI framework has its own way of defining tools. MCP is the attempt to make that a solved problem.

The problem MCP solves

If you've built tool-using agents, you've hit this wall. LangChain has one tool format. OpenAI has function calling with its own JSON schema. Anthropic has a different tool-use API. Every other framework invents its own convention. The tools themselves — search a database, call an API, read a file — are the same. The wiring is different every time.

So tool implementations aren't portable. A tool built for LangChain has to be rewritten for OpenAI's Assistants API, and rewritten again for whatever custom agent framework you're using this quarter. Multiply that by every tool in your stack and every model provider you want to support, and the integration surface becomes unmanageable.

The Model Context Protocol, introduced by Anthropic, standardizes the interface. It defines how AI models discover, describe, and invoke external tools, regardless of which model or framework is doing the calling.

Architecture

MCP follows a client-server architecture:

An MCP server is a process that exposes tools (and optionally resources and prompts) through a standardized interface. The server declares what tools it has, what parameters they take, and what they return.

An MCP client is the AI model's runtime, or the framework wrapping it, that connects to MCP servers, discovers available tools, and invokes them when the model decides to use one.

The transport is JSON-RPC, typically over stdio for local processes or HTTP with Server-Sent Events for remote servers. The protocol is transport-agnostic.

The point is separation of concerns. The tool implementer doesn't need to know which model will call their tool. The model doesn't need to know how the tool is implemented. MCP is the contract between them.

Building an MCP server

An MCP server in Python is short. Here's a minimal example that exposes a weather lookup tool:

from mcp.server import Server from mcp.types import Tool, TextContent import mcp.server.stdio server = Server("weather-server") @server.list_tools() async def list_tools() -> list[Tool]: return [ Tool( name="get_weather", description="Get current weather for a city", inputSchema={ "type": "object", "properties": { "city": { "type": "string", "description": "City name, e.g. 'San Francisco'" } }, "required": ["city"] } ) ] @server.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: if name == "get_weather": city = arguments["city"] # Your actual weather API call here weather = await fetch_weather(city) return [TextContent(type="text", text=f"Weather in {city}: {weather}")] raise ValueError(f"Unknown tool: {name}") async def main(): async with mcp.server.stdio.stdio_server() as (read, write): await server.run(read, write, server.create_initialization_options())

The TypeScript SDK follows the same pattern:

import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const server = new Server({ name: "weather-server", version: "1.0.0" }, { capabilities: { tools: {} } }); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [{ name: "get_weather", description: "Get current weather for a city", inputSchema: { type: "object", properties: { city: { type: "string", description: "City name" } }, required: ["city"] } }] })); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "get_weather") { const weather = await fetchWeather(request.params.arguments.city); return { content: [{ type: "text", text: weather }] }; } }); const transport = new StdioServerTransport(); await server.connect(transport);

Tool schemas

MCP uses JSON Schema to describe tool inputs, which gives clients real type information for parameter validation and helps models figure out what arguments to provide.

A well-designed tool schema needs clear descriptions on every property (the model reads these to decide how to use the tool), proper type constraints (string, number, array, enum values), required versus optional parameters so the model knows what it has to provide, and examples in the descriptions so the model can format arguments correctly.

{ "name": "search_database", "description": "Search the product database with filters. Returns up to 20 results.", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "Natural language search query" }, "category": { "type": "string", "enum": ["electronics", "clothing", "home", "books"], "description": "Product category to filter by" }, "max_price": { "type": "number", "description": "Maximum price in USD" }, "in_stock": { "type": "boolean", "description": "If true, only return items currently in stock", "default": true } }, "required": ["query"] } }

The quality of your tool descriptions decides how well models use them. Vague descriptions produce wrong arguments. Be specific.

Beyond tools: resources and prompts

MCP servers can expose more than tools.

Resources are read-only data sources the client can pull into context. Think of them as files, database records, or API responses the model can reference. A resource has a URI, a description, and content.

@server.list_resources() async def list_resources(): return [ Resource( uri="docs://api-reference", name="API Reference", description="Complete API documentation", mimeType="text/markdown" ) ]

Prompts are reusable prompt templates the server provides. A client can list available prompts and render them with arguments. That lets tool providers ship optimized prompts alongside their tools.

Tools handle actions, resources carry context, prompts hold instructions. Together they give MCP servers a complete vocabulary for AI integration.

How it compares to OpenAI function calling

OpenAI's function calling and MCP solve related but different problems.

Function calling is a model-level feature. You describe functions in the API request, and the model outputs structured JSON to call them. Execution happens on your side. It's a request-response pattern tied to one API call.

MCP is a protocol-level standard. It defines how a runtime discovers and invokes tools across process boundaries. The model doesn't need to know it's using MCP — the client handles the protocol. MCP supports persistent connections, streaming, and server-initiated notifications.

Another way to think about it: function calling defines the format of a single tool call. MCP defines the whole lifecycle — discovery, invocation, streaming results, error handling — across multiple tools and servers.

In practice they're complementary. An MCP client might translate MCP tool schemas into OpenAI function calling format when it talks to GPT-4, and into Anthropic tool-use format when it talks to Claude. The MCP server doesn't care which model is on the other end.

Why this matters for multi-agent systems

Agentic systems where multiple agents collaborate on complex tasks need a common tool interface more than single-agent systems do. Without a standard, every agent-to-tool connection is a custom integration. That's how integration backlogs swallow projects.

MCP gives you tool marketplaces. Build a tool once, publish it as an MCP server, and any MCP-compatible agent can use it. The community already has MCP servers for databases, file systems, web browsing, GitHub, Slack, and dozens of other integrations.

It gives you dynamic tool discovery. An agent can connect to MCP servers at runtime and find out what tools are available, instead of having tools hardcoded at development time.

It gives you composability. An orchestrating agent can connect to several MCP servers at once — one for database access, one for email, one for code execution — and the model reasons across the union of available tools.

Practical deployment

MCP servers are already supported in Claude Desktop (configure them in app settings), Claude Code (add them to your project configuration for code-aware tools), and IDE integrations like Cursor, Windsurf, and Cline. LangChain and LlamaIndex have framework integrations for existing agent pipelines.

For production deployment, the HTTP+SSE transport lets MCP servers run as remote services behind authentication. You deploy the server on your infrastructure, put it behind an API gateway, and let authorized clients connect to it.

The protocol is young and still evolving. But the trajectory is clear. If you're building tools that AI models should be able to use, MCP is the interface that gives your tool the most reach.