MCP HubMCP Hub
punkpeye

fastmcp

by: punkpeye

A TypeScript framework for building MCP servers.

1192created 27/12/2024
Visit
TypeScript
framework

📌Overview

Purpose: FastMCP is a TypeScript framework for building MCP servers that effectively manage client sessions.

Overview: FastMCP simplifies the development of MCP servers by providing an intuitive interface for defining tools, managing sessions, and handling client interactions via various transport methods, including SSE. It is designed to facilitate seamless communication between clients and servers.

Key Features:

  • Simple Tool and Resource Definition: Easily create and manage executable functions and resources that clients can access.

  • Authentication: Implement custom client authentication mechanisms to safeguard server interactions.

  • Sessions Management: Supports dedicated sessions for each client, enabling personalized communication and state management.

  • SSE and Real-time Communication: Built-in support for Server-Sent Events (SSE) to facilitate real-time updates between server and clients.

  • Progress Notifications and Error Handling: Features like progress reporting and user-friendly error handling enhance user experience and debugging.


FastMCP

A TypeScript framework for building MCP servers capable of handling client sessions.

For a Python implementation, see FastMCP.

Features

  • Simple Tool, Resource, Prompt definition
  • Authentication
  • Sessions
  • Image content
  • Audio content
  • Logging
  • Error handling
  • SSE
  • CORS (enabled by default)
  • Progress notifications
  • Typed server events
  • Prompt argument auto-completion
  • Sampling
  • Automated SSE pings
  • Roots
  • CLI for testing and debugging

Installation

npm install fastmcp

Quickstart

There are many real-world examples of using FastMCP in the wild. See the Showcase for examples.

import { FastMCP } from "fastmcp";
import { z } from "zod"; // Or any validation library that supports Standard Schema

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  name: "add",
  description: "Add two numbers",
  parameters: z.object({
    a: z.number(),
    b: z.number(),
  }),
  execute: async (args) => {
    return String(args.a + args.b);
  },
});

server.start({
  transportType: "stdio",
});

You can test the server in terminal with:

git clone https://github.com/punkpeye/fastmcp.git
cd fastmcp

pnpm install
pnpm build

# Test the addition server example using CLI:
npx fastmcp dev src/examples/addition.ts
# Test the addition server example using MCP Inspector:
npx fastmcp inspect src/examples/addition.ts

SSE

Server-Sent Events (SSE) provide a mechanism for servers to send real-time updates to clients over HTTPS. In MCP, SSE is used to enable remote MCP communication over the network.

Run the server with SSE support:

server.start({
  transportType: "sse",
  sse: {
    endpoint: "/sse",
    port: 8080,
  },
});

This starts the server listening for SSE connections at http://localhost:8080/sse.

You can connect to the server using SSEClientTransport:

import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));

await client.connect(transport);

Core Concepts

Tools

Tools in MCP allow servers to expose executable functions that clients and LLMs can use.

FastMCP supports the Standard Schema specification for defining tool parameters. Any schema validation library implementing this spec (like Zod, ArkType, or Valibot) can be used.

Zod Example:

import { z } from "zod";

server.addTool({
  name: "fetch-zod",
  description: "Fetch the content of a url (using Zod)",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

ArkType Example:

import { type } from "arktype";

server.addTool({
  name: "fetch-arktype",
  description: "Fetch the content of a url (using ArkType)",
  parameters: type({
    url: "string",
  }),
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

Valibot Example:

import * as v from "valibot";

server.addTool({
  name: "fetch-valibot",
  description: "Fetch the content of a url (using Valibot)",
  parameters: v.object({
    url: v.string(),
  }),
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

Returning a string

execute methods can return simple strings:

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return "Hello, world!";
  },
});

Equivalent to:

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "text",
          text: "Hello, world!",
        },
      ],
    };
  },
});

Returning a list

Return multiple messages as a list in an object with a content property:

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        { type: "text", text: "First message" },
        { type: "text", text: "Second message" },
      ],
    };
  },
});

Returning an image

Use the imageContent helper to return images:

import { imageContent } from "fastmcp";

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return imageContent({
      url: "https://example.com/image.png",
    });
  },
});

Options for imageContent include: url, path, or buffer. Only one should be specified.

Equivalent to:

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "image",
          data: "base64-encoded-image-data",
          mimeType: "image/png",
        },
      ],
    };
  },
});

Returning an audio

Use audioContent similarly for audio files:

import { audioContent } from "fastmcp";

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return audioContent({
      url: "https://example.com/audio.mp3",
    });
  },
});

Options include url, path, or buffer.

Equivalent to:

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "audio",
          data: "base64-encoded-audio-data",
          mimeType: "audio/mpeg",
        },
      ],
    };
  },
});

Logging

Tools can log messages to the client using a log object in the execution context:

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args, { log }) => {
    log.info("Downloading file...", { url: args.url });

    // ...

    log.info("Downloaded file");

    return "done";
  },
});

Available log methods:

  • debug(message: string, data?)
  • error(message: string, data?)
  • info(message: string, data?)
  • warn(message: string, data?)

Errors

Throw errors meant for user display as UserError instances:

import { UserError } from "fastmcp";

server.addTool({
  name: "download",
  parameters: z.object({ url: z.string() }),
  execute: async (args) => {
    if (args.url.startsWith("https://example.com")) {
      throw new UserError("This URL is not allowed");
    }
    return "done";
  },
});

Progress

Report progress via the reportProgress function in context:

server.addTool({
  name: "download",
  parameters: z.object({ url: z.string() }),
  execute: async (args, { reportProgress }) => {
    reportProgress({ progress: 0, total: 100 });

    // ...

    reportProgress({ progress: 100, total: 100 });

    return "done";
  },
});

Resources

Resources represent data the MCP server exposes to clients, such as files, images, or logs. Each has a unique URI and contains text or binary data.

Example:

server.addResource({
  uri: "file:///logs/app.log",
  name: "Application Logs",
  mimeType: "text/plain",
  async load() {
    return {
      text: await readLogFile(),
    };
  },
});

load() can return multiple resources (e.g., multiple files):

async load() {
  return [
    { text: "First file content" },
    { text: "Second file content" },
  ];
}

Binary data can be returned as a base64-encoded blob:

async load() {
  return {
    blob: 'base64-encoded-data'
  };
}

Resource templates

Define resource templates with URI patterns and arguments:

server.addResourceTemplate({
  uriTemplate: "file:///logs/{name}.log",
  name: "Application Logs",
  mimeType: "text/plain",
  arguments: [
    {
      name: "name",
      description: "Name of the log",
      required: true,
    },
  ],
  async load({ name }) {
    return {
      text: `Example log content for ${name}`,
    };
  },
});

Argument auto-completion

Provide complete functions for arguments to enable auto-completion:

server.addResourceTemplate({
  uriTemplate: "file:///logs/{name}.log",
  name: "Application Logs",
  mimeType: "text/plain",
  arguments: [
    {
      name: "name",
      description: "Name of the log",
      required: true,
      complete: async (value) => {
        if (value === "Example") {
          return { values: ["Example Log"] };
        }
        return { values: [] };
      },
    },
  ],
  async load({ name }) {
    return {
      text: `Example log content for ${name}`,
    };
  },
});

Prompts

Prompts define reusable prompt templates and workflows for clients and LLMs.

Example:

server.addPrompt({
  name: "git-commit",
  description: "Generate a Git commit message",
  arguments: [
    {
      name: "changes",
      description: "Git diff or description of changes",
      required: true,
    },
  ],
  load: async (args) => {
    return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
  },
});

Prompt argument auto-completion

Prompts support argument auto-completion with complete functions:

server.addPrompt({
  name: "countryPoem",
  description: "Writes a poem about a country",
  load: async ({ name }) => {
    return `Hello, ${name}!`;
  },
  arguments: [
    {
      name: "name",
      description: "Name of the country",
      required: true,
      complete: async (value) => {
        if (value === "Germ") {
          return { values: ["Germany"] };
        }
        return { values: [] };
      },
    },
  ],
});

Auto-completion using enum

Provide an enum array to enable automatic completions:

server.addPrompt({
  name: "countryPoem",
  description: "Writes a poem about a country",
  load: async ({ name }) => {
    return `Hello, ${name}!`;
  },
  arguments: [
    {
      name: "name",
      description: "Name of the country",
      required: true,
      enum: ["Germany", "France", "Italy"],
    },
  ],
});

Authentication

FastMCP supports client authentication with a custom function:

import { AuthError } from "fastmcp";

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  authenticate: ({request}) => {
    const apiKey = request.headers["x-api-key"];

    if (apiKey !== '123') {
      throw new Response(null, {
        status: 401,
        statusText: "Unauthorized",
      });
    }

    // This returned object is accessible in `context.session`.
    return {
      id: 1,
    };
  },
});

Access session data in tools:

server.addTool({
  name: "sayHello",
  execute: async (args, { session }) => {
    return `Hello, ${session.id}!`;
  },
});

Sessions

The session object represents active client sessions (FastMCPSession) with one server instance allocated per client connection.

server.sessions;

Typed server events

Listen to server events with on:

server.on("connect", (event) => {
  console.log("Client connected:", event.session);
});

server.on("disconnect", (event) => {
  console.log("Client disconnected:", event.session);
});

FastMCPSession

FastMCPSession represents a client session and provides methods to interact with the client.

requestSampling

Create a sampling request and receive the response:

await session.requestSampling({
  messages: [
    {
      role: "user",
      content: {
        type: "text",
        text: "What files are in the current directory?",
      },
    },
  ],
  systemPrompt: "You are a helpful file system assistant.",
  includeContext: "thisServer",
  maxTokens: 100,
});

Properties

  • clientCapabilities: The client capabilities.
  • loggingLevel: Logging level set by client.
  • roots: Roots set by the client.
  • server: MCP server instance associated with the session.

Typed session events

Listen to session events:

session.on("rootsChanged", (event) => {
  console.log("Roots changed:", event.roots);
});

session.on("error", (event) => {
  console.error("Error:", event.error);
});

Running Your Server

Test with mcp-cli

Test and debug your server easily with:

npx fastmcp dev server.js
npx fastmcp dev server.ts

This uses mcp-cli in the terminal.

Inspect with MCP Inspector

Inspect your server via the web UI:

npx fastmcp inspect server.ts

FAQ

How to use with Claude Desktop?

Add the following configuration:

{
  "mcpServers": {
    "my-mcp-server": {
      "command": "npx",
      "args": [
        "tsx",
        "/PATH/TO/YOUR_PROJECT/src/index.ts"
      ],
      "env": {
        "YOUR_ENV_VAR": "value"
      }
    }
  }
}

Showcase

If you've developed a server using FastMCP, please submit a PR to showcase it!

Acknowledgements

  • Inspired by the Python implementation by Jonathan Lowin
  • Codebase parts adopted from LiteMCP
  • Codebase parts adopted from Model Context protocol SSE exploration article