Once you move beyond simple LLM demos, the complexity shifts from the model to everything around it. The real problem becomes how your system interacts with tools, APIs, and data in a way the model can reliably use.
Most implementations handle this by wiring tools directly into the application layer. That usually leads to duplicated definitions, hardcoded execution paths, and tightly coupled logic. It works at small scale, but breaks down as the number of tools and use cases grows.
This is the gap Model Context Protocol(MCP) is trying to solve.
What MCP Actually Does
MCP is a standard way to expose tools and data to a model.
Instead of embedding tool logic inside every app, you define them once and expose them through an MCP server. Any agent or client can connect to it and use those capabilities.
This separates:
- capabilities (tools) → MCP server
- decision-making → agent
- Why This Matters for Agents
Most so-called agents are still structured like this:
if (intent === "create_ticket") {
return createTicket()
}
That’s just routing logic.
An actual agent should:
- choose tools dynamically
- decide the sequence of actions
- adapt based on results
For that to work, tools need to be:
- discoverable
- structured
- decoupled from application logic
That’s exactly what MCP enables.
Where JSON-RPC Fits In
MCP uses JSON-RPC 2.0 as its communication layer.
It’s a simple protocol for calling functions using JSON. Nothing fancy, but very effective for this use case.
JSON-RPC Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_user",
"arguments": {
"user_id": "42"
}
}
}
JSON-RPC Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"name": "John Doe",
"email": "john@example.com"
}
}
This is the core interaction. MCP builds on top of this structure.
Minimal MCP Setup
Define tools once on the server:
mcpServer.tool("get_user", async ({ user_id }) => {
return db.users.find(user_id)
})
mcpServer.tool("create_ticket", async ({ issue }) => {
return jira.create(issue)
})
This becomes your reusable capability layer.
What Execution Looks Like
Input:
User 42 has a billing issue
Agent flow:
- call get_user
- call create_ticket
- return response Under the Hood (JSON-RPC Calls)
{
"method": "tools/call",
"params": {
"name": "get_user",
"arguments": { "user_id": "42" }
}
}
{
"method": "tools/call",
"params": {
"name": "create_ticket",
"arguments": { "issue": "Billing issue" }
}
}
There is no predefined workflow here. The agent decides what to do based on available tools.
Why MCP + JSON-RPC Works
- tools are defined once and reused
- no repeated integration logic
- agents can chain calls naturally
- clean separation between execution and decision-making
This is what makes systems feel more agentic instead of scripted.
Without vs With MCP
Without MCP
- hardcoded flows
- duplicated integrations
- tightly coupled logic
With MCP
- shared tool layer
- dynamic execution
- cleaner architecture
What MCP Does Not Handle
MCP does not solve:
- authentication
- validation
- safety
Those still need to be implemented at the tool level.
if (!query.toLowerCase().startsWith("select")) {
throw new Error("Only read queries allowed")
}
When This Makes Sense
Use MCP when:
- multiple tools are involved
- tools need to be reused across systems
- building agent-style workflows
Skip it when:
- scope is small
- only a few functions are needed
- Final Take
This shifts the model from:
calling predefined functions
to:
interacting with a system of capabilities
That shift is what enables real agent behavior instead of scripted flows.
Top comments (0)