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.
FastMCP is a TypeScript framework for building Model Context Protocol (MCP) servers. This skill provides patterns for tools, resources, prompts, authentication, and transport configuration.
npm install fastmcp
import { FastMCP } from "fastmcp";
import { z } from "zod";
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
});
server.start({ transportType: "stdio" });
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.addTool({
name: "sayHello",
description: "Say hello",
execute: async () => {
return "Hello, world!";
},
});
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";
},
});
server.addTool({
name: "download",
execute: async (args, { reportProgress }) => {
await reportProgress({ progress: 0, total: 100 });
// ... process ...
await reportProgress({ progress: 100, total: 100 });
return "done";
},
});
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;
},
});
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" },
],
};
},
});
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(...)
},
});
import { audioContent } from "fastmcp";
server.addTool({
name: "getAudio",
execute: async (args) => {
return audioContent({
url: "https://example.com/audio.mp3",
});
},
});
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";
},
});
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);
},
});
server.addTool({
name: "admin-tool",
description: "An admin-only tool",
canAccess: (auth) => auth?.role === "admin",
execute: async () => "Welcome, admin!",
});
import { type } from "arktype";
server.addTool({
name: "fetch-arktype",
parameters: type({ url: "string" }),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
import * as v from "valibot";
server.addTool({
name: "fetch-valibot",
parameters: v.object({ url: v.string() }),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load() {
return {
text: await readLogFile(),
};
},
});
server.addResource({
uri: "file:///logs/app.log",
async load() {
return {
blob: 'base64-encoded-data'
};
},
});
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}`,
};
},
});
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}`,
};
},
});
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}`),
},
],
};
},
});
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;
},
});
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: [] };
},
},
],
});
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"],
},
],
});
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 + "!";
},
});
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.";
},
});
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,
},
});
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.on("connect", (event) => {
console.log("Client connected:", event.session);
});
server.on("disconnect", (event) => {
console.log("Client disconnected:", event.session);
});
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);
});
});
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,
});
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
},
});
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
endpoint: "/mcp",
},
});
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
stateless: true,
},
});
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
},
});
// SSE automatically available at http://localhost:8080/sse
server.start({
transportType: "stdio",
});
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(),
});
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
health: {
enabled: true,
message: "healthy",
path: "/healthz",
status: 200,
},
});
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
ping: {
enabled: true,
intervalMs: 10000,
logLevel: "debug",
},
});
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.",
});
# 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
UserError for user-facing errorsreportProgressstreamContent with streamingHint: truecanAccess for role-based access controllog object for debuggingSearch for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer