With so much recent focus on Agent Interoperability I thought to examine the Model Context Protocol. This is a follow up to my recent work with multi-agent systems.

Model Context Protocol (MCP) is a standardized JSON-RPC communication layer that enables AI applications to discover and execute tools across different servers. Instead of building custom integrations for each API, developers write one MCP client that works with any MCP server. Whether providing weather data, database queries, or file operations, MCP servers self-describe their capabilities and parameters.

The need to eliminate integration complexity becomes important if there are multiple systems that need to be leveraged by an AI agent. MCP eliminates the “N different integration patterns” problem when building multi-tool AI agents. Each service I want to connect has its own quirks, but MCP standardizes how my AI agent talks to all of them. The following example illustrates how integration is simplified in a multi-system context.

Without MCP: If my AI agent needs to use multiple different services – maybe weather data from one API, customer data from another API, and document search from a third API – I would need to write custom integration code for each one’s different JSON format, authentication, and response structure.

With MCP: However if these different systems are exposed as MCP servers then I connect to weather MCP server, database MCP server, file MCP server using the same standardized integration approach for all

What is this post all about: This post is a documentation of my understanding of Model Context Protocol based on a little project that I have been playing with.

In the interaction that happens between LLM, MCP client and MCP server – a lot of things get hidden under the surface – what is calling what, what is making the decision to call a specific tool – I have tried to think through all this – and that’s what I have documented here.

It is quite likely that this article is too rudimentary for you and the Model Context Protocol mechanics are obvious for you. However, personally I had a hard time with high level examples/demos (e.g., a broker agent calling multiple MCP servers to get done something) – as such I thought to examine – MCP server – client – LLM/AI – interactions from a perspective of foundational technology.

Getting Started with MCP

I started out by deploying locally a MCP server and MCP client – I set up a MCP (Model Context Protocol) server to add weather functionality to Claude Desktop. This is very well documented (together with full code) on the MCP site (see references section for links). I only needed to make minimal changes to make it all work in my Python environment.

So, I didn’t set out to create a cool MCP server from scratch. However, the process of working through the MCP server project helped me think through what’s happening under the hood in MCP client/server interactions.

I thought of various questions about MCP such as:

  • What makes MCP different from REST APIs?
  • Are there scenarios when REST would be more suitable?
  • Can MCP work without AI?
  • What makes MCP especially suitable for AI workflows?
  • How does the whole interaction between MCP server, MCP client and LLM work?
  • Is there a possibility that the MCP client/LLM can select the wrong tools/servers especially if there are many tools/servers doing similar jobs? What would make the tool selection accurate?
  • When to use tools and when to use resources (beyond the high level definitions)
  • What is the concept of reflection in AI agents – and how does MCP facilitate it?

Let’s continue to examine these questions in context of MCP project I have mentioned.

Unpacking My First MCP Project: Architecture & Interactions

I first downloaded the Claude desktop. Claude desktop is an AI LLM powered agent that has a MCP client built into it. The client could also have been a web based AI agent (e.g., Salesforce Agentforce) – in fact the client app for a MCP server could also be a non-AI application program.

The MCP server is a python program – weather.py which I got from the MCP site itself. I saved it in a directory on my Mac. I updated the Claude desktop config file with the path to this Python file along with some additional parameters.

The Claude desktop when it first starts on my Mac, reads its configuration file (claude_desktop_config.json) and sees that weather.py is to be run. So, it runs the weather.py file. The weather.py acts as my MCP server.

This MCP server – i.e., the Python program – weather.py – runs in the background and calls the US Weather API – then the weather.py waits for user inputs. The user inputs come via Claude desktop (e.g. “Show me SFO weather”).

So to summarize – Claude Desktop has built-in MCP client capability – it automatically reads claude_desktop_config.json file that instructs it to launch the server (weather.py).

The config only tells it the server lists, where to find the servers (file path on my mac) and how to launch it (i.e. how to run weather.py). The MCP server (weather.py) itself declares what tools it provides.

Interaction flow between Claude desktop (my MCP client), Claude LLM and MCP server (weather.py running locally on my laptop).

