跪拜 Guibai
← Back to the summary

MCP Ends the Chaos of Hand-Rolled Agent Tooling

Students developing AI Agents have probably all taken this detour: writing all tool logic directly inside the Agent project, mixing database queries, file reading, and API calls together. It's quick and easy at first, but maintenance becomes a nightmare — rewriting everything when switching projects, inability to directly reuse capabilities written in Java or Python, tight coupling between tools and the model, and code that grows messier over time.

Until MCP (Model Context Protocol) emerged, bringing an industry standard to this chaotic tool-calling system. It's not just another HTTP interface wrapper, but a context-extension communication standard specifically designed for large models, bridging 'local + remote, cross-language + cross-process' scenarios. Starting from pain points and combined with practical code, this article thoroughly explains the essence, principles, and practical application of MCP.

1. Review: What exactly is wrong with the Agent tools we hand-wrote?

Let's look at a very typical 'in-project tool writing' approach, which is also the default implementation for many beginners: writing logic like user queries, data calculations, and file operations directly into the tool functions of the Agent project.

This approach seems simple and direct at first glance, but it inherently has two fatal flaws:

  1. Strong project binding, cannot be reused across projects Tool logic is deeply coupled with the current Agent code. Switching projects, frameworks, or large models requires copying, pasting, and modifying again, with no standardized output or integration method.
  2. Technology stack is completely locked in If you write an Agent in Node.js, you can only write tools in JavaScript. To call complex computing power in Java, algorithm libraries in Python, or high-performance processing in Rust, you have to build wheels yourself — writing process calls, parsing messages, handling exceptions and lifecycles, repeatedly building low-level infrastructure.

A deeper problem is the lack of a unified specification. The format of tool returns, how parameters are described, and how results are fed into the large model's context are all fully customized by the developer. When someone else takes over, they have to re-understand the entire logic.

The MCP protocol is the standard answer specifically designed to solve these problems.

2. What exactly is the MCP protocol? Explained in one sentence

MCP stands for Model Context Protocol. Its core goal is very clear: standardize communication between LLMs and external tools and resources, completely decoupling large models from tool implementations.

In one sentence: No matter if your tool is written in Node, Java, or Python, whether it runs in a local subprocess or on a remote server, the Agent can call it using the exact same protocol, and the returned results are automatically transformed into context content that the large model can read.

Its essence is not 'calling an interface to get data,' but extending context for the large model — allowing an LLM that originally only had training knowledge and a conversation window to access local files, databases, third-party services, and cross-language programs, turning information and capabilities from the external world into context resources the model can command.

3. MCP's Two Communication Methods: Covering All Local and Remote Scenarios

MCP defines a unified message format and semantics at the upper layer, and provides two transport channels at the bottom layer, corresponding to local cross-process and remote service-oriented scenarios respectively.

1. Stdio: The Native Solution for Local Cross-Process Communication

Stdio stands for standard input/output streams (stdin/stdout/stderr), the communication pipes that the operating system inherently provides for every process.

2. HTTP: Remote Service-Oriented Invocation

In addition to local processes, MCP also supports HTTP as a transport layer, enabling cross-machine, cross-network tool invocation.

The most critical point is: the upper-layer invocation logic is completely identical. The Agent side doesn't need to care whether the underlying layer is stdio or HTTP; it only needs to change the configuration, and the business code doesn't need a single line changed to seamlessly switch between local and remote tools.

4. Don't Get Confused: MCP and fetch / Regular HTTP Interfaces Are Not the Same Thing

Many people wonder: What's the difference between calling a tool via HTTP and directly calling an interface with fetch? This is the most common misunderstanding about MCP; the core positioning of the two is worlds apart.

Table

Dimension fetch / Regular HTTP Interface MCP Protocol
Core Positioning General-purpose data request tool, unrelated to large models Model context extension-specific protocol, natively serving LLMs
Semantic Specification No unified standard, interface format is fully custom Built-in standard semantics like listTools/callTool/readResource, unified tool discovery and invocation
Context Capability Context-unaware, data must be manually spliced into prompt Natively designed to expand LLM context, standardized external resource import process
Communication Method Only supports HTTP network calls Stdio local cross-process + HTTP remote dual mode
Cross-language Support Requires self-built process communication and message parsing Protocol layer shields language differences, unified access for multi-language tools

Simply put:

5. Hands-On: Writing a Standard MCP Service with Node.js

Let's write a runnable MCP service: implementing user information query capability, running independently of the Agent, and providing services externally via stdio.

1. Prerequisites

Initialize the project and install dependencies:

bash

Run

pnpm init -y
pnpm add @modelcontextprotocol/sdk zod

Enable ESM modules in package.json:

json

{
  "type": "module"
}

2. Complete Runnable Code

Create src/my-mcp-server.mjs and write the following code:

javascript

Run

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

