easy-mcp
by: zcaceres
Absurdly easy Model Context Protocol Servers in Typescript
📌Overview
Purpose: EasyMCP aims to simplify the creation of Model Context Protocol (MCP) servers using TypeScript by minimizing boilerplate code and providing an intuitive API.
Overview: EasyMCP offers a user-friendly framework for building MCP servers, focusing on ease of use and flexibility. It allows developers to define essential server components with minimal configuration, making it accessible for both beginners and advanced users.
Key Features:
-
Simple Express-like API: Provides a high-level interface that resembles ExpressJS, allowing easy definition of Tools, Prompts, and Resources with optional parameters hidden unless needed, enhancing developer experience.
-
Experimental Decorators API: Automatically infers method parameters without requiring explicit input schema definitions, streamlining the development process while still being experimental.
-
Context Object: Enables access to MCP functionalities like logging and progress reporting directly within tools, facilitating better interaction and management of server processes.
-
Great Type Safety: Enhances developer experience (DX) and reduces runtime errors by leveraging TypeScript's strong typing capabilities.
easy-mcp
EasyMCP is usable but in beta. Please report any issues you encounter.
EasyMCP is the simplest way to create Model Context Protocol (MCP) servers in TypeScript. It hides the plumbing, formatting, and other boilerplate definitions behind simple declarations.
Easy MCP allows you to define the bare minimum of what you need to get started, or you can define more complex resources, templates, tools, and prompts.
Features
- Simple Express-like API: EasyMCP provides a high-level, intuitive API. Define Tools, Prompts, Resources, Resource Templates, and Roots with calls similar to defining endpoints in ExpressJS. Optional parameters are hidden unless needed.
- Experimental Decorators API: Automatically infers tool, prompt, and resource arguments without input schema definition.
- Context Object: Access MCP capabilities like logging and progress reporting through a context object in your tools.
- Great Type Safety: Better developer experience and fewer runtime errors.
Beta Limitations
- No support for MCP sampling yet.
- No support for SSE yet.
- No resource update notifications yet.
- Prompt input support is incomplete due to SDK limitations.
Installation
Run the following in your project directory:
bun install
Quick Start with (Experimental) Decorators API
EasyMCP's decorator API infers types and input configuration automatically.
It is experimental and may change or have undiscovered issues.
import EasyMCP from "./lib/EasyMCP";
import { Tool, Resource, Prompt } from "./lib/experimental/decorators";
class MyMCP extends EasyMCP {
@Resource("greeting/{name}")
getGreeting(name: string) {
return `Hello, ${name}!`;
}
@Prompt()
greetingPrompt(name: string) {
return `Generate a greeting for ${name}.`;
}
@Tool()
greet(name: string, optionalContextFromServer: Context) {
optionalContextFromServer.info(`Greeting ${name}`);
return `Hello, ${name}!`;
}
}
const mcp = new MyMCP({ version: "1.0.0" });
Complex Example with Decorators API
import EasyMCP from "./lib/EasyMCP";
import { Prompt } from "./lib/decorators/Prompt";
import { Resource } from "./lib/decorators/Resource";
import { Root } from "./lib/decorators/Root";
import { Tool } from "./lib/decorators/Tool";
@Root("/my-sample-dir/photos")
@Root("/my-root-dir", { name: "My laptop's root directory" })
class ZachsMCP extends EasyMCP {
@Tool()
simpleFunc(nickname: string, height: number) {
return `${nickname} of ${height} height`;
}
@Tool({
description: "An optional description",
optionals: ["active", "items", "age"],
})
middleFunc(name: string, active?: string, items?: string[], age?: number) {
return `exampleFunc called: name ${name}, active ${active}, items ${items}, age ${age}`;
}
@Tool({
description: "A function with various parameter types",
parameters: [
{ name: "date", type: "string", optional: false },
{ name: "season", type: "string", optional: false },
{ name: "year", type: "number", optional: true },
],
})
complexTool(date: string, season: string, year?: number) {
return `complexTool called: date ${date}, season ${season}, year ${year}`;
}
@Tool({
description: "A tool that uses context",
})
async processData(dataSource: string, context: Context) {
context.info(`Starting to process data from ${dataSource}`);
try {
const data = await context.readResource(dataSource);
context.debug("Data loaded");
for (let i = 0; i < 5; i++) {
await new Promise((resolve) => setTimeout(resolve, 1000));
await context.reportProgress(i * 20, 100);
context.info(`Processing step ${i + 1} complete`);
}
return `Processed ${data.length} bytes of data from ${dataSource}`;
} catch (error) {
context.error(`Error processing data: ${(error as Error).message}`);
throw error;
}
}
@Resource("simple-resource")
simpleResource() {
return "Hello, world!";
}
@Resource("greeting/{name}")
myResourceTemplate(name: string) {
return `Hello, ${name}!`;
}
@Prompt()
simplePrompt(name: string) {
return `Prompting... ${name}`;
}
@Prompt({
name: "configured-prompt",
description: "A prompt with a name and description",
args: [
{
name: "name",
description: "The name of the thing to prompt",
required: true,
},
],
})
configuredPrompt(name: string) {
return `Prompting... ${name}`;
}
}
const mcp = new ZachsMCP({ version: "1.0.0" });
console.log(mcp.name, "is now serving!");
Quick Start with Express-like API
This API is more verbose and stable.
import EasyMCP from "easy-mcp";
const mcp = EasyMCP.create("my-mcp-server", {
version: "0.1.0",
});
mcp.resource({
uri: "dir://desktop",
name: "Desktop Directory",
description: "Lists files on the desktop",
mimeType: "text/plain",
fn: async () => {
return "file://desktop/file1.txt\nfile://desktop/file2.txt";
},
});
mcp.template({
uriTemplate: "file://{filename}",
name: "File Template",
description: "Template for accessing files",
mimeType: "text/plain",
fn: async ({ filename }) => {
return `Contents of ${filename}`;
},
});
mcp.tool({
name: "greet",
description: "Greets a person",
inputs: [
{
name: "name",
type: "string",
description: "The name to greet",
required: true,
},
],
fn: async ({ name }) => {
return `Hello, ${name}!`;
},
});
mcp.prompt({
name: "introduction",
description: "Generates an introduction",
args: [
{
name: "name",
type: "string",
description: "Your name",
required: true,
},
],
fn: async ({ name }) => {
return `Hi there! My name is ${name}. It's nice to meet you!`;
},
});
mcp.serve().catch(console.error);
Express-Like API Methods
-
EasyMCP.create(name: string, options: ServerOptions)
Creates a new EasyMCP instance.
-
mcp.resource(config: ResourceConfig)
Defines a resource.
-
mcp.template(config: ResourceTemplateConfig)
Defines a resource template.
-
mcp.tool(config: ToolConfig)
Defines a tool.
-
mcp.prompt(config: PromptConfig)
Defines a prompt.
-
mcp.root(config: Root)
Defines a root.
-
mcp.serve()
Starts the MCP server.
(Experimental) Decorator API
@Tool(config?: ToolConfig)
Defines a method as a tool. Supports optional context
argument as last parameter for MCP capabilities.
Example:
@Tool({
description: "Greets a person",
optionals: ["title"],
})
greet(name: string, title?: string, optionalContext: Context) {
return `Hello, ${title ? title + " " : ""}${name}!`;
}
@Resource(uri: string, config?: Partial<ResourceDefinition>)
Defines a method as a resource or resource template.
Example:
@Resource("greeting/{name}")
getGreeting(name: string) {
return `Hello, ${name}!`;
}
@Prompt(config?: PromptDefinition)
Defines a method as a prompt.
Example:
@Prompt({
description: "Generates a greeting prompt",
args: [
{ name: "name", description: "Name to greet", required: true },
],
})
greetingPrompt(name: string) {
return `Generate a friendly greeting for ${name}.`;
}
@Root(uri: string, config?: { name?: string })
Defines a root directory. Applied to the class.
Example:
@Root("/my-sample-dir/photos")
@Root("/my-root-dir", { name: "My laptop's root directory" })
class MyMCP extends EasyMCP {
// ...
}
The decorator API drastically reduces boilerplate by inferring types and configurations but is experimental.
Contributing
Contributions are welcome! Submit a PR.
License
This project is licensed under the MIT License.
Credits
EasyMCP was created by Zach Caceres but inspired by FastMCP by kjlowin, a Python MCP server library.