- Introduction to MCP
- Core MCP Concepts
- Workshop Setup
- Console Client
- EntraID Client
- Demo 1: Basic Stock Server
- Demo 2: OAuth-Protected Weather Server
- Demo 3: OAuth 2.0 On-Behalf-Of MCP Server
- Demo 4: Secure Access to MCP Servers in Azure APIM
- Demo 5: MCP with Azure AI Foundry
- Demo 6: MCP Bindings for Azure Functions
- Demo 7: Logic Apps Connectors as MCP Servers using Azure API Center
- Demo 8: Extend Copilot Studio Agent Using MCP
- Demo 9: Private and Enterprise-Ready MCP Registry
- Security Risks in MCP
- Demo 10: XPIA Attacks in MCP
- Demo 11: Remote Code Execution (RCE) in MCP
- Demo 12: MCP Tool Poisoning
- Demo 13: MCP Tool Shadowing
- Hands-on Exercises
- Key Takeaways
- Resources
- Next Steps
The Model Context Protocol (MCP) is an open standard that enables AI assistants to securely connect to data sources and tools. Think of it as a universal API that allows language models to interact with external systems in a standardized way.
- Standardization: One protocol for all integrations
- Security: Built-in authentication and authorization
- Flexibility: Supports multiple transport methods
- Extensibility: Easy to add new capabilities
Resources are read-only data sources that can be accessed by AI models. They represent information that the model can read but not modify.
Example from our stock server:
@mcp.resource("stock://{symbol}")
def stock_resource(symbol: str) -> str:
"""Expose stock price data as a resource."""
price = get_stock_price(symbol)
return f"The current price of '{symbol}' is ${price:.2f}."Tools are functions that AI models can call to perform actions or retrieve dynamic data. Unlike resources, tools can have side effects.
Example from our stock server:
@mcp.tool()
def get_stock_price(symbol: str) -> float:
"""Retrieve the current stock price for the given ticker symbol."""
# Tool implementationPrompts are reusable templates that help structure interactions with AI models. They provide context and formatting for specific use cases.
Example from our stock server:
@mcp.prompt("stock_analysis_prompt")
def stock_analysis(symbol: str, period: str = "1mo"):
"""Build a prompt for analyzing a stock's recent performance."""
# Returns formatted prompt messagesSampling allows MCP servers to request completions from the connected AI model, enabling interactive and context-aware responses.
Example from our stock server:
@mcp.tool()
async def stock_headline_sampling(symbol: str, ctx: Context[ServerSession, None]) -> str:
"""Use MCP sampling to generate a market-style headline."""
# Uses the client's model to generate contentElicitation refers to the process of gathering information from users to better understand their needs and preferences.
Example from our stock server:
@mcp.tool()
async def get_ticker_info(symbol: str, ctx: Context[ServerSession, None]) -> str:
"""Get comprehensive information for a ticker symbol with optional 52-week range."""
# Uses the client's model to ask clarifying questionsThe latest MCP spec (2025-06-18) supports OAuth 2.1 for secure access to protected resources, ensuring that only authorized users can access sensitive data.
Key Features and Standards Supported
| Feature | Description |
|---|---|
| OAuth 2.1 Compliance | MCP adopts the latest OAuth 2.1 draft (draft-ietf-oauth-v2-1-13) for improved security and simplicity. |
| Authorization Server Metadata (RFC 8414) | MCP servers advertise their associated authorization server endpoints for discovery. |
| Dynamic Client Registration (RFC 7591) | MCP clients can register dynamically with the authorization server. |
| Protected Resource Metadata (RFC 9728) | MCP servers publish metadata about protected resources to guide client authorization. |
| Resource Indicators (RFC 8707) | Clients specify the intended audience for tokens, preventing misuse across services. |
-
Azure account with permission to create resources
-
Azure OpenAI service (for the console client)
-
Entra ID (for authorization support)
-
(Optional) VS Code with Azure Functions extension
# Clone this repository
git clone https://github.com/huangyingting/mcp-workshop
cd mcp-workshop
# Install dependencies
uv syncCreate and deploy an Azure OpenAI in Azure AI Foundry, refer to here.
To enable OAuth support for MCP, two applications must be registered in Entra ID: one representing the MCP client, and the other representing the MCP server.
A script is provided to automate the registration process. To use it, run:
chmod +x prepare.sh
./prepare.shThis script will automatically create both Entra ID applications and generate separate .env files for the MCP client and server.
If you prefer to register the applications manually, follow these steps:
- From Azure portal Microsoft Entra ID->Manage->App Registration->New registration, register a MCP client application.
- Manage->Authentication->Settings, enable Allow public client flows.
- Manage->Manifest, modify
accessTokenAcceptedVersionto 2. - Record Application (client) ID from Overview, we will need it later in MCP client configuration.
- In the Azure portal, go to Microsoft Entra ID > App registrations > New registration and register an MCP server application.
- Under Manage > Certificates & secrets > Client secrets, create a new client secret and securely save its Value.
- Under Manage > Manifest, set
"accessTokenAcceptedVersion": 2and save. - Under Manage > Expose an API > Scopes defined by this API, select Add a scope and create these scopes (record the full URIs):
api://<server_app_id>/MCP.Promptsapi://<server_app_id>/MCP.Toolsapi://<server_app_id>/MCP.Resources
- Under Manage > Expose an API > Authorized client applications, add your MCP client application ID (from step 4 of the MCP Client Application Registration). Also add
aebc6443-996d-45c2-90f0-388ff96faa56(Visual Studio Code). - Under Manage > API permissions, ensure Microsoft Graph > Delegated permissions > User.Read is included, then select Grant admin consent for .
- From Overview, record Application (client) ID, Directory (tenant) ID, and the client secret you created; you’ll use them in the MCP server configuration.
Create a .env file in the servers directory for authorization (used by servers/entraid_weather_server.py):
# OAuth Configuration (for weather server demo)
TENANT_ID=your-azure-tenant-id # Tenant ID from MCP Server Application Registration
CLIENT_ID=your-azure-app-client-id # Client ID from MCP Server Application Registration
CLIENT_SECRET=your-azure-app-client-secret # Client Secret from MCP Server Application Registration
SCOPES=MCP.Tools,MCP.Resources,MCP.Prompts # Scopes from MCP Server Application RegistrationCreate a .env file in the clients directory (used by clients/console_client.py and clients/entraid_client.py):
# Azure OpenAI Configuration (required for console client)
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
AZURE_OPENAI_API_KEY=your-api-key
AZURE_OPENAI_API_VERSION=2024-02-15-preview
AZURE_OPENAI_DEPLOYMENT=your-deployment-name
CLIENT_ID=your-azure-app-client-id # Client ID from MCP Client Application RegistrationThe workshop includes a comprehensive console client (clients/console_client.py) that demonstrates how to build MCP clients.
graph TB
subgraph "Initialization"
Start([Start]) --> ParseArgs[Parse Command Line Arguments]
ParseArgs --> CheckArg{Check Argument Type}
CheckArg -->|.py file| ConnectStdio[Connect to STDIO Server]
CheckArg -->|http://.../sse| ConnectSSE[Connect to SSE Server]
CheckArg -->|http://.../mcp| ConnectHTTP[Connect to HTTP Server]
ConnectStdio --> InitSession[Initialize Client Session]
ConnectSSE --> InitSession
ConnectHTTP --> InitSession
InitSession --> ListTools[List Available Tools]
ListTools --> ChatLoop[Start Chat Loop]
end
subgraph "Chat Loop"
ChatLoop --> UserInput[Get User Input]
UserInput --> CheckQuit{Quit?}
CheckQuit -->|Yes| Cleanup[Cleanup & Exit]
CheckQuit -->|No| ProcessQuery[Process Query]
end
subgraph "Query Processing"
ProcessQuery --> PrepareMessages[Prepare Messages Array]
PrepareMessages --> PrepareTools[Format Available Tools]
PrepareTools --> CallOpenAI[Call Azure OpenAI API]
CallOpenAI --> CheckToolCalls{Has Tool Calls?}
CheckToolCalls -->|No| ReturnResponse[Return Response]
CheckToolCalls -->|Yes| AddAssistantMsg[Add Assistant Message]
AddAssistantMsg --> ProcessToolCalls[Process Tool Calls]
ProcessToolCalls --> CallMCPTool[Call MCP Tool via Session]
CallMCPTool --> AddToolResponse[Add Tool Response to Messages]
AddToolResponse --> CallOpenAI
end
subgraph "MCP Session Components"
Session[ClientSession]
Session --> SamplingCB[Sampling Callback]
Session --> ElicitationCB[Elicitation Callback]
SamplingCB --> AzureOpenAI2[Azure OpenAI]
ElicitationCB --> Decline[Always Decline]
end
ReturnResponse --> DisplayResult[Display Result]
DisplayResult --> UserInput
subgraph "Transport Layers"
StdioTransport[STDIO Transport<br/>Python subprocess]
SSETransport[SSE Transport<br/>Server-Sent Events]
HTTPTransport[HTTP Transport<br/>Streamable HTTP]
end
ConnectStdio -.-> StdioTransport
ConnectSSE -.-> SSETransport
ConnectHTTP -.-> HTTPTransport
- User query is sent to Azure OpenAI with available MCP tools
- If tools are called, the client executes them via MCP
- Tool results are sent back to Azure OpenAI
- Final response is presented to the user
The client supports all MCP transport protocols:
- stdio: For local Python servers
- HTTP: For streamable HTTP MCP servers
- Server-Sent Events (SSE): For SSE MCP servers
- Uses Azure OpenAI for chat completions
- Implements MCP sampling callbacks
- Supports function calling with MCP tools
- Handles tool execution and response formatting
- Provides a readline-enabled console interface
- Automatically discovers and uses available MCP tools
- Handles tool calls and responses seamlessly
# Connect to the stock server
uv run clients/console_client.py servers/simple_stock_server.py
# Connect to the weather server
uv run clients/entraid_client.py servers/entraid_weather_server.py# Connect to HTTP/MCP endpoint
uv run clients/console_client.py http://localhost:8000/mcp
# Connect to SSE endpoint
uv run clients/console_client.py http://localhost:8000/sse$ uv run clients/console_client.py servers/simple_stock_server.py
MCP Client Started! Type your queries or `quit` to exit.
Query: What's the current price of Apple stock?
Tool get_stock_price({'symbol': 'AAPL'}) -> [{'type': 'text', 'text': 'The current price of AAPL is $150.25'}]
The current price of Apple stock (AAPL) is $150.25.
Query: Compare Apple and Microsoft stock prices
Tool compare_stock_prices({'symbol1': 'AAPL', 'symbol2': 'MSFT'}) -> [{'type': 'text', 'text': 'AAPL ($150.25) vs MSFT ($380.50): MSFT is 153.2% higher than AAPL'}]
Comparing Apple (AAPL) and Microsoft (MSFT):
- AAPL: $150.25
- MSFT: $380.50
Microsoft's stock is currently 153.2% higher than Apple's.
Query: quitThe workshop also includes an EntraID client (clients/entraid_client.py) that demonstrates how to implement OAuth 2.0 authentication with Entra ID.
sequenceDiagram
participant User
participant Client as MCP Client
participant MCPServer as MCP Server
participant EntraID as Microsoft Entra ID
User->>+Client: Run script
Note over Client: Creates EntraIDDeviceCodeAuth instance
Client->>+MCPServer: Initial API Request (no auth token)
MCPServer-->>-Client: 401 Unauthorized with WWW-Authenticate header
Client->>Client: Discover protected resource metadata URL from header
alt Metadata URL not in header
Client->>+MCPServer: GET /.well-known/oauth-protected-resource
MCPServer-->>-Client: Return resource metadata (auth server, scopes)
else Metadata URL present
Client->>+MCPServer: GET metadata from URL
MCPServer-->>-Client: Return resource metadata (auth server, scopes)
end
Note over Client: Initializes MSAL PublicClientApplication
Client->>+EntraID: Initiate Device Code Flow
EntraID-->>-Client: Device Code & User Verification URL
Client->>User: Display verification URL and code
User->>+EntraID: Authenticates in browser
Client->>+EntraID: Poll for token with device code
EntraID-->>-Client: Access Token
Note over Client: Store token and prepare authed request
Client->>+MCPServer: Retry API Request with Bearer Token
MCPServer-->>-Client: 200 OK
Note over Client: Establish MCP Session
Client->>+MCPServer: MCP Handshake (Initialize)
MCPServer-->>-Client: MCP Handshake (Ack)
Client->>+MCPServer: list_tools()
MCPServer-->>-Client: Tools list
Client->>+MCPServer: list_resources()
MCPServer-->>-Client: Resources list
Client->>-User: Print available tools and resources
- Send request without a token; receive 401.
- Discover protected resource metadata:
- Prefer resource_metadata from WWW-Authenticate.
- Fallback to
/.well-known/oauth-protected-resourceat the server origin.
- Parse scopes and authorization_servers from metadata.
- Initialize MSAL PublicClientApplication with discovered authority.
- Try silent token; if absent/expired, start device code flow (prints a code and URL).
- Apply Bearer token and retry the original MCP request.
- Implements httpx.Auth to handle OAuth for MCP over Streamable HTTP.
- Discovers OAuth metadata (RFC 9728) from WWW-Authenticate or falls back to
/.well-known/oauth-protected-resource. - Parses ProtectedResourceMetadata to auto-configure scopes and authorization server.
- Uses MSAL device code flow with silent token acquisition and in-memory cache.
- Retries the original request after obtaining a token and then lists tools and resources.
- Server running at http://localhost:8000/mcp (entraid_weather_server.py).
- Environment variable for the client app registration:
- CLIENT_ID=your-azure-app-client-id
Example .env (in clients directory):
CLIENT_ID=00000000-0000-0000-0000-000000000000# Terminal 1: start the OAuth-protected server
uv run servers/entraid_weather_server.py
# Terminal 2: run the Entra ID client
uv run clients/entraid_client.py$ uv run clients/entraid_client.py
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code ABCD-EFGH to authenticate.
Available tools: ['get_weather']
Available resources: []Our first demo showcases a comprehensive MCP server that provides stock market data through various MCP primitives.
graph TB
%% Main Server
Server[FastMCP Stock Server<br/>simple_stock_server.py]
%% Mock Data Store
MockData[(MOCK_STOCK_DATA<br/>AAPL, MSFT, GOOGL<br/>AMZN, TSLA, META, NVDA)]
%% Resources
subgraph Resources ["MCP Resources"]
StockResource["stock://{symbol}<br/>Current Price Data"]
TickersResource["stock://tickers<br/>Available Tickers List"]
end
%% Tools
subgraph Tools ["MCP Tools"]
GetPrice["get_stock_price(symbol)<br/>→ float"]
GetHistory["get_stock_history(symbol, period)<br/>→ CSV string"]
CompareStocks["compare_stock_prices(symbol1, symbol2)<br/>→ comparison string"]
GetTickerInfo["get_ticker_info(symbol)<br/>→ comprehensive info"]
HeadlineSampling["stock_headline_sampling(symbol)<br/>→ AI-generated headline"]
end
%% Prompts
subgraph Prompts ["MCP Prompts"]
StockAnalysis["stock_analysis_prompt(symbol, period)<br/>→ Analysis prompt for AI"]
end
%% Helper Functions
subgraph Helpers ["Helper Functions"]
GenHistorical["generate_mock_historical_data()<br/>→ pandas DataFrame"]
GetPerformance["get_performance_summary()<br/>→ performance string"]
HumanizeNumber["_humanize_number()<br/>-> formatted numbers"]
end
%% Schemas
subgraph Schemas ["Pydantic Schemas"]
WeekRangeSchema["WeekRangePreference<br/>include_week_range: bool"]
end
%% External Interactions
Client[MCP Client]
AIModel[AI Model<br/>for sampling]
%% Main connections
Client -->|MCP Protocol| Server
Server --> Resources
Server --> Tools
Server --> Prompts
%% Data flow
MockData --> GetPrice
MockData --> GetHistory
MockData --> CompareStocks
MockData --> GetTickerInfo
MockData --> StockResource
MockData --> TickersResource
%% Tool dependencies
GetHistory --> GenHistorical
GetTickerInfo --> HumanizeNumber
GetTickerInfo --> WeekRangeSchema
StockAnalysis --> GetPerformance
GetPerformance --> GenHistorical
HeadlineSampling -->|sampling request| AIModel
%% Resource dependencies
StockResource --> GetPrice
@mcp.resource("stock://{symbol}") # Individual stock data
@mcp.resource("stock://tickers") # List of available tickers- Simple data retrieval:
get_stock_price() - Historical data:
get_stock_history() - Comparison logic:
compare_stock_prices() - Interactive tools:
get_ticker_info()with user elicitation
The stock_analysis_prompt demonstrates how to create structured prompts for AI analysis.
- Sampling: Generate headlines using the client's AI model
- User Elicitation: Ask users for preferences during tool execution
# Run with stdio transport (for console client)
uv run servers/simple_stock_server.py
# Run with HTTP transport (for web clients)
uv run servers/simple_stock_server.py -t streamable-http
# Development mode with auto-reload
uv run mcp dev servers/simple_stock_server.py- Launch the
simple_stock_serverfrom.vscode/mcp.jsonby clicking theStartlink above its configuration. - In GitHub Copilot Chat, switch to
Agentmode and select theMCP Server: simple_stock_servertool. - Ask the agent questions like,
- What's the price of Tesla stock?
- Compare Apple and Google stock prices
- Get Microsoft stock info (elicitation)
- Generate a headline for Amazon stock (sampling)
uv run mcp dev servers/simple_stock_server.py- Resource Access: Try accessing
stock://AAPLorstock://tickers - Tool Calls: Use
get_stock_price("AAPL")orcompare_stock_prices("AAPL", "MSFT") - Prompts: Invoke the
stock_analysis_promptfor AAPL - Interactive Tools: Use
get_ticker_info()and see the elicitation in action
# Start the console client with the stock server
uv run clients/console_client.py servers/simple_stock_server.py
# Try these example queries:
# - "What's the price of Tesla stock?"
# - "Compare Apple and Google stock prices"
# - "Get Microsoft stock info" (elicitation)
# - "Generate a headline for Amazon stock" (sampling)Our second demo shows how to implement OAuth support with Azure Entra ID, demonstrating enterprise-grade security. This example focuses on implementing authorization directly within the MCP server.
graph TD
%% External Systems
Client[Client Application]
EntraID[Microsoft Entra ID<br/>OAuth Provider]
%% Main Application Components
subgraph OAuth_Weather_Server ["OAuth Weather Server"]
direction TB
%% FastMCP Framework
FastMCP[FastMCP Server<br/>HTTP Transport]
AuthSettings[Auth Settings<br/>- Issuer URL<br/>- Resource Server URL<br/>- Required Scopes]
%% Token Verification
EntraIdTokenVerifier[EntraIdTokenVerifier<br/>- Tenant ID<br/>- Client ID<br/>- JWKS Cache]
JWKS[JWKS Cache<br/>1-hour TTL]
%% Business Logic
WeatherTool[get_weather Tool<br/>Random Weather Data]
WellKnown[OAuth Metadata<br/>/.well-known/oauth-protected-resource]
%% Configuration
Config[Environment Config<br/>- TENANT_ID<br/>- CLIENT_ID<br/>- SCOPES]
end
%% External JWKS Endpoint
JWKSEndpoint[JWKS Endpoint<br/>login.microsoftonline.com]
%% Flow Connections
Client -->|Request with Bearer Token| FastMCP
FastMCP -->|Verify Token| EntraIdTokenVerifier
EntraIdTokenVerifier -->|Check Cache| JWKS
EntraIdTokenVerifier -->|Fetch if Expired| JWKSEndpoint
JWKSEndpoint -->|Return Keys| JWKS
EntraIdTokenVerifier -->|JWT Verification| EntraID
EntraID -->|Validate Signature| EntraIdTokenVerifier
FastMCP -->|Authorized Request| WeatherTool
WeatherTool -->|Weather Data| FastMCP
FastMCP -->|Response| Client
%% OAuth Discovery
Client -->|OAuth Discovery| WellKnown
WellKnown -->|Metadata| Client
%% Configuration
Config -.->|Configure| AuthSettings
Config -.->|Configure| EntraIdTokenVerifier
class EntraIdTokenVerifier(TokenVerifier):
"""JWT token verifier for Entra ID (Azure AD)."""
async def verify_token(self, token: str) -> AccessToken | None:
# Verifies JWT signatures, expiration, audience, and issuerThe server requires specific scopes to access weather data:
REQUIRED_SCOPES = [f"api://{CLIENT_ID}/{SCOPE}" for SCOPE in SCOPES.split(",")]Implements the OAuth 2.0 Protected Resource Metadata standard:
@mcp.custom_route("/mcp/.well-known/oauth-protected-resource", methods=["GET"])
async def custom_well_known_endpoint(request: Request) -> Response:
# Returns metadata about the protected resourceRefer to the Entra ID Configuration section for setup instructions.
# Ensure .env file is configured with Entra ID settings
uv run servers/entraid_weather_server.pyThe server will:
- Start on
http://localhost:8000/mcp - Serve the
.well-known/oauth-protected-resourceendpoint - Require valid JWT tokens for weather data access
# Start the OAuth-protected server in one terminal
uv run servers/entraid_weather_server.py
# In another terminal, authenticate and connect using the Entra ID client
uv run clients/entraid_client.py- Launch the
entraid_weather_serverfrom.vscode/mcp.jsonby clicking theStartlink. VS Code will automatically handle the OAuth authentication flow. - In GitHub Copilot Chat, switch to
Agentmode and select theMCP Server: entraid_weather_servertool. - Ask the agent questions like, "What is the weather like in Seattle?"
On-behalf-of (OBO) is an OAuth 2.0 flow where a middle-tier service exchanges a user's access token for a new token to call downstream APIs on the user's behalf.
The demo below shows how the MCP server accesses the user's profile using the OBO flow.
Refer to the Entra ID Configuration section for setup instructions.
# Ensure .env file is configured with Entra ID settings
uv run servers/entraid_obo_server.pyThe server will start on http://localhost:9000/mcp and act on behalf of the user.
- Launch the
entraid_obo_serverfrom.vscode/mcp.jsonby clicking theStartlink. VS Code will automatically handle the OAuth authentication flow. - In GitHub Copilot Chat, switch to
Agentmode and select theMCP Server: entraid_obo_servertool. - Ask the agent questions like, "Who am I?", the agent will respond with the user's profile information.
While MCP servers can directly implement authorization as shown in Demo 2, this approach demands in-depth knowledge of the MCP specification and OAuth 2.1 protocols. For a more streamlined and resilient production solution, we recommend using Azure API Management (APIM) as a secure gateway. APIM simplifies securing MCP servers by handling complex authorization logic, enforcing governance through configurable policies, and enabling you to expose existing REST APIs or MCP endpoints as new MCP servers. For more information, see Secure access to MCP servers in API Management.
Refer to Expose REST API in API Management as an MCP server
In this pattern, APIM acts as an authorization server (AS), implementing dynamic client registration while delegating the underlying authentication and authorization to Microsoft Entra ID.
graph TD
%% MCP Clients
subgraph MCPClients["MCP Clients"]
VSCode["VS Code & Copilot"]
Inspector["MCP Inspector"]
end
%% AI Gateway
subgraph Gateway["AI Gateway"]
subgraph APIM["Azure API Management<br/>Remote MCP Proxy"]
SSE["MCP Endpoint /sse"]
MSG["MCP Endpoint /message"]
OAuth["OAuth"]
end
end
%% MCP Server
subgraph MCPServer["MCP Server"]
subgraph AzFunc["Azure Function"]
Tool1["MCP Tool"]
Tool2["MCP Tool"]
Tool3["MCP Tool"]
end
end
%% Microsoft Entra ID
EntraID["Microsoft Entra ID"]
%% Connections
MCPClients <-->|MCP Protocol| APIM
APIM <-->|Secured Connection| AzFunc
Gateway <--> EntraID
MCPClients -.->|Login / Consent| EntraID
The latest draft version of MCP Authorization specification with Protected Resource Metadata (PRM), which simplifies the authorization implementation a lot.
An example of using APIM with PRM to protect MCP servers can be found at: MCP Servers authorization with Protected Resource Metadata (PRM) sample
By leveraging APIM, you can centralize security, reduce boilerplate code in your MCP servers, and adopt a more robust, scalable architecture for enterprise environments.
In this demo, we will show how to use MCP tools within an Azure AI Foundry agent. This allows the agent to leverage external data and services seamlessly.
# Run the ai foundry agent in terminal
$ uv run ai-foundry/agent_uses_mcp.py
Created agent, ID: asst_5VBk72Rx8N12BJM2YLStOYpc
MCP Server: microsoft_docs at https://learn.microsoft.com/api/mcp
Created thread, ID: thread_dj3meOJ0oa6OlcvOLR3DwYka
Created message, ID: msg_8pMY1dsoXXXAw3VyrzJeoxc6
Created run, ID: run_d7FGCdBLks1dGSnBI1xFjZvL
Current run status: RunStatus.IN_PROGRESS
Approving tool call: {'id': 'call_3EB0mS6J0UN8Adeb9LHFUmpi', 'type': 'mcp', 'arguments': '{"query":"Azure CLI commands to create Azure Container App with managed identity","question":"Azure CLI commands to create Azure Container App with managed identity"}', 'name': 'microsoft_docs_search', 'server_label': 'microsoft_docs'}
tool_approvals: [{'tool_call_id': 'call_3EB0mS6J0UN8Adeb9LHFUmpi', 'approve': True, 'headers': {}}]
Current run status: RunStatus.REQUIRES_ACTION
Current run status: RunStatus.IN_PROGRESS
Approving tool call: {'id': 'call_FD3JPUAsEhhHTXPlMVimQn35', 'type': 'mcp', 'arguments': '{"url":"https://learn.microsoft.com/en-us/azure/container-apps/managed-identity#configure-managed-identities"}', 'name': 'microsoft_docs_fetch', 'server_label': 'microsoft_docs'}
tool_approvals: [{'tool_call_id': 'call_FD3JPUAsEhhHTXPlMVimQn35', 'approve': True, 'headers': {}}]
Current run status: RunStatus.REQUIRES_ACTION
Current run status: RunStatus.IN_PROGRESS
Current run status: RunStatus.IN_PROGRESS
Current run status: RunStatus.IN_PROGRESS
Current run status: RunStatus.IN_PROGRESS
Current run status: RunStatus.COMPLETED
Run completed with status: RunStatus.COMPLETED
Step step_HQvC4YL1cg4ebZCJoBNU5ToW status: completed
Step step_EJBaxdu49XyJQrwajyZJEnqp status: completed
MCP Tool calls:
Tool Call ID: call_FD3JPUAsEhhHTXPlMVimQn35
Type: mcp
Step step_FnSTk0ivQlz0da5NwAhauVay status: completed
MCP Tool calls:
Tool Call ID: call_3EB0mS6J0UN8Adeb9LHFUmpi
Type: mcp
Conversation:
--------------------------------------------------
USER: Give me the Azure CLI commands to create an Azure Container App with a managed identity. search Microsoft docs
--------------------------------------------------
ASSISTANT: To create an Azure Container App with a managed identity using Azure CLI, you first create the container app and then assign the managed identity. There are two types of managed identities you can assign: system-assigned and user-assigned.
...
--------------------------------------------------
Deleted agentAzure AI Foundry can also act as an MCP server, exposing its capabilities through the MCP protocol. This enables MCP clients to interact with Foundry's features in a standardized way.
- Launch the
mcp_foundry_serverfrom.vscode/mcp.jsonby clicking theStartlink. - In GitHub Copilot Chat, switch to
Agentmode and select theMCP Server: mcp_foundry_servertool. - Ask the agent questions like, "What models can I use from Azure AI Foundry?" or "Show me the model card for Phi-4-reasoning."
For more information, see the MCP Server that interacts with Azure AI Foundry (experimental)
Azure Functions MCP extension allows you to use Azure Functions to create remote MCP servers. These servers can host MCP tool trigger functions, which MCP clients, such as language models and agents, can query and access to do specific tasks.
The repo contains a sample Azure Functions project with a math evaluation MCP tool. To run the sample project, follow these steps:
cd mcp-workshop/func
pip install -r requirements.txt
func startThis will start the Azure Functions MCP server locally. You can test the math evaluation server by using VS Code.
- Launch the
local_matheval_serverfrom.vscode/mcp.jsonby clicking theStartlink. - In GitHub Copilot Chat, switch to
Agentmode and select theMCP Server: local_matheval_servertool. - Ask the agent questions like, "101 + 202 = ?"
cd mcp-workshop/func
azd up- Launch the
remote_matheval_serverfrom.vscode/mcp.jsonby clicking theStartlink. VS Code will prompt you for two inputs:functionapp-name: the name of your deployed Function App.functions-mcp-extension-system-key: the system key for the MCP extension, which you can find in the Azure Portal under your Function App > Functions > App keys > System keys > mcp_extension.
- In GitHub Copilot Chat, switch to
Agentmode and select theMCP Server: remote_matheval_servertool. - Ask the agent questions like, "101 + 202 = ?"
To avoid charges, remove resources when done:
azd downSetting up Logic Apps as MCP servers lets you reuse existing workflows and tap into over 1,400 connectors—so your AI agents can securely interact with enterprise systems without reinventing the wheel. You also get flexible deployment options, built-in security with OAuth 2.0, and full monitoring support to keep everything compliant and traceable.
For more information, see: Introducing Logic Apps MCP servers (Public Preview) Set up Standard logic apps as remote MCP servers (Preview)
To demonstrate Logic Apps as MCP servers:
- Create a Logic App (Standard) using the Workflow Standard plan.
- Create an Azure API Center resource (Free tier is fine).
- In API Center: Discovery > MCP (preview) > Azure Logic Apps > Register.
- Complete the registration wizard: select the Logic App you created and choose the workflows/connectors you want exposed.
- In the Logic App: Settings > Authentication.
- Select the Microsoft identity provider and click Edit.
- Under Allowed client applications add: aebc6443-996d-45c2-90f0-388ff96faa56 (Visual Studio Code).
- Save changes. The Logic App is now discoverable and usable as an MCP server via API Center.
- Add an MCP server entry to
.vscode/mcp.jsonpointing to your Logic App MCP endpoint, e.g.https://<logic-app-name>.azurewebsites.net/api/mcp. - Launch it from
.vscode/mcp.jsonby clicking Start. Sign in to Azure if prompted. - In GitHub Copilot Chat, switch to Agent mode and select the MCP Server
<your_logic_app_mcp>. - Ask the agent questions related to the exposed connector, for example:
- Retrieve all todo tasks from the Jira Cloud instance "geticsu". (Jira connector)
- Retrieve all pages for Cloud ID
09ed35c8-a2fb-49a0-82a0-82e2a2fbe63d. (Confluence connector)
Using Copilot Studio, you can extend your agent with:
- MCP connectors (prebuilt MCP servers): Connect to Microsoft services such as Dataverse, Dynamics 365, and Fabric.
- Custom connectors (direct MCP integration): Connect to any MCP server using custom connectors configured in Power Apps or Power Automate.
Note: Copilot Studio currently supports MCP tools only.
Connect a prebuilt MCP connector (for example, Microsoft Learn Docs MCP Server) in Copilot Studio:
- In Copilot Studio, go to Agents > your agent (or create one) > Tools > Add a tool > Model Context Protocol.
- Select
Microsoft Learn Docs MCP Server, then chooseAdd to agent. - Open
Test your agentand ask a question such as: "How do I create an Azure Storage account using the Azure CLI?"
Connect to any MCP server using custom connectors in Power Apps or Power Automate.
To demonstrate a custom connector, deploy the sample Python-based news MCP server to Azure Container Apps, then create the connector in Copilot Studio:
cd mcp-workshop/aca
azd upAfter deployment, copy the Application URL from the Azure portal (Container Apps > your app > Overview).
Then follow these steps:
- In Copilot Studio, go to Agents > your agent (or create one) > Tools > Add a tool > New tool.
- Choose
Custom connector. You'll be redirected to Power Apps > Custom connectors; selectNew custom connector. - Choose
Import from GitHub. - Set options:
- Connector type: Custom
- Branch: dev
- Connector: MCP-Streamable-HTTP
- Select Continue.
- Set:
- Connector name: for example, News-MCP
- Description: optional
- Host: paste your Azure Container Apps host (for example, something.azurecontainerapps.io)
- Select
Create connector. - Close the connector.
The new MCP connector will appear under Model Context Protocol when you click Add a tool in Copilot Studio. Add it and try a prompt such as: "Today's sports news in the United States."
Using Azure API Center as a private MCP registry streamlines enterprise-wide discovery, cataloging, and governance of Model Context Protocol assets.
Sample private registry: MCP Center
By enabling tool invocation and data access, MCP introduces new security challenges at the intersection of LLMs and external systems. Threats span multiple components – from the prompt and model (AI logic) to the client and server (infrastructure) and the tools (external APIs).
Authentication Vulnerabilities
- Leaked OAuth tokens enable silent account takeovers
- Weak authentication allows unauthorized access
- No standardized authorization mechanisms
Tool Manipulation
- Tool Poisoning: Malicious tools impersonate legitimate ones
- Tool Shadowing: Fake tools intercept requests to authentic services
- Service Spoofing: Rogue MCP servers trick clients into connecting
Model Security
- Supply chain vulnerabilities in model development and distribution
- Model tampering through training data poisoning or weight manipulation
- Insufficient validation of deployed models
Data Exfiltration
- Compromised agents affect entire user sessions due to lack of native sandboxing
- Sensitive data embedded in model context gets exposed inappropriately
- Weak access controls on shared resources
System Vulnerabilities
- Local MCP servers without sandboxing exploit OS vulnerabilities
- Access to environment variables and system modification capabilities
- Improper network configurations expose infrastructure to external threats
Cascading Failures: Compromise at one layer can escalate across the entire system through trust relationships and privilege escalation chains.
Monitoring Gaps: Traditional security tools lack visibility into MCP-specific interactions, creating detection blind spots.
- Implement defense-in-depth strategies across all layers
- Adopt zero-trust principles for component interactions
- Use only approved models with comprehensive validation
- Establish proper sandboxing and isolation mechanisms
- Implement continuous monitoring tailored to MCP environments
An XPIA (indirect prompt injection) attack embeds malicious instructions within external content (for example, a web page, document, or email). When a generative AI system ingests that content, it may execute the hidden instructions as if they were legitimate user commands, leading to issues such as data exfiltration, unsafe output, or manipulation of future responses.
The notebook xpia.ipynb demonstrates XPIA attacks in MCP. To run the demo, start the MCP server first:
cd mcp-workshop/risks
uv run xpia.py -t streamable-httpThen follow the instructions in the notebook.
MCP is a context‑exchange protocol, but insecure integrations can open Remote Code Execution (RCE) paths.
The notebook rce.ipynb demonstrates RCE attacks in MCP. To run the demo, start the MCP server first:
cd mcp-workshop/risks
uv run rce.py -t streamable-httpThen follow the instructions in the notebook.
MCP tool poisoning is a cybersecurity vulnerability where attackers embed malicious instructions within the descriptions of tools offered via the MCP. These instructions are often hidden from the user but are processed by the AI model. The AI is tricked into performing unauthorized actions, such as exfiltrating sensitive data or hijacking the AI's behavior.
The notebook tool_poisoning.ipynb demonstrates tool poisoning attacks in MCP. To run the demo, start the MCP server first:
cd mcp-workshop/risks
uv run tool_poisoning.py -t streamable-httpThen follow the instructions in the notebook.
MCP tool shadowing is a type of tool poisoning where a malicious MCP tool's description contains hidden instructions that secretly alter the behavior of a separate, trusted tool from a different server. The AI model, processing all available tool descriptions, is tricked into applying these malicious instructions when the trusted tool is used, even if the malicious tool itself isn't directly invoked for that specific task. This can lead to actions like data exfiltration or unauthorized operations, all while the user believes they are interacting safely with the trusted tool.
The notebook tool_shadowing.ipynb demonstrates tool shadowing attacks in MCP. To run the demo, start the MCP server first:
cd mcp-workshop/risks
uv run tool_shadowing.py -t streamable-httpThen follow the instructions in the notebook.
Add a new tool that calculates portfolio value:
@mcp.tool()
def calculate_portfolio_value(holdings: dict[str, int]) -> str:
"""
Calculate total portfolio value.
holdings: dict mapping stock symbols to share counts
"""
# Your implementation here
passCreate a resource that provides market sector information:
@mcp.resource("stock://sectors")
def sectors_resource() -> str:
"""Return available market sectors."""
# Your implementation here
passDesign a prompt for risk assessment:
@mcp.prompt("risk_assessment_prompt")
def risk_assessment(symbol: str, risk_tolerance: str):
"""Generate a risk assessment prompt."""
# Your implementation here
passCreate a tool that asks users for their investment preferences:
class InvestmentPreference(BaseModel):
risk_level: str = Field(description="low, medium, or high")
time_horizon: str = Field(description="short, medium, or long-term")
@mcp.tool()
async def get_investment_advice(symbol: str, ctx: Context[ServerSession, None]) -> str:
"""Provide investment advice based on user preferences."""
# Use ctx.elicit() to get user preferences
passEnhance the stock server by adding OAuth 2.0 authentication. Use the entraid_weather_server.py implementation as a reference to protect tools that handle sensitive user data, such as the portfolio value tool from Exercise 1.
Create an MCP tool trigger using the Azure Functions MCP extension. See the documentation.
Connect the Azure Functions MCP tool from Exercise 6 to Copilot Studio by creating a custom connector.
Research mitigation strategies for Cross-domain Prompt Injection Attacks (XPIA) and fix the issues in risks/xpia.py.
Research mitigation strategies for Remote Code Execution (RCE) risks and fix the issues in risks/rce.py.
Research mitigation strategies for MCP Tool Poisoning and fix the issues in risks/tool_poisoning.py.
Research mitigation strategies for MCP Tool Shadowing and fix the issues in risks/tool_shadowing.py.
- MCP provides a standardized way to connect AI models to external data and tools
- Resources, Tools, and Prompts are the core primitives for different interaction patterns
- OAuth 2.0 integration enables enterprise-grade security
- Azure API Management (APIM) offers a robust and scalable solution for securing MCP servers in production environments.
- Treat external content and tools as untrusted: XPIA, tool poisoning/shadowing, and RCE are real risks—use allow-lists, provenance checks, and explicit trust boundaries.
- Enforce Zero Trust and least privilege: scope tokens to specific resources, restrict tool capabilities, and avoid leaking secrets into model context.
- Sandbox and control egress: isolate servers/tools (OS/container), restrict filesystem and network access, and limit outbound traffic by default.
- Observe and guardrail: log tool calls and sampling, monitor for anomalies, and add prompt-injection filters and output redaction to prevent data exfiltration.
- MCP Specification
- Python SDK Documentation
- FastMCP Guide
- OAuth 2.0 RFC 6749
- Protected Resource Metadata RFC 9728
- About MCP servers in Azure API Management
- Expose REST API in API Management as an MCP server
- Expose and govern an existing MCP server
- Secure access to MCP servers in API Management
- Connect to Model Context Protocol servers (preview)
- Extend your agent with Model Context Protocol
- Model Context Protocol bindings for Azure Functions overview
- Build your own MCP server for your domain
- Integrate with existing APIs
- Implement authorization for sensitive data
- Deploy to production with proper monitoring
This workshop provides hands-on experience with MCP development. For production deployments, ensure proper security reviews and testing.