Skip to content
fastmcp logo

FastMCP Development Skill

fastmcp

FastMCP TypeScript framework patterns for MCP servers. Auto-loads when building MCP servers, creating tools/resources/prompts, implementing authentication, configuring transports, or working with FastMCP in TypeScript.

jpoutrin/product-forge0installs16stars

SKILL.md

Full skill instructions

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

More skills from jpoutrin

mcp-security logo
jpoutrin/product-forge

mcp-security

Use when securing MCP servers, preventing prompt injection, implementing authorization, validating user input, or building secure multi-agent pipelines. Provides 5-layer defense architecture patterns.

16 0
View
create-skill logo
jpoutrin/product-forge

create-skill

Create a new Claude Code agent skill with proper folder structure and SKILL.md format. Use when the user wants to add a model-invoked skill that Claude uses autonomously based on context. Handles skill folder creation with SKILL.md and optional reference files.

16 0
View
design-system logo
jpoutrin/product-forge

design-system

Design system management for building and reusing UI components, tokens, and patterns. Use when working with component libraries, design tokens, style guides, or reusable UI patterns to ensure consistency and promote component reuse.

16 0
View
portman logo
jpoutrin/product-forge

portman

Port management for local development with git worktrees. Use when setting up local development environments, booking ports for services to avoid collisions between parallel worktrees, or configuring docker-compose with dynamic ports.

16 0
View
qa-test-management logo
jpoutrin/product-forge

qa-test-management

Automatic QA test lifecycle management, naming conventions, and directory structure. Use when creating, organizing, or tracking QA tests to ensure proper naming, directory structure, and status transitions.

16 0
View
create-command logo
jpoutrin/product-forge

create-command

Create a new Claude Code slash command with proper YAML frontmatter structure. Use when the user wants to add a custom slash command to a plugin. Handles command file creation with description, argument hints, allowed tools, and all advanced features.

16 0
View
dependency-alignment logo
jpoutrin/product-forge

dependency-alignment

Dependency version alignment and conflict resolution for parallel development. Use when extracting dependencies from Tech Specs to ensure version compatibility with existing project dependencies and transitive dependencies.

16 0
View
code-review logo
jpoutrin/product-forge

code-review

Review code changes between commits for security, logic, performance, and style issues

16 0
View
create-persona logo
jpoutrin/product-forge

create-persona

Create detailed user persona with guided questions

16 0
View
bl-sync logo
jpoutrin/product-forge

bl-sync

Sync git-branchless stack with main branch - fetch, rebase, and handle merges

16 0
View
bl-undo logo
jpoutrin/product-forge

bl-undo

Undo git-branchless operations - recover from mistakes interactively

16 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View