fastmcp

SKILL.md

FastMCP Development Skill

FastMCP is a TypeScript framework for building Model Context Protocol (MCP) servers. This skill provides patterns for tools, resources, prompts, authentication, and transport configuration.

Installation

npm install fastmcp

Basic Server Setup

import { FastMCP } from "fastmcp";
import { z } from "zod";

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

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

Tools

Basic Tool with Zod Schema

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);
  },
});

Tool Without Parameters

server.addTool({
  name: "sayHello",
  description: "Say hello",
  execute: async () => {
    return "Hello, world!";
  },
});

Tool with Logging

server.addTool({
  name: "download",
  parameters: z.object({ url: z.string() }),
  execute: async (args, { log }) => {
    log.info("Downloading file...", { url: args.url });
    // ... process ...
    log.info("Downloaded file");
    return "done";
  },
});

Tool with Progress Reporting

server.addTool({
  name: "download",
  execute: async (args, { reportProgress }) => {
    await reportProgress({ progress: 0, total: 100 });
    // ... process ...
    await reportProgress({ progress: 100, total: 100 });
    return "done";
  },
});

Tool with Streaming Output

server.addTool({
  name: "generateText",
  parameters: z.object({ prompt: z.string() }),
  annotations: {
    streamingHint: true,
    readOnlyHint: true,
  },
  execute: async (args, { streamContent }) => {
    await streamContent({ type: "text", text: "Starting...\n" });
    const words = "The quick brown fox".split(" ");
    for (const word of words) {
      await streamContent({ type: "text", text: word + " " });
      await new Promise(resolve => setTimeout(resolve, 300));
    }
    return;
  },
});

Tool with Multiple Content Types

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" },
      ],
    };
  },
});

Tool with Image Content

import { imageContent } from "fastmcp";

server.addTool({
  name: "getImage",
  execute: async (args) => {
    return imageContent({
      url: "https://example.com/image.png",
    });
    // Or: path: "/path/to/image.png"
    // Or: buffer: Buffer.from(...)
  },
});

Tool with Audio Content

import { audioContent } from "fastmcp";

server.addTool({
  name: "getAudio",
  execute: async (args) => {
    return audioContent({
      url: "https://example.com/audio.mp3",
    });
  },
});

Tool with Error Handling

import { UserError } from "fastmcp";

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

Tool with Annotations

server.addTool({
  name: "fetch-content",
  description: "Fetch content from a URL",
  parameters: z.object({ url: z.string() }),
  annotations: {
    title: "Web Content Fetcher",
    readOnlyHint: true,
    openWorldHint: true,
  },
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

Tool with Authorization

server.addTool({
  name: "admin-tool",
  description: "An admin-only tool",
  canAccess: (auth) => auth?.role === "admin",
  execute: async () => "Welcome, admin!",
});

Alternative Schema Libraries

ArkType

import { type } from "arktype";

server.addTool({
  name: "fetch-arktype",
  parameters: type({ url: "string" }),
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

Valibot

import * as v from "valibot";

server.addTool({
  name: "fetch-valibot",
  parameters: v.object({ url: v.string() }),
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

Resources

Basic Resource

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

Resource with Binary Content

server.addResource({
  uri: "file:///logs/app.log",
  async load() {
    return {
      blob: 'base64-encoded-data'
    };
  },
});

Resource Template

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: `Log content for ${name}`,
    };
  },
});

Resource Template with Auto-Completion

server.addResourceTemplate({
  uriTemplate: "file:///logs/{name}.log",
  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: `Log content for ${name}`,
    };
  },
});

Embedded Resources in Tools

server.addTool({
  name: "get_user_data",
  parameters: z.object({ userId: z.string() }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "resource",
          resource: await server.embedded(`user://profile/${args.userId}`),
        },
      ],
    };
  },
});

Prompts

Basic Prompt

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 commit message:\n\n" + args.changes;
  },
});

Prompt with Auto-Completion

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

Prompt with Enum Arguments

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

Authentication

Basic API Key Authentication

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

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

    return { id: 1 };
  },
});

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

Role-Based Authentication

const server = new FastMCP<{ role: "admin" | "user" }>({
  authenticate: async (request) => {
    const role = request.headers["x-role"] as string;
    return { role: role === "admin" ? "admin" : "user" };
  },
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  name: "admin-dashboard",
  description: "An admin-only tool",
  canAccess: (auth) => auth?.role === "admin",
  execute: async () => {
    return "Welcome to the admin dashboard!";
  },
});

server.addTool({
  name: "public-info",
  description: "A tool available to everyone",
  execute: async () => {
    return "This is public information.";
  },
});

OAuth with Google Provider

import { FastMCP } from "fastmcp";
import { GoogleProvider } from "fastmcp/auth";

const authProxy = new GoogleProvider({
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  baseUrl: "https://your-server.com",
  scopes: ["openid", "profile", "email"],
});

const server = new FastMCP({
  name: "My Server",
  oauth: {
    enabled: true,
    authorizationServer: authProxy.getAuthorizationServerMetadata(),
    proxy: authProxy,
  },
});

Session Management

Session and Request ID Tracking

const sessionCounters = new Map<string, number>();

server.addTool({
  name: "increment_counter",
  description: "Increment a per-session counter",
  parameters: z.object({}),
  execute: async (args, context) => {
    if (!context.sessionId) {
      return "Session ID not available (requires HTTP transport)";
    }

    const counter = sessionCounters.get(context.sessionId) || 0;
    const newCounter = counter + 1;
    sessionCounters.set(context.sessionId, newCounter);

    return "Counter: " + newCounter;
  },
});

Server Events

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

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

Session Events

server.on("connect", (event) => {
  const session = event.session;

  console.log("Initial roots:", session.roots);

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

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

Session Sampling

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,
});

Transport Options

HTTP Streaming Transport

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
  },
});

HTTP Streaming with Custom Endpoint

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

Stateless Mode (for Serverless)

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
    stateless: true,
  },
});

SSE Transport

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
  },
});
// SSE automatically available at http://localhost:8080/sse

Stdio Transport

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

Server Configuration

Custom Logger

import { FastMCP, Logger } from "fastmcp";

class CustomLogger implements Logger {
  debug(...args: unknown[]): void {
    console.log("[DEBUG]", new Date().toISOString(), ...args);
  }
  error(...args: unknown[]): void {
    console.error("[ERROR]", new Date().toISOString(), ...args);
  }
  info(...args: unknown[]): void {
    console.info("[INFO]", new Date().toISOString(), ...args);
  }
  log(...args: unknown[]): void {
    console.log("[LOG]", new Date().toISOString(), ...args);
  }
  warn(...args: unknown[]): void {
    console.warn("[WARN]", new Date().toISOString(), ...args);
  }
}

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

Health Check Endpoint

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  health: {
    enabled: true,
    message: "healthy",
    path: "/healthz",
    status: 200,
  },
});

Ping Configuration

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  ping: {
    enabled: true,
    intervalMs: 10000,
    logLevel: "debug",
  },
});

Instructions

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  instructions: "This server provides file management tools. Use the list_files tool to see available files.",
});

CLI Development Commands

# Development mode with hot reload
npx fastmcp dev src/server.ts

# HTTP streaming transport
npx fastmcp dev src/server.ts --transport http-stream --port 8080

# Stateless mode
npx fastmcp dev src/server.ts --transport http-stream --port 8080 --stateless true

# Or via environment variable
FASTMCP_STATELESS=true npx fastmcp dev src/server.ts

Best Practices

  1. Use descriptive tool names: Choose names that clearly indicate what the tool does
  2. Provide descriptions: Always include descriptions for tools, resources, and prompts
  3. Validate inputs: Use Zod/ArkType/Valibot for comprehensive parameter validation
  4. Handle errors gracefully: Use UserError for user-facing errors
  5. Report progress: For long-running operations, use reportProgress
  6. Use streaming: For text generation, use streamContent with streamingHint: true
  7. Implement authorization: Use canAccess for role-based access control
  8. Log appropriately: Use the context log object for debugging
  9. Configure health checks: Enable health endpoints for production deployments
  10. Use stateless mode: For serverless deployments, enable stateless mode
Weekly Installs
1
GitHub Stars
8
First Seen
6 days ago
Installed on
zencoder1
amp1
cline1
openclaw1
opencode1
cursor1