DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Building Your First MCP Server: Tools, Resources, and Security Checklist

What Is an MCP Server?

Model Context Protocol (MCP) is Anthropic's open standard for giving AI models access to tools, data, and services. An MCP server exposes capabilities that any MCP-compatible client (Claude Desktop, Cursor, your custom app) can use.

Building one is straightforward. Here's the complete pattern.

Project Setup

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
Enter fullscreen mode Exit fullscreen mode
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "strict": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Basic MCP Server

// src/index.ts
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: 'my-mcp-server', version: '1.0.0' },
  { capabilities: { tools: {} } }
)

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'get_time',
      description: 'Get the current time in a timezone',
      inputSchema: {
        type: 'object',
        properties: {
          timezone: {
            type: 'string',
            description: 'IANA timezone (e.g. America/New_York)',
          }
        },
        required: ['timezone']
      }
    },
    {
      name: 'calculate',
      description: 'Evaluate a mathematical expression',
      inputSchema: {
        type: 'object',
        properties: {
          expression: { type: 'string', description: 'Math expression to evaluate' }
        },
        required: ['expression']
      }
    }
  ]
}))

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

  if (name === 'get_time') {
    const { timezone } = args as { timezone: string }
    try {
      const time = new Date().toLocaleString('en-US', { timeZone: timezone })
      return { content: [{ type: 'text', text: `Current time in ${timezone}: ${time}` }] }
    } catch {
      return { content: [{ type: 'text', text: `Invalid timezone: ${timezone}` }], isError: true }
    }
  }

  if (name === 'calculate') {
    const { expression } = args as { expression: string }
    // Safe eval alternative
    try {
      const result = Function('"use strict"; return (' + expression + ')')
      return { content: [{ type: 'text', text: `${expression} = ${result()}` }] }
    } catch (e) {
      return { content: [{ type: 'text', text: `Error: ${e}` }], isError: true }
    }
  }

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

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

main().catch(console.error)
Enter fullscreen mode Exit fullscreen mode

Claude Desktop Configuration

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "my-mcp-server": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/dist/index.js"],
      "env": {
        "MY_API_KEY": "your-api-key-here"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Adding Resources (Read-only Data)

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

const server = new Server(
  { name: 'my-mcp-server', version: '1.0.0' },
  { capabilities: { tools: {}, resources: {} } } // Add resources capability
)

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

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  if (request.params.uri === 'config://app/settings') {
    return {
      contents: [{
        uri: 'config://app/settings',
        mimeType: 'application/json',
        text: JSON.stringify({ theme: 'dark', language: 'en' })
      }]
    }
  }
  throw new Error('Resource not found')
})
Enter fullscreen mode Exit fullscreen mode

Security: What to Check Before Installing

MCP servers run on your machine with your credentials. Before installing any server:

  1. Input validation -- does it validate all tool inputs with schemas?
  2. Command injection -- does it ever pass user input to shell commands?
  3. Path traversal -- does it use user-provided file paths directly?
  4. Hardcoded secrets -- are API keys in the source code?
  5. SSRF -- can it make arbitrary HTTP requests based on user input?

The MCP Security Scanner checks all 22 of these rules automatically. Scan any MCP server before you install it.

$29/mo at whoffagents.com

Top comments (0)