GitHub Copilot Day Workshop

Using and building MCP servers

Pamela Fox

pamelafox.github.io/github-copilot-mcp-tutorial

Introductions

About me

Photo of Pamela smiling with an Olaf statue

Python Cloud Advocate at Microsoft

Formerly: UC Berkeley, Coursera, Khan Academy, Google

Find me online at:

Mastodon @pamelafox@fosstodon.org
BlueSky @pamelafox.bsky.social
Twitter @pamelafox
LinkedIn 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

PartTime
Welcome8 min
MCP 10112 min
Exercise: Connect to MCP servers20 min
Exercise: Use the GitHub MCP server15 min
Building an MCP server10 min
Exercise: Build your own server15 min
Advanced server features20 min
Next steps5 min

MCP 101

AI agents

Agent loop
User query LLM Goal Tools

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

Agent loop
User query LLM Goal Tools
User
Can I reorder my last bakery order for pickup today?
LLM
find_customer("Pamela")
Tool result
customer_id = "cust_42"
LLM
get_last_order("cust_42")
Tool result
items = [croissant, baguette]
LLM
check_inventory(items)
Tool result
croissant yes; baguette sold out
LLM
find_substitute("baguette")
Tool result
substitute = sourdough
Answer
I can reorder it with sourdough instead. Want me to place it?

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.

User request LLM suggested tool call get_last_order("cust_42") Agent runtime executes tool tool result items = [...]

MCP: Model Context Protocol

MCP is the open standard that tells agent runtimes how to discover and connect to the tools they can call.

🤖 AI agent 🗄️ Database 💬 Slack 🐙 GitHub MCP MCP MCP

🔗 MCP specification: modelcontextprotocol.io

MCP architecture

MCP Host AI agents and apps MCP Client A MCP Client B MCP Server A MCP Server B Tools Prompts Resources Tools Prompts Resources MCP MCP

MCP client/server request flow

MCP Client MCP Server ① Discover tools {"method": "tools/list"} {"tools": [{"name": "get_product", "description": "...", "inputSchema": {...}}, ...]} ② Call a tool {"method": "tools/call", "params": {"name": "get_product", "arguments": {"id": 1}}} {"content": [{"type": "text", "text": "Product: Widget (id=1), $9.99"}]}

MCP clients

Clients with the strongest support:

💻
VS Code + GitHub Copilot
⌨️
GitHub Copilot CLI
🔬
MCPJam Inspector
🔍
MCP Inspector
🤖
Claude Code
🖥️
Claude Desktop

Support across 100+ clients varies:

CapabilitySupport
ToolsAll
Prompts43/113
Resources47/113
OAuth with DCR16/113
Elicitation16/113
Apps10/113
⏳ 22 min

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

FrameworkDescription
python-sdkOfficial 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.
fastmcpFramework 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

STDIOHTTP (Streamable)
Client configCommand to run:
uv run mcp_server.py
Server URL:
http://localhost:8420/mcp
StartupClient launches the server processServer runs before clients connect
Best forLocal tools, quick tests, single-user appsRemote access, web apps, multiple clients
TradeoffSimple, but tied to one local processMore 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
Terminal running the FastMCP server with log output

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.

⏳ 15 min

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?

Cancel Confirm
connect-payments URL ELICITATION

Open a secure page to connect your payment provider.

https://payments.example.com/connect
Cancel Open URL

Form mode elicitation flow

User MCP Client MCP Server tools/call id: 1 Server needs more info InputRequiredResult: elicitation/create mode: "form" Client presents elicitation UI User provides requested information Client retries request with new information tools/call id: 2 + user response

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():

🖥️ MCP App flame-graph
main() - 420ms
handle_request() - 302ms
db.query()
serialize()

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

User Agent MCP App iframe MCP Server "show me analytics" Interactive app rendered in chat tools/call tool input/result tool result pushed to app user interacts tools/call request forwarded call + fresh data

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

⏳ 13 min

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: