startupbricks logo

Startupbricks

MCP Explained: What It Is, Why It Matters, and How to Build Your Own

MCP Explained: What It Is, Why It Matters, and How to Build Your Own

2025-01-22
7 min read
AI & Modern Stacks

The frustration was palpable.

"My AI assistant knows about Python and JavaScript, but it has no idea what our internal APIs do," said Maya, a senior engineer at a fintech startup. "Every time I ask it to help with our codebase, it gives me generic answers that don't match our actual architecture."

She wasn't alone. This is the problem that Model Context Protocol (MCP) was designed to solve.

I've watched Maya's team struggle with the same issue. Their AI tools were powerful but disconnected. They could write code, but they couldn't access the company's documentation, APIs, or internal tools. The AI was smart but ignorant about what actually mattered.

Then they discovered MCP.

Six weeks later, their AI assistant could answer questions about their API endpoints, fetch documentation on demand, and even execute commands in their development environment. What took hours of context-switching now took seconds.

This is the promise of MCP: not just connecting AI to more data, but connecting AI to your entire world.


What Exactly Is Model Context Protocol?

Model Context Protocol isn't another acronym to memorize. It's a bridge—a standardized way for AI models to connect with the tools, data, and services that actually matter to your work.

Think of MCP as the USB-C of AI integrations. Just as USB-C gives you one standard way to connect any device to any computer, MCP gives you one standard way to connect any AI model to any data source or tool.

The Problem MCP Solves

Before MCP, integrating AI with your tools was like building a custom electrical system for every device in your house. You needed different adapters for different devices, different protocols for different tools, and endless custom code to make everything work together.

Without MCP, connecting AI to your documentation required custom code. Connecting to your APIs required custom code. Connecting to your databases, your project management tools, your CI/CD pipelines—all required custom code, all maintained separately, all breaking in different ways when things changed.

With MCP, you define a standard interface once, and any AI that speaks MCP can connect to it. Your documentation server becomes discoverable by any MCP-compatible AI. Your API becomes accessible to any MCP-compatible tool. The connection is standardized; the intelligence is portable.

A Simple Analogy

Imagine you're working with a brilliant consultant who has deep expertise in many areas but has never seen your company's specific systems.

Without MCP: You spend hours explaining your systems, sending documents, walking through processes. Every new consultant needs to start from scratch.

With MCP: You give the consultant a standardized handbook that explains exactly how to access your systems, where to find information, and what tools are available. They can start being productive immediately, and that handbook works for any consultant who knows how to use it.

MCP is that handbook—but for AI.


Why MCP Matters in 2025

The AI landscape has changed dramatically. Models are more capable than ever, but the bottleneck has shifted from "can the model think?" to "does the model know?"

The Context Gap

Here's what I've observed watching teams adopt AI tools: the initial excitement fades fast when the AI doesn't know about their specific systems.

The first question every team asks is: "Can it access our documentation?" The answer, without MCP, is usually "not easily."

The second question is: "Can it work with our APIs?" The answer is still "not easily."

The third question is: "Can it help us build and deploy?" And the answer? You guessed it: "not easily."

MCP closes these gaps. Not by making AI smarter (that's the model providers' job), but by making connections easier (that's MCP's job).

The Ecosystem Effect

When Anthropic released MCP support for Claude, something interesting happened. The community didn't just use MCP—they extended it. Developers started building MCP servers for every conceivable tool:

  • File systems and codebases
  • Databases and data warehouses
  • Documentation platforms like Notion, Confluence, and GitBook
  • Project management tools like Linear, Jira, and Asana
  • CI/CD pipelines and deployment platforms
  • Communication tools like Slack and Discord
  • Cloud services and infrastructure

The ecosystem grew because the barrier to entry was low. Building an MCP server doesn't require deep AI expertise—it requires understanding your tool and exposing it through a standard interface.

The Competitive Advantage

Teams using MCP effectively are shipping faster. Here's why:

  1. Faster onboarding - New team members get AI assistance that knows the codebase immediately
  2. Better answers - AI can reference actual documentation instead of guessing
  3. Automated workflows - AI can execute real actions in your development environment
  4. Consistent context - Everyone gets the same high-quality AI interactions

MCP Architecture: How It Works

Understanding MCP doesn't require a computer science degree. The core concepts are straightforward.

The Three Pillars

MCP operates on three fundamental concepts:

1. Resources represent data that MCP can provide to the AI. Resources can be files, database records, API responses, or any structured data. Each resource has a URI that uniquely identifies it and a type that describes its format.

2. Tools represent actions the AI can trigger through MCP. Unlike resources (which provide information), tools perform actions. A tool might execute a command, make an API call, or modify data. Each tool has a name, a description of what it does, and input parameters it accepts.

3. Prompts are templates that help the AI use resources and tools effectively. Prompts guide the AI in formulating requests and interpreting responses, making interactions more reliable and predictable.

The Communication Flow

When you ask an AI something that requires MCP:

  1. The AI analyzes your request and determines what information or actions are needed
  2. The AI consults the MCP server to discover available resources and tools
  3. The AI selects the appropriate resources and/or tools based on your request
  4. The AI receives the results and synthesizes a response
  5. The AI presents the response, often with the ability to take further action

This all happens in milliseconds, hidden from the user but enabling powerful interactions.

A Practical Example

Let's say you're working on a project and ask Claude: "What's the status of our latest deployment and were there any failures?"

Here's what happens with MCP:

  1. Claude analyzes your question and identifies two pieces of information needed: deployment status and failure logs
  2. Claude queries the MCP server for deployment resources and tool available
  3. The MCP server returns information about a deployment status tool and access to CI/CD logs
  4. Claude invokes the deployment status tool to get current state
  5. Claude queries the CI/CD logs resource to check for failures
  6. Claude synthesizes: "Your latest deployment completed 2 hours ago. All tests passed, and there were no failures. The deployment went to production successfully."

Without MCP, Claude would have to guess or say it couldn't access that information.


Setting Up MCP: Getting Started

Ready to use MCP? Let's walk through the basics of getting started with MCP-compatible tools.

Prerequisites

Before diving in, make sure you have:

  • An MCP-compatible AI assistant (Claude Code, Continue, or others with MCP support)
  • Node.js 18+ installed
  • Access to the tools or data you want to connect
  • Basic familiarity with your terminal

Installing Claude Code with MCP Support

If you haven't already, install Claude Code with MCP support:

bash
npm install -g @anthropic-ai/claude-code

Once installed, you can configure MCP servers in your project's .claude/mcp.json file or globally in ~/.claude/mcp.json.

Configuring Your First MCP Server

Let's set up an MCP server that provides access to your project documentation:

json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
"disabled": false
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
},
"disabled": false
}
}
}

This configuration enables two MCP servers:

  • filesystem: Lets Claude read and search files in your project
  • github: Lets Claude interact with GitHub issues, pull requests, and repositories

Verifying Your Setup

After configuring MCP servers, verify they're working:

bash
claude
> /mcp

This command shows which MCP servers are connected and what resources/tools they provide. You'll see something like:

Connected MCP Servers:

  • filesystem: Provides file reading and searching
  • github: Provides GitHub API access (requires GITHUB_TOKEN)

Building Your Own MCP Server

This is where it gets exciting. Building an MCP server means creating a bridge between AI and your specific tools, data, or APIs. Let's build one together.

The Project Structure

Create a new directory for your MCP server:

bash
mkdir my-mcp-server
cd my-mcp-server
npm init -y

Install the MCP SDK:

bash
npm install @modelcontextprotocol/sdk

Building a Documentation MCP Server

Let's create an MCP server that provides access to your project's documentation. This is a common use case—imagine asking AI about your API and having it pull from your actual docs.

Step 1: Create the Server File

javascript
// server.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ListPromptsRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// Create the server instance
const server = new Server("docs-mcp-server", {
capabilities: {
resources: {},
tools: {},
prompts: {},
},
});
// Your documentation data - in a real app, this might come from a database or API
const documentation = {
"getting-started": {
title: "Getting Started",
content: `Welcome to our API! Here's how to get started:
## Authentication
All API requests require an API key in the header:
\`\`\`
Authorization: Bearer YOUR_API_KEY
\`\`\`
## Base URL
\`https://api.example.com/v1\`
## Rate Limits
- Free tier: 100 requests/hour
- Pro tier: 10,000 requests/hour
- Enterprise: Custom limits`
},
"endpoints": {
title: "API Endpoints",
content: `## Users Endpoint
### GET /users
Returns a list of users.
**Parameters:**
- \`page\` (optional): Page number, default 1
- \`limit\` (optional): Items per page, default 20
**Response:**
\`\`\`json
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 150
}
}
\`\`\`
### POST /users
Create a new user.
**Request Body:**
\`\`\`json
{
"name": "John Doe",
"email": "[email protected]"
}
\`\`\``
},
"authentication": {
title: "Authentication",
content: `## API Keys
API keys can be generated from your dashboard. Each key has a prefix for identification:
\`\`\`
sk_live_abc123...
\`\`\`
## OAuth 2.0
We also support OAuth 2.0 for user-facing integrations.
1. Redirect users to \`/oauth/authorize\`
2. Exchange code for token at \`/oauth/token\`
3. Use token in API requests`
}
};
// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: Object.entries(documentation).map(([id, doc]) => ({
uri: `docs://${id}`,
name: doc.title,
mimeType: "text/markdown",
description: `Documentation: ${doc.title}`
}))
};
});
// Read a specific resource
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const docId = request.params.uri.replace("docs://", "");
const doc = documentation[docId];
if (!doc) {
throw new Error(`Documentation not found: ${docId}`);
}
return {
contents: [{
uri: request.params.uri,
mimeType: "text/markdown",
text: doc.content
}]
};
});
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "search_docs",
description: "Search documentation for a keyword",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "The search query"
}
},
required: ["query"]
}
}
]
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "search_docs") {
const query = args.query.toLowerCase();
const results = [];
for (const [id, doc] of Object.entries(documentation)) {
if (doc.content.toLowerCase().includes(query) ||
doc.title.toLowerCase().includes(query)) {
results.push({
id,
title: doc.title,
snippet: doc.content.substring(0, 200) + "..."
});
}
return {
content: [{
type: "text",
text: JSON.stringify(results, null, 2)
}]
};
}
throw new Error(`Unknown tool: ${name}`);
});
// List prompts
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: "api-help",
description: "Get help with API usage",
arguments: [
{
name: "endpoint",
description: "The API endpoint you need help with"
}
]
}
]
};
});
// Connect using stdio transport
const transport = new StdioServerTransport();
server.connect(transport);
console.log("Documentation MCP Server running!");

Step 2: Configure package.json

json
{
"name": "docs-mcp-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.5.0"
}
}

Step 3: Test the Server

bash
npm install
npm start

Step 4: Connect to Claude Code

Add it to your .claude/mcp.json:

json
{
"mcpServers": {
"docs": {
"command": "node",
"args": ["/path/to/your/server.js"],
"disabled": false
}
}
}

Now Claude can access your documentation through MCP!

Building an API MCP Server

Let's extend the concept to create an MCP server for your actual API. This allows AI to not just read about your API but make real requests.

The API MCP Server:

javascript
// api-server.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server("api-mcp-server", {
capabilities: {
tools: {},
},
});
// Your API configuration
const API_BASE_URL = process.env.API_BASE_URL || "https://api.example.com";
const API_KEY = process.env.API_KEY;
async function makeApiRequest(endpoint, method = "GET", body = null) {
const headers = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
const options = {
method,
headers,
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${API_BASE_URL}${endpoint}`, options);
const data = await response.json();
return {
status: response.status,
data,
};
}
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_users",
description: "Fetch a list of users from the API",
inputSchema: {
type: "object",
properties: {
page: { type: "number", description: "Page number" },
limit: { type: "number", description: "Items per page" },
},
},
},
{
name: "get_user",
description: "Fetch a specific user by ID",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "The user ID" },
},
required: ["userId"],
},
},
{
name: "create_user",
description: "Create a new user",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "User's name" },
email: { type: "string", description: "User's email" },
},
required: ["name", "email"],
},
},
{
name: "get_user_stats",
description: "Get statistics for a specific user",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "The user ID" },
},
required: ["userId"],
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result;
switch (name) {
case "get_users":
const usersParams = new URLSearchParams();
if (args.page) usersParams.set("page", args.page.toString());
if (args.limit) usersParams.set("limit", args.limit.toString());
result = await makeApiRequest(`/users?${usersParams}`);
break;
case "get_user":
result = await makeApiRequest(`/users/${args.userId}`);
break;
case "create_user":
result = await makeApiRequest("/users", "POST", {
name: args.name,
email: args.email,
});
break;
case "get_user_stats":
result = await makeApiRequest(`/users/${args.userId}/stats`);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error calling ${name}: ${error.message}`,
},
],
};
}
});
const transport = new StdioServerTransport();
server.connect(transport);
console.log("API MCP Server running!");

Making It Production-Ready

A real MCP server needs more than just basic functionality. Here's what to add:

Error Handling:

javascript
// Wrap each tool call in error handling
try {
// Tool logic
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
isError: true,
};
}

Rate Limiting:

javascript
const rateLimit = new Map();
async function checkRateLimit(clientId) {
const now = Date.now();
const window = 60 * 1000; // 1 minute
const maxRequests = 100;
const requests = rateLimit.get(clientId) || [];
const recentRequests = requests.filter((t) => now - t < window);
if (recentRequests.length >= maxRequests) {
throw new Error("Rate limit exceeded. Please try again later.");
}
recentRequests.push(now);
rateLimit.set(clientId, recentRequests);
}

Authentication:

javascript
function authenticateRequest(request) {
const apiKey = request.headers.get("Authorization");
if (!apiKey || !apiKey.startsWith("Bearer ")) {
throw new Error("Missing or invalid authorization header");
}
const key = apiKey.replace("Bearer ", "");
if (!isValidKey(key)) {
throw new Error("Invalid API key");
}
return getClientFromKey(key);
}

MCP Best Practices

Building MCP servers is one thing; building good MCP servers is another. Here are patterns that separate great MCP implementations from mediocre ones.

Design Principles

1. Be Descriptive, Not Prescriptive

Bad MCP tool description:

"get_data" - gets data

Good MCP tool description:

"get_user_activity" - Retrieves the activity history for a specific user.
Returns timestamps, action types, and metadata for the last 30 days.
Requires userId parameter. Useful for understanding user behavior patterns.

2. Handle Errors Gracefully

Users should never see raw errors. Always wrap exceptions:

javascript
try {
// Operation
} catch (error) {
return {
content: [
{
type: "text",
text: `Unable to complete that action: ${error.message}`,
},
],
isError: true,
};
}

3. Document Everything

MCP servers should be self-documenting. Include clear descriptions for every resource, tool, and prompt:

javascript
{
name: "search_documents",
description: `Search through project documentation for specific terms or concepts.
This tool searches across all indexed documentation including:
- API reference guides
- Architecture decision records
- Development guides
- Runbook entries
Returns matching sections with relevance scores.`,
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query - can include specific terms, error messages, or concept names"
},
maxResults: {
type: "number",
description: "Maximum number of results to return (default: 5)"
}
},
required: ["query"]
}
}

Common Patterns

Database Access Pattern:

javascript
// Don't expose raw SQL - use parameterized queries
{
name: "find_customers",
description: "Search for customers by email or name",
inputSchema: {
type: "object",
properties: {
email: { type: "string", description: "Customer email (partial match)" },
name: { type: "string", description: "Customer name (partial match)" },
limit: { type: "number", description: "Max results (default: 20)" }
}
}
}

Command Execution Pattern:

javascript
// For safe command execution, use allowlists
const ALLOWED_COMMANDS = ["npm run", "git status", "ls -la"];
{
name: "run_command",
description: "Run safe development commands",
inputSchema: {
type: "object",
properties: {
command: {
type: "string",
description: "Command to run (must be in allowed list)"
},
args: {
type: "array",
items: { type: "string" },
description: "Command arguments"
}
},
required: ["command"]
}
}

The Future of MCP

MCP is still evolving, but the direction is clear.

What's Coming

More Built-in Servers: The ecosystem is building pre-packaged MCP servers for common tools—Salesforce, Stripe, AWS, and more will soon have official MCP connectors.

Better Discovery: Finding MCP servers will become as easy as finding npm packages. Expect MCP server registries and package managers.

Cross-Model Compatibility: While MCP started with Claude, other providers are adopting the standard. Your MCP server will work with any compatible AI.

Enterprise Features: Authentication, auditing, and compliance features are being standardized into the protocol itself.

Why This Matters for Your Business

MCP represents a fundamental shift in how AI integrates with business systems. Companies that invest in MCP infrastructure now will have:

  • Faster AI integration for new tools and data sources
  • Lower maintenance costs as integration code becomes standardized
  • Better AI performance as context improves
  • Competitive advantage as AI becomes more central to operations

Tools & Resources

MCP Documentation & SDKs

Community MCP Servers

Learning Resources


Your MCP Journey Starts Now

MCP might seem complex at first, but the core idea is simple: connect AI to your tools using a standard interface, and the possibilities are endless.

Start small. Build a documentation MCP server for your project. Connect it to Claude Code. Watch how much faster you can find information and answer questions about your codebase.

Then expand. Add API access. Add database queries. Add deployment commands. Each addition multiplies the value of your AI assistant.

The developers who thrive in 2025 won't just use AI—they'll build the bridges that make AI useful. MCP is your toolkit for building those bridges.


Related Reading:


Need Help Building MCP Integrations?

At Startupbricks, we help startups build AI infrastructure that scales. From MCP servers to full AI integrations, we can help you connect your tools to intelligence.

Let's build your AI infrastructure

Share: