Tekko

Language

Get in Touch

Usually respond within 24 hours

Back to BlogArchitecture

Building Custom MCP Servers: Securely Connecting Local Data to AI Agents

7 min read
MCPAI AgentsTypeScriptLLMModel Context Protocol
Building Custom MCP Servers: Securely Connecting Local Data to AI Agents

Large Language Models (LLMs) have reached a point of remarkable reasoning capability, yet they remain fundamentally constrained by a 'context gap.' They know the world's public data, but they don't know your specific project, your internal API schemas, or the contents of your local database.

Until recently, the solution was either manual copy-pasting or building complex, bespoke RAG (Retrieval-Augmented Generation) pipelines. The Model Context Protocol (MCP), introduced by Anthropic, changes this dynamic. It provides an open standard for connecting AI models to data sources. Instead of writing a new integration for every AI tool, you build an MCP server once, and any MCP-compatible client (like Claude Desktop or an IDE) can consume it.

In this article, we will dive into the architectural specifics of MCP and walk through implementing a custom server using TypeScript to securely expose local data to AI agents.

The Architecture of MCP

MCP follows a client-server architecture that mirrors the Language Server Protocol (LSP) used by modern IDEs. This design decoupling is its greatest strength.

  1. MCP Host: The application the user interacts with (e.g., Claude Desktop, a terminal-based agent, or a custom IDE extension).
  2. MCP Client: A component within the host that initiates connections to servers.
  3. MCP Server: A lightweight process that exposes specific capabilities (Resources, Tools, and Prompts) via a standardized JSON-RPC interface.

The communication typically happens over Standard Input/Output (stdio) for local processes or Server-Sent Events (SSE) for remote connections. For most local development use cases, stdio is the preferred method because it simplifies security; the server process inherits the permissions of the host and doesn't require exposing a network port.

Why TypeScript for MCP?

While MCP is language-agnostic, TypeScript is the pragmatic choice for building servers for several reasons:

  • Official SDK Support: Anthropic maintains a robust TypeScript SDK (@modelcontextprotocol/sdk) that handles the low-level JSON-RPC heavy lifting.
  • Type Safety: When defining schemas for tools and resources, TypeScript ensures that your implementation matches the protocol's expectations, reducing runtime errors during LLM tool-calling.
  • Ecosystem: Most data source connectors (database drivers, API clients) have first-class TypeScript support.

Setting Up Your MCP Development Environment

To get started, you'll need Node.js installed. We’ll initialize a new project and install the necessary dependencies.

mkdir my-mcp-server cd my-mcp-server npm init -y npm install @modelcontextprotocol/sdk zod npm install -D typescript @types/node npx tsc --init

We use zod for schema validation. This is crucial because when an LLM calls a tool on your server, it sends a JSON payload. You need to validate this payload before processing it to ensure the safety and integrity of your system.

Implementing the Server: A Real-World Example

Let’s build a server that allows an AI agent to query a local SQLite database containing project management tasks. This is a common scenario where a developer wants to ask an AI, "What are the high-priority tickets assigned to me?"

1. Defining the Server Instance

First, we initialize the server and define its metadata.

import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; const server = new Server({ name: "project-manager-server", version: "1.0.0", }, { capabilities: { tools: {}, }, });

2. Registering Tools

Tools are functions that the AI can execute. We need to tell the MCP host what tools are available and what parameters they require.

server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "query_tasks", description: "Queries the local SQLite database for project tasks.", inputSchema: { type: "object", properties: { priority: { type: "string", enum: ["high", "medium", "low"] }, assignee: { type: "string" } }, required: ["priority"] } } ] }; });

3. Handling Tool Execution

Now we implement the logic for query_tasks. This is where the actual data fetching happens.

server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "query_tasks") { const { priority, assignee } = request.params.arguments as { priority: string; assignee?: string; }; // In a real scenario, you'd use a database driver here // For this example, we return mock data const results = [ { id: 1, task: "Fix auth bug", priority: "high", assignee: "alice" }, { id: 2, task: "Update docs", priority: "high", assignee: "bob" } ].filter(t => t.priority === priority && (!assignee || t.assignee === assignee)); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; } throw new Error("Tool not found"); });

4. Connecting the Transport

Finally, we connect the server to the stdio transport.

async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Project Manager MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

Security Considerations: The "Local First" Advantage

One of the primary concerns with AI agents is data privacy. Traditional integrations often require uploading data to a third-party cloud. MCP flips this model.

Data Sovereignty

By running the MCP server locally, your data never leaves your machine unless the LLM specifically requests it. The LLM only sees the results of the tools you provide, not the entire database.

Explicit Permissions

When building custom servers, follow the principle of least privilege:

  • Read-Only by Default: If an agent only needs to read logs, don't provide a tool that can delete them.
  • Input Validation: Use Zod or similar libraries to strictly validate LLM-generated arguments. Never pass raw strings directly into a shell command or a SQL query (preventing "Prompt Injection" leading to SQL injection).
  • Human-in-the-loop: For sensitive actions (like deleting a file or deploying code), the MCP host can be configured to require user approval before the tool is executed.

Advanced Patterns

As you scale your MCP implementation, you will encounter more complex requirements.

Resources vs. Tools

While tools are for actions, Resources are for data that the LLM can reference. If you have a large documentation file, expose it as a resource. This allows the MCP client to decide how to handle the data (e.g., by summarizing it or using it for RAG) rather than the LLM "calling" it like a function.

Context Management

LLMs have finite context windows. If your MCP server returns 50MB of logs, the client will likely crash or truncate the data. Implement pagination or summarization within your MCP server tools to ensure the data returned is high-density and relevant.

Error Handling

When a tool fails (e.g., a database timeout), don't just throw a generic error. Return a descriptive error message in the content field. The LLM can often read these errors and attempt to "self-correct" its request by changing the parameters.

Testing Your MCP Server

Testing stdio-based servers can be tricky. The best way to test is using the MCP Inspector, a utility provided by the MCP team. It allows you to simulate a host and interact with your server via a web interface.

npx @modelcontextprotocol/inspector node dist/index.js

This will provide a UI where you can list your tools and trigger them manually, seeing exactly what the server returns before you plug it into a production client like Claude.

Conclusion: The Path Forward

The Model Context Protocol is a significant step toward making AI agents truly useful in professional workflows. By building custom MCP servers, you aren't just giving an AI access to data; you are creating a secure, standardized interface for your entire technical stack.

To get started:

  1. Identify a repetitive task where you currently manually provide context to an AI (e.g., checking logs, querying a database, or reading API docs).
  2. Scaffold a TypeScript MCP server using the @modelcontextprotocol/sdk.
  3. Expose that data source as a Resource or Tool.
  4. Connect it to your IDE or Desktop AI client and observe how much more effective the agent becomes when it can "see" your local environment.

MCP turns the LLM from a distant oracle into a localized, informed collaborator.