Standardizing AI Integrations with Model Context Protocol (MCP)
A hands-on guide to building and securing custom Model Context Protocol (MCP) servers to safely connect enterprise data to AI developer agents.

Connecting large language models to internal data sources and systems has historically been a fragmented, ad-hoc chore. Every AI engineering project usually starts with writing custom API wrappers, mapping JSON schemas, or writing proprietary glue code inside orchestrator runtimes to handle tool-calling.
If you switch from OpenAI to Anthropic, or transition your developers from one AI-assisted IDE to another, you often have to rewrite the integration layer.
This friction is why the Model Context Protocol (MCP), an open standard spearheaded by Anthropic, has quickly gained traction. Much like the Language Server Protocol (LSP) standardized how compilers talk to code editors, MCP standardizes how AI models talk to data sources and developer tools.
Here is a practical guide to building, structuring, and securing a custom MCP server to safely expose enterprise data to your developer agents.
The Architecture: Decoupling Clients and Servers
The core design principle of MCP is decoupling. Instead of hardcoding API integrations directly into an agent runtime, MCP introduces a client-server architecture:
[ AI Agent / IDE ] (MCP Client)
│
│ (JSON-RPC 2.0 over Stdio or SSE)
▼
[ Custom MCP Server ]
│
├─► Secure Database (PostgreSQL / BigQuery)
├─► Internal Microservices
└─► Local Development Tools- MCP Client: The coordinator (e.g., Cursor, Windsurf, or a custom LangChain orchestrator). It manages LLM reasoning, holds user context, and initiates tool requests.
- MCP Server: A lightweight process or service that exposes three core primitives:
- Prompts: Reusable templates for guiding model interactions.
- Resources: Read-only data sources (file contents, database rows, API responses).
- Tools: Executable functions that can perform operations or call external APIs.
Building a Custom MCP Server in TypeScript
Let’s build a production-ready Node.js MCP server using the official TypeScript SDK. This server will expose a secure tool to search internal database records (for example, candidates or case studies) with built-in runtime argument validation.
1. Project Initialization
First, set up a TypeScript project and install the necessary dependencies:
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install --save-dev typescript @types/node
npx tsc --init2. Implementing the Server
Below is the code for a secure, stdio-based MCP server. We register a search tool and use zod to enforce strict schema-validation on incoming payloads:
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";
// 1. Initialize the MCP Server
const server = new Server(
{
name: "enterprise-search-service",
version: "1.0.0",
},
{
capabilities: {
tools: {}, // Advertise tool capabilities to the client
},
}
);
// 2. Define the Search Schema using Zod
const CandidateSearchSchema = z.object({
query: z.string().min(2),
limit: z.number().optional().default(5),
department: z.enum(["Engineering", "Design", "Product", "Sales"]).optional(),
});
// 3. Register Available Tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "search_candidates",
description: "Search internal database for candidates matching specific skills and departments.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search term or required skill" },
limit: { type: "number", description: "Maximum records to return" },
department: { type: "string", enum: ["Engineering", "Design", "Product", "Sales"] },
},
required: ["query"],
},
},
],
};
});
// Mock database resolver
async function queryDatabase(query: string, limit: number, dept?: string) {
// Real implementation would connect to Cloud SQL / BigQuery
return [
{ id: 101, name: "Sarah Connor", role: "Staff Platform Engineer", tags: ["Kubernetes", "Go", "MACH"] },
{ id: 102, name: "Marcus Wright", role: "Solutions Architect", tags: ["Next.js", "React", "AI Agents"] }
];
}
// 4. Handle Tool Executions
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== "search_candidates") {
throw new Error(`Tool ${request.params.name} not found`);
}
try {
// Validate the arguments at runtime
const args = CandidateSearchSchema.parse(request.params.arguments);
// Fetch secure records
const results = await queryDatabase(args.query, args.limit, args.department);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error validating arguments: ${error.message}`,
},
],
isError: true,
};
}
});
// 5. Start Server Transport (stdio is standard for local IDE integrations)
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Enterprise Search MCP Server running on stdio");Hardening Security for Enterprise Use
While writing the TypeScript logic is straightforward, exposing internal production data to LLMs requires strict operational guardrails. AI models are prone to hallucinations, prompt injections, and unintended tool execution loops.
Here are the four pillars of enterprise MCP security:
1. Schema Validation and Input Sanitization
Never trust arguments generated by an LLM. Always validate parameters using libraries like Zod, and sanitize strings to prevent database risks:
- Use parameterized queries or ORMs to prevent SQL Injection.
- Validate directory paths to prevent directory traversal exploits if your tool reads local files.
2. Authentication and OAuth Context Passing
When running in an enterprise network, you must verify the authorization level of the user behind the agent.
- Expose MCP over Server-Sent Events (SSE) instead of stdio. This allows running the MCP server as an independent microservice behind an API Gateway.
- Forward the user’s identity token (JWT) in the request headers, allowing your MCP server to enforce Row-Level Security (RLS) in the target database.
3. Human-in-the-Loop (HITL) Gatekeeping
Exposing read tools (like search_candidates) is low risk. However, exposing write tools (like update_records or deploy_service) is dangerous.
- Implement explicit approvals for write actions. The MCP client should intercept write requests and prompt the developer with a diff showing what changes the agent wants to commit.
[ Agent Tool Request ] ──► [ Security Middleware ] ──► (Diff Check / UI Popup)
│
┌──────────────────────────────────────────┘
▼
[ User Approves? ] ──(Yes)──► [ Execute Write on Server ]4. Rate Limiting and Guardrails
Agents can enter recursive reasoning loops where they call a tool dozens of times in a few seconds, racking up major database loads or token costs.
- Enforce request rate-limiting per session.
- Cap maximum result payloads to prevent token overflow.
Looking Ahead: Exposing Legacies as Agentic Primitives
The power of the Model Context Protocol is that it turns legacy software interfaces into clean, discoverable capabilities.
By wrapping internal databases, deployment scripts, and APIs in a standardized MCP layer, you ensure that any AI tool—whether it's an editor like Cursor, a terminal companion, or an autonomous deployment agent—can interact with your business logic securely, predictably, and with zero translation overhead.