The Model Context Protocol (MCP) is an open standard for connecting AI applications to tools and data. Instead of writing a custom integration for every assistant, you build one MCP server and any MCP-capable client can use it.

That convenience has a cost. An MCP server is called by a language model, not by code you reviewed, and the model treats tool descriptions and tool output as natural language that can also carry instructions. Security has to be part of the design, not an afterthought.

The building blocks

An MCP server exposes three kinds of capability:

  • Tools – functions the model can call, such as search_issues or create_invoice.
  • Resources – read-only data the client can load into context, such as a file or a record.
  • Prompts – reusable prompt templates the user can invoke.

Local servers usually run over stdio. Remote servers run over HTTP, and the specification uses OAuth 2.1 for authorising remote servers.

A minimal tool

Here is a small TypeScript server with one read-only tool, using the official SDK.

typescript
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: "issues", version: "1.0.0" });

server.tool(
  "search_issues",
  "Search open issues by keyword. Use when the user asks about known bugs.",
  { query: z.string().min(2).max(100), limit: z.number().int().min(1).max(20).default(5) },
  async ({ query, limit }) => {
    const results = await searchIssues(query, limit); // your data layer
    return { content: [{ type: "text", text: JSON.stringify(results) }] };
  }
);

await server.connect(new StdioServerTransport());

Note the validated, bounded inputs. The model will eventually send something unexpected.

Security decisions that matter

1. Least privilege, per tool

Give each tool the narrowest credential that works. A search tool should not share a token that can also delete data. Split read and write tools so clients can approve them differently.

2. Treat tool output as untrusted

If a tool returns content from the outside world (web pages, emails, issue comments), that content can contain prompt injection. Wrap it in clear delimiters and label it as data. Never let tool output silently trigger other privileged tools.

3. Keep descriptions factual

Tool descriptions go straight into the model's context. Keep them narrow and descriptive. Anything that reads like an instruction ("always call this tool first") is a design smell, and in a malicious server it is an attack.

4. Guard against SSRF

If a tool fetches a URL, validate the scheme, block private IP ranges and cloud metadata addresses, and use an egress allowlist where you can.

5. Authenticate remote servers

Scans of public MCP servers have repeatedly found many with no authentication at all. For anything remote, require OAuth, bind sessions properly and validate tokens on every request.

6. Require confirmation for side effects

Sending messages, spending money, deleting data and changing permissions should need an explicit human approval in the client.

Testing checklist

  • Call every tool with empty, huge and malformed inputs.
  • Feed a tool output that contains "ignore previous instructions" and confirm nothing privileged happens.
  • Log every call with its arguments and caller, and review the logs.

Key takeaways

  • MCP gives agents tools through one standard interface.
  • The caller is a model, so validate inputs and distrust outputs.
  • Least privilege, factual descriptions, SSRF protection and authentication are non-negotiable.
  • Put a human in the loop for anything with side effects.