[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-building-first-mcp-server-hands-on-developer-guide":3},{"article":4,"author":50},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":7,"meta_description":16,"focus_keyword":17,"og_image":18,"canonical_url":18,"robots_meta":19,"created_at":15,"updated_at":15,"tags":20,"category_name":30,"related_articles":31},"da000000-0000-0000-0000-000000000001","a0000000-0000-0000-0000-000000000006","Building Your First MCP Server: A Hands-On Developer Guide","building-first-mcp-server-hands-on-developer-guide","A practical step-by-step tutorial for building a Model Context Protocol (MCP) server in TypeScript and Python, connecting it to Claude Desktop or Cursor, and deploying it to production.","## What Is the Model Context Protocol (MCP)?\n\nThe **Model Context Protocol (MCP)** is an open standard created by Anthropic that defines how AI applications communicate with external data sources and tools. Think of MCP as **USB-C for AI integration**: just as USB-C provides a single universal connector for charging, data transfer, and display output, MCP provides a single universal protocol for connecting AI models to databases, APIs, file systems, and any other service. MCP eliminates the need for custom integrations between every AI application and every tool.\n\nIn 2026, MCP has become the dominant standard for AI-tool communication. Gartner predicts that **40% of enterprise applications will include AI agents by end of 2026**, and MCP is the protocol that makes this practical at scale. Before MCP, connecting an AI model to N tools required N custom integrations. With M AI applications and N tools, you needed M x N integration adapters. MCP reduces this to M + N: every AI app implements one MCP client, every tool implements one MCP server.\n\n## MCP Architecture: Hosts, Clients, Servers, and Transports\n\nUnderstanding MCP architecture is essential before writing any code. The protocol defines four key roles:\n\n| Component | Role | Example |\n|-----------|------|---------|\n| **Host** | The AI application that end users interact with | Claude Desktop, Cursor, VS Code Copilot |\n| **Client** | The MCP protocol handler inside the host | Built into the host application |\n| **Server** | Exposes tools, resources, and prompts via MCP | Your custom server (what we will build) |\n| **Transport** | The communication layer between client and server | stdio, HTTP+SSE, Streamable HTTP |\n\nThe communication flow works like this:\n\n1. The **host** starts and initializes an MCP **client** for each configured server.\n2. The **client** connects to the **server** via a **transport** (stdio for local, HTTP for remote).\n3. The **client** sends an `initialize` request, negotiating protocol version and capabilities.\n4. The **server** responds with its available **tools**, **resources**, and **prompts**.\n5. When the AI model needs external data or actions, the **client** calls the appropriate server method.\n6. The **server** executes the operation and returns results via JSON-RPC 2.0 messages.\n\n### Tools vs Resources vs Prompts\n\nMCP servers can expose three types of capabilities:\n\n- **Tools** are functions the AI model can call. They take structured input and return structured output. Example: `query_database(sql: string)` or `send_email(to: string, subject: string, body: string)`.\n- **Resources** are data the AI model can read. They are identified by URIs and return content. Example: `file:\u002F\u002F\u002Fpath\u002Fto\u002Fdocument.md` or `postgres:\u002F\u002Flocalhost\u002Fmydb\u002Fusers`.\n- **Prompts** are reusable prompt templates that the server provides. They help standardize how the AI interacts with the server's domain. Example: a `summarize_ticket` prompt template for a Jira MCP server.\n\n## Step-by-Step: Building an MCP Server in TypeScript\n\nLet us build a practical MCP server that provides a tool for querying a SQLite database. This is a common use case: giving an AI model safe, read-only access to your application data.\n\n### Step 1: Initialize the Project\n\n```bash\nmkdir mcp-sqlite-server && cd mcp-sqlite-server\nnpm init -y\nnpm install @modelcontextprotocol\u002Fsdk better-sqlite3\nnpm install -D typescript @types\u002Fnode @types\u002Fbetter-sqlite3\nnpx tsc --init\n```\n\nConfigure `tsconfig.json`:\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"Node16\",\n    \"moduleResolution\": \"Node16\",\n    \"outDir\": \".\u002Fdist\",\n    \"rootDir\": \".\u002Fsrc\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"declaration\": true\n  },\n  \"include\": [\"src\u002F**\u002F*\"]\n}\n```\n\n### Step 2: Implement the Server\n\nCreate `src\u002Findex.ts`:\n\n```typescript\nimport { McpServer } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js\";\nimport Database from \"better-sqlite3\";\nimport { z } from \"zod\";\n\n\u002F\u002F Initialize the database\nconst db = new Database(process.env.DB_PATH || \".\u002Fdata.db\", {\n  readonly: true,\n});\n\n\u002F\u002F Create the MCP server\nconst server = new McpServer({\n  name: \"sqlite-query\",\n  version: \"1.0.0\",\n});\n\n\u002F\u002F Register a tool for querying the database\nserver.tool(\n  \"query\",\n  \"Execute a read-only SQL query against the SQLite database\",\n  {\n    sql: z.string().describe(\"The SQL SELECT query to execute\"),\n  },\n  async ({ sql }) => {\n    \u002F\u002F Security: only allow SELECT statements\n    const normalized = sql.trim().toUpperCase();\n    if (!normalized.startsWith(\"SELECT\")) {\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: \"Error: Only SELECT queries are allowed.\",\n          },\n        ],\n        isError: true,\n      };\n    }\n\n    try {\n      const rows = db.prepare(sql).all();\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(rows, null, 2),\n          },\n        ],\n      };\n    } catch (error) {\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: `Query error: ${(error as Error).message}`,\n          },\n        ],\n        isError: true,\n      };\n    }\n  }\n);\n\n\u002F\u002F Register a tool to list all tables\nserver.tool(\n  \"list_tables\",\n  \"List all tables in the database with their schemas\",\n  {},\n  async () => {\n    const tables = db\n      .prepare(\n        `SELECT name, sql FROM sqlite_master\n         WHERE type='table' AND name NOT LIKE 'sqlite_%'\n         ORDER BY name`\n      )\n      .all();\n\n    return {\n      content: [\n        {\n          type: \"text\",\n          text: JSON.stringify(tables, null, 2),\n        },\n      ],\n    };\n  }\n);\n\n\u002F\u002F Register a resource for database schema\nserver.resource(\n  \"schema\",\n  \"sqlite:\u002F\u002Fschema\",\n  async (uri) => {\n    const tables = db\n      .prepare(\n        `SELECT name, sql FROM sqlite_master\n         WHERE type='table' AND name NOT LIKE 'sqlite_%'`\n      )\n      .all();\n\n    return {\n      contents: [\n        {\n          uri: uri.href,\n          mimeType: \"application\u002Fjson\",\n          text: JSON.stringify(tables, null, 2),\n        },\n      ],\n    };\n  }\n);\n\n\u002F\u002F Start the server with stdio transport\nasync function main() {\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n  console.error(\"SQLite MCP server running on stdio\");\n}\n\nmain().catch(console.error);\n```\n\n### Step 3: Build and Test Locally\n\n```bash\nnpx tsc\necho '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0\"}}}' | node dist\u002Findex.js\n```\n\nYou should see a JSON-RPC response with the server's capabilities.\n\n### Step 4: Build the Same Server in Python\n\nFor Python developers, here is the equivalent using the official MCP Python SDK:\n\n```python\n# server.py\nimport sqlite3\nimport json\nfrom mcp.server.fastmcp import FastMCP\n\nmcp = FastMCP(\"sqlite-query\")\n\nDB_PATH = \"data.db\"\n\n@mcp.tool()\ndef query(sql: str) -> str:\n    \"\"\"Execute a read-only SQL query against the SQLite database.\"\"\"\n    normalized = sql.strip().upper()\n    if not normalized.startswith(\"SELECT\"):\n        raise ValueError(\"Only SELECT queries are allowed.\")\n\n    conn = sqlite3.connect(DB_PATH)\n    conn.row_factory = sqlite3.Row\n    try:\n        cursor = conn.execute(sql)\n        rows = [dict(row) for row in cursor.fetchall()]\n        return json.dumps(rows, indent=2, default=str)\n    finally:\n        conn.close()\n\n@mcp.tool()\ndef list_tables() -> str:\n    \"\"\"List all tables in the database with their schemas.\"\"\"\n    conn = sqlite3.connect(DB_PATH)\n    conn.row_factory = sqlite3.Row\n    try:\n        cursor = conn.execute(\n            \"SELECT name, sql FROM sqlite_master \"\n            \"WHERE type='table' AND name NOT LIKE 'sqlite_%'\"\n        )\n        tables = [dict(row) for row in cursor.fetchall()]\n        return json.dumps(tables, indent=2)\n    finally:\n        conn.close()\n\n@mcp.resource(\"sqlite:\u002F\u002Fschema\")\ndef get_schema() -> str:\n    \"\"\"Return the database schema as a resource.\"\"\"\n    conn = sqlite3.connect(DB_PATH)\n    conn.row_factory = sqlite3.Row\n    try:\n        cursor = conn.execute(\n            \"SELECT name, sql FROM sqlite_master WHERE type='table'\"\n        )\n        return json.dumps([dict(r) for r in cursor.fetchall()], indent=2)\n    finally:\n        conn.close()\n\nif __name__ == \"__main__\":\n    mcp.run(transport=\"stdio\")\n```\n\nInstall dependencies and run:\n\n```bash\npip install mcp[cli]\npython server.py\n```\n\n## Connecting to Claude Desktop\n\nClaude Desktop natively supports MCP servers. To connect your server, edit the Claude Desktop configuration file:\n\n**macOS:** `~\u002FLibrary\u002FApplication Support\u002FClaude\u002Fclaude_desktop_config.json`\n**Windows:** `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n```json\n{\n  \"mcpServers\": {\n    \"sqlite-query\": {\n      \"command\": \"node\",\n      \"args\": [\"\u002Fabsolute\u002Fpath\u002Fto\u002Fdist\u002Findex.js\"],\n      \"env\": {\n        \"DB_PATH\": \"\u002Fabsolute\u002Fpath\u002Fto\u002Fyour\u002Fdata.db\"\n      }\n    }\n  }\n}\n```\n\nFor the Python version:\n\n```json\n{\n  \"mcpServers\": {\n    \"sqlite-query\": {\n      \"command\": \"python\",\n      \"args\": [\"\u002Fabsolute\u002Fpath\u002Fto\u002Fserver.py\"]\n    }\n  }\n}\n```\n\nRestart Claude Desktop. You should see a hammer icon in the chat interface indicating available MCP tools. Now you can ask Claude questions like \"What tables are in the database?\" or \"Show me the top 10 users by signup date\" and it will use your MCP server to query the database directly.\n\n## Connecting to Cursor\n\nCursor also supports MCP servers. Add configuration to `.cursor\u002Fmcp.json` in your project root:\n\n```json\n{\n  \"mcpServers\": {\n    \"sqlite-query\": {\n      \"command\": \"node\",\n      \"args\": [\".\u002Fdist\u002Findex.js\"],\n      \"env\": {\n        \"DB_PATH\": \".\u002Fdata.db\"\n      }\n    }\n  }\n}\n```\n\nAfter saving, restart Cursor. The MCP tools appear in the AI assistant panel and can be invoked during code generation and debugging sessions.\n\n## Testing and Debugging Your MCP Server\n\n### Using the MCP Inspector\n\nThe MCP Inspector is the official debugging tool. It provides a web UI for interacting with your server:\n\n```bash\nnpx @modelcontextprotocol\u002Finspector node dist\u002Findex.js\n```\n\nThis opens a browser interface at `http:\u002F\u002Flocalhost:5173` where you can:\n\n- View all registered tools, resources, and prompts\n- Call tools with custom inputs and inspect responses\n- Monitor the JSON-RPC message stream in real time\n- Test error handling by sending malformed requests\n\n### Unit Testing with the SDK\n\nWrite automated tests using the in-memory transport:\n\n```typescript\nimport { McpServer } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol\u002Fsdk\u002FinMemory.js\";\nimport { Client } from \"@modelcontextprotocol\u002Fsdk\u002Fclient\u002Findex.js\";\n\ndescribe(\"SQLite MCP Server\", () => {\n  let client: Client;\n\n  beforeEach(async () => {\n    const [clientTransport, serverTransport] =\n      InMemoryTransport.createLinkedPair();\n    const server = createServer(); \u002F\u002F your server factory\n    await server.connect(serverTransport);\n    client = new Client({ name: \"test\", version: \"1.0\" });\n    await client.connect(clientTransport);\n  });\n\n  it(\"should list tables\", async () => {\n    const result = await client.callTool({\n      name: \"list_tables\",\n      arguments: {},\n    });\n    expect(result.content[0].text).toContain(\"users\");\n  });\n\n  it(\"should reject non-SELECT queries\", async () => {\n    const result = await client.callTool({\n      name: \"query\",\n      arguments: { sql: \"DROP TABLE users\" },\n    });\n    expect(result.isError).toBe(true);\n  });\n});\n```\n\n### Common Debugging Issues\n\n| Problem | Cause | Solution |\n|---------|-------|----------|\n| Server not appearing in Claude Desktop | Config path or JSON syntax error | Validate JSON, check absolute paths |\n| \"Tool not found\" errors | Server did not register tools before connect | Register tools before calling `server.connect()` |\n| Timeout on tool calls | Long-running operations without progress | Add progress notifications via `server.sendProgress()` |\n| stderr output breaking protocol | Console.log writes to stdout (stdio transport) | Use `console.error()` for logging with stdio |\n| Connection drops after idle | Transport timeout | Implement heartbeat or use HTTP transport |\n\n## Best Practices for Production\n\n1. **Input validation**: Always validate and sanitize tool inputs. Use Zod schemas (TypeScript) or Pydantic models (Python) for strict type checking.\n\n2. **Read-only by default**: Start with read-only access. Only add write capabilities when explicitly needed, and always require confirmation for destructive operations.\n\n3. **Error handling**: Return structured error messages with `isError: true`. Never expose internal stack traces or database connection strings.\n\n4. **Logging**: Log all tool invocations with timestamps, inputs, and execution duration. Use stderr for logs (not stdout) when using stdio transport.\n\n5. **Rate limiting**: Implement per-tool rate limits to prevent runaway AI loops from overwhelming your backend services.\n\n6. **Timeouts**: Set execution timeouts on all tool handlers. An AI model might call a tool that triggers an expensive query — protect your infrastructure.\n\n7. **Environment separation**: Use environment variables for all configuration. Never hardcode database URLs, API keys, or file paths.\n\n8. **Versioning**: Follow semantic versioning for your MCP server. The `initialize` handshake includes version negotiation — breaking changes require a major version bump.\n\n## FAQ\n\n**Q: What is the difference between MCP and function calling?**\nA: Function calling (used by OpenAI, Anthropic, and others) defines tools inline within each API request. MCP externalizes tool definitions into standalone servers that any MCP-compatible host can discover and use. Function calling is per-request; MCP is a persistent protocol with stateful sessions.\n\n**Q: Can I use MCP with models other than Claude?**\nA: Yes. MCP is an open protocol. OpenAI, Google DeepMind, and Microsoft have adopted MCP support in their platforms as of early 2026. Any AI application that implements an MCP client can connect to any MCP server.\n\n**Q: Is MCP only for local tools?**\nA: No. While stdio transport is designed for local servers, HTTP+SSE and Streamable HTTP transports support remote MCP servers. You can deploy MCP servers as cloud services accessible over the network.\n\n**Q: How does MCP handle authentication?**\nA: The protocol supports OAuth 2.0 for remote servers. Local stdio servers inherit the security context of the host process. For enterprise deployments, MCP gateways can centralize authentication and authorization.\n\n**Q: What languages can I build MCP servers in?**\nA: Official SDKs exist for TypeScript, Python, Java, Kotlin, C#, and Swift. Community SDKs cover Rust, Go, Ruby, and PHP. The protocol is language-agnostic — any language that can read\u002Fwrite JSON-RPC over stdio or HTTP can implement an MCP server.\n\n**Q: How do I update my MCP server without restarting the host?**\nA: MCP supports capability change notifications. When your server's tools change, it can send a `notifications\u002Ftools\u002Flist_changed` message, prompting the client to re-fetch the tool list. For stdio servers, the host typically needs a restart. HTTP servers can be updated without host restart.","\u003Ch2 id=\"what-is-the-model-context-protocol-mcp\">What Is the Model Context Protocol (MCP)?\u003C\u002Fh2>\n\u003Cp>The \u003Cstrong>Model Context Protocol (MCP)\u003C\u002Fstrong> is an open standard created by Anthropic that defines how AI applications communicate with external data sources and tools. Think of MCP as \u003Cstrong>USB-C for AI integration\u003C\u002Fstrong>: just as USB-C provides a single universal connector for charging, data transfer, and display output, MCP provides a single universal protocol for connecting AI models to databases, APIs, file systems, and any other service. MCP eliminates the need for custom integrations between every AI application and every tool.\u003C\u002Fp>\n\u003Cp>In 2026, MCP has become the dominant standard for AI-tool communication. Gartner predicts that \u003Cstrong>40% of enterprise applications will include AI agents by end of 2026\u003C\u002Fstrong>, and MCP is the protocol that makes this practical at scale. Before MCP, connecting an AI model to N tools required N custom integrations. With M AI applications and N tools, you needed M x N integration adapters. MCP reduces this to M + N: every AI app implements one MCP client, every tool implements one MCP server.\u003C\u002Fp>\n\u003Ch2 id=\"mcp-architecture-hosts-clients-servers-and-transports\">MCP Architecture: Hosts, Clients, Servers, and Transports\u003C\u002Fh2>\n\u003Cp>Understanding MCP architecture is essential before writing any code. The protocol defines four key roles:\u003C\u002Fp>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Component\u003C\u002Fth>\u003Cth>Role\u003C\u002Fth>\u003Cth>Example\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>\u003Cstrong>Host\u003C\u002Fstrong>\u003C\u002Ftd>\u003Ctd>The AI application that end users interact with\u003C\u002Ftd>\u003Ctd>Claude Desktop, Cursor, VS Code Copilot\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>\u003Cstrong>Client\u003C\u002Fstrong>\u003C\u002Ftd>\u003Ctd>The MCP protocol handler inside the host\u003C\u002Ftd>\u003Ctd>Built into the host application\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>\u003Cstrong>Server\u003C\u002Fstrong>\u003C\u002Ftd>\u003Ctd>Exposes tools, resources, and prompts via MCP\u003C\u002Ftd>\u003Ctd>Your custom server (what we will build)\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>\u003Cstrong>Transport\u003C\u002Fstrong>\u003C\u002Ftd>\u003Ctd>The communication layer between client and server\u003C\u002Ftd>\u003Ctd>stdio, HTTP+SSE, Streamable HTTP\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>The communication flow works like this:\u003C\u002Fp>\n\u003Col>\n\u003Cli>The \u003Cstrong>host\u003C\u002Fstrong> starts and initializes an MCP \u003Cstrong>client\u003C\u002Fstrong> for each configured server.\u003C\u002Fli>\n\u003Cli>The \u003Cstrong>client\u003C\u002Fstrong> connects to the \u003Cstrong>server\u003C\u002Fstrong> via a \u003Cstrong>transport\u003C\u002Fstrong> (stdio for local, HTTP for remote).\u003C\u002Fli>\n\u003Cli>The \u003Cstrong>client\u003C\u002Fstrong> sends an \u003Ccode>initialize\u003C\u002Fcode> request, negotiating protocol version and capabilities.\u003C\u002Fli>\n\u003Cli>The \u003Cstrong>server\u003C\u002Fstrong> responds with its available \u003Cstrong>tools\u003C\u002Fstrong>, \u003Cstrong>resources\u003C\u002Fstrong>, and \u003Cstrong>prompts\u003C\u002Fstrong>.\u003C\u002Fli>\n\u003Cli>When the AI model needs external data or actions, the \u003Cstrong>client\u003C\u002Fstrong> calls the appropriate server method.\u003C\u002Fli>\n\u003Cli>The \u003Cstrong>server\u003C\u002Fstrong> executes the operation and returns results via JSON-RPC 2.0 messages.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>Tools vs Resources vs Prompts\u003C\u002Fh3>\n\u003Cp>MCP servers can expose three types of capabilities:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Tools\u003C\u002Fstrong> are functions the AI model can call. They take structured input and return structured output. Example: \u003Ccode>query_database(sql: string)\u003C\u002Fcode> or \u003Ccode>send_email(to: string, subject: string, body: string)\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Resources\u003C\u002Fstrong> are data the AI model can read. They are identified by URIs and return content. Example: \u003Ccode>file:\u002F\u002F\u002Fpath\u002Fto\u002Fdocument.md\u003C\u002Fcode> or \u003Ccode>postgres:\u002F\u002Flocalhost\u002Fmydb\u002Fusers\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Prompts\u003C\u002Fstrong> are reusable prompt templates that the server provides. They help standardize how the AI interacts with the server’s domain. Example: a \u003Ccode>summarize_ticket\u003C\u002Fcode> prompt template for a Jira MCP server.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"step-by-step-building-an-mcp-server-in-typescript\">Step-by-Step: Building an MCP Server in TypeScript\u003C\u002Fh2>\n\u003Cp>Let us build a practical MCP server that provides a tool for querying a SQLite database. This is a common use case: giving an AI model safe, read-only access to your application data.\u003C\u002Fp>\n\u003Ch3>Step 1: Initialize the Project\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-bash\">mkdir mcp-sqlite-server &amp;&amp; cd mcp-sqlite-server\nnpm init -y\nnpm install @modelcontextprotocol\u002Fsdk better-sqlite3\nnpm install -D typescript @types\u002Fnode @types\u002Fbetter-sqlite3\nnpx tsc --init\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Configure \u003Ccode>tsconfig.json\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"Node16\",\n    \"moduleResolution\": \"Node16\",\n    \"outDir\": \".\u002Fdist\",\n    \"rootDir\": \".\u002Fsrc\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"declaration\": true\n  },\n  \"include\": [\"src\u002F**\u002F*\"]\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Step 2: Implement the Server\u003C\u002Fh3>\n\u003Cp>Create \u003Ccode>src\u002Findex.ts\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-typescript\">import { McpServer } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js\";\nimport Database from \"better-sqlite3\";\nimport { z } from \"zod\";\n\n\u002F\u002F Initialize the database\nconst db = new Database(process.env.DB_PATH || \".\u002Fdata.db\", {\n  readonly: true,\n});\n\n\u002F\u002F Create the MCP server\nconst server = new McpServer({\n  name: \"sqlite-query\",\n  version: \"1.0.0\",\n});\n\n\u002F\u002F Register a tool for querying the database\nserver.tool(\n  \"query\",\n  \"Execute a read-only SQL query against the SQLite database\",\n  {\n    sql: z.string().describe(\"The SQL SELECT query to execute\"),\n  },\n  async ({ sql }) =&gt; {\n    \u002F\u002F Security: only allow SELECT statements\n    const normalized = sql.trim().toUpperCase();\n    if (!normalized.startsWith(\"SELECT\")) {\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: \"Error: Only SELECT queries are allowed.\",\n          },\n        ],\n        isError: true,\n      };\n    }\n\n    try {\n      const rows = db.prepare(sql).all();\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(rows, null, 2),\n          },\n        ],\n      };\n    } catch (error) {\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: `Query error: ${(error as Error).message}`,\n          },\n        ],\n        isError: true,\n      };\n    }\n  }\n);\n\n\u002F\u002F Register a tool to list all tables\nserver.tool(\n  \"list_tables\",\n  \"List all tables in the database with their schemas\",\n  {},\n  async () =&gt; {\n    const tables = db\n      .prepare(\n        `SELECT name, sql FROM sqlite_master\n         WHERE type='table' AND name NOT LIKE 'sqlite_%'\n         ORDER BY name`\n      )\n      .all();\n\n    return {\n      content: [\n        {\n          type: \"text\",\n          text: JSON.stringify(tables, null, 2),\n        },\n      ],\n    };\n  }\n);\n\n\u002F\u002F Register a resource for database schema\nserver.resource(\n  \"schema\",\n  \"sqlite:\u002F\u002Fschema\",\n  async (uri) =&gt; {\n    const tables = db\n      .prepare(\n        `SELECT name, sql FROM sqlite_master\n         WHERE type='table' AND name NOT LIKE 'sqlite_%'`\n      )\n      .all();\n\n    return {\n      contents: [\n        {\n          uri: uri.href,\n          mimeType: \"application\u002Fjson\",\n          text: JSON.stringify(tables, null, 2),\n        },\n      ],\n    };\n  }\n);\n\n\u002F\u002F Start the server with stdio transport\nasync function main() {\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n  console.error(\"SQLite MCP server running on stdio\");\n}\n\nmain().catch(console.error);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Step 3: Build and Test Locally\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-bash\">npx tsc\necho '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0\"}}}' | node dist\u002Findex.js\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>You should see a JSON-RPC response with the server’s capabilities.\u003C\u002Fp>\n\u003Ch3>Step 4: Build the Same Server in Python\u003C\u002Fh3>\n\u003Cp>For Python developers, here is the equivalent using the official MCP Python SDK:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\"># server.py\nimport sqlite3\nimport json\nfrom mcp.server.fastmcp import FastMCP\n\nmcp = FastMCP(\"sqlite-query\")\n\nDB_PATH = \"data.db\"\n\n@mcp.tool()\ndef query(sql: str) -&gt; str:\n    \"\"\"Execute a read-only SQL query against the SQLite database.\"\"\"\n    normalized = sql.strip().upper()\n    if not normalized.startswith(\"SELECT\"):\n        raise ValueError(\"Only SELECT queries are allowed.\")\n\n    conn = sqlite3.connect(DB_PATH)\n    conn.row_factory = sqlite3.Row\n    try:\n        cursor = conn.execute(sql)\n        rows = [dict(row) for row in cursor.fetchall()]\n        return json.dumps(rows, indent=2, default=str)\n    finally:\n        conn.close()\n\n@mcp.tool()\ndef list_tables() -&gt; str:\n    \"\"\"List all tables in the database with their schemas.\"\"\"\n    conn = sqlite3.connect(DB_PATH)\n    conn.row_factory = sqlite3.Row\n    try:\n        cursor = conn.execute(\n            \"SELECT name, sql FROM sqlite_master \"\n            \"WHERE type='table' AND name NOT LIKE 'sqlite_%'\"\n        )\n        tables = [dict(row) for row in cursor.fetchall()]\n        return json.dumps(tables, indent=2)\n    finally:\n        conn.close()\n\n@mcp.resource(\"sqlite:\u002F\u002Fschema\")\ndef get_schema() -&gt; str:\n    \"\"\"Return the database schema as a resource.\"\"\"\n    conn = sqlite3.connect(DB_PATH)\n    conn.row_factory = sqlite3.Row\n    try:\n        cursor = conn.execute(\n            \"SELECT name, sql FROM sqlite_master WHERE type='table'\"\n        )\n        return json.dumps([dict(r) for r in cursor.fetchall()], indent=2)\n    finally:\n        conn.close()\n\nif __name__ == \"__main__\":\n    mcp.run(transport=\"stdio\")\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Install dependencies and run:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">pip install mcp[cli]\npython server.py\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"connecting-to-claude-desktop\">Connecting to Claude Desktop\u003C\u002Fh2>\n\u003Cp>Claude Desktop natively supports MCP servers. To connect your server, edit the Claude Desktop configuration file:\u003C\u002Fp>\n\u003Cp>\u003Cstrong>macOS:\u003C\u002Fstrong> \u003Ccode>~\u002FLibrary\u002FApplication Support\u002FClaude\u002Fclaude_desktop_config.json\u003C\u002Fcode>\n\u003Cstrong>Windows:\u003C\u002Fstrong> \u003Ccode>%APPDATA%\\Claude\\claude_desktop_config.json\u003C\u002Fcode>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  \"mcpServers\": {\n    \"sqlite-query\": {\n      \"command\": \"node\",\n      \"args\": [\"\u002Fabsolute\u002Fpath\u002Fto\u002Fdist\u002Findex.js\"],\n      \"env\": {\n        \"DB_PATH\": \"\u002Fabsolute\u002Fpath\u002Fto\u002Fyour\u002Fdata.db\"\n      }\n    }\n  }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>For the Python version:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  \"mcpServers\": {\n    \"sqlite-query\": {\n      \"command\": \"python\",\n      \"args\": [\"\u002Fabsolute\u002Fpath\u002Fto\u002Fserver.py\"]\n    }\n  }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Restart Claude Desktop. You should see a hammer icon in the chat interface indicating available MCP tools. Now you can ask Claude questions like “What tables are in the database?” or “Show me the top 10 users by signup date” and it will use your MCP server to query the database directly.\u003C\u002Fp>\n\u003Ch2 id=\"connecting-to-cursor\">Connecting to Cursor\u003C\u002Fh2>\n\u003Cp>Cursor also supports MCP servers. Add configuration to \u003Ccode>.cursor\u002Fmcp.json\u003C\u002Fcode> in your project root:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  \"mcpServers\": {\n    \"sqlite-query\": {\n      \"command\": \"node\",\n      \"args\": [\".\u002Fdist\u002Findex.js\"],\n      \"env\": {\n        \"DB_PATH\": \".\u002Fdata.db\"\n      }\n    }\n  }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>After saving, restart Cursor. The MCP tools appear in the AI assistant panel and can be invoked during code generation and debugging sessions.\u003C\u002Fp>\n\u003Ch2 id=\"testing-and-debugging-your-mcp-server\">Testing and Debugging Your MCP Server\u003C\u002Fh2>\n\u003Ch3>Using the MCP Inspector\u003C\u002Fh3>\n\u003Cp>The MCP Inspector is the official debugging tool. It provides a web UI for interacting with your server:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">npx @modelcontextprotocol\u002Finspector node dist\u002Findex.js\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This opens a browser interface at \u003Ccode>http:\u002F\u002Flocalhost:5173\u003C\u002Fcode> where you can:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>View all registered tools, resources, and prompts\u003C\u002Fli>\n\u003Cli>Call tools with custom inputs and inspect responses\u003C\u002Fli>\n\u003Cli>Monitor the JSON-RPC message stream in real time\u003C\u002Fli>\n\u003Cli>Test error handling by sending malformed requests\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Unit Testing with the SDK\u003C\u002Fh3>\n\u003Cp>Write automated tests using the in-memory transport:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-typescript\">import { McpServer } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol\u002Fsdk\u002FinMemory.js\";\nimport { Client } from \"@modelcontextprotocol\u002Fsdk\u002Fclient\u002Findex.js\";\n\ndescribe(\"SQLite MCP Server\", () =&gt; {\n  let client: Client;\n\n  beforeEach(async () =&gt; {\n    const [clientTransport, serverTransport] =\n      InMemoryTransport.createLinkedPair();\n    const server = createServer(); \u002F\u002F your server factory\n    await server.connect(serverTransport);\n    client = new Client({ name: \"test\", version: \"1.0\" });\n    await client.connect(clientTransport);\n  });\n\n  it(\"should list tables\", async () =&gt; {\n    const result = await client.callTool({\n      name: \"list_tables\",\n      arguments: {},\n    });\n    expect(result.content[0].text).toContain(\"users\");\n  });\n\n  it(\"should reject non-SELECT queries\", async () =&gt; {\n    const result = await client.callTool({\n      name: \"query\",\n      arguments: { sql: \"DROP TABLE users\" },\n    });\n    expect(result.isError).toBe(true);\n  });\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Common Debugging Issues\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Problem\u003C\u002Fth>\u003Cth>Cause\u003C\u002Fth>\u003Cth>Solution\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Server not appearing in Claude Desktop\u003C\u002Ftd>\u003Ctd>Config path or JSON syntax error\u003C\u002Ftd>\u003Ctd>Validate JSON, check absolute paths\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>“Tool not found” errors\u003C\u002Ftd>\u003Ctd>Server did not register tools before connect\u003C\u002Ftd>\u003Ctd>Register tools before calling \u003Ccode>server.connect()\u003C\u002Fcode>\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Timeout on tool calls\u003C\u002Ftd>\u003Ctd>Long-running operations without progress\u003C\u002Ftd>\u003Ctd>Add progress notifications via \u003Ccode>server.sendProgress()\u003C\u002Fcode>\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>stderr output breaking protocol\u003C\u002Ftd>\u003Ctd>Console.log writes to stdout (stdio transport)\u003C\u002Ftd>\u003Ctd>Use \u003Ccode>console.error()\u003C\u002Fcode> for logging with stdio\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Connection drops after idle\u003C\u002Ftd>\u003Ctd>Transport timeout\u003C\u002Ftd>\u003Ctd>Implement heartbeat or use HTTP transport\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Ch2 id=\"best-practices-for-production\">Best Practices for Production\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>Input validation\u003C\u002Fstrong>: Always validate and sanitize tool inputs. Use Zod schemas (TypeScript) or Pydantic models (Python) for strict type checking.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Read-only by default\u003C\u002Fstrong>: Start with read-only access. Only add write capabilities when explicitly needed, and always require confirmation for destructive operations.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Error handling\u003C\u002Fstrong>: Return structured error messages with \u003Ccode>isError: true\u003C\u002Fcode>. Never expose internal stack traces or database connection strings.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Logging\u003C\u002Fstrong>: Log all tool invocations with timestamps, inputs, and execution duration. Use stderr for logs (not stdout) when using stdio transport.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Rate limiting\u003C\u002Fstrong>: Implement per-tool rate limits to prevent runaway AI loops from overwhelming your backend services.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Timeouts\u003C\u002Fstrong>: Set execution timeouts on all tool handlers. An AI model might call a tool that triggers an expensive query — protect your infrastructure.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Environment separation\u003C\u002Fstrong>: Use environment variables for all configuration. Never hardcode database URLs, API keys, or file paths.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Versioning\u003C\u002Fstrong>: Follow semantic versioning for your MCP server. The \u003Ccode>initialize\u003C\u002Fcode> handshake includes version negotiation — breaking changes require a major version bump.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"faq\">FAQ\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>Q: What is the difference between MCP and function calling?\u003C\u002Fstrong>\nA: Function calling (used by OpenAI, Anthropic, and others) defines tools inline within each API request. MCP externalizes tool definitions into standalone servers that any MCP-compatible host can discover and use. Function calling is per-request; MCP is a persistent protocol with stateful sessions.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Q: Can I use MCP with models other than Claude?\u003C\u002Fstrong>\nA: Yes. MCP is an open protocol. OpenAI, Google DeepMind, and Microsoft have adopted MCP support in their platforms as of early 2026. Any AI application that implements an MCP client can connect to any MCP server.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Q: Is MCP only for local tools?\u003C\u002Fstrong>\nA: No. While stdio transport is designed for local servers, HTTP+SSE and Streamable HTTP transports support remote MCP servers. You can deploy MCP servers as cloud services accessible over the network.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Q: How does MCP handle authentication?\u003C\u002Fstrong>\nA: The protocol supports OAuth 2.0 for remote servers. Local stdio servers inherit the security context of the host process. For enterprise deployments, MCP gateways can centralize authentication and authorization.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Q: What languages can I build MCP servers in?\u003C\u002Fstrong>\nA: Official SDKs exist for TypeScript, Python, Java, Kotlin, C#, and Swift. Community SDKs cover Rust, Go, Ruby, and PHP. The protocol is language-agnostic — any language that can read\u002Fwrite JSON-RPC over stdio or HTTP can implement an MCP server.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Q: How do I update my MCP server without restarting the host?\u003C\u002Fstrong>\nA: MCP supports capability change notifications. When your server’s tools change, it can send a \u003Ccode>notifications\u002Ftools\u002Flist_changed\u003C\u002Fcode> message, prompting the client to re-fetch the tool list. For stdio servers, the host typically needs a restart. HTTP servers can be updated without host restart.\u003C\u002Fp>\n","en","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:33.206965Z","Step-by-step tutorial for building an MCP server in TypeScript and Python. Connect to Claude Desktop or Cursor, test with MCP Inspector, and deploy to production.","mcp server tutorial",null,"index, follow",[21,26],{"id":22,"name":23,"slug":24,"created_at":25},"c0000000-0000-0000-0000-000000000008","AI","ai","2026-03-28T10:44:21.513630Z",{"id":27,"name":28,"slug":29,"created_at":25},"c0000000-0000-0000-0000-000000000002","TypeScript","typescript","Engineering",[32,38,44],{"id":33,"title":34,"slug":35,"excerpt":36,"locale":12,"category_name":30,"published_at":37},"d0200000-0000-0000-0000-000000000003","Why Bali Is Becoming Southeast Asia's Impact-Tech Hub in 2026","why-bali-becoming-southeast-asia-impact-tech-hub-2026","Bali ranks #16 among Southeast Asian startup ecosystems. With a growing concentration of Web3 builders, AI sustainability startups, and eco-travel tech companies, the island is carving a niche as the region's impact-tech capital.","2026-03-28T10:44:37.748283Z",{"id":39,"title":40,"slug":41,"excerpt":42,"locale":12,"category_name":30,"published_at":43},"d0200000-0000-0000-0000-000000000002","ASEAN Data Protection Patchwork: A Developer's Compliance Checklist","asean-data-protection-patchwork-developer-compliance-checklist","Seven ASEAN countries now have comprehensive data protection laws, each with different consent models, localization requirements, and penalty structures. Here is a practical compliance checklist for developers building multi-country applications.","2026-03-28T10:44:37.374741Z",{"id":45,"title":46,"slug":47,"excerpt":48,"locale":12,"category_name":30,"published_at":49},"d0200000-0000-0000-0000-000000000001","Indonesia's $29 Billion Digital Transformation: Opportunities for Software Companies","indonesia-29-billion-digital-transformation-opportunities-software-companies","Indonesia's IT services market is projected to reach $29.03 billion in 2026, up from $24.37 billion in 2025. Cloud infrastructure, AI, e-commerce, and data centers are driving the fastest growth in Southeast Asia.","2026-03-28T10:44:37.349311Z",{"id":13,"name":51,"slug":52,"bio":53,"photo_url":18,"linkedin":18,"role":54,"created_at":55,"updated_at":55},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]