// Mock database, can be replaced with a real database/service later
const database = {
  users: {
    '001': { id: '001', name: 'Zuhao', email: '[email protected]', role: 'admin' },
    '002': { id: '002', name: 'Guangguang', email: '[email protected]', role: 'user' },
    '003': { id: '003', name: 'Xiaohong', email: '[email protected]', role: 'user' },
  }
};

// 1. Create MCP service instance
const server = new McpServer({
  name: 'my-mcp-server',
  version: '1.0.0'
});

// 2. Register user query tool
server.registerTool(
  'query_user',
  {
    description: 'Query user information in the database. Input user ID, return the user\'s detailed information (name, email, role)',
    // Input parameters must be wrapped with z.object for the large model to recognize the parameter structure
    inputSchema: z.object({
      userId: z.string().describe('User ID, e.g.: 001, 002, 003')
    })
  },
  // Callback receives the complete parameter object, destructure to get fields
  async ({ userId }) => {
    const user = database.users[userId];
    
    if (!user) {
      return {
        content: [
          { type: 'text', text: `User ID ${userId} does not exist. Available IDs: 001, 002, 003` }
        ]
      };
    }

    return {
      content: [
        {
          type: 'text',
          text: `User ${user.id}'s information: Name: ${user.name}, Email: ${user.email}, Role: ${user.role}`
        }
      ]
    };
  }
);

// 3. Bind stdio transport channel, start service
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP User Query Service started, waiting for Agent calls...");

3. Common Pitfalls for Beginners

These are also the places where many people make mistakes most easily when writing MCP:

  1. inputSchema must be wrapped with z.object(), you cannot write fields directly, otherwise the SDK cannot parse the parameter structure, and the large model cannot recognize the tool.
  2. The tool callback receives a complete parameter object, you need to destructure specific fields, you cannot directly receive a single parameter.
  3. Logs must be output to stderr using console.error. console.log writes to stdout, polluting the MCP protocol JSON messages and causing client parsing failures.
  4. The method name is connect not connent, a spelling error will directly report a method does not exist error.

6. Agent-Side Integration: LangChain Connects Multiple MCP Services with One Click

Once the MCP service is written, the Agent side doesn't need to manually write process communication or message parsing. The official @langchain/mcp-adapters provided by LangChain can directly integrate and also supports managing multiple MCP services simultaneously.

1. Install Client Dependencies

bash

Run

pnpm add @langchain/mcp-adapters @langchain/openai @langchain/langgraph

2. Client Invocation Code

javascript

Run

import { MultiServerMCPClient } from "@langchain/mcp-adapters";
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";

// Configure multiple MCP services, can mix local stdio and remote HTTP
const mcpServersConfig = {
  userServer: {
    command: "node",
    args: ["src/my-mcp-server.mjs"] // Local MCP service file path
  }
  // Can continue adding MCP services written in Java/Python
  // calcServer: { command: "java", args: ["-jar", "calc-mcp.jar"] }
};

async function runAgent() {
  // 1. Initialize multi-MCP client, automatically spawn all subprocesses
  const client = new MultiServerMCPClient(mcpServersConfig);
  await client.initialize();

  // 2. Automatically get all tools exposed by MCP, convert to LangChain standard tools
  const allTools = client.getTools();
  console.error("Loaded tool list:", allTools.map(t => t.name));

  // 3. Initialize large model and ReAct Agent
  const llm = new ChatOpenAI({ model: "gpt-3.5-turbo" });
  const agent = createReactAgent({ llm, tools: allTools });

  // 4. Conversation: Large model automatically determines and calls MCP tools
  const res = await agent.invoke({
    messages: [{ role: "user", content: "Help me query information for user 001" }]
  });

  console.log("Final answer:", res.messages.at(-1).content);

  // Destroy all MCP subprocesses, release resources
  await client.close();
}

runAgent().catch(console.error);

7. What is the Real Engineering Value of MCP?

Looking back at this point, MCP brings not just a communication protocol, but an architectural upgrade for Agent development.

  1. Complete decoupling, tools can be iterated independently Tool services and Agents are completely separated, developed, deployed, and upgraded independently. Adding new tools only requires maintaining the MCP service, the Agent side automatically discovers and uses them with zero changes.
  2. Breaking down technology stack barriers One Agent can simultaneously interface with various MCP services written in Node, Java, Python, Rust, no more reinventing the wheel for language adaptation, each domain can implement tools with the most suitable technology stack.
  3. Standardization ends chaos Unified tool definitions, parameter descriptions, and return formats mean tool invocation is no longer a 'black box approach' where each project does its own thing, drastically reducing the cost of team collaboration, project migration, and framework switching.
  4. Natively oriented towards context extension Designed from the start around 'expanding large model context,' all returned content naturally fits the LLM's dialogue system, without requiring developers to manually do format conversion and prompt splicing.