by zcaceres
Absurdly easy Model Context Protocol Servers in Typescript
# Add to your Claude Code skills
git clone https://github.com/zcaceres/easy-mcpGuides for using mcp servers skills like easy-mcp.
Last scanned: 5/30/2026
{
"issues": [
{
"type": "npm-audit",
"message": "brace-expansion: brace-expansion: Zero-step sequence causes process hang and memory exhaustion",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "ip-address: ip-address has XSS in Address6 HTML-emitting methods",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "picomatch: Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching",
"severity": "high"
}
],
"status": "WARNING",
"scannedAt": "2026-05-30T15:43:27.172Z",
"npmAuditRan": true,
"pipAuditRan": true
}easy-mcp is an open-source mcp servers skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by zcaceres. Absurdly easy Model Context Protocol Servers in Typescript. It has 194 GitHub stars.
easy-mcp returned warnings in SkillsLLM's automated security scan. It has no critical vulnerabilities, but review the flagged issues in the Security Report section before adding it to your workflow.
Clone the repository with "git clone https://github.com/zcaceres/easy-mcp" and add it to your Claude Code skills directory (see the Installation section above).
easy-mcp is primarily written in TypeScript. It is open-source under zcaceres on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other MCP Servers skills you can browse and compare side by side. Open the MCP Servers category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh easy-mcp against similar tools.
No comments yet. Be the first to share your thoughts!
Top skills in this category by stars
Requires a passing catalog security scan. Resolve the flagged issues and resubmit to enable featuring.

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.
To install EasyMCP, run the following command in your project directory:
bun install
Also see examples/express-decorators.ts or run bun start:decorators
EasyMCP's decorator API is dead simple and infers types and input configuration automatically.
But it's experimental and may change or have not-yet-discovered problems.
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" });
See examples/express-express.ts or run bun start:express
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" }) // Optionally you can name the root
class ZachsMCP extends EasyMCP {
/**
You can declare a Tool with zero configuration. Relevant types and plumbing will be inferred and handled.
By default, the name of the Tool will be the name of the method.
*/
@Tool()
simpleFunc(nickname: string, height: number) {
return `${nickname} of ${height} height`;
}
/**
* You can enhance a tool with optional data like a description.
Due to limitations in Typescript, if you want the Tool to serialize certain inputs as optional to the Client, you need to provide an optionals list.
*/
@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}`;
}
/**
* You can also provide a schema for the input arguments of a tool, if you want full control.
*/
@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}`;
}
/**
* Tools can use a context object to access MCP capabilities like logging, progress reporting, and meta data from the request
*/
@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;
}
}
/**
* Resources can be declared with a simple URI.
By default, the name of the resource will be the name of the method.
*/
@Resource("simple-resource")
simpleResource() {
return "Hello, world!";
}
/**
* Or include handlebars which EasyMCP will treat as a Resource Template.
Both Resources and Resource Templates can be configured with optional data like a description.
*/
@Resource("greeting/{name}")
myResourceTemplate(name: string) {
return `Hello, ${name}!`;
}
/**
* By default, prompts need no configuration.
They will be named after the method they decorate.
*/
@Prompt()
simplePrompt(name: string) {
return `Prompting... ${name}`;
}
/**
* Or you can override and configure a Prompt with a name, description, and explicit arguments.
*/
@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!");
Also see examples/example-minimal.ts or run bun start:express
This API is more verbose and less magical, but it's more stable and tested.
import EasyMCP from "easy-mcp";
const mcp = EasyMCP.create("my-mcp-server", {
version: "0.1.0",
});
// Define a resource
mcp.resource({
uri: "dir://desktop",
name: "Desktop Directory", // Optional
description: "Lists files on the desktop", // Optional
mimeType: "text/plain", // Optional
fn: async () => {
return "file://desktop/file1.txt\nfile://desktop/file2.txt";
},
});
// Define a resource template
mcp.template({
uriTemplate: "file://{filename}",
name: "File Template", // Optional
description: "Template for accessing files", // Optional
mimeType: "text/plain", // Optional
fn: async ({ filename }) => {
return `Contents of ${filename}`;
},
});
// Define a tool
mcp.tool({
name: "greet",
description: "Greets a person", // Optional
inputs: [ // Optional
{
name: "name",
type: "string",
description: "The name to greet",
required: true,
},
],
fn: async ({ name }) => {
return `Hello, ${name}!`;
},
});
// Define a prompt
mcp.prompt({
name: "introduction",
description: "Generates an introduction", // Optional
args: [ // Optional
{
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!`;
},
});
// Start the server
mcp.serve().catch(console.error);
EasyMCP.create(name: string, options: ServerOptions)Creates a new EasyMCP instance.
name: The name of your MCP server.options: Server options, including the version.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.
EasyMCP provides decorators for a more concise and declarative way to define your MCP server components. Here's an overview of the available decorators:
@Tool(config?: ToolConfig)Defines a method as a tool. The method will take in any arguments you declare and infer types and input configurations based on your TS annotations. An optional context argument can be added as the last argument to access MCP capabilities.
config: Optional configuration object for the tool.
description: Optional description of the tool.optionals: Optional array of parameter names that should be marked as optional.parameters: Optional array of parameter definitions for full control over the input schema.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. A resource template is defined by using handlebars in the URI.
uri: The URI or URI template for the resource.config: Optional configuration object for the resource.
name: Optional name for the resource.description: Optional description of the resource.mimeType: Optional MIME type of the resource.Example:
@Resource("greeting/{name}")
getGreeting(name: string) {
return `Hel