GitHub Copilot Day Workshop
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 |
Drop a message in the Teams chat with:
| 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 |
An AI agent uses an LLM to run tools in a loop to achieve a goal.
Agents are often augmented by:
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).
find_customer("Pamela")get_last_order("cust_42")check_inventory(items)find_substitute("baguette")LLMs have been trained to know how to "call tools".
The model does not execute the tool.
The runtime does.
MCP is the open standard that tells agent runtimes how to discover and connect to the tools they can call.
🔗 MCP specification: modelcontextprotocol.io
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 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!
| 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. |
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
| 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
@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
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"]
}
}
@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
@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
Start the server:
uv run servers/expenses_tracker.py
Point agent at the local URL:
http://localhost:8000/mcp
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 3:
Build your own product-store MCP server
exercise3.md
🙋🏽♀️ 🙋🏻♂️ 🙋🏿 🙋🏼♀️ 🙋🏾♂️ Got a question? Post it in the chat!
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 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.
@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 let tools return rich, interactive UI that renders inside chat.
Example uses
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.
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
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 4:
Add visual UI and purchase confirmation
exercise4.md
🙋🏽♀️ 🙋🏻♂️ 🙋🏿 🙋🏼♀️ 🙋🏾♂️ Got a question? Post it in the chat!
Connect your agents to more MCP servers to enhance your productivity:
If you are putting an MCP server in production, consider:
When using a server:
When building a server:
Repository:
github.com/pamelafox/github-copilot-mcp-tutorial
Check out more of my MCP talks: