Skip to content

Chapter 7 · Build

Build Your Own MCP Server

The repeatable 15-step method distilled from the reference implementation. Use it to design tools, choose an auth model, add trimming and tests, and ship a connector — for Jira or any other system.

🎯 What you'll be able to do

  • Define the user and system boundary before writing code
  • Choose a safe authentication model and design narrow tools
  • Scaffold a Python MCP project with the right dependencies
  • Plan trimming, tests, the connector, container, and deployment

Step 1 — Define the user and system boundary

Answer these before writing any code:

QuestionExample answer for Jira
Who is the user?A Copilot Studio user signed into Atlassian.
What system is called?Jira Cloud.
Does the call run as user or app?User.
Where are tokens stored?Power Platform connector, not the MCP server.
What endpoint does the agent call?/mcp.
What gateway fronts the server?Azure API Management.

Step 2 — Pick the authentication model

For enterprise user data, prefer delegated identity.

Prefer

  • Delegated per-user OAuth tokens
  • Tokens held by the connector
  • Request-scoped, never stored
  • Refresh handled by the platform

Avoid

  • Service accounts
  • Shared API keys
  • Hardcoded tokens
  • Refresh tokens stored in your server

Step 3 — Design tools

Good tools are…

  • Small and clear
  • Permission-safe and predictable
  • Documented by their docstring
  • Compact in response size

Bad tools are…

  • Vague or multi-purpose
  • Raw query passthroughs without guardrails
  • Unlimited search operations
  • Returning entire upstream payloads
BetterWorse
jira_get_issue(issue_key)jira_api(method, path, body)
jira_search(jql, max_results, next_page_token)run_any_jql_without_limits(query)
jira_add_comment(issue_key, body)modify_issue_arbitrarily(payload)

Steps 4–6 — Scaffold, dependencies, configuration

Minimum Python project shape:

Text
my-mcp-server
|-- app
|   |-- __init__.py
|   |-- server.py
|   |-- config.py
|   |-- middleware.py
|   |-- context.py
|   |-- errors.py
|   |-- tools/__init__.py
|-- tests
|-- pyproject.toml
|-- Dockerfile
|-- README.md

The reference dependency set:

dependencies
mcp>=1.2.0
starlette>=0.37.0
uvicorn[standard]>=0.30.0
httpx>=0.27.0
pydantic>=2.7.0
pydantic-settings>=2.3.0

Steps 7–11 — Middleware, client, trimming, tools, tests

  • Middleware: origin checks, bearer extraction, gateway integrity, request-scoped token binding, cleanup in finally.
  • Upstream client: build URLs from settings, attach the delegated token, set timeouts, retry only transient failures, convert errors safely, return trimmed models.
  • Trimming: limit fields, cap page size, use cursor paging, enforce a byte budget, note omitted results.
  • Tools: typed arguments, clear docstring, use the client, return a dict or model, covered by tests.
  • Tests: start with unit tests (token extraction, origin allow-list, missing token, trimming, paging), then integration tests (initialize, tools/list, a real call, auth failure, gateway failure, upstream 401/403/429/5xx).

Steps 12–15 — Connector, container, deploy, operate

For Copilot Studio MCP, expose a single /mcp POST operation and include the agentic-protocol extension:

JSON
"x-ms-agentic-protocol": "mcp-streamable-1.0"
Connector fieldValue
hostAPIM host, e.g. example-apim.azure-api.net
basePath/jira-mcp
paths/mcp
OAuth providerGeneric OAuth 2
Authorization URLhttps://auth.atlassian.com/authorize?audience=api.atlassian.com&prompt=consent
Token URLhttps://auth.atlassian.com/oauth/token

Then containerize (multi-stage slim image, non-root user, health check, python -m app.server), deploy behind APIM, and operate with logs, health and readiness probes, autoscale, and secret rotation. Details are in Deployment and Security.

🛠️ Mini-project: Port the scaffold to a new tool

Without writing production code yet, design a new read tool for a system you know (e.g. ServiceNow incidents or GitHub issues). Write down: the tool name and typed arguments, the smallest useful response model, the page-size cap, and one unit test you would add. Compare your design against the “good tools” checklist above.

Concept check

Your upstream system does support per-user OAuth, but a colleague wants to use one service account “to keep it simple.” Which step of this method addresses that, and what is the correct call?

📌 Chapter summary

  • Start from the boundary (who is the user, what system, user or app identity) and let it drive every later decision.
  • Prefer delegated identity; design small, clear, capped tools; return trimmed models.
  • Keep the FastMCP structure, request-scoped token, middleware, APIM, Key Vault, trimming, tests, connector, and Dockerfile patterns.

✅ End-of-chapter review

0/5 done