Claude desktop is an AI LLM powered agent that has a MCP client built into it. Following is the interaction flow that happens when I start Claude Desktop and ask a question:

1. Claude Desktop starts up and reads its config. The MCP client built into the Claude Desktop then launches all MCP servers i.e. my weather.py file
2. I ask question – it goes to Claude Desktop. Claude Desktop has already got the inventory of all tools in all available MCP servers.
3. Claude Desktop (MCP client) always sends my question PLUS all available tool schemas to Claude LLM (not selective)
4. Claude LLM → Decides whether to use any tools or ignore them completely

The MCP client (Claude Desktop) calls the tools/list method (not the LLM). The LLM never directly calls MCP methods – it just generates tool call requests that the MCP client executes.

Claude LLM can elect to use these tools instead of web search or hallucination when answering relevant queries. The user doesn’t choose the tools – Claude decides which tools to use based on the query context.

The Claude LLM generates tool call requests when it recognizes that a user’s question requires external or real-time data that it cannot provide from its training knowledge alone – for example, when asked “What’s the weather in Paris right now?”. LLM knows it needs current weather data and sees that weather tools are available in its context (as the list of tools were sent to it with the question by AI agent), so it generates a structured tool call request for the MCP client to execute.

I am thinking that in the above interaction sending all available tools to the LLM with every user question creates a scalability problem. As the number of MCP servers and tools would grow as AI agents mature, this approach becomes slow and unwieldy. We would eventually need to explore smarter solutions like tool filtering, categorization, or lazy loading to make MCP practical for enterprise deployments with many tools.

What are the situation when Claude – or another MCP client – can choose the wrong tool and what is there to prevent – especially if there are multiple tools in multiple servers which do the same job?

Tool selection is done by the Claude LLM (not Claude Desktop, the MCP client). The MCP client (Claude Desktop) just discovers all available tools and presents them to the LLM, then the LLM decides which tool to use based on ther user query.

It may be possible that Claude LLM – might choose wrong tools when there are multiple weather servers with similar tools (get_weather vs get_forecast), ambiguous tool names (search could be web search, database search, or file search), or similar functionality across servers (multiple map/navigation tools like Waze/Google Maps).

Accuracy of choosing the right tool for the right job depends on the detailed tool descriptions in the MCP server – that distinguishes purpose (“Get weather alerts for US states” vs “Get international weather forecasts”), clear specific naming (get_nws_alerts vs get_weather_forecast), and comprehensive schema documentation with parameter descriptions that clarify scope and usage.

In my example – the tool descriptions are defined in the server file (weather.py) using the @mcp.tool() decorator with the docstring description – that’s what gets sent to Claude Desktop during tool discovery to help distinguish between similar tools.

Programming is as much as an art as it is science. There’s no perfect prevention for wrong tool selection – LLM makes decisions based on available information and may choose arbitrarily between similar tools (like multiple mapping services), with tool selection quality depending heavily on how well MCP server developers document their tools’ purposes and capabilities.

Current API Protocols (esp. REST) vs MCP
From a practical perspective, a MCP server can be called just as a REST endpoint from a client program. There is no mandatory requirement that the client program has to be an LLM agent. From a networking perspective, an MCP server using HTTP transport is essentially a REST endpoint – both use HTTPS, both receive JSON payloads via POST requests, and both return JSON responses.

The key difference is that MCP standardizes the specific JSON structure (request format with messages/parameters/context and response format with choices/message/content) while REST APIs can use any JSON structure they (i.e. the REST API developer) choose. So MCP over HTTP is really “REST with a standardized contract” rather than a fundamentally different protocol.

This standardization of response is a big reason that makes MCP easier to use from AI client applications.

MCP: Every tool call follows the exact same JSON-RPC structure – same request format, same response format, same error handling. An AI agent can write one piece of client code that works with any MCP server’s tools.

REST + OpenAPI: Even with schemas (as in Open API), each API can have different authentication, error formats, response structures, status codes, pagination styles, etc. An AI agent needs custom code for each API’s quirks.

What Makes MCP Special for AI Agents?

MCP mandates standardized tool interface: AI agents can use one consistent pattern for all tools – same request/response format whether calling weather, database, or file systems.

The JSON-RPC structure is always the same:
{ “jsonrpc”: “2.0”, “id”: 1, “result”: { “content”: [{“type”: “text”, “text”: “Weather data…”}] } }

Whether I am calling weather, database queries, or file operations, the outer JSON structure (jsonrpc, id, method, params) never changes. Only the name and arguments inside params vary per tool.

MCP’s standardized JSON-RPC structure means:

• Parsing: Same parsing logic for all responses. Building custom deserialization logic for variously structured JSON responses is what I find most cumbersome in API development – and MCP simplifies this with its standardized response format.

• Error format: All errors follow JSON-RPC error structure ({“error”: {“code”: -1, “message”: “…”}})

• Message types: Standard methods like tools/list, tools/call, initialize

Tools vs Resources in MCP

In the MCP world a very frequently discussed topic is – when to use tools and when to use resources.

I have only used tools in weather.py, not resources. My weather.py only had tools because weather APIs are action-based (call an API, get data). MCP spec tells us that resources would be more relevant for things like file servers, content management systems, or databases where you’re accessing existing data rather than triggering computations.

However, I have been thinking further through the tools vs resources concept.

In the weather.py example – when the MCP client calls get_alerts(“CA”) on behalf of the LLM, it’s accessing existing weather data from NWS servers, not triggering new computations.

The distinction between tools and resources seems to be more about interface pattern:
Tools: You call with parameters → get results (get_alerts(state=”CA”))
Resources: You access by identifier/URI → get content (weather://alerts/CA)

Functionally they (i.e., tools and resources) are very similar – both provide access to data. The tool vs resource distinction appears to be more about API design patterns than fundamental differences in what they do. I have looked at various pre-written MCP servers and I find that in practice, most MCP servers use tools. This is likely because the function call pattern is more intuitive for developers than resource URIs. The resource pattern might be more useful for things like file systems where URI-based access makes more sense.

Theoretically you can always stick to tools – the resource pattern might only be truly beneficial for scenarios where you need to expose large amounts of static data that the LLM can reference without explicit calls, but even then, theoretically, you could achieve the same with tools.

MCP has Self-describing capabilities i.e., tools declare their own schemas, so agents know exactly what parameters are needed without parsing docs. When a client (i.e., Claude desktop) calls my weather.py app (i.e., the MCP server), the app responds with:
{
“tools”: [{
“name”: “get_alerts”,
“description”: “Get weather alerts for a US state”,
“inputSchema”: {
“type”: “object”,
“properties”: {
“state”: {“type”: “string”, “description”: “Two-letter US state code (e.g. CA, NY)”}
},
“required”: [“state”]
}
}]
}

Any client automatically knows how to use the tools without needing separate documentation – the app (i.e., my MCP server – the weather.py python program) itself declares its capabilities. If you examine this file (weather.py) you would see that this program is declaring:

Available tools: “name”: “get_alerts” – what functions that the MCP client can call
Purpose: “description”: “Get weather alerts for a US state” – what each tool does
Required inputs: “required”: [“state”] – which parameters are mandatory
Input types: “type”: “string” – what data type each parameter expects
Input constraints: “Two-letter US state code (e.g. CA, NY)” – format/validation rules

So the app tells clients: “I have a tool called get_alerts, it gets weather alerts, you must provide a state parameter as a string in 2-letter format like ‘CA’.”

The client knows exactly how to call the tool correctly without reading external documentation.

REST APIs can also be self-describing through discovery endpoints (GET /tools or GET /capabilities), OpenAPI specs, and capability responses, so the technical self-describing capability isn’t unique to MCP – it’s more about MCP making it a standard requirement rather than an optional feature.

MCP standardizes tool discovery as a mandatory part of the protocol. MCP requires discovery with all servers responding to the tools/list method using the same JSON structure, while REST APIs would use varying endpoints (/tools, /api/docs, /capabilities) with different response formats, requiring custom parsing logic for each API.

While REST implementations would vary and discovery would be optional – requiring custom integration logic for each API’s specific discovery pattern, MCP servers have a built-in discovery flow that AI frameworks expect.

Now, what does this mean, “a built-in discovery flow that AI frameworks expect.” What it really means is this: Since MCP is becoming popular, Python libraries like LangGraph and AutoGen have created MCP client SDKs/integrations that know how to automatically discover and use MCP tools. MCP’s adoption isn’t purely about technical advantages – it’s also about timing and ecosystem momentum.

MCP enables writing one universal client that works with any MCP server (weather, database, file system) because they all use standardized discovery and calling patterns, while REST APIs require separate connectors for each service due to different endpoints, authentication, response formats, and discovery mechanisms. In our scenario – Claude Desktop has a universal MCP client built into it. When I created the weather.py I didn’t need to do any instrumentation in the client side to make it work.

When would REST APIs be more suitable? In MCP client-server design, one consideration is response flexibility while ensuring client standardization. While MCP enables universal clients through standardized JSON-RPC structure, REST APIs allow optimized response formats for specific use cases like binary downloads, streaming data, and complex structured objects that may not easily fit MCP’s text-centric response format.

MCP protocol also brings persistent bidirectional communication: Unlike REST’s request-response, MCP maintains context and supports real-time updates during long operations. : MCP keeps one persistent connection open (like a phone call), while REST opens/closes a new connection for each request (like sending separate text messages). For example, our weather.py MCP server upon running continuously waits for incoming user questions routed to it via the Claude desktop.

This persistent bidirectional communication is not impossible without MCP – however MCP enforces this through the specs and as such SDKs and libraries now provide support for this.

So how would a persistent bidirectional communication happen without MCP? WebSockets can provide two-way persistent connection where both client and server can send messages anytime. Your connection would start as HTTP(S) then upgrade to WebSocket protocol. For example, chat applications, real-time gaming utilize this pattern.

My language of choice for most of my AI projects is Python. Let’s see how it happens in Python under the hood.

Following is my understanding. Python’s MCP implementation uses asyncio for handling asynchronous operations, sys.stdin/stdout for the actual bidirectional pipe communication with the client, and the FastMCP library wraps these low-level components into proper MCP protocol handling with JSON-RPC message parsing and tool routing.

Under the hood: When you run mcp.run(transport=’stdio’), it’s essentially doing:
# Read JSON-RPC messages from stdin
async for line in sys.stdin:
message = json.loads(line)
# Process message, call your tools
response = handle_message(message)
# Write response to stdout
sys.stdout.write(json.dumps(response))

Since in weather.py I am using transport=’stdio’, FastMCP likely uses Python’s built-in asyncio and sys.stdin/stdout for JSON-RPC communication over pipes. FastMCP would likely use something like FastAPI or Starlette under the hood if I were to specify transport=’http’ instead of transport=’stdio’.

MCP is specially built for AI workflows. MCP is designed specifically for tool calling patterns that LLMs use, rather than adapting web APIs for AI. Let’s unpack this a bit further.

LLMs have been trained from millions of examples that function calls have a name and structured arguments, so they reliably work with this format. For example, LLMs have been trained on patterns like the following patterns that are found in code repositories and documentation:

# Call weather function result = get_weather(city=”Paris”, country=”France”)
# Get user data user = fetch_user(user_id=123)
So when prompted: “Get weather for Paris”, the LLM generates:
{ “name”: “get_weather”, “arguments”: {“city”: “Paris”} }

The structured text helps because it’s predictable and parseable. Structured text provides consistent, parseable output that applications can execute directly, versus free-form text like “check Paris weather” that would require additional natural language parsing to extract the intended tool and parameters.

In our server and client – in the MCP workflows – structured text flows both ways.

Let’s consider our example.

In our little project, Claude Desktop is the application – it contains both: MCP client functionality (connects to weather.py, calls tools) and interface to Claude LLM (the actual language model running on Anthropic’s servers).

What happens at startup:

1. Claude Desktop reads claude_desktop_config.json
2. Claude Desktop launches weather.py and calls tools/list
3. weather.py returns available tools (get_alerts, get_forecast) with their schemas
4. Claude Desktop stores this tool inventory

Claude Desktop is the MCP client – it just happens to also be a chat application.

What happens when I ask a question:

1. Claude Desktop sends both my question and the complete tool schemas to Claude LLM
2. Claude LLM sees: “User asked about weather + I have these tools available: get_alerts, get_forecast”
3. Claude LLM decides to generate a tool call based on this complete context
4. Claude LLM (on Anthropic servers) generates structured tool call
5. Claude Desktop’s MCP client calls weather.py
5. weather.py returns structured response
6. Claude Desktop gives response back to Claude LLM
7. Claude LLM generates final response to me.

The LLM doesn’t magically know about tools – Claude Desktop explicitly tells it what’s available with every conversation. It’s like giving the LLM a toolbox and saying “here’s what you can use” each time I ask a question.

In addition to the above flow there is the concept of reflection. Reflection is an emerging capability of increasingly sophisticated LLMs that enables them to reason about their own tool usage and decision-making processes, rather than just making isolated function calls. As LLMs become more intelligent, they can evaluate whether their tool usage was sufficient, decide if additional tools are needed, or self-correct when they’ve chosen the wrong approach. MCP facilitates this by providing a standardized interface that makes it easy for LLMs to discover available tools and chain multiple tool calls together, enabling more autonomous and thoughtful problem-solving where the AI can reflect on its progress and adapt its strategy dynamically rather than following predetermined workflows.

Another thing which helps in AI workflows is schema-driven interactions. For example, in weather.py MCP server – the @mcp.tool() decorator with docstring provides the following schema.

async def get_alerts(state: str) -> str:
“””Get weather alerts for a US state.
Args:
state: Two-letter US state code (e.g. CA, NY)
“””

This tells the MCP client exactly what parameter format to use.

  • MCP server (weather.py) declares the schema
  • MCP client (Claude Desktop) receives this schema during tool discovery
  • MCP client presents available tools to the LLM
  • LLM generates tool calls based on what the client told it was available
  • MCP also provides the specs for context preservation: However, our weather.py example doesn’t do this – each tool call is independent. This would be relevant for tools that need conversation history.

AI workflows – and client designs – also benefit from the error structures in MCP specs. MCP errors are structured JSON like {“error”: {“code”: -1, “message”: “Invalid state code”}} rather than HTML error pages or HTTP status codes that typical web APIs (e.g., REST) return.

Ecosystem integration: MCP works natively with AI frameworks (LangGraph, AutoGen) without custom adapters.

As an example, AI frameworks like LangGraph and AutoGen have built-in MCP client libraries, allowing developers to connect to any MCP server with minimal configuration (e.g., langraph.add_mcp_server(“weather”, “python3 weather.py”)) rather than writing custom integration code for each API’s different response formats, authentication, and calling patterns.

Setting Up the MCP Weather Server on macOS – The Practical Steps

The Goal

I wanted to create a weather MCP server that would give Claude real-time weather data from the National Weather Service API.

The server would provide two tools:

get_alerts() – Weather alerts by US state
get_forecast() – 5-day forecast by coordinates

I used the quick start article on the MCP site: https://modelcontextprotocol.io/quickstart/server

My Setup Approach

I followed a configuration slightly different from how it is advised in the article. I decided to keep things simple and avoid virtual environments (uv or .venv) for this first project. Instead, I used Homebrew’s Python directly.

Step 1: Install Python and Packages
First, I made sure I had Python 3.10+ via Homebrew (FastMCP requires this):
brew install python@3.10

Then I installed the required packages:
pip3 install httpx fastmcp

I then downloaded Claude desktop and created a free account.

Step 2: Create the Weather Server
As I have mentioned above – I did not set out to write a cool MCP server from scratch. Instead I used the MCP server (weather.py) provided on the MCP site.

“My” weather.py file is using FastMCP python library, which is much simpler than the low-level MCP server setup.
The script uses the National Weather Service API (free, no API key needed) and defines two tools with the @mcp.tool() decorator.

An interesting observation – REST does not use or support stdio and is built entirely on HTTP(S). However MCP supports both stdio (for local integration like Claude Desktop) and HTTP (for web/cloud deployment).

My weather.py MCP server uses stdio since I need Claude Desktop to launch my MCP server as local subprocess and communicates and hence via stdin/stdout pipes.

The workflow is: Claude Desktop reads my config → launches python3 weather.py as a subprocess → sends JSON-RPC messages via stdin → receives responses via stdout (to the locally running MCP server). This is simpler than running a web server and avoids network complexity for local tools.

Step 3: The Configuration Issues
I needed to create a claude_desktop_config.json file since the installation of Claude desktop app doesn’t automatically create it. It has a config.json only by default.

Configuration of the environment is where I spent most of my time troubleshooting.

My initial claude_desktop_config.json (as recommended on MCP site) looked like:

{
“mcpServers”: {
“weather”: {
“command”: “uv”,
“args”: [
“–directory”,
“/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather”,
“run”,
“weather.py”
]
}
}
}

But since I am not using uv or .venv and directly using system Python I needed to change my config file. However, I kept getting “Server disconnected” errors.

The issue was that Claude Desktop was using the system Python (/usr/bin/python3 – version 3.9) while my packages were installed in Homebrew’s Python (/opt/homebrew/opt/python@3.10/bin/python3.10).

I discovered this by checking the MCP logs, which showed:
• ModuleNotFoundError: No module named ‘httpx’
• ModuleNotFoundError: No module named ‘mcp’

I needed to tell Claude Desktop to use the exact Python interpreter where I had installed the packages. I found my Python path:

$which python3
Output: python3: aliased to /opt/homebrew/opt/python@3.10/bin/python3.10

Then updated my config with the full path:
{
“mcpServers”: {
“weather”: {
“command”: “/opt/homebrew/opt/python@3.10/bin/python3.10”,
“args”: [“/full/path/to/weather.py”]
}
}
}

Testing and Debugging

To test if the script worked independently, I ran:
python3 weather.py

The script started and waited silently (this is correct MCP server behavior). The real debugging happened through Claude Desktop’s MCP logs which showed exactly what was failing.

Lessons Learned

Lessons for Deploying the Python MCP Server (on Macbook) to Work with Claude Desktop

Use full paths: Don’t rely on python3 aliases in MCP configs – use the full path to avoid confusion

Match your Python versions: Make sure Claude Desktop uses the same Python where you installed packages

Check the logs: MCP logs (mcp.log and mcp-server-weather.log) show the real errors

FastMCP needs Python 3.10+: The system Python is often 3.9, which won’t work

Test independently first: Always test your MCP server script directly before connecting to Claude Desktop

Working through my project – I was thinking what would I need to do if I were to use a programming language in which MCP support did not exist. Following are two questions I have been thinking through.

Q1. How would I go about building a MCP Server if a programming language did not have built-in MCP libraries? If I were to use a programming language in which a library (e.g., FastMCP in Python) did not exist – I would need to implement the JSON-RPC protocol manually – parsing incoming messages, routing to tools, formatting responses. The “persistent bidirectional” part is just reading/writing to stdin/stdout continuously in a loop. For HTTP transport I would use whatever web server framework exists in that language (like Express.js, Flask, etc.).

Q2. How would I go about building a MCP Client if a programming language did not have built-in MCP libraries?

If I were not using Claude Desktop – for a custom AI agent built from scratch, I would have needed to implement the full MCP client stack – tool discovery (tools/list), tool selection logic (deciding when tools are relevant), tool calling with proper formatting, and LLM integration that presents discovered tools as available functions, since the underlying LLM has no inherent knowledge of MCP.

If I were to build such an MCP client in a programming language without existing MCP libraries, I would need to build in custom parsing languages and other things. For example, I would need to implement the client-side JSON-RPC protocol manually – constructing standardized request payloads with the required messages, parameters, and context fields, making HTTP POST calls to MCP server endpoints, and parsing the standard choices[0].message.content response structure. For stdio transport, I’d need to handle bidirectional pipe communication by writing JSON-RPC messages to the process’s stdin and reading responses from stdout, while for HTTP transport I’d use the language’s standard HTTP client libraries with proper authentication headers and error handling.

Summarizing My Journey with MCP

After getting the Python path right, everything just worked. If you want to follow through the MCP example in the MCP site – the MCP server setup is very straightforward once you get the Python environment right – most of my complexity came from managing multiple Python installations on laptop.

I can now ask Claude for weather alerts by state or forecasts by coordinates, and it uses my custom MCP server to fetch real data from the National Weather Service.

MCP has been variously summarized as ‘api on top of api’ or an ‘api wrapper’. Perhaps it would be more accurate to say that MCP is a standardization protocol between AI agents and custom tools, rather than a mere wrapper for APIs.

However for ease of understanding we can think of MCP as an API wrapper protocol that takes existing APIs, databases, or file systems and presents them through a standardized JSON-RPC tool interface. For example, our weather.py wraps National Weather Service HTTP/REST calls into discoverable tools like get_alerts() that AI agents can use consistently.

Glue logic vs MCP tool calling represents a fundamental shift in how we integrate external services with AI systems. Glue logic is the custom code you write to connect software components that weren’t designed to work together – like an adapter that translates between different systems, handling data format conversions and API differences to make everything communicate smoothly.

With traditional glue logic, developers write explicit conditional code like the following:

if “weather” in user_input:
call_weather_api()

The above code segment is essentially hardcoding decision trees for when to use which API.

MCP tool calling flips this paradigm: instead of the developer deciding, the LLM autonomously reasons about which tools to use based on the user’s natural language request. The LLM sees available tools (weather, database, search) with their schemas and decides “I should call get_weather with city=Paris” without any predetermined logic. This transforms tools from procedural function calls into declarative capabilities that AI agents can discover and orchestrate dynamically, enabling true reasoning-driven automation rather than scripted workflows.

In my example in this article I have focused on a single MCP server – not multiple servers. However the technical concepts would be very similar in a multi-server environmnt. I have discussed in my article how LLMs can choose various tools in various servers and how to make that selection accurate by providing better tool descriptions.

How about security?

MCP does not mandate any specific security strategy in its protocol specification. It relies on the common api security standards. Pls see below.

  • Local stdio servers (like our weather.py) run with user-level permissions and communicate through local pipes with no network exposure (except of course for the web APIs that they are wrapping in – which would have their own protection).
  • HTTP-deployed servers require standard web authentication like API keys, OAuth, or mTLS. MCP relies on HTTP-layer security rather than prescribing its own authentication methods.

Key risk considerations in MCP security include malicious MCP servers executing harmful operations, AI agents making unintended tool calls, and potential data exposure through tool responses. Developers should implement proper authentication, input validation, and run servers with minimal required permissions regardless of source. Remember best security is a zero trust model security.

MCP Architectural Questions

Following are the questions to ask yourself when thinking of MCP

  • What’s your overall project context – e.g., AI agents … for which systems… where is the data?
  • What are the data sources that you would want to be presented as MCP servers?
  • What are the AI applications that would act as MCP clients – what would they do with the data from MCP servers?
  • Are you planning to use 3rd party MCP servers or build your own – have you considered MuleSoft MCP connector?
  • Where would you deploy MCP servers … have you considered Heroku?

References – Model Context Protocol (MCP)

MCP User Guide (This page also has link to the MCP specification): https://modelcontextprotocol.io/quickstart/server

Building Agents with Model Context Protocol – Full Workshop with Mahesh Murag of Anthropic: https://www.youtube.com/watch?v=kQmXtrmQ5Zg

My MCP Server and MCP Client

  • MCP support across Salesforce https://developer.salesforce.com/blogs/2025/06/introducing-mcp-support-across-salesforce

    A universe of many prebuilt MCP servers https://github.com/punkpeye/awesome-mcp-servers

    Heroku support for MCP

    I have included here a recording of session that I participated in: – “Elevate Your Agents with Agentforce & MuleSoft” – on AI/LLM Powered Multi-Agent Systems (MAS) .The session recording provides you best practices, real-world implementation patterns, a comprehensive code-to-cloud demonstration of a multi-agent AI/LLM system, plus an under the hood look at how to build such a multi-agent system.

     4,378 total views,  5 today