# How to Create an MCP Server with Node.js and TypeScript

The Node.js SDK for MCP enables JavaScript and TypeScript developers to build tools and resources that works alongside Claude and other LLMs. This guide walks you through setting up a project, creating your first tool, and testing your server.

Source: https://fast.io/resources/mcp-server-nodejs/
Last reviewed: 2026-02-09

## What is an MCP Server?

A Model Context Protocol (MCP) server connects Large Language Models (LLMs) like Claude to external systems. Without MCP, integrating AI into your workflow means copying and pasting data into a chat window. An MCP server removes that step, letting the AI query databases, read files, or run code directly within a controlled environment. This solves the "context window" problem. Instead of stuffing everything into a prompt, the AI discovers and pulls in specific information when it needs it. The MCP specification defines three server capabilities:
*   **Resources**: Passive data sources that the AI can read. Think of them as specialized files or database rows that provide background for a task.
*   **Tools**: Executable functions. They let the AI perform actions like sending an email, making an API call, or running a calculation.
*   **Prompts**: Pre-defined templates that shape how the AI interacts with the user or the data. The official TypeScript SDK gives Node.js developers full type safety over the JSON-RPC messages exchanged between client and server. If you need to wire up custom AI tools to existing enterprise systems or npm packages, it's a strong starting point.

Helpful references: [Fastio Workspaces](/product/workspaces/), [Fastio Collaboration](/product/collaboration/), and [Fastio AI](/product/ai/).

## Step 1: Project Setup

You'll need Node.js v18 or higher installed. We'll use TypeScript because the MCP protocol relies on structured data, and TypeScript catches schema mismatches at compile time instead of at runtime. First, create a new directory for your project and initialize it with a `package.json` file:

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

Next, install the dependencies. `@modelcontextprotocol/sdk` handles the protocol, and `zod` handles runtime schema validation for your tool inputs:

```bash
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
```

`zod` matters here because TypeScript types disappear at runtime. LLMs can occasionally send malformed JSON or hallucinated arguments, and `zod` catches that before your function runs. It verifies that the arguments the AI provides (`a` and `b` in our calculator) are actually numbers. If they're not, your server returns a clear error to the model instead of crashing. Create a `tsconfig.json` in your project root. The MCP SDK requires ESM, so the module settings matter here:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true.
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
```

## Step 2: Building Your First Tool

Let's create a server that exposes a tool. We'll build a basic calculator the AI can call. Create `src/index.ts`:

```typescript
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";

// Initialize the server
const server = new Server(
  {
    name: "example-calculator",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "calculate_sum",
        description: "Add two numbers together",
        inputSchema: {
          type: "object",
          properties: {
            a: { type: "number" },
            b: { type: "number" },
          },
          required: ["a", "b"],
        },
      },
    ],
  };
});

// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "calculate_sum") {
    const { a, b } = request.params.arguments as { a: number; b: number };
    return {
      content: [
        {
          type: "text",
          text: String(a + b),
        },
      ],
    };
  }
  throw new Error("Tool not found");
});

// Start the server using stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
```

Here's what each piece does:

*   **`ListToolsRequestSchema`**: This handler tells the AI what tools are available. The `inputSchema` is the most important part because it's the documentation the AI reads to figure out how to call the tool. Better descriptions and schemas mean fewer bad calls.
*   **`CallToolRequestSchema`**: This is the execution engine. When the AI decides to call a tool, this handler receives the request, parses the arguments, and runs your logic.
*   **Stdio Transport**: We use standard input/output for the connection. This is the simplest setup: no network configuration needed. The client spawns your process and talks to it through command line pipes.

## Step 3: Testing with Claude Desktop

To test your server, configure Claude Desktop to run the script. Open your Claude config file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS) and add the server:

```json
{
  "mcpServers": {
    "my-calculator": {
      "command": "npx",
      "args": ["-y", "tsx", "/absolute/path/to/my-mcp-server/src/index.ts"]
    }
  }
}
```

Restart Claude Desktop. You should now see a 🔌 icon indicating the server is connected. Try asking Claude: "Can you use the calculator tool to add 50 and 100?" It should invoke your code and return "150". Consider how this fits into your broader workflow and what matters most for your team. The right choice depends on your specific requirements: file types, team size, security needs, and how you collaborate with external partners. Testing with a free account is the fast way to know if a tool works for you.

## Why Build vs. Buy?

Building your own MCP server makes sense for custom internal logic or proprietary APIs, which is exactly what the walkthrough above is for. For standard file operations and cloud storage, though, maintaining your own server is work you probably do not need to take on.

**Fastio runs a managed MCP server** covering file management, search, and storage through a consolidated MCP toolset. *   **Nothing to run**: the server is remote and hosted, so there is no boilerplate, no `stdio` wiring, and no deployment of your own. Your client config carries a `url`, not a `command`. *   **Persistent storage**: files an agent writes survive the session that created them, instead of vanishing with the process. *   **Intelligence**: when enabled, files are indexed automatically for semantic search and citation-backed chat. *   **Works with any MCP client**: Claude, Cursor, Cline, OpenClaw, and anything else that speaks the protocol. Connecting it takes a URL and a scoped key, which is covered step by step in the [Fastio MCP server integration guide](/resources/fastio-mcp-server-integration-developers/).

## Advanced: Serving over HTTP

`stdio` works well for local desktop apps, but cloud-hosted agents need a different transport. The Node.js SDK also supports Server-Sent Events (SSE) over HTTP. To switch, replace `StdioServerTransport` with `SSEServerTransport` and wrap it in an Express or Fastify server. Remote agents can then connect to your tools over the web. The way SSE works: the server pushes messages to the client over a persistent HTTP connection, and the client sends requests back via standard HTTP POST. You get real-time updates without the complexity of full WebSockets, and it's firewall-friendly enough to deploy on serverless platforms. Fastio's managed MCP server uses this same architecture for streamable connections to your file storage from anywhere.

## Frequently asked questions

### What is the best language for building MCP servers?

TypeScript is a strong choice for MCP servers because its type safety maps directly to the JSON-RPC protocol schemas, catching errors before runtime. Python is another popular option, especially for data science tools.

### Can I run an MCP server on a hosting provider?

Yes, you can deploy MCP servers to any platform that supports Node.js or Docker, such as Railway, Render, or AWS. You will need to use the SSE (Server-Sent Events) transport layer instead of stdio for remote connections.

### Is Fastio's MCP server compatible with custom servers?

Yes. MCP is designed to be modular, so you can run Fastio's server for file storage and RAG alongside your custom Node.js server for business logic. Clients connect to both at the same time.

## About Fast.io

Fast.io provides shared workspaces where people and AI agents work on the same files, with built-in semantic search and citation-backed chat over what they hold. Agents reach it through a remote MCP server at https://mcp.fast.io/mcp, a REST API at https://api.fast.io/current/, and a command line client published on npm as @vividengine/fastio-cli.
