GitHub Copilot Day Workshop
Using and building MCP servers
Pamela Fox
Introductions
About me
Python Cloud Advocate at Microsoft
Formerly: UC Berkeley, Coursera, Khan Academy, Google
Find me online at:
| Mastodon | @pamelafox@fosstodon.org |
| BlueSky | @pamelafox.bsky.social |
| @pamelafox | |
| www.linkedin.com/in/pamela-s-fox/ | |
| GitHub | www.github.com/pamelafox |
| Website | pamelafox.org |
About you
Drop a message in the Teams chat with:
- Name & location
- How are you using GitHub Copilot - in VS Code, App, or CLI?
- What are you hoping to learn (or what have you learnt) at GitHub Copilot Day?
- Have you used any MCP servers before?
- Make up a new meaning for "MCP" (e.g. "More Coffee Please")
Today's agenda
| Part | Time |
|---|---|
| Welcome | 8 min |
| MCP 101 | 12 min |
| Exercise: Connect to MCP servers | 20 min |
| Exercise: Use the GitHub MCP server | 15 min |
| Building an MCP server | 10 min |
| Exercise: Build your own server | 15 min |
| Advanced server features | 20 min |
| Next steps | 5 min |
MCP 101
AI agents
An AI agent uses an LLM to run tools in a loop to achieve a goal.
Agents are often augmented by:
- Context from files, docs, or databases
- Memory across a session or project
- Human approvals and feedback
You've probably used an AI agent...
Coding agents like GitHub Copilot are AI agents.
Chatbots like Copilot and Scout also have agentic loops.
You can also build your own agents in code (Python/C#/etc).
The agentic loop in action
find_customer("Pamela")get_last_order("cust_42")check_inventory(items)find_substitute("baguette")Agents rely on LLM tool-calling
LLMs have been trained to know how to "call tools".
- LLM sees the available tools and their descriptions.
- LLM suggests a tool name and arguments.
- Agent runtime runs the actual code.
- Tool result is sent back to the LLM as context.
- LLM decides whether to call another tool or answer.
The model does not execute the tool.
The runtime does.
MCP: Model Context Protocol
MCP is the open standard that tells agent runtimes how to discover and connect to the tools they can call.
🔗 MCP specification: modelcontextprotocol.io
MCP architecture
MCP client/server request flow
MCP clients
Clients with the strongest support:
Support across 100+ clients varies:
| Capability | Support |
|---|---|
| Tools | All |
| Prompts | 43/113 |
| Resources | 47/113 |
| OAuth with DCR | 16/113 |
| Elicitation | 16/113 |
| Apps | 10/113 |
Exercise time
Exercise 1:
Connect GitHub Copilot to a public MCP server
exercise1.md
Exercise 2:
Use the GitHub MCP server to find and fix issues
exercise2.md
🙋🏽♀️ 🙋🏻♂️ 🙋🏿 🙋🏼♀️ 🙋🏾♂️ Got a question? Post it in the chat!
Building an MCP server
Python MCP frameworks
| Framework | Description |
|---|---|
| python-sdk | Official Python SDK for MCP. Lets you build both clients and servers fully compatible with the protocol specification. Handles transport (stdio, SSE, HTTP), message cycles, tools, resources, and prompts. |
| fastmcp | Framework built on top of the official SDK. Adds production-focused features like server composition, proxying, OpenAPI/FastAPI generation, enterprise authentication (Google, GitHub, Azure, Auth0, etc.), deployment utilities, and client libraries. |
FastMCP server skeleton
from fastmcp import FastMCP
mcp = FastMCP("Expenses tracker")
# Define tools, prompts, and resources here...
if __name__ == "__main__":
mcp.run(transport="streamable-http", host="0.0.0.0", port=8420)
🔗 Docs: gofastmcp.com
MCP transports: STDIO vs. HTTP
| STDIO | HTTP (Streamable) | |
|---|---|---|
| Client config | Command to run:uv run mcp_server.py | Server URL:http://localhost:8420/mcp |
| Startup | Client launches the server process | Server runs before clients connect |
| Best for | Local tools, quick tests, single-user apps | Remote access, web apps, multiple clients |
| Tradeoff | Simple, but tied to one local process | More flexible, but needs host/port setup |
Learn more: gofastmcp.com/deployment/running-server
Tool with FastMCP
@mcp.tool
async def add_expense(
date: Annotated[date, "Date of the expense in YYYY-MM-DD format"],
amount: Annotated[float, "Positive numeric amount of the expense"],
category: Annotated[Category, "Category label"],
description: Annotated[str, "Human-readable description of the expense"],
payment_method: Annotated[PaymentMethod, "Payment method used"],
) -> str:
"""Add a new expense to the expenses.csv file."""
# Implementation ...
return f"Added expense: ${amount} for {description} on {date_iso}"
🔗 Full example: expenses_tracker.py
Tool signature → JSON schema
This tool signature:
@mcp.tool
async def add_expense(
date: Annotated[date, "YYYY-MM-DD"],
amount: Annotated[float, "Amount"],
category: Category,
description: str,
payment_method: PaymentMethod,
) -> str:
"""Add a new expense."""
...
becomes this schema:
{
"name": "add_expense",
"description": "Add a new expense.",
"inputSchema": {
"type": "object",
"properties": {
"date": {
"type": "string",
"format": "date"
},
"amount": {"type": "number"},
"category": {"type": "string"},
"description": {"type": "string"},
"payment_method": {"type": "string"}
},
"required": ["date", "amount", "category",
"description", "payment_method"]
}
}
Resource with FastMCP
@mcp.resource("resource://expenses")
async def get_expenses_data():
csv_content = f"Expense data ({len(expenses_data)} entries):\n\n"
for expense in expenses_data:
csv_content += (
f"Date: {expense['date']}, "
f"Amount: ${expense['amount']}, "
f"Category: {expense['category']}, "
f"Description: {expense['description']}, "
f"Payment: {expense['payment_method']}\n"
)
return csv_content
🔗 Full example: expenses_tracker.py
Prompt with FastMCP
@mcp.prompt
def analyze_spending_prompt(
category: str | None = None,
start_date: str | None = None,
end_date: str | None = None,
) -> str:
# Build filters ...
return f"""Please analyze my spending patterns {filter_text} and provide:
1. Total spending breakdown by category
2. Average daily/weekly spending
3. Most expensive single transaction
4. Payment method distribution"""
🔗 Full example: expenses_tracker.py
Run the server
Start the server:
uv run servers/expenses_tracker.py
Point agent at the local URL:
http://localhost:8000/mcp
Test with MCP Inspector or MCPJam
MCP Inspector
npx @modelcontextprotocol/inspector
Official local tester for inspecting tools, resources, prompts, and raw MCP messages.
MCPJam
npx @mcpjam/inspector@latest
A friendly web UI for trying MCP servers during development. mcpjam.com
Useful debugging loop: direct server logs first, Inspector or MCPJam second, LLM client last.
Exercise time
Exercise 3:
Build your own product-store MCP server
exercise3.md
🙋🏽♀️ 🙋🏻♂️ 🙋🏿 🙋🏼♀️ 🙋🏾♂️ Got a question? Post it in the chat!
MCP as a collaboration protocol
New MCP features turn agents from executors into collaborators.
Elicitations
Servers request structured input from the user mid-flow, so agents can ask instead of guess.
MCP Apps
Render interactive UI inside the client, so users can inspect and manipulate results.
Tasks
Run long work without blocking, with progress updates and partial failure handling.
Elicitations
Elicitations let servers request user input through the client.
Form mode
Structured data collection in the client, optionally schema-validated.
Example uses: Clarification of parameters, confirmation (especially before write ops)
URL mode
Out-of-band flow for sensitive info that must not pass through the MCP client.
Example uses: API keys, OAuth, payment
purchase-product
FORM ELICITATION
Buy 2x Raspberry Pi Pico for $13.98?
connect-payments
URL ELICITATION
Open a secure page to connect your payment provider.
Form mode elicitation flow
Elicitations in FastMCP
@mcp.tool
async def remove_expenses_for_date(
expense_date: Annotated[date, "Date to remove expenses for"],
ctx: Context,
) -> str:
class DeleteExpensesConfirmation(BaseModel):
confirm: bool = Field(title="Delete expenses")
response = await ctx.elicit(
message=f"Delete expenses from {expense_date}?",
response_type=DeleteExpensesConfirmation,
)
if response.action != "accept" or not response.data.confirm:
return "Deletion cancelled."
# Delete matching rows from expenses.csv ...
🔗 Full example: expenses_tracker.py
📖 Learn more: gofastmcp.com/servers/elicitation
MCP Apps
MCP Apps let tools return rich, interactive UI that renders inside chat.
Example uses
- Drag-and-drop ordering
- Visual profiling and flame graphs
- Form-based selection
- Data visualization dashboards
Here's the performance profile for handle_request():
flame-graph
Examples of MCP servers using MCP Apps
Storybook
Interactive component previews from design-system tasks.
Excalidraw
Live diagrams in chat for visual planning and edits.
Figma
Design context and assets rendered where agents work.
MCP Apps flow
Making MCP Apps with FastMCP
Four paths, from Python components to custom UI.
Prefab Apps
Quickest path: add app=True to a tool and return Prefab components.
FastMCPApp
For multi-tool UIs: separate model-visible entry points from UI-only backend tools.
Custom HTML
Use your own HTML, CSS, JavaScript, or framework for maps, 3D, video, and bespoke UI.
Generative AI
Let the model design Prefab UI at runtime, execute it in a sandbox, and stream the result.
Learn more: gofastmcp.com/apps/overview
Prefab app with FastMCP
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, Heading
from prefab_ui.components.table import Table, TableBody, TableCell, TableHead, TableHeader, TableRow
@mcp.tool(app=True)
def render_expenses() -> PrefabApp:
"""Render the expenses as an interactive table."""
expenses = load_expenses_from_csv()
with Column(gap=4) as view:
Heading("Expenses")
with Table():
with TableHeader():
with TableRow():
TableHead("Date")
TableHead("Amount")
with TableBody():
for expense in expenses:
with TableRow():
TableCell(expense["date"])
TableCell(f"${float(expense['amount']):.2f}")
return PrefabApp(view=view)
🔗 Full example: expenses_tracker.py
Exercise time
Exercise 4:
Add visual UI and purchase confirmation
exercise4.md
🙋🏽♀️ 🙋🏻♂️ 🙋🏿 🙋🏼♀️ 🙋🏾♂️ Got a question? Post it in the chat!
Next steps
Use more MCP servers
Connect your agents to more MCP servers to enhance your productivity:
- Azure DevOps MCP — query work items, repos, pipelines, and wikis in Azure DevOps
- Work IQ MCP — search emails, meetings, documents, and Teams messages from Microsoft 365
- Azure MCP — manage Azure resources, storage, and AI services
Build more MCP servers
If you are putting an MCP server in production, consider:
- Testing: Test with both direct MCP tools and LLM-driven clients.
- Evaluation: Optimize tool descriptions for user scenarios across agents.
- Usage controls: Rate-limit expensive or state-changing tools.
- Observability: Log tool calls without logging sensitive payloads.
- Security: Continuously audit tools for security risks and logs for abuse.
Security questions around MCP servers
When using a server:
- Is this server from a trusted source?
- What data and systems can it access?
- Could a malicious tool description manipulate your agent? (tool poisoning)
- Are you aware when a tool will change state?
When building a server:
- Who is allowed to call each tool?
- What data can a tool read or mutate?
- Can prompt injection alter the server's behaviour?
- What happens when the model passes unexpected arguments?
Thank you!
Repository:
github.com/pamelafox/github-copilot-mcp-tutorial
Check out more of my MCP talks:
