# Model Context Protocol (MCP) for TypeScript Developers: A 10-Minute Guide

> Source: <https://dev.to/raju_dandigam/model-context-protocol-mcp-for-typescript-developers-a-10-minute-guide-4dfe>
> Published: 2026-07-07 17:01:45+00:00

Anthropic released Model Context Protocol in late 2024 to solve a real problem: every AI agent needs custom code to connect to data sources. MCP standardizes this. Here's what it is and how to use it with TypeScript.

Before MCP, connecting an AI agent to your database looked like this:

``` js
// Custom integration for Agent A
async function agentAGetData() {
 const db = new DatabaseClient();
 return await db.query('SELECT * FROM users');
}

// Different custom integration for Agent B
async function agentBGetData() {
 const conn = connectToDatabase();
 return conn.fetchUsers();
}
```

Every agent, every tool, every integration required custom code. MCP creates a standard protocol so you write the integration once and any MCP-compatible agent can use it.

Think of it like OAuth did for authentication—instead of building login flows for every app, you implement OAuth once and everyone can use it.

Model Context Protocol is:

**In practice:** You write an MCP server that exposes your filesystem, database, or API. Any agent that speaks MCP can then access that data without custom integration code.

**Use MCP when:**

**Skip MCP when:**

MCP adds structure. That's great for maintainability, but it's overhead for simple projects.

We'll build a filesystem server that lets agents read files.

```
mkdir mcp-filesystem-server
cd mcp-filesystem-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node tsx
npx tsc --init
```

Create `src/index.ts`

:

``` 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';
import fs from 'fs/promises';
import path from 'path';

// Create the MCP server
const server = new Server(
 {
   name: 'filesystem-server',
   version: '1.0.0',
 },
 {
   capabilities: {
     tools: {},
   },
 }
);

// Define what tools this server provides
server.setRequestHandler(ListToolsRequestSchema, async () => {
 return {
   tools: [
     {
       name: 'read_file',
       description: 'Read the contents of a file',
       inputSchema: {
         type: 'object',
         properties: {
           path: {
             type: 'string',
             description: 'Path to the file to read',
           },
         },
         required: ['path'],
       },
     },
     {
       name: 'list_directory',
       description: 'List files in a directory',
       inputSchema: {
         type: 'object',
         properties: {
           path: {
             type: 'string',
             description: 'Path to the directory',
           },
         },
         required: ['path'],
       },
     },
   ],
 };
});

// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
 const { name, arguments: args } = request.params;

 try {
   if (name === 'read_file') {
     const filePath = args.path as string;
     const content = await fs.readFile(filePath, 'utf-8');
     return {
       content: [
         {
           type: 'text',
           text: content,
         },
       ],
     };
   }

   if (name === 'list_directory') {
     const dirPath = args.path as string;
     const files = await fs.readdir(dirPath);
     const fileList = files.join('\n');
     return {
       content: [
         {
           type: 'text',
           text: `Files in ${dirPath}:\n${fileList}`,
         },
       ],
     };
   }

   return {
     content: [
       {
         type: 'text',
         text: `Unknown tool: ${name}`,
       },
     ],
     isError: true,
   };
 } catch (error) {
   return {
     content: [
       {
         type: 'text',
         text: `Error: ${error instanceof Error ? error.message : String(error)}`,
       },
     ],
     isError: true,
   };
 }
});

// Start the server
async function main() {
 const transport = new StdioServerTransport();
 await server.connect(transport);
 console.error('Filesystem MCP server running on stdio');
}

main().catch(console.error);
# Build
npx tsc

# Test (the server communicates over stdio)
node dist/index.js
```

The server is now running and waiting for MCP requests over stdin/stdout.

You created an MCP server with two capabilities:

When a client (like Claude Desktop) connects:

Claude Desktop has built-in MCP support. To use your server:

On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`

On Windows: `%APPDATA%/Claude/claude_desktop_config.json`

```
{
 "mcpServers": {
   "filesystem": {
     "command": "node",
     "args": ["/absolute/path/to/your/dist/index.js"]
   }
 }
}
```

After restarting, Claude can now read files and list directories using your MCP server.

Ask Claude: "What files are in my Documents folder?" and it will use your MCP server to answer.

You don't have to write every integration. Several official servers exist:

```
# Install pre-built servers
npm install -g @modelcontextprotocol/server-filesystem
npm install -g @modelcontextprotocol/server-github
npm install -g @modelcontextprotocol/server-postgres
```

Configure them in `claude_desktop_config.json`

:

```
{
 "mcpServers": {
   "filesystem": {
     "command": "npx",
     "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/Documents"]
   },
   "github": {
     "command": "npx",
     "args": ["-y", "@modelcontextprotocol/server-github"],
     "env": {
       "GITHUB_TOKEN": "your-token-here"
     }
   }
 }
}
```

Now Claude can access your filesystem and GitHub without you writing any integration code.

Let's build something more practical—a PostgreSQL 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';
import { Client } from 'pg';

const dbClient = new Client({
 connectionString: process.env.DATABASE_URL,
});

await dbClient.connect();

const server = new Server(
 {
   name: 'postgres-server',
   version: '1.0.0',
 },
 {
   capabilities: {
     tools: {},
   },
 }
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
 return {
   tools: [
     {
       name: 'query',
       description: 'Execute a SQL query (SELECT only for safety)',
       inputSchema: {
         type: 'object',
         properties: {
           sql: {
             type: 'string',
             description: 'SQL query to execute',
           },
         },
         required: ['sql'],
       },
     },
   ],
 };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
 const { name, arguments: args } = request.params;

 if (name === 'query') {
   const sql = args.sql as string;

   // Safety check - only allow SELECT
   if (!sql.trim().toLowerCase().startsWith('select')) {
     return {
       content: [
         {
           type: 'text',
           text: 'Only SELECT queries are allowed',
         },
       ],
       isError: true,
     };
   }

   try {
     const result = await dbClient.query(sql);
     return {
       content: [
         {
           type: 'text',
           text: JSON.stringify(result.rows, null, 2),
         },
       ],
     };
   } catch (error) {
     return {
       content: [
         {
           type: 'text',
           text: `Query error: ${error instanceof Error ? error.message : String(error)}`,
         },
       ],
       isError: true,
     };
   }
 }

 return {
   content: [{ type: 'text', text: `Unknown tool: ${name}` }],
   isError: true,
 };
});

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

Now any MCP client can query your database safely.

Want to use MCP servers from your own agent? Use the client SDK:

``` js
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

// Connect to an MCP server
const transport = new StdioClientTransport({
 command: 'node',
 args: ['./path/to/mcp-server.js'],
});

const client = new Client(
 {
   name: 'my-agent',
   version: '1.0.0',
 },
 {
   capabilities: {},
 }
);

await client.connect(transport);

// List available tools
const tools = await client.listTools();
console.log('Available tools:', tools);

// Call a tool
const result = await client.callTool({
 name: 'read_file',
 arguments: {
   path: '/path/to/file.txt',
 },
});

console.log('Result:', result);
```

This lets you build agents that use MCP servers as tools.

MCP servers can expose resources (data) and tools (actions):

``` js
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';

// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
 return {
   resources: [
     {
       uri: 'config://app/settings',
       name: 'App Settings',
       mimeType: 'application/json',
     },
   ],
 };
});

// Read a specific resource
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
 const { uri } = request.params;

 if (uri === 'config://app/settings') {
   const settings = { theme: 'dark', notifications: true };
   return {
     contents: [
       {
         uri,
         mimeType: 'application/json',
         text: JSON.stringify(settings, null, 2),
       },
     ],
   };
 }

 return { contents: [] };
});
```

Resources are for data that agents read; tools are for actions they execute.

**MCP servers have full system access.** Consider:

Example safe file access:

``` js
const ALLOWED_DIR = '/safe/directory';

server.setRequestHandler(CallToolRequestSchema, async (request) => {
 if (request.params.name === 'read_file') {
   const requestedPath = request.params.arguments.path as string;
   const resolvedPath = path.resolve(ALLOWED_DIR, requestedPath);

   // Prevent directory traversal
   if (!resolvedPath.startsWith(ALLOWED_DIR)) {
     return {
       content: [{ type: 'text', text: 'Access denied' }],
       isError: true,
     };
   }

   // Safe to proceed
   const content = await fs.readFile(resolvedPath, 'utf-8');
   return { content: [{ type: 'text', text: content }] };
 }
});
```

**Good fit:**

**Overkill:**

MCP trades flexibility for structure. That's great when you need consistency across projects, but it's extra work for simple cases.

The MCP ecosystem is early but growing. More servers are being published, and frameworks are adding MCP support.

To learn more:

For your projects:

MCP standardizes how AI agents access data. Instead of building custom integrations for every agent, you write an MCP server once and any compatible agent can use it.

It's useful when you have multiple agents or want reusable integrations. For simple projects, custom tools are often faster.

The TypeScript SDK makes building MCP servers straightforward. The pattern is consistent: define tools, handle requests, return results. Everything else is application-specific logic.

If you're building agent infrastructure, MCP is worth learning. If you're building a single agent, evaluate whether the standardization benefits outweigh the added complexity.
