Tekko

Language

Get in Touch

Usually respond within 24 hours

Back to BlogAI & ML

Implementing Custom MCP Servers with TypeScript

7 min read
MCPTypeScriptLLMAI AgentsArchitecture
Implementing Custom MCP Servers with TypeScript

Large Language Models (LLMs) have reached a point where their reasoning capabilities often outstrip their access to relevant data. While RAG (Retrieval-Augmented Generation) and fine-tuning are common solutions for cloud-scale data, they often fail to address the 'last mile' of productivity: the private, local data living on a developer's machine or within a secure corporate perimeter.

This is where the Model Context Protocol (MCP) becomes transformative. Developed as an open standard, MCP allows you to build a bridge between AI agents—like Claude Desktop—and your local data sources, APIs, and tools. By using TypeScript to implement custom MCP servers, we can provide AI models with a secure, standardized way to interact with our local environments without ever uploading sensitive raw data to a third-party cloud for training.

Understanding the MCP Architecture

Before diving into the code, it is essential to understand the MCP topology. It operates on a client-server architecture, but with a twist: the 'client' is typically the AI application (the host), and the 'server' is a lightweight process you run locally that exposes specific capabilities.

There are three primary primitives in MCP:

  1. Resources: These are essentially the 'GET' endpoints of the AI world. They provide read-only data, such as local log files, database schemas, or documentation.
  2. Tools: These represent 'POST' or 'PUT' actions. Tools allow the AI to perform side effects, like executing a shell command, writing a file, or triggering a CI/CD pipeline.
  3. Prompts: These are reusable templates that help the AI understand how to interact with the resources and tools provided.

Communication typically happens over stdio (standard input/output) for local processes or SSE (Server-Sent Events) for remote connections. For local development, stdio is the gold standard because it inherits the security context of the user running the host application.

Setting Up the TypeScript Environment

To build an MCP server, we rely on the official @modelcontextprotocol/sdk. Start by initializing a new Node.js project:

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 runtime type safety, which is critical because the AI will be passing unstructured data to our server. We need to ensure that the inputs match our expectations before executing any logic.

Building a Custom Server: The Knowledge Base Example

Let's build a practical server that allows an AI agent to query a local SQLite database and read specific documentation files. This is a common requirement for engineers working on proprietary codebases where cloud-based indexing is prohibited.

1. Initializing the Server

Create a file named index.ts. Our first task is to instantiate the MCP server and define its metadata.

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

2. Exposing Resources

Resources are identified by URIs. In this example, we’ll expose a virtual resource that provides a summary of the local project structure.

server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [{ uri: "project://structure", name: "Project Directory Structure", mimeType: "text/plain", description: "A recursive list of files in the current workspace" }] })); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { if (request.params.uri === "project://structure") { // Implementation logic to read local FS return { contents: [{ uri: request.params.uri, mimeType: "text/plain", text: "src/\n index.ts\n utils.ts\npackage.json" }] }; } throw new Error("Resource not found"); });

3. Implementing Tools with Type Safety

Tools are the powerhouse of MCP. They allow the AI to 'do' things. Suppose we want to allow the AI to query our local SQLite database. We define the tool schema and the handler.

const QueryDatabaseSchema = z.object({ sql: z.string().describe("The SQL query to execute"), }); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [{ name: "query_db", description: "Execute a read-only SQL query on the local development database", inputSchema: { type: "object", properties: { sql: { type: "string" } }, required: ["sql"] } }] })); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "query_db") { const args = QueryDatabaseSchema.parse(request.params.arguments); // In a real scenario, you'd execute this via a sqlite3 driver // For security, ensure you only allow SELECT statements here! console.error(`Executing query: ${args.sql}`); return { content: [{ type: "text", text: "Result: [ { id: 1, name: 'Sample Record' } ]" }] }; } throw new Error("Tool not found"); });

Security Best Practices for MCP Servers

When you connect an AI to your local machine, you are effectively creating a remote procedure call (RPC) interface for a non-human agent. Security is not an afterthought; it is the foundation.

Principle of Least Privilege

Your MCP server should only have the permissions it absolutely needs. If the AI only needs to read logs, do not give the server write access to the filesystem.

Input Validation and Sanitization

AI models can be 'jailbroken' or hallucinate malicious strings. Never trust the arguments passed to a tool. Use Zod for schema validation and, if you are executing shell commands or SQL, use parameterized queries or strict allow-lists. For example, if you provide a tool to read files, validate that the requested path stays within the project directory to prevent directory traversal attacks.

Transport Security

For local agents, use stdio. This ensures that the MCP server lives and dies with the host process. Avoid exposing MCP servers over open TCP ports unless you have implemented a robust authentication layer (like mTLS or bearer tokens), as anyone on your local network could potentially issue commands to your AI tools.

Connecting the Server to a Host

Once your TypeScript server is written, you need to compile it and point your host (e.g., Claude Desktop) to the entry point. In your claude_desktop_config.json, you would add:

{ "mcpServers": { "my-local-assistant": { "command": "node", "args": ["/path/to/your/project/dist/index.js"] } } }

When Claude starts, it spawns your Node.js process. You can use console.error for logging and debugging, as console.log is reserved for the protocol's JSON-RPC messages.

Why This Matters for Engineering Teams

The move toward local-first AI integration addresses the primary blocker for AI adoption in the enterprise: data privacy. Instead of trying to build a 'Corporate GPT' that knows everything (and risks leaking everything), teams can build a suite of small, modular MCP servers.

One server might handle Jira integration, another might interface with the local Kubernetes context, and a third might provide access to internal documentation. This modularity allows for granular permission control. A junior developer's AI assistant might have access to the 'Documentation' MCP server but not the 'Production Deployment' MCP server.

Conclusion

Implementing custom MCP servers with TypeScript is more than just a technical exercise; it is a shift toward a more integrated and secure way of working with AI. By defining clear resources, tools, and prompts, you empower AI agents to become true force multipliers that understand your specific local context.

To get started, identify one repetitive task in your workflow—be it querying a local log file or checking the status of a local service—and wrap it in a simple MCP server. The efficiency gains from having an AI that actually knows what is happening on your machine will be immediately apparent.

Actionable Next Steps:

  1. Audit your local workflow for data sources that AI currently can't reach.
  2. Clone the MCP TypeScript SDK and implement a simple 'Hello World' resource.
  3. Connect the server to Claude Desktop and experiment with how the model utilizes the new context.