Your IDE connects to a code-hosting MCP server. It shows up in three places:
Type / in the chat box and you see /review-pr. That's a prompt: you chose to run it.
The IDE quietly attaches the repo's README.md to every conversation. That's a resource: the application decided to include it.
Mid-conversation the model decides to call create_issue. That's a tool: the model chose it, and the host asks you to confirm.
Get the boss wrong and things feel off. If "delete branch" were a resource, nobody would expect it to do anything. If "review this PR" were a tool, the model might fire it unprompted.
Tools are model-controlled: the model discovers and invokes them (with a human able to deny).
Resources are application-driven: the host decides how to use them as context. Each has a uri, name, optional mimeType, and returns text or base64 blob. Templates use RFC 6570 URI templates. Annotations include audience ("user", "assistant") and priority (0 to 1). A missing resource MUST return -32602, never an empty contents array. Servers MUST sanitize file paths against traversal.
Prompts are user-controlled: "who decides when the prompt is used, not who authors its content". Typically shown as slash commands. prompts/get takes arguments and returns messages.
Completion (completion/complete) suggests argument values for a prompt (ref/prompt) or resource template (ref/resource): at most 100 values, plus total and hasMore.
Pagination covers the four list methods: an opaque cursor, page size chosen by the server, missing nextCursor means the end. An empty-string cursor is valid and is not the end. An invalid cursor SHOULD get -32602.
Who controls a primitive decides how it's presented and how much it can be trusted to run unprompted. A model-controlled action needs a confirmation step; application context needs relevance rules; user-invoked templates need a menu. Separating them lets each host build the right UI.
First time here? Set up the test helper (once per terminal)
ShellSetup
# 1. In a SECOND terminal, start the reference server (Node 18+, no dependencies)curl -sO https://www.diegozuluaga.dev/mcpa/reference-server.mjsnode reference-server.mjs # http://localhost:3000/mcp, logs appear here# 2. In THIS terminal, define the helper every test uses (bash or zsh)export MCP=http://localhost:3000/mcpMETA='"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}'mcp() { # usage: mcp <method> '<json body>' [extra curl args...] curl -sS -N "$MCP" \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'MCP-Protocol-Version: 2026-07-28' \ -H "Authorization: Bearer ${MCP_USER:-alice}" \ -H "Mcp-Method: $1" "${@:3}" -d "$2" \ -w '\nHTTP %{http_code}\n'}# Demo auth: the reference server treats the bearer token as the user's name.# Prefix a command with MCP_USER=bob to act as someone else.
ShellTests
# 1. Prompts: list, then get with and without the required argumentmcp prompts/list '{"jsonrpc":"2.0","id":1,"method":"prompts/list","params":{'"$META"'}}'mcp prompts/get '{"jsonrpc":"2.0","id":2,"method":"prompts/get","params":{"name":"code_review","arguments":{"code":"print(1)","language":"python"},'"$META"'}}' -H 'Mcp-Name: code_review'mcp prompts/get '{"jsonrpc":"2.0","id":3,"method":"prompts/get","params":{"name":"code_review","arguments":{},'"$META"'}}' -H 'Mcp-Name: code_review'# expect: the last one is 400 with -32602 (missing required argument)# 2. Completionmcp completion/complete '{"jsonrpc":"2.0","id":4,"method":"completion/complete","params":{"ref":{"type":"ref/prompt","name":"code_review"},"argument":{"name":"language","value":"py"},'"$META"'}}'# 3. Pagination: the server returns 2 per page; follow nextCursor until it's gonemcp resources/list '{"jsonrpc":"2.0","id":5,"method":"resources/list","params":{'"$META"'}}' | tee /tmp/page1.txtNEXT=$(sed -n 's/.*"nextCursor":"\([^"]*\)".*/\1/p' /tmp/page1.txt)mcp resources/list '{"jsonrpc":"2.0","id":6,"method":"resources/list","params":{"cursor":"'"$NEXT"'",'"$META"'}}'# expect: the last page has no nextCursormcp resources/list '{"jsonrpc":"2.0","id":7,"method":"resources/list","params":{"cursor":"not-a-cursor",'"$META"'}}'# expect: 400 with -32602 (invalid cursor)