A TypeScript framework for building MCP servers capable of handling client sessions.
Note
For a Python implementation, see FastMCP.
- Simple Tool, Resource, Prompt definition
- Sessions
- Image content
- Logging
- Error handling
- SSE
- Progress notifications
- Typed server events
- Prompt argument auto-completion
- Sampling
- Automated SSE pings
- Roots
- CLI for testing and debugging
npm install fastmcp
import { FastMCP } from "fastmcp";
import { z } from "zod";
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",
});
That's it! You have a working MCP server.
You can test the server in terminal with:
git clone https://github.com/punkpeye/fastmcp.git
cd fastmcp
npm install
# 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
You can also run the server with SSE support:
server.start({
transportType: "sse",
sse: {
endpoint: "/sse",
port: 8080,
},
});
This will start the server and listen for SSE connections on http://localhost:8080/sse
.
You can then use SSEClientTransport
to connect to the server:
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);
Tools in MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions.
server.addTool({
name: "fetch",
description: "Fetch the content of a url",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
execute
can return a string:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return "Hello, world!";
},
});
The latter is 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!",
},
],
};
},
});
If you want to return a list of messages, you can return 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" },
],
};
},
});
Use the imageContent
to create a content object for an image:
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",
});
// or...
// return imageContent({
// path: "/path/to/image.png",
// });
// or...
// return imageContent({
// buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
// });
// or...
// return {
// content: [
// await imageContent(...)
// ],
// };
},
});
The imageContent
function takes the following options:
url
: The URL of the image.path
: The path to the image file.buffer
: The image data as a buffer.
Only one of url
, path
, or buffer
must be specified.
The above example is equivalent to:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "image",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "image/png",
},
],
};
},
});
Tools can log messages to the client using the log
object in the context object:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args, { log }) => {
log.info("Downloading file...", {
url,
});
// ...
log.info("Downloaded file");
return "done";
},
});
The log
object has the following methods:
debug(message: string, data?: SerializableValue)
error(message: string, data?: SerializableValue)
info(message: string, data?: SerializableValue)
warn(message: string, data?: SerializableValue)
The errors that are meant to be shown to the user should be thrown as UserError
instances:
import { UserError } from "fastmcp";
server.addTool({
name: "download",
description: "Download a file",
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";
},
});
Tools can report progress by calling reportProgress
in the context object:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args, { reportProgress }) => {
reportProgress({
progress: 0,
total: 100,
});
// ...
reportProgress({
progress: 100,
total: 100,
});
return "done";
},
});
Resources represent any kind of data that an MCP server wants to make available to clients. This can include:
- File contents
- Screenshots and images
- Log files
- And more
Each resource is identified by a unique URI and can contain either text or binary data.
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load() {
return {
text: await readLogFile(),
};
},
});
Note
load
can return multiple resources. This could be used, for example, to return a list of files inside a directory when the directory is read.
async load() {
return [
{
text: "First file content",
},
{
text: "Second file content",
},
];
}
You can also return binary contents in load
:
async load() {
return {
blob: 'base64-encoded-data'
};
}
You can also define resource templates:
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}`,
};
},
});
Provide complete
functions for resource template arguments to enable automatic 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 enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions.
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}`;
},
});
Prompts can provide auto-completion for their arguments:
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: [],
};
},
},
],
});
If you provide an enum
array for an argument, the server will automatically provide completions for the argument.
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"],
},
],
});
The session
object is an instance of FastMCPSession
and it describes active client sessions.
server.sessions;
We allocate a new server instance for each client connection to enable 1:1 communication between a client and the server.
You can listen to events emitted by the server using the on
method:
server.on("connect", (event) => {
console.log("Client connected:", event.session);
});
server.on("disconnect", (event) => {
console.log("Client disconnected:", event.session);
});
FastMCPSession
represents a client session and provides methods to interact with the client.
Refer to Sessions for examples of how to obtain a FastMCPSession
instance.
requestSampling
creates a sampling request and returns 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,
});
The clientCapabilities
property contains the client capabilities.
session.clientCapabilities;
The loggingLevel
property describes the logging level as set by the client.
session.loggingLevel;
The roots
property contains the roots as set by the client.
session.roots;
The server
property contains an instance of MCP server that is associated with the session.
session.server;
You can listen to events emitted by the session using the on
method:
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
session.on("error", (event) => {
console.error("Error:", event.error);
});
The fastest way to test and debug your server is with fastmcp dev
:
npx fastmcp dev server.js
npx fastmcp dev server.ts
This will run your server with mcp-cli
for testing and debugging your MCP server in the terminal.
Another way is to use the official MCP Inspector
to inspect your server with a Web UI:
npx fastmcp inspect server.ts
Note
If you've developed a server using FastMCP, please submit a PR to showcase it here!
- FastMCP is inspired by the Python implementation by Jonathan Lowin.
- Parts of codebase were adopted from LiteMCP.
- Parts of codebase were adopted from Model Context protocolでSSEをやってみる.