How to Build an MCP Server for Cursor: Complete Tutorial
Want to extend Cursor AI with custom capabilities? The Model Context Protocol (MCP) lets you connect Cursor to any data source or tool you can imagine. This tutorial walks you through building your...
How to Build an MCP Server for Cursor: Complete Tutorial
Want to extend Cursor AI with custom capabilities? The Model Context Protocol (MCP) lets you connect Cursor to any data source or tool you can imagine. This tutorial walks you through building your first MCP server from scratch using TypeScript.
By the end, you'll have a working MCP server that adds new tools to Cursor's Agent mode—no prior MCP experience required.
We'll create an MCP server that gives Cursor the ability to fetch and analyze any webpage, search the web, and remember context across sessions. These are practical tools you'll actually use daily.
What is the Model Context Protocol?
MCP is an open standard that connects AI assistants like Cursor to external tools and data. Think of it as a plugin system—instead of building custom integrations for each AI application, you build one MCP server that works everywhere MCP is supported.
Key concepts:- MCP Servers expose tools, resources, and prompts to AI applications
- MCP Clients (like Cursor) discover and use these capabilities
- Transport handles communication between server and client (stdio or HTTP)
---
Prerequisites
Before starting, make sure you have:
- Node.js 18+ installed
- Cursor IDE (latest version with MCP support)
- Basic TypeScript knowledge (we'll explain MCP-specific parts)
MCP tools only work in Cursor's Agent mode, not Ask mode. Make sure you're using Agent mode when testing.
---
Part 1: Setting Up Your MCP Server Project
``bash
mkdir my-cursor-mcp && cd my-cursor-mcp
npm init -y
`
`bash
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
`
The MCP SDK provides server infrastructure, and Zod handles input validation.
Create tsconfig.json:
`json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"include": ["src/*/"]
}
`
Add these fields to your package.json:
`json
{
"type": "module",
"bin": {
"my-cursor-mcp": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
`
---
Part 2: Building the Server
Create src/index.ts with the basic MCP server structure:
typescript
#!/usr/bin/env node
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";
// Create the MCP server
const server = new Server(
{
name: "my-cursor-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Define your tools
const TOOLS = [
{
name: "fetch_webpage",
description: "Fetches and extracts text content from a webpage URL",
inputSchema: {
type: "object" as const,
properties: {
url: {
type: "string",
description: "The URL to fetch",
},
},
required: ["url"],
},
},
{
name: "remember",
description: "Stores a piece of information for later recall",
inputSchema: {
type: "object" as const,
properties: {
key: {
type: "string",
description: "A unique key to identify this memory",
},
value: {
type: "string",
description: "The information to remember",
},
},
required: ["key", "value"],
},
},
{
name: "recall",
description: "Retrieves previously stored information",
inputSchema: {
type: "object" as const,
properties: {
key: {
type: "string",
description: "The key to look up",
},
},
required: ["key"],
},
},
];
// Simple in-memory storage for the remember/recall tools
const memory = new Map();
// Handle list_tools requests
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: TOOLS };
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "fetch_webpage": {
const url = (args as { url: string }).url;
try {
const response = await fetch(url);
const html = await response.text();
// Basic HTML to text extraction
const text = html
.replace(/