Skip to main content

MCP - You can run but you can't hide

·1176 words·6 mins· loading · loading ·
Automation MCP Microsoft Azure LLM
Author
PandaEatsBamboo
Dingbat
Table of Contents
MCP - This article is part of a series.
Part 1: This Article

Introduction
#

The growth of large‑language models has been exponential since Google introduced the transformer in June 2017. Just a couple of years ago, I was deploying an OpenAI‑powered RAG pipeline while still wrestling with zero‑, one‑, and few‑shot prompting techniques. Then, in June 2023, OpenAI added function calling to the Chat Completions API—shortly followed by multi‑step Agents with tool‑chaining and self‑calling. Finally, on November 25, 2024, Anthropic released the Model Context Protocol (MCP), a new standard for securely connecting AI assistants to the systems and content repositories where enterprise data lives.

Official Documentation

Core Components
#

  • Model Context Protocol Specification & SDKs

    A formal schema (usually in TypeScript/JSON‑RPC) that defines the wire‑level messages, data types, and session semantics, plus official SDKs in Python, TypeScript, C#, Java, etc. so every implementation speaks the same “language”

  • Hosts

    LLM applications (e.g. Claude Desktop, IDE plugins, custom agents or builds) that initiate MCP sessions

  • MCP Client

    Lightweight connectors running inside the host. Each MCP client maintains a 1:1 JSON‑RPC session with a single server, handling protocol framing, capability negotiation, and bidirectional message routing

  • MCP Server

    External services (local or remote) that expose “tools,” “resources,” and “prompts” via MCP primitives. A server implements the MCP spec over stdio (for local), SSE and HTTP (for remote), and responds to client calls with structured results. It also offers features like Tools, Resources and Prompts

  • Local or Remote Services

    Local or remote DataSources/APIs that the Servers can connect to

High Level Flow
#

[ USER ]  
   │  
   ▼  
[ Host App ]  ← Claude Desktop, IDE Plugin, Custom Client/Agent  
   │  
   └──▶ [ MCP Client ]  
         (Implements JSON-RPC 2.0)  
         - Maintains session state  
         - Handles tool calls, context updates  
         - Talks to…  
               ▼  
           [ MCP Server(s) ]  ← Exposes tools/resources  
                - Responds to tool invocation  
                - Exposes APIs, context documents, databases, etc.

Proof of Concept
#

DISCLAIMER! : This script is provided as a proof of concept (POC) and is intended for demonstration and testing purposes only. It does not implement authentication, authorization, or other security best practices. Do not use this code in production environments without proper security measures.

I’ve developed many MCP tools using Claude (personal use), but I thought, why not build our own MCP as a proof of concept using VS Code as it just landed. The goal is to create an MCP Server to fetch Azure Inventory and Azure Recommendations. POC requirements

  • VS Code /w copilot
  • Python SDK and npm
  • Azure Account

MCP support in agent mode in VS Code is available starting from VS Code 1.99 and is currently in preview. Documentation

There are a few prerequisites before proceeding,

  • Install the Github Copilot VS Code Extension
  • Enable MCP support on settings.json manually or by going to Files > Preferences > Settings , and set these parameters "chat.mcp.discovery.enabled": true and "chat.mcp.enabled": true
  • Setup MCP.json for your MCP Server discovery , add .vscode/mcp.json in your VS Code workspace or global settings.json

I will not cover setting up an Azure account and service principal here. In short, you need to create a service principal or app registration, obtain the client ID and secret, and add them to the .env file. I have set the environment using uv , here the dependencies

dependencies = [
    "azure-identity>=1.23.0",
    "azure-mgmt-advisor>=9.0.0",
    "azure-mgmt-resource>=24.0.0",
    "mcp[cli]>=1.10.1",
    "python-dotenv>=1.1.1",
]

That said, creating an MCP server is fairly simple. All you need to do is import the libraries , setup @mcp.tool() decorator to define your tools and use type hints along with doc strings to give the tool a context for the LLM.


from mcp.server.fastmcp import FastMCP  # MCP server framework

# Instantiate the FastMCP server with the name of your server "azure-mcp"
mcp = FastMCP("azure-mcp")

# Asynchronous function to fetch all Azure resources in the subscription
@mcp.tool()
async def list_azure_resources() -> list[dict[str, Any]]:
    """List all Azure resources. Uses subscription ID from environment."""
    return await fetch_azure_resources()

# Asynchronous function to fetch Azure Advisor recommendations
@mcp.tool()
async def get_azure_advisor_recommendations() -> list[dict[str, Any]]:
    """Get Azure Advisor recommendations. Uses subscription ID from environment."""
    return await fetch_advisor_recommendations()

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

Now, all that’s left is to create the actual tool functionality and add your tool to the MCP.json. As per the code snippet, my mcp.tool list_azure_resources() calls fetch_azure_resources() . We will write a function that uses the Azure ResourceManagementClient to retrieve the resources.

async def fetch_azure_resources() -> list[dict[str, Any]]:
    subscription_id = os.environ.get("AZURE_SUBSCRIPTION_ID")
    if not subscription_id:
        raise ValueError("AZURE_SUBSCRIPTION_ID not set in env")

    credential = DefaultAzureCredential()
    client = ResourceManagementClient(credential, subscription_id)
    results = []

    async for rg in client.resource_groups.list():
        async for res in client.resources.list_by_resource_group(rg.name):
            results.append({
                "resource_group": rg.name,
                "name": res.name,
                "type": res.type,
                "location": res.location,
                "id": res.id
            })

    await client.close()
    await credential.close()
    return results

Finally, you need to add the MCP Server in .vscode/mcp.json

The Server name needs to match what you instantiated with FastMCP earlier

{
	"servers": {
      "azure-mcp": {
                "url": "http://localhost:8000/mcp/",
        }
    },
}

Now you can run the MCP server with uv/python or just use VS Code command palette (ctrl/cmd + shift + p) and look for MCP: List Servers. Change Copilot mode to Agent from Ask.

(mcppoc) @Panda ➜ mcppoc git(master)  uv run main.py
INFO:     Started server process [15728]
INFO:     Waiting for application startup.
[07/13/25 17:04:59] INFO     StreamableHTTP session        streamable_http_manager.py:112
                             manager started
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

Here’s the MCP server in action,

While I could have used stdio transport since everything is running locally, I opted for http streamable transport to capture the traffic over the network for analysis. With stdio, I’d need to explicitly enable and parse server-side logging to observe tool interactions , though VS Code does provide logging if you use the MCP Server extension to manage the server.

Alt Text
WireShark Captures from streamable-http

Below are logs from the perspective of VScode to MCP Server showing discovery of tools using JSON-RPC

[debug] [editor -> server] 
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{"roots":{"listChanged":true},"sampling":{},"elicitation":{}},"clientInfo":{"name":"Visual Studio Code","version":"1.102.0"}}}

[debug] [server -> editor] 
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"azure-mcp","version":"1.10.1"}}}

[debug] [editor -> server] 
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}

[debug] [server -> editor] 
{"jsonrpc":"2.0","id":2,"result":
			{"tools":[
				{"name":"list_azure_resources","description":"List all Azure resources in tabular format. Uses subscription ID from environment.","inputSchema":{"properties":{},"title":"list_azure_resourcesArguments","type":"object"},"outputSchema":{"properties":{"result":{"items":{"additionalProperties":true,"type":"object"},"title":"Result","type":"array"}},"required":["result"],"title":"list_azure_resourcesOutput","type":"object"}},
				
				{"name":"get_azure_advisor_recommendations","description":"Get Azure Advisor recommendations in tabular format. Uses subscription ID from environment.","inputSchema":{"properties":{},"title":"get_azure_advisor_recommendationsArguments","type":"object"},"outputSchema":{"properties":{"result":{"items":{"additionalProperties":true,"type":"object"},"title":"Result","type":"array"}},"required":["result"],"title":"get_azure_advisor_recommendationsOutput","type":"object"}}]}}

[info] Discovered 2 tools

Final Words
#

In conclusion, is MCP solving anything? Well, yes and no. Core developers are largely content with existing methods - MCP hasn’t fundamentally changed the paradigm. However, what it does offer is valuable: a layer of standardization that turns the spaghetti mess of tool invocation into a clean, structured protocol. It’s not revolutionary, but it’s a pragmatic step toward maintainable, scalable tooling … for now.

If you would like to read more , do check this white paper https://arxiv.org/pdf/2504.16736

What’s Next
#

Next MCP posts would be focused on,

  • S in MCP stands for Security - Security Mechanism in MCP framework (oAuth)
  • From Theory to Exploit: Practical Attacks on MCP Deployment - We will take a look at Attack Vectors related to some ongoing CVE and implement a Proof of concept
  • Cisco’s Foundation-sec‑8B Isn’t Just Another LLM — But Is It Any Better?
  • Can MCP Replace the CLI? - We will attempt to interface with the Cli world.
MCP - This article is part of a series.
Part 1: This Article