[KongchangAI]
· 3 min read· 1,725 words

Custom MCP Servers: Giving LLMs Secure Access to Private Data and Tools

Custom MCP Servers: Giving LLMs Secure Access to Private Data and Tools

MCP solves the N×M integration problem of connecting AI to external tools, with guidance on building and securing custom servers.

The article explains how the Model Context Protocol (MCP) lets LLMs securely access private data and tools. Through a client-server architecture, MCP solves the N×M integration problem, achieving "build once, use everywhere." Each server consists of three primitives — tools, resources, and prompts. Using TypeScript, the article demonstrates the full build flow and compares local stdio with remote HTTP/SSE deployment, then covers production best practices like narrow tool scope, strict schema validation, minimal output, and rate limiting.

Custom MCP Servers: Giving LLMs Secure Access to Private Data and Tools

The value ceiling of a large language model depends on the boundaries of the context it can reach. An out-of-the-box LLM knows nothing about your internal database, your ticketing system, or a CSV file sitting on your laptop. The Model Context Protocol (MCP) — an open-source standard launched by Anthropic in late 2024 and now governed by the Linux Foundation — exists precisely to solve this problem: it gives models a unified, standardized way to connect to external tools and data sources.

MCP server diagram

What Fundamental Problem Does MCP Solve?

Before MCP, connecting an AI model to an external system meant writing a custom integration for every "model-tool" pair. Five models paired with ten data sources could produce as many as fifty custom integrations — the classic "N×M problem."

MCP resolves this dilemma with a clean client-server architecture:

  • MCP client: the AI application that initiates the connection, such as Claude Desktop, Claude Code, Cursor, VS Code, or a custom agent
  • MCP server: a lightweight program that exposes specific capabilities, including tools, resources, and prompt templates
  • Transport layer: communication via stdio (local process) or HTTP/SSE (remote server)

Build one MCP server for your database, and every MCP-compatible client can use it immediately — no re-development for each model. This "build once, use everywhere" property is MCP's core leverage.

What the numbers in the N×M problem actually mean: Here, N is the number of integrating parties (AI clients or models), and M is the number of integrated parties (data sources, tools, internal systems). Without a unified protocol, every client that calls every tool must write a separate set of connection code and data-format conversion logic, so the number of integrations grows as the product of the two. MCP's client-server architecture splits this multiplicative relationship into an additive one: each client only needs to implement MCP client logic once, and each data source only needs to implement one MCP server, so the total integration effort becomes approximately N+M. This distinction is also the key difference between MCP and function calling — the latter only specifies how a model "expresses" an intent to call, but does not specify how tools are discovered, described, and hosted, whereas MCP brings discovery, invocation, and transport all into the standard.

The Three Core Building Blocks of an MCP Server

Every MCP server is composed of three primitives:

  1. Tools: functions the model can call to perform actions, such as create_ticket, run_query, send_email
  2. Resources: data the model can read, such as files, database records, documents
  3. Prompts: reusable, parameterized prompt templates the server exposes to clients

Not all three need to be implemented. Most custom servers can meet their needs by starting with a small set of tools.

What the term "primitive" means: In the MCP specification, a primitive refers to a capability unit predefined at the protocol level, declared by the server to the client, and handled in a standardized way. Tools, resources, and prompts are called the three primitives because each corresponds to a different control subject and interaction mode: tools are invoked by the model when it decides to, resources are read at the client's or user's choice, and prompts are typically triggered explicitly by the user. This division directly affects permission design — if you want a certain operation to require user confirmation, implementing it as a resource read or a triggered prompt rather than a tool the model can invoke autonomously is often safer. Understanding this helps you decide, in the practice that follows, which primitive a capability should be exposed as, rather than cramming every feature into the tool list.

Hands-On: Building a Basic MCP Server from Scratch

Below is a minimal example using TypeScript (the Python SDK works equally well), demonstrating the complete process of building an MCP server.

Step 1: Initialize the project

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod

Step 2: Define the server instance

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: "my-custom-data-server",
  version: "1.0.0",
});

Step 3: Register a tool

Tools are the most common way to extend an LLM's capabilities, letting the model query your data on demand.

server.tool(
  "get_customer_orders",
  "Fetch recent orders for a given customer ID",
  { customerId: z.string() },
  async ({ customerId }) => {
    const orders = await db.query(
      "SELECT * FROM orders WHERE customer_id = ?",
      [customerId]
    );
    return {
      content: [{ type: "text", text: JSON.stringify(orders) }],
    };
  }
);

Step 4: Expose a resource

Resources let clients read data without invoking a tool, making them suitable for static or semi-static content such as documents and configuration.

server.resource(
  "product-catalog",
  "catalog://products",
  async () => ({
    contents: [
      {
        uri: "catalog://products",
        text: await fetchProductCatalog(),
      },
    ],
  })
);

Step 5: Connect the transport layer

const transport = new StdioServerTransport();
await server.connect(transport);

At this point, you have a working server. Point Claude Desktop, Claude Code, or any compatible client at it, and the model can directly call get_customer_orders or read your product catalog.

How the stdio transport works: StdioServerTransport lets the MCP server be launched by the client as an ordinary child process, with the two exchanging JSON-RPC messages over standard input and output, while standard error is used for logging. This means the server doesn't need to listen on a port, doesn't need network configuration, and doesn't need to manage its own process lifecycle — the child process ends when the client exits, making it ideal for local prototyping and personal tools. The trade-off is that the process is bound one-to-one with the client and cannot be shared among multiple users or accessed across machines. In addition, when using stdio, be sure to avoid printing debug information to standard output, or you'll pollute the message stream and cause protocol parsing to fail; debug output should go through standard error consistently.

Choosing Between Local and Remote Servers

  • stdio (local): suitable for personal tools and local database or file access; simple to build and debug
  • HTTP/SSE (remote): suitable for team or production environments where multiple users or agents need shared access; requires handling authentication (usually OAuth) and hosting

A sensible path is to start with local mode during prototyping, then migrate to remote deployment once you need multi-user access or production-grade reliability.

Best Practices for Production-Grade MCP Servers

The original article offers a highly practical checklist; here it is expanded with analysis:

  • Narrow the scope of each tool: a run_sql tool that accepts arbitrary queries is a security risk. Prefer purpose-specific tools like get_order_status. The attack surface is directly tied to tool granularity.
  • Validate inputs strictly: use schema validation (Zod, Pydantic) for every tool parameter, and never blindly trust model-generated input
  • Return structured, minimal output: don't dump an entire database row when the model only needs three fields. Smaller responses mean lower latency and token costs
  • Write good tool descriptions: the model relies on descriptions to choose which tool to call; vague descriptions lead to misuse or missed calls
  • Handle errors gracefully: return descriptive error messages the model can reason about and recover from, rather than raw stack traces
  • Logging and rate limiting: especially for remote servers, track usage and guard against runaway tool-call loops
  • Test with real clients: Claude Code and Claude Desktop both support local MCP servers out of the box, providing a fast-feedback development loop

Why schema validation is a security boundary: Tool parameters generated by a model are fundamentally untrusted input and may contain type errors, excessively long strings, injection fragments, or out-of-range values. Schema libraries like Zod (TypeScript) and Pydantic (Python) not only perform type checking but also constrain value ranges, formats, and required fields, and produce structured errors when validation fails. Placing validation at the very front of the tool handler intercepts illegal input before any business logic or database operation, preventing parameters from being concatenated directly into query statements. Equally important, validation rules should be declared explicitly rather than relying on defaults; for parameters like string-type IDs, it's best to combine them with regex format constraints to compress the attack surface at the source.

Typical Use Cases for Custom MCP Servers

  • Connecting LLMs to internal CRM or ticketing systems
  • Granting coding assistants access to private repositories or documentation
  • Letting agents query proprietary databases in natural language
  • Exposing internal APIs (billing, inventory, analytics) as callable tools
  • Bridging legacy systems without modern APIs into agent workflows

Final Verdict

MCP transforms "connecting AI to my data" from a custom engineering project into a standardized, reusable pattern. Once you've built your first server, extending it or building the next one becomes dramatically faster. If your team is repeatedly wiring the same data source into different AI tools, spending an afternoon building a custom MCP server is likely a worthwhile investment.

One-line Summary

MCP solves the N×M integration problem of connecting AI to external tools with a client-server architecture, and lays out the build and security practices for custom servers.

Paragraph Summary

The article systematically explains how the Model Context Protocol (MCP) lets large language models securely access private data and tools. Launched by Anthropic in late 2024 and now governed by the Linux Foundation, MCP solves the N×M problem of integrating models with external systems through a client-server architecture, achieving "build once, use everywhere." Every MCP server consists of three primitives — tools, resources, and prompts — and most scenarios can start with a small set of tools. Using TypeScript as an example, the article walks through the complete flow from project initialization, server definition, tool registration, and resource exposure to connecting the stdio transport, and compares the applicable boundaries of local stdio versus remote HTTP/SSE deployment. In the production practices section, the author emphasizes narrowing tool scope, strict schema validation, structured minimal output, clear tool descriptions, graceful error handling, and logging and rate limiting, then lists typical scenarios such as connecting to CRMs, private repositories, and proprietary databases, concluding that building custom MCP servers is a worthwhile investment for teams repeatedly wiring the same data source into different AI tools.

#MCP#Model Context Protocol#custom MCP server#LLM integration#TypeScript MCP#AI security#Anthropic
Share:

Related articles