AI engineeringThe Model Context Protocol18 modules, spec 2026-07-28
MCP
Unpacked
One open protocol that lets AI apps like Claude Code plug into your tools and data. Learn how it works, connect it to Figma, Azure DevOps and Postman, then build, test and secure a server of your own.
Four steps
Understand the idea, connect servers others built, build your own, then keep it safe and well designed.
What MCP is, its parts and how one call travels.
Plug ready made servers into Claude Code.
Write, test and publish a server of your own.
Guard against risky tools and design tools models use well.
What MCP is, and why
An open standard that lets any AI app use any tool or data source through one common plug, instead of a custom integration for every pair.
MCP is USB-C for AI. Before USB-C every gadget had its own charger; now one port fits all. Before MCP every AI app needed its own connector for Figma, GitHub or Jira; now a tool ships one MCP server and every MCP app can use it.
Without a standard, M apps times N tools means M × N integrations. MCP turns that into M clients plus N servers. Anthropic released it in November 2024; in December 2025 it moved to the Agentic AI Foundation under the Linux Foundation. Claude, ChatGPT, VS Code, Cursor and many others speak it.
Pick it forGiving an AI assistant real, live access to your tools and data.
- Created
- Anthropic, November 2024
- Governed by
- Agentic AI Foundation
- Wire format
- JSON-RPC 2.0
- Current spec
- 2026-07-28
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Custom integration | One plugin per app per tool | Full control, no standard | Before MCP; one off scripts |
Function calling | tools=[…] in one API request | The model can call your code, but only inside your app | A single app with private tools |
MCP server | One server, any MCP host | Write once, works in every MCP app | Tools many people or apps should use |
MCP connector | Add a remote URL in Claude, ChatGPT or an IDE | No install for users, OAuth sign in | SaaS products exposing their data |
Try it
# one command connects Claude Code to Figma
claude mcp add --transport http figma https://mcp.figma.com/mcp
# the same server works in VS Code, Cursor and other MCP apps
claude mcp list
Write the integration once. A team that ships one MCP server reaches every MCP compatible assistant, and users can switch assistants without losing tools.
# a recorded session, replayed when you press Run claude mcp add --transport http figma https://mcp.figma.com/mcp Added HTTP MCP server figma with URL: https://mcp.figma.com/mcp to local config claude mcp list Checking MCP server health... figma: https://mcp.figma.com/mcp (HTTP) - ⚠ Needs authentication
Key terms
| Term | Simple meaning |
|---|---|
MCP | Model Context Protocol, the shared plug |
Context | The facts and tools a model can use right now |
Integration | Code that connects one app to one tool |
Agentic AI Foundation | The Linux Foundation home for MCP |
Hosts, clients and servers
The host is the AI app, each client is one connection inside it, and each server exposes one tool or data source.
Think of a hotel. The host is the front desk you talk to. Each client is a phone line from the desk to one service. Each server is a service: the kitchen, the laundry, the taxi desk. You never call the kitchen yourself; the desk does it for you.
The host (Claude Code, Claude Desktop, an IDE) runs the model, holds the conversation and enforces consent. It creates one MCP client per server, keeping each connection isolated. Servers are small programs, local processes or remote URLs, that expose capabilities. The model never talks to a server directly; the host decides what to send and when.
Pick it forReasoning about who can see what, and where to put security checks.
- Host
- Claude Code, Claude, IDEs
- Client
- One per server, inside the host
- Server
- Local process or remote URL
- Model
- Never calls servers directly
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Host | Claude Code, Claude Desktop, VS Code, Cursor | Owns the model, the UI and permissions | You pick it; it is your AI app |
Client | Created by the host per server | Isolates each connection | Automatic; you configure servers, not clients |
Local server | "command": "npx", "args": […] | Runs on your machine with your files and CLIs | Desktop apps, local repos, private networks |
Remote server | "type": "http", "url": "https://…" | Nothing to install, vendor keeps it updated | SaaS tools such as Figma and Postman |
Try it
{
"mcpServers": {
"figma": { "type": "http", "url": "https://mcp.figma.com/mcp" },
"postman": { "type": "http", "url": "https://mcp.postman.com/minimal" },
"ado": {
"command": "npx",
"args": ["-y", "@azure-devops/mcp", "contoso"]
}
}
}
One client per server keeps them apart. The Figma server never sees what the Postman server returns. Only the host and model see everything, which is why the host asks you before risky actions.
# a recorded session, replayed when you press Run cat .mcp.json | jq '.mcpServers | keys' ["ado", "figma", "postman"] claude mcp list ado: npx -y @azure-devops/mcp contoso - ✓ Connected figma: https://mcp.figma.com/mcp (HTTP) - ✓ Connected postman: https://mcp.postman.com/minimal (HTTP) - ✓ Connected
Key terms
| Term | Simple meaning |
|---|---|
Host | The AI app you use |
Client | The host's connection to one server |
Server | A program that offers tools or data |
Isolation | Servers cannot see each other's data |
Tools, resources and prompts
Servers offer three things: tools the model can call, resources the app can read, and prompts the user can pick.
A workshop has power tools you operate (tools), reference books on the shelf (resources) and printed recipe cards for common jobs (prompts). The model uses the tools, the app hands it books to read, and you choose which recipe to follow.
Tools are model controlled functions with a JSON Schema input and optional outputSchema; results return content plus structuredContent. Resources are app controlled data addressed by URI (file:///, postgres://), with templates and change subscriptions. Prompts are user controlled templates, usually shown as slash commands. Servers can also ask the user for input through elicitation. Roots, sampling and logging are deprecated in 2026-07-28.
Pick it forDeciding what your server should expose and who controls it.
- Tools
- Model decides when to call
- Resources
- App decides what to load
- Prompts
- User picks from a menu
- Elicitation
- Server asks the user a question
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Tools | tools/list, tools/call | Let the model act or fetch live data | Search, create, update, run anything |
Resources | resources/list, resources/read | Stable data the app can attach as context | Files, schemas, docs, records |
Resource templates | users://{id}/profile | Parameterised URIs for many items | Large collections |
Prompts | prompts/list, prompts/get | Reusable workflows the user triggers | Code review, release notes, triage |
Elicitation | input_required result asking for a value | Ask the user mid task instead of guessing | Missing details or confirmations |
Try it
server.registerTool("get_weather", {
description: "Current weather for a city",
inputSchema: { city: z.string() },
}, async ({ city }) => ({ content: [{ type: "text", text: await weather(city) }] }));
server.registerResource("readme", "docs://readme", { mimeType: "text/markdown" },
async (uri) => ({ contents: [{ uri: uri.href, text: await readFile("README.md", "utf8") }] }));
server.registerPrompt("review_pr", { argsSchema: { pr: z.string() } },
({ pr }) => ({ messages: [{ role: "user", content: { type: "text", text: `Review PR ${pr}` } }] }));
Most servers need only tools. Start with tools. Add resources when the app should preload context, and prompts when users repeat the same request.
# a recorded session, replayed when you press Run npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list | jq '.tools[].name' "get_weather" npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list | jq '.prompts[].name' "review_pr"
Key terms
| Term | Simple meaning |
|---|---|
Tool | A function the model can run |
Resource | A piece of data with a URI |
Prompt | A ready made instruction the user picks |
Input schema | The shape of a tool's arguments |
Structured content | Machine readable tool output |
How one tool call travels
The full loop: discover tools, let the model choose, ask your permission, call the server, feed the result back.
Like a waiter with a menu. The kitchen hands over its menu (tools/list). You say what you want; the waiter decides which dish matches, checks with you if it is pricey, sends the order (tools/call) and brings the plate back so you can carry on.
The host lists tools and gives their names, descriptions and schemas to the model. The model emits a tool use with arguments; the host checks permissions and may ask you; the client sends tools/call; the server validates input, runs, and returns content with resultType complete, or input_required to ask for more. The host appends the result to the conversation and the model continues, often calling more tools.
Pick it forUnderstanding why descriptions and schemas matter so much.
- Discovery
- tools/list, cacheable
- Decision
- Made by the model
- Permission
- Enforced by the host
- Result
- content + structuredContent
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
tools/list | Host asks once, caches by ttlMs | Gives the model its options | Start of a session and on list changes |
tools/call | { name, arguments } | Runs one tool | Every action the model takes |
Permission prompt | Allow once, always, or deny | Keeps you in control of side effects | Writes, deletes, anything outward |
isError result | { isError: true, content: [ … ] } | The model can read the error and retry differently | Bad input, missing data, API failures |
input_required | resultType "input_required" | Server asks for more, client retries with answers | Confirmations, missing parameters |
Try it
// what the model sees for each tool
{
"name": "wit_my_work_items",
"description": "List work items assigned to the current user",
"inputSchema": {
"type": "object",
"properties": { "project": { "type": "string" }, "state": { "type": "string" } },
"required": ["project"]
}
}
The description is the tool's sales pitch. The model only knows what a tool does from its name, description and schema. Vague words mean wrong or missed calls.
# a recorded session, replayed when you press Run # inside Claude Code > what are my open bugs in Contoso? ⏺ ado - wit_my_work_items (MCP)(project: "Contoso", state: "Active") ⎿ 3 work items ⏺ You have 3 active bugs: #4812 login timeout, #4830 CSV export, #4841 dark mode contrast.
Key terms
| Term | Simple meaning |
|---|---|
Tool use | The model's request to run a tool |
Tool result | What comes back and joins the chat |
Agent loop | Think, call, read, repeat until done |
Consent | Your approval before an action runs |
Messages and the stateless model
Every MCP message is JSON-RPC 2.0. Since 2026-07-28 each request carries its own version and capabilities, so there is no handshake and no session.
Each message is a self addressed envelope: it says which protocol version it speaks and what the sender can do, so any server instance can answer it without remembering earlier letters.
Requests have jsonrpc, id, method and params; results or errors come back with the same id; notifications have no id. In 2026-07-28 the initialize handshake and sessions are gone: params._meta carries io.modelcontextprotocol/protocolVersion, clientCapabilities and clientInfo. Servers must implement server/discover. Results include resultType (complete or input_required), and list results add ttlMs and cacheScope for caching. Servers on 2025-11-25 still use initialize; SDKs handle both.
Pick it forReading logs, writing a client, or debugging a server by hand.
- Envelope
- jsonrpc, id, method, params
- Version
- _meta per request
- Discovery
- server/discover
- Results
- resultType complete or input_required
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Request | { id, method, params } | Expects exactly one response | Every call |
Notification | { method, params } with no id | Fire and forget updates | Progress, list changed |
server/discover | { "method": "server/discover" } | Learn versions and capabilities up front | New clients, compatibility probes |
subscriptions/listen | one long lived POST stream | Opt in change notifications | Tool or resource lists that change |
initialize (legacy) | 2025-11-25 and earlier | Old handshake that set up a session | Talking to older servers; SDKs do it for you |
Try it
// request
{ "jsonrpc": "2.0", "id": 7, "method": "tools/call",
"params": { "name": "get_weather", "arguments": { "city": "Delhi" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": { "name": "claude-code", "version": "2.1.0" } } } }
// response
{ "jsonrpc": "2.0", "id": 7,
"result": { "resultType": "complete",
"content": [{ "type": "text", "text": "Delhi: 31°C, haze" }] } }
Stateless means easy to scale. Any instance behind a load balancer can answer any request. If a tool needs state across calls, return a handle such as a cart id and accept it as an argument.
# a recorded session, replayed when you press Run npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name get_weather --tool-arg city=Delhi { "content": [ { "type": "text", "text": "Delhi: 31°C, haze" } ] }
Key terms
| Term | Simple meaning |
|---|---|
JSON-RPC | A tiny standard for calling methods with JSON |
_meta | Envelope details riding with each request |
Stateless | No memory between requests at the protocol level |
Handle | An id your server returns so later calls can refer back |
Transports: stdio and Streamable HTTP
Local servers talk over stdin and stdout; remote servers use Streamable HTTP. The older HTTP+SSE transport is deprecated.
stdio is an intercom between two rooms in the same house: fast and private. Streamable HTTP is a phone line to another city: anyone with the number and permission can call.
With stdio the host launches the server as a child process and exchanges newline delimited JSON-RPC on stdin and stdout; stderr is for logs. Streamable HTTP uses one endpoint: the client POSTs each message, with MCP-Protocol-Version, Mcp-Method and Mcp-Name headers, and the server answers with JSON or an SSE stream for progress and long results. HTTP+SSE (two endpoints) is deprecated; use mcp-remote to bridge stdio only hosts to remote servers.
Pick it forLocal tools on your machine use stdio; shared or SaaS servers use Streamable HTTP.
- stdio
- Child process, stdin and stdout
- Streamable HTTP
- One URL, POST, JSON or SSE
- HTTP+SSE
- Deprecated since 2025-03-26
- Bridge
- mcp-remote
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
stdio | "command": "node", "args": ["build/index.js"] | No network, no auth layer, fastest | Local files, CLIs, desktop apps |
Streamable HTTP | "type": "http", "url": "…/mcp" | Works across machines, supports OAuth and scaling | Team or SaaS servers |
SSE responses | Content-Type: text/event-stream | Streams progress for long calls | Builds, big queries, reports |
HTTP+SSE (legacy) | --transport sse | Old two endpoint transport | Only for servers that have not upgraded |
mcp-remote | npx -y mcp-remote <url> | Lets stdio only hosts reach remote servers | Older clients |
Try it
# stdio: Claude Code starts the process
claude mcp add ado -- npx -y @azure-devops/mcp contoso
# Streamable HTTP: Claude Code calls a URL
claude mcp add --transport http postman https://mcp.postman.com/minimal
# bridge a remote server into a stdio only client
npx -y mcp-remote https://mcp.example.com/mcp
Never print to stdout in a stdio server. stdout carries the protocol. A stray console.log corrupts the stream; send logs to stderr.
# a recorded session, replayed when you press Run claude mcp get postman postman: Scope: Local config (private to you in this project) Status: ✓ Connected Type: http URL: https://mcp.postman.com/minimal
Key terms
| Term | Simple meaning |
|---|---|
Transport | The pipe messages travel through |
stdio | Standard input and output of a process |
Streamable HTTP | MCP over normal HTTPS requests |
SSE | Server-Sent Events, a one way stream |
Authorization with OAuth
Remote servers protect themselves with OAuth 2.1: the client discovers the login server, you sign in once, and every request carries a token.
Like a hotel key card. Reception checks who you are once and gives you a card that opens only your room for a limited time. You never give the room your passport.
An MCP server is an OAuth resource server. A 401 points the client to Protected Resource Metadata, which names the authorization server. The client registers (Client ID Metadata Documents preferred; dynamic client registration is deprecated), opens your browser for login with PKCE, receives tokens bound to that server (resource indicators), and sends Authorization: Bearer on every request. stdio servers usually read API keys from environment variables instead.
Pick it forAny remote server that touches private data.
- Remote servers
- OAuth 2.1 with PKCE
- Discovery
- Protected Resource Metadata
- Registration
- Client ID Metadata Documents
- Local servers
- Env vars or the app's own login
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
OAuth in the host | /mcp, then Authenticate | Browser sign in, scoped tokens, refresh | Figma, Postman, GitHub remote servers |
Bearer header | --header "Authorization: Bearer …" | Simple for servers that accept API keys | CI or headless use |
Environment variable | --env API_KEY=… | Keeps keys out of shared config | Local stdio servers |
App login | Figma desktop, az login | Reuses a session you already have | Desktop and CLI backed servers |
Variable expansion | "Authorization": "Bearer ${API_KEY}" in .mcp.json | Share config without sharing secrets | Team .mcp.json files |
Try it
# OAuth: add the URL, then sign in from the /mcp menu inside Claude Code
claude mcp add --transport http figma https://mcp.figma.com/mcp
# API key in a header, for servers that support it
claude mcp add --transport http postman https://mcp.postman.com/code \
--header "Authorization: Bearer $POSTMAN_API_KEY"
# API key as an environment variable for a local server
claude mcp add postman --env POSTMAN_API_KEY=$POSTMAN_API_KEY -- npx @postman/postman-mcp-server@latest
Prefer OAuth over pasted keys. Tokens are scoped, expire, and can be revoked per app. A long lived key in a config file is the most common MCP leak.
# a recorded session, replayed when you press Run # inside Claude Code, type /mcp Manage MCP servers figma · ⚠ needs authentication # choose figma, then Authenticate; the browser opens Authentication successful. Connected to figma.
Key terms
| Term | Simple meaning |
|---|---|
OAuth | Standard way to grant limited access without a password |
PKCE | A one time secret that stops stolen login codes being used |
Bearer token | A pass sent with each request |
Scope | What the token is allowed to do |
Resource indicator | Binds a token to one server |
Connecting Claude Code
One command adds a server; scopes decide who shares it; /mcp shows status and handles sign in.
Adding a server is like adding a contact. Local keeps it in your phone for one project, project shares it with the team through the repo, and user makes it available in every project you open.
claude mcp add name -- command adds a stdio server; --transport http adds a remote URL. --scope local (default, in ~/.claude.json for this project), project (.mcp.json in the repo, approved on first use) or user (all your projects). Tools appear as mcp__server__tool, resources are referenced with @server:uri, and prompts become /mcp__server__prompt. Outputs over 10,000 tokens warn and MAX_MCP_OUTPUT_TOKENS sets the cap; MCP_TIMEOUT sets startup timeout.
Pick it forAny time you want Claude Code to read or act in another tool.
- Add
- claude mcp add
- Scopes
- local · project · user
- Shared file
- .mcp.json in the repo
- Inside a session
- /mcp
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Local scope | claude mcp add name … | Private to you, this project only | Experiments, personal tokens |
Project scope | -s project → .mcp.json | Shared through git, approved on first use | Team standard servers |
User scope | -s user | Available in all your projects | Tools you use everywhere |
Reference a resource | @github:repo://acme/api/README.md | Attach data to your prompt | Docs, files, records |
Run a prompt | /mcp__github__review_pr 42 | Server provided workflows as slash commands | Repeatable tasks |
Import | claude mcp add-from-claude-desktop | Reuse servers you set up in Claude Desktop | Moving to Claude Code |
Try it
claude mcp add --transport http figma https://mcp.figma.com/mcp # remote
claude mcp add ado -- npx -y @azure-devops/mcp contoso # local stdio
claude mcp add -s project --transport http postman https://mcp.postman.com/minimal
claude mcp add -s user --transport http github https://api.githubcopilot.com/mcp/
claude mcp list # health of every server
claude mcp get figma # details of one
claude mcp remove figma # remove it
claude mcp add-json local-db '{"command":"node","args":["db.js"]}'
Put team servers in project scope. .mcp.json in git means every teammate gets the same servers. Keep personal or secret ones in local or user scope.
# a recorded session, replayed when you press Run claude mcp add -s project --transport http postman https://mcp.postman.com/minimal Added HTTP MCP server postman with URL: https://mcp.postman.com/minimal to project config cat .mcp.json { "mcpServers": { "postman": { "type": "http", "url": "https://mcp.postman.com/minimal" } } } claude mcp list postman: https://mcp.postman.com/minimal (HTTP) - ✓ Connected
Key terms
| Term | Simple meaning |
|---|---|
Scope | Where a server's config lives and who gets it |
.mcp.json | The project's shared server list |
mcp__server__tool | How MCP tools are named inside Claude Code |
/mcp | The in session menu for status and sign in |
Example: Figma to code
Give Claude Code the real design: frames, variables, components and screenshots, so generated UI matches the file.
Instead of describing a design over the phone, you hand the developer the actual blueprint with exact measurements, colours and the names of parts already in your kit.
Figma's remote server lives at https://mcp.figma.com/mcp with OAuth, or install the Figma plugin for Claude Code, which bundles the server and skills. A desktop server runs at http://127.0.0.1:3845/mcp for some enterprise cases. Tools include get_design_context (code ready structure for a frame or selection), get_variable_defs (tokens), get_screenshot, get_metadata and Code Connect mappings to your real components.
Pick it forTurning frames into components that use your design system, not generic markup.
- Remote URL
- https://mcp.figma.com/mcp
- Desktop URL
- http://127.0.0.1:3845/mcp
- Auth
- OAuth in /mcp
- Best with
- Code Connect
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
get_design_context | Frame link or current selection | Structured layout and styles to code from | Building a screen or component |
get_variable_defs | Same node | Exact tokens instead of guessed hex values | Theming and design system work |
get_screenshot | Same node | A picture for visual checking | Comparing output to design |
Code Connect | Map Figma components to repo components | Generated code reuses real components | Teams with a component library |
Plugin install | claude plugin install figma@claude-plugins-official | Server plus ready workflows | The quickest setup |
Try it
# recommended: the Figma plugin (server + skills)
claude plugin install figma@claude-plugins-official
# or add the remote server directly, then sign in from /mcp
claude mcp add --transport http figma https://mcp.figma.com/mcp
# desktop server (Figma desktop app, Dev Mode enabled)
claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp
Link a frame, not the whole file. Copy the link to one frame or layer. Smaller context means faster, more accurate code.
# a recorded session, replayed when you press Run > Implement https://www.figma.com/design/AbC123/App?node-id=12-345 as a React component using our Button and Card ⏺ figma - get_design_context (MCP)(nodeId: "12:345") ⏺ figma - get_variable_defs (MCP)(nodeId: "12:345") ⎿ color/primary: #FFB703, spacing/md: 16px, radius/none: 0 ⏺ Created src/components/PlanCard.tsx using <Card> and <Button variant="primary">
Key terms
| Term | Simple meaning |
|---|---|
Node id | The id of one frame or layer, from the link |
Variables | Figma's design tokens |
Code Connect | A map from Figma components to your code |
Dev Mode | Figma's developer view |
Example: Azure DevOps
Work items, repos, pull requests and pipelines from Azure DevOps, available to Claude Code while you code.
Like having the project board, the code review queue and the build screen open beside your editor, with an assistant who can read and update them for you.
Microsoft's server runs locally with npx @azure-devops/mcp <org>; on first use it signs you in through the browser, or reuses az login with --authentication azcli. Limit tools with domains such as core, work, work-items, repositories and pipelines. A hosted remote server at https://mcp.dev.azure.com/{org} is in preview, but Microsoft currently recommends the local server for Claude Code because of Entra ID sign in support.
Pick it forPicking up a ticket, writing the code, opening the PR and checking the build, without leaving the terminal.
- Package
- @azure-devops/mcp
- Transport
- stdio via npx
- Auth
- Browser sign in or az login
- Remote
- Preview; local advised for Claude Code
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Work items | wit_my_work_items, wit_get_work_item | Read the task without leaving the terminal | Starting or triaging work |
Create and update items | wit_create_work_item, wit_add_work_item_comment | Keep the board current | Logging bugs found while coding |
Repositories | repo_create_pull_request | Open PRs linked to work items | Finishing a change |
Pipelines | pipelines tools (domain: pipelines) | Check build status and failures | After pushing |
Domains | -d core work work-items | Fewer tools, less noise | Every setup |
Try it
# local server for organization "contoso"; browser sign in on first use
claude mcp add ado -- npx -y @azure-devops/mcp contoso
# reuse your Azure CLI login instead
az login
claude mcp add ado -- npx -y @azure-devops/mcp contoso --authentication azcli
# load only the tool groups you need
claude mcp add ado -- npx -y @azure-devops/mcp contoso -d core work work-items repositories
Load fewer domains. The full server has many tools. Every tool description costs context, so enable only the areas this project uses.
# a recorded session, replayed when you press Run > Show my active bugs in the Payments project ⏺ ado - wit_my_work_items (MCP)(project: "Payments") ⎿ #4812 Login times out on slow networks · Active ⎿ #4830 CSV export drops the last row · Active > Fix 4830 and open a PR ⏺ ado - repo_create_pull_request (MCP)(sourceRefName: "refs/heads/fix/4830-csv", …) ⎿ Pull request !318 created and linked to #4830
Key terms
| Term | Simple meaning |
|---|---|
Organization | Your Azure DevOps account name, as in dev.azure.com/contoso |
Work item | A bug, task or user story |
Domain | A group of related tools you can switch on |
Entra ID | Microsoft's sign in service |
Example: Postman
Let Claude Code read your collections and environments, create requests and generate client code from real API definitions.
Your Postman workspace becomes a shared notebook of every API you use. Claude can look up the exact request and response instead of guessing from memory.
Postman hosts the server at https://mcp.postman.com with three modes: minimal (essential tools, default), code (for generating client code from API definitions) and mcp (the full toolset). Use OAuth where the host supports it, or an API key in a Bearer header; an EU endpoint exists at mcp.eu.postman.com. A local server runs with npx @postman/postman-mcp-server and POSTMAN_API_KEY.
Pick it forKeeping code in sync with documented APIs and building or updating collections as you code.
- Minimal
- https://mcp.postman.com/minimal
- Code
- https://mcp.postman.com/code
- Full
- https://mcp.postman.com/mcp
- Local
- @postman/postman-mcp-server
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Minimal mode | …/minimal | Essential tools, small context cost | Everyday use |
Code mode | …/code | Search API definitions and generate client code | Writing API clients |
Full mode | …/mcp or --full | Every Postman API tool | Admin and bulk workspace work |
EU region | https://mcp.eu.postman.com/… | Data stays in the EU region | EU workspaces |
API key auth | --header "Authorization: Bearer …" | Works without OAuth support | Headless or CI setups |
Try it
# remote, minimal toolset (sign in with OAuth from /mcp)
claude mcp add --transport http postman https://mcp.postman.com/minimal
# remote, code generation mode with an API key
claude mcp add --transport http postman https://mcp.postman.com/code \
--header "Authorization: Bearer $POSTMAN_API_KEY"
# local server, full toolset
claude mcp add postman --env POSTMAN_API_KEY=$POSTMAN_API_KEY -- npx @postman/postman-mcp-server@latest --full
Start with minimal. The full mode exposes many tools. Use minimal day to day and switch to code or full when you need them.
# a recorded session, replayed when you press Run > List the requests in our Payments collection ⏺ postman - getCollection (MCP)(collectionId: "…") ⎿ POST /payments · GET /payments/:id · POST /refunds > Generate a typed fetch client for these ⏺ Created src/api/payments.ts with createPayment, getPayment and createRefund
Key terms
| Term | Simple meaning |
|---|---|
Collection | A saved set of API requests |
Environment | Variables such as base URL and tokens |
Workspace | Where a team keeps collections |
Mode | Which set of tools the server exposes |
More servers: GitHub and Playwright
Two servers most developers add next: GitHub for issues and pull requests, Playwright for driving a real browser.
GitHub gives Claude a desk in your code review room. Playwright gives it hands and eyes in a browser, so it can click through the page it just built and see what breaks.
GitHub's official remote server at https://api.githubcopilot.com/mcp/ signs in with OAuth and covers repos, issues, PRs and Actions, with toolsets to limit scope. Microsoft's Playwright server (npx @playwright/mcp@latest) controls a browser through accessibility snapshots rather than screenshots, which is fast and reliable. Reference servers such as filesystem, git, fetch and memory live in modelcontextprotocol/servers.
Pick it forEnd to end loops: open an issue, fix it, test it in a browser, open the PR.
- GitHub
- https://api.githubcopilot.com/mcp/
- Playwright
- npx @playwright/mcp@latest
- Filesystem
- @modelcontextprotocol/server-filesystem
- Fetch
- uvx mcp-server-fetch
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
GitHub | remote URL + OAuth | Issues, PRs, reviews, Actions | Any GitHub hosted project |
Playwright | npx @playwright/mcp@latest | Real browser checks after code changes | Frontend work and bug repro |
Filesystem | server-filesystem <allowed dirs> | File access limited to chosen folders | Hosts without built in file tools |
Fetch | uvx mcp-server-fetch | Turns web pages into model friendly text | Reading docs and pages |
Memory | server-memory | A small knowledge graph across chats | Experiments with long term notes |
Try it
claude mcp add --transport http github https://api.githubcopilot.com/mcp/
claude mcp add playwright -- npx @playwright/mcp@latest
claude mcp add fs -- npx -y @modelcontextprotocol/server-filesystem ~/projects/app
claude mcp add fetch -- uvx mcp-server-fetch
Combine servers in one prompt. "Reproduce issue 57 in the browser, fix it, and open a PR" uses GitHub, Playwright and Claude Code's own editing together.
# a recorded session, replayed when you press Run > Reproduce issue 57 on localhost:3000 and fix it ⏺ github - get_issue (MCP)(owner: "acme", repo: "web", issue_number: 57) ⏺ playwright - browser_navigate (MCP)(url: "http://localhost:3000/login") ⏺ playwright - browser_click (MCP)(element: "Sign in button") ⎿ Console error: Cannot read properties of undefined (reading 'token') ⏺ Fixed src/auth/session.ts; the login flow now passes in the browser.
Key terms
| Term | Simple meaning |
|---|---|
Toolset | A group of tools you can switch on or off |
Accessibility snapshot | A text map of the page's buttons, links and fields |
Reference server | An example server maintained by the MCP project |
uvx | Runs a Python tool without installing it first |
Build your own: TypeScript
About 20 lines give you a working server: create McpServer, register a tool with a Zod schema, connect a transport.
Writing a server is like opening a small shop counter: put up a sign saying what you offer (the description), say what you need from customers (the schema), and serve them (the handler).
The TypeScript SDK gives you McpServer plus registerTool, registerResource and registerPrompt. Inputs use Zod; outputs return content and, with an outputSchema, structuredContent. Use StdioServerTransport for local use or the Streamable HTTP transport behind Express for remote. The v1 package is @modelcontextprotocol/sdk; newer v2 packages track the 2026-07-28 spec, so check the SDK README for your version.
Pick it forWrapping an internal API, database or script so any MCP host can use it.
- Install
- @modelcontextprotocol/sdk zod
- Server
- McpServer
- Local transport
- StdioServerTransport
- Test
- MCP Inspector
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
registerTool | name, { description, inputSchema }, handler | The model can call it | Every action or live lookup |
outputSchema | { tempC: z.number() } | Clients get validated structured data | Results other code will parse |
annotations | { readOnlyHint: true, destructiveHint: false } | Hosts can skip prompts for safe tools | Every tool |
registerResource | uri, metadata, reader | Context the app can attach | Docs, schemas, records |
Streamable HTTP | Express + the SDK's HTTP transport | Share one server with a team | Remote deployment |
Try it
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "weather", version: "1.0.0" });
server.registerTool("get_weather", {
title: "Current weather",
description: "Current temperature and conditions for a city. Use for weather questions.",
inputSchema: { city: z.string().describe("City name, e.g. Delhi") },
outputSchema: { tempC: z.number(), summary: z.string() },
annotations: { readOnlyHint: true },
}, async ({ city }) => {
const r = await fetch(`https://api.example.com/current?q=${encodeURIComponent(city)}`);
if (!r.ok) return { isError: true, content: [{ type: "text", text: `No weather for ${city}` }] };
const { tempC, summary } = await r.json();
return { content: [{ type: "text", text: `${city}: ${tempC}°C, ${summary}` }], structuredContent: { tempC, summary } };
});
await server.connect(new StdioServerTransport());
console.error("weather server ready"); // stderr, never stdout
Return errors, do not throw them. isError: true lets the model read what went wrong and try another city. A thrown exception just looks like a broken server.
# a recorded session, replayed when you press Run npm i @modelcontextprotocol/sdk zod && npx tsc npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name get_weather --tool-arg city=Delhi { "content": [{ "type": "text", "text": "Delhi: 31°C, haze" }], "structuredContent": { "tempC": 31, "summary": "haze" } } claude mcp add weather -- node $(pwd)/build/index.js Added stdio MCP server weather with command: node /home/shree/weather/build/index.js to local config
Key terms
| Term | Simple meaning |
|---|---|
SDK | A library that handles the protocol for you |
Zod | A TypeScript library for describing and checking data |
Handler | Your function that does the work |
Annotations | Hints such as read only or destructive |
Build your own: Python
FastMCP in the official Python SDK turns decorated functions into tools, with type hints as the schema.
Write a normal Python function, put a sticker on it saying "tool", and FastMCP handles the rest: the schema from your type hints, the description from your docstring, and the protocol.
The mcp package's FastMCP class gives decorators: @mcp.tool(), @mcp.resource("uri") and @mcp.prompt(). Type hints and Pydantic models become JSON Schema, docstrings become descriptions, and return values become content (and structured output when typed). mcp.run() defaults to stdio; transport="streamable-http" serves HTTP. uv makes running it simple.
Pick it forData, ML and scripting teams who already live in Python.
- Install
- uv add "mcp[cli]"
- Server
- FastMCP("name")
- Decorators
- tool · resource · prompt
- Run
- mcp.run()
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
@mcp.tool() | def fn(x: int) -> Model | Type hints become the schema | Actions and lookups |
@mcp.resource() | @mcp.resource("config://app") | Expose data by URI | Config, docs, records |
@mcp.prompt() | def review(code: str) -> str | Reusable user workflows | Repeat tasks |
mcp dev | uv run mcp dev server.py | Opens the Inspector against your server | While building |
HTTP | mcp.run(transport="streamable-http") | Serve over the network | Shared deployments |
Try it
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel
import asyncpg, os
mcp = FastMCP("sales")
class Customer(BaseModel):
name: str
revenue: float
@mcp.tool()
async def top_customers(limit: int = 5) -> list[Customer]:
"""Top customers by revenue this quarter. Read only."""
conn = await asyncpg.connect(os.environ["READONLY_DATABASE_URL"])
rows = await conn.fetch("SELECT name, revenue FROM customer_revenue ORDER BY revenue DESC LIMIT $1", min(limit, 50))
await conn.close()
return [Customer(**dict(r)) for r in rows]
if __name__ == "__main__":
mcp.run() # stdio by default
Use a read only database role. The model will try creative queries. Parameterised SQL plus a role that cannot write keeps a bad call harmless.
# a recorded session, replayed when you press Run uv init sales && cd sales && uv add "mcp[cli]" asyncpg uv run mcp dev server.py MCP Inspector is up and running at http://localhost:6274 claude mcp add sales --env READONLY_DATABASE_URL=$READONLY_DATABASE_URL -- uv run --directory $(pwd) server.py Added stdio MCP server sales with command: uv run --directory /home/shree/sales server.py to local config
Key terms
| Term | Simple meaning |
|---|---|
FastMCP | The high level server class in the Python SDK |
Decorator | The @ line that registers a function |
Pydantic | Python models that validate data |
uv | A fast Python package and project tool |
Test and debug
Use the MCP Inspector before any AI is involved, then check the host's status and logs.
Test the shop counter yourself before opening the doors: walk up, read the sign, place an order, check the receipt. Only then let real customers, the models, in.
The MCP Inspector (npx @modelcontextprotocol/inspector) opens a web UI to list and call tools, read resources and see raw JSON-RPC; --cli scripts the same checks for CI. In Claude Code, claude mcp list and /mcp show status, claude --debug prints MCP connection details, and MCP_TIMEOUT raises the startup limit for slow servers. Common faults: logging to stdout, wrong paths (use absolute), missing env vars, and huge outputs.
Pick it forEvery time a server will not connect or a tool misbehaves.
- UI
- npx @modelcontextprotocol/inspector
- CLI
- --cli --method tools/list
- Claude Code
- claude --debug, /mcp
- Timeout
- MCP_TIMEOUT=10000
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Inspector UI | npx @modelcontextprotocol/inspector <cmd> | See tools, call them, read raw messages | Building and fixing |
Inspector CLI | --cli --method tools/call --tool-name … | Repeatable checks | CI and quick tests |
Host status | claude mcp list, /mcp | Connected, failed or needs sign in | First check when tools vanish |
Debug output | claude --debug | Connection and error details | Servers that fail to start |
Output limit | MAX_MCP_OUTPUT_TOKENS=50000 | Allows larger results | Tools that legitimately return a lot |
Try it
# interactive UI against a local server
npx @modelcontextprotocol/inspector node build/index.js
# scriptable checks, good for CI
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
# inside Claude Code
claude mcp list
MCP_TIMEOUT=10000 claude --debug
Use absolute paths in configs. Hosts start servers from their own working folder, so node build/index.js often fails where node /home/you/app/build/index.js works.
# a recorded session, replayed when you press Run npx @modelcontextprotocol/inspector node build/index.js Starting MCP inspector... MCP Inspector is up and running at http://localhost:6274 claude mcp list weather: node build/index.js - ✗ Failed to connect # relative path: re-add with an absolute one claude mcp remove weather && claude mcp add weather -- node $(pwd)/build/index.js claude mcp list weather: node /home/shree/weather/build/index.js - ✓ Connected
Key terms
| Symptom | Likely fix |
|---|---|
Failed to connect | Absolute path, correct command, env vars set |
Garbled messages | Logs went to stdout; move them to stderr |
Needs authentication | Run /mcp and sign in |
Tool never chosen | Rewrite the description and parameter names |
Output truncated | Paginate or raise MAX_MCP_OUTPUT_TOKENS |
Open source tools and the registry
Official SDKs in many languages, the Inspector, reference servers and a public registry to find and publish servers.
The MCP ecosystem is a well stocked hardware store: kits to build your own (SDKs), a testing bench (Inspector), sample builds to copy (reference servers) and a catalogue of what others made (the registry).
Official SDKs cover TypeScript, Python, Java, Kotlin, C#, Go, PHP, Ruby, Rust and Swift. The MCP Registry at registry.modelcontextprotocol.io is the open catalogue of public servers, published with the mcp-publisher CLI and a server.json. modelcontextprotocol/servers holds reference servers. Vendors publish official servers (GitHub, Figma, Postman, Microsoft, Playwright), and community lists help with discovery; review any server before trusting it.
Pick it forStarting from proven pieces instead of from scratch.
- SDKs
- TS, Python, Java, Kotlin, C#, Go, PHP, Ruby, Rust, Swift
- Registry
- registry.modelcontextprotocol.io
- Publish
- mcp-publisher + server.json
- Examples
- modelcontextprotocol/servers
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Official SDKs | github.com/modelcontextprotocol | Spec compliant and maintained | Building any server or client |
FastMCP | Python decorators | Fastest path in Python | Python servers |
MCP Inspector | npx @modelcontextprotocol/inspector | Test without a model | Every server |
Registry | registry.modelcontextprotocol.io | Find and publish public servers | Discovery and distribution |
mcp-remote | npx -y mcp-remote <url> | Bridge remote servers to stdio hosts | Older clients |
Try it
# search the public registry
curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=weather" | jq '.servers[].server.name'
# publish your own (package it on npm or PyPI first)
mcp-publisher init # creates server.json
mcp-publisher login github
mcp-publisher publish
Namespaces prove ownership. io.github.yourname/... is checked against your GitHub login, and domain namespaces against DNS, so names cannot be squatted.
# a recorded session, replayed when you press Run curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=weather" | jq '.servers[].server.name' "io.github.example/weather" "io.github.someone/weather-mcp" mcp-publisher publish Publishing to https://registry.modelcontextprotocol.io... ✓ Successfully published
Key terms
| Term | Simple meaning |
|---|---|
Registry | The public catalogue of MCP servers |
server.json | Your server's listing: name, package, transport |
Namespace | The verified prefix of a server name |
Reference server | An official example to learn from |
Security essentials
An MCP server runs with your access. Treat every server like a new dependency and every tool result like untrusted input.
Giving an AI tools is like giving a new assistant keys. Hand over only the keys they need, check before they open the safe, and remember that a note slipped under the door is not an order from you.
Main risks: prompt injection (a web page, issue or email tells the model to do something else), tool poisoning (a malicious server hides instructions in descriptions), over broad tokens, and data leaving through another tool. Defences: install trusted servers only and pin versions, use read only or scoped tokens, keep write and delete tools behind confirmation, separate secrets from shared config, validate inputs server side, and never pass tokens through to upstream APIs.
Pick it forBefore connecting any server to real accounts or production data.
- Top risk
- Prompt injection via tool results
- Supply chain
- Pin versions, prefer official servers
- Tokens
- Scoped, short lived, per server
- Writes
- Always ask first
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Trusted sources | Official vendor servers, the registry | Less chance of malicious code | Every install |
Pin versions | npx @scope/server@1.4.2 | No surprise updates | Shared and production setups |
Least privilege | Read only tokens, limited toolsets | A bad call can do less harm | Every server |
Permission rules | allow and deny lists for mcp__ tools | Fast for safe tools, careful for risky ones | Team settings |
Server side checks | Validate input, enforce auth per call | Never trust the model's arguments | Your own servers |
Try it
{
"permissions": {
"allow": [
"mcp__figma__get_design_context",
"mcp__ado__wit_my_work_items"
],
"deny": [
"mcp__github__delete_repository"
]
}
}
Allow reads, ask for writes. Pre approve read only tools you trust so work flows, and leave anything that changes data on the confirm prompt.
# a recorded session, replayed when you press Run ⏺ github - create_gist (MCP)(files: { ".env": … }) Do you want to allow this action? ❯ No, and tell Claude what to do differently # the model reads an injected instruction; the permission prompt catches it
Key terms
| Term | Simple meaning |
|---|---|
Prompt injection | Hidden instructions inside content the model reads |
Tool poisoning | Malicious text hidden in a tool's description |
Least privilege | Only the access needed, nothing more |
Exfiltration | Data quietly sent somewhere it should not go |
Designing tools models use well
Good servers have few, clearly named tools, precise descriptions, small outputs and helpful errors.
Design tools like a good restaurant menu: a few clear dishes, each described in plain words, served in sensible portions, and a waiter who explains when something is unavailable.
Model a few high level tasks rather than wrapping every API endpoint. Use verb_noun names and say in the description what it does, when to use it and when not to. Keep parameters few, typed and described, with enums for fixed choices. Return only what is needed, paginate, and prefer human readable ids. Errors should explain how to fix the call. Mark tools with annotations and keep tools/list order stable for caching.
Pick it forEvery server you write, and when choosing between servers to install.
- Count
- Fewer, task level tools
- Names
- verb_noun, unique
- Outputs
- Small, paginated, structured
- Errors
- Say how to fix it
How a conversation looks
requestresponsepush or streamcontrol
Methods, usage, why and when
| Method | Usage | Why | When to use |
|---|---|---|---|
Task level tools | find_open_bugs, not GET /api/v7/wit | Fewer calls, less confusion | Wrapping large APIs |
Clear descriptions | what, when, when not | The model picks the right tool | Every tool |
Enums and limits | z.enum([…]), max(50) | Valid arguments by construction | Fixed choices and sizes |
Pagination | limit + cursor → nextCursor | Results fit in context | Lists and searches |
Helpful errors | "Did you mean 'Payments'?" | The model fixes its own call | Every failure path |
Annotations | readOnlyHint, destructiveHint, idempotentHint | Hosts can tune prompts and retries | Every tool |
Try it
server.registerTool("find_open_bugs", {
description:
"Find open bugs in a project, newest first. Use for questions about current bugs. " +
"Not for closed items; use search_work_items for history.",
inputSchema: {
project: z.string().describe("Project name, e.g. Payments"),
severity: z.enum(["critical", "high", "medium", "low"]).optional(),
limit: z.number().int().min(1).max(50).default(10),
cursor: z.string().optional().describe("From the previous result's nextCursor"),
},
annotations: { readOnlyHint: true },
}, handler);
Write the description for the model. Say what it is for, when to use it and what not to use it for. That one paragraph decides whether the tool gets picked correctly.
# a recorded session, replayed when you press Run npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name find_open_bugs --tool-arg project=Payments --tool-arg limit=2 { "content": [{ "type": "text", "text": "#4812 Login times out (high, Asha) · #4830 CSV export (medium, Ravi) · nextCursor: b2" }] } npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name find_open_bugs --tool-arg project=Paymnts { "isError": true, "content": [{ "type": "text", "text": "Unknown project 'Paymnts'. Did you mean 'Payments'? Projects: Payments, Web, Mobile" }] }
Key terms
| Term | Simple meaning |
|---|---|
Task level | One tool per job a person would ask for |
Pagination | Returning results a page at a time |
Cursor | A bookmark for the next page |
Idempotent | Safe to call twice with the same result |
Principles to keep
Eight rules that keep MCP setups simple, safe and pleasant for both the model and you.
| Principle | In practice | Why |
|---|---|---|
Start remote, go local when needed | Vendor URLs first; local stdio for files, CLIs and desktop apps | Less to install and update |
Fewer tools, better tools | Load only the domains or modes you use | Every description costs context |
Allow reads, ask for writes | Permission allow lists for read only tools | Speed without blind side effects |
Scope every token | OAuth or read only keys, one per server | Limits damage from mistakes or leaks |
Share config, not secrets | .mcp.json with ${VAR} expansion | Teams stay in sync safely |
Test before the model | MCP Inspector UI and CLI | Find schema and path bugs early |
Describe for the model | What, when, when not, plus typed params | Correct tool choice |
Treat results as untrusted | Never follow instructions found in tool output | Stops prompt injection |