EasyMCP

repository·main·Indexed 41 days ago

https://github.com/zcaceres/easy-mcp

A TypeScript library for simplifying the creation of Model Context Protocol (MCP) servers. It offers a stable, Express-like API for explicit component definition and an experimental decorator-based API that uses TypeScript annotations to automatically infer schemas for tools, prompts, and resources. Version 1.0.1.

Tokens
8.1K
Snippets
30
Records
36
Agent score
50%

What's inside EasyMCP

  1. Using the Context object in Tools

    main

    Tools can access an MCP Context object by including it as the last argument in the decorated method. The Context object provides capabilities for logging, progress reporting, and interacting with the server.

    Available methods on Context include:

    • info(message: string): Log informational messages.
    • debug(message: string): Log debug messages.
    • error(message: string): Log error messages.
    • reportProgress(current: number, total: number): Report progress to the client.
    • readResource(uri: string): Read the content of a resource.
    @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;
      }
    }
  2. Quick Start with the Express-like API

    main

    The Express-like API is more stable and explicit than the Decorators API. You create an instance using EasyMCP.create() and then define components using method calls.

    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",
      description: "Lists files on the desktop",
      mimeType: "text/plain",
      fn: async () => {
        return "file://desktop/file1.txt\nfile://desktop/file2.txt";
      },
    });
    
    // Define a resource template
    mcp.template({
      uriTemplate: "file://{filename}",
      name: "File Template",
      description: "Template for accessing files",
      mimeType: "text/plain",
      fn: async ({ filename }) => {
        return `Contents of ${filename}`;
      },
    });
    
    // Define a tool
    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}!`;
      },
    });
    
    // Define a prompt
    mcp.prompt({
      name: "introduction",
      description: "Generates an introduction",
      args: [
        {
          name: "name",
          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);
  3. Quick Start with the (Experimental) Decorators API

    main

    The Decorators API is an experimental way to define MCP servers by extending the EasyMCP class. It automatically infers tool, prompt, and resource arguments from TypeScript annotations, reducing boilerplate.

    Note: This API is experimental and subject to change.

    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" });
  4. How the Decorators API works in EasyMCP

    main

    When you extend the EasyMCP class, the constructor automatically scans the class prototype for methods decorated with easy-mcp decorators (like @tool, @prompt, @resource, etc.).

    It extracts the configuration metadata from these decorated methods and automatically registers them with the server's respective managers during initialization. This allows for a declarative way to define your MCP server capabilities.

  5. Manage MCP resources and templates with ResourceManager

    main

    The ResourceManager is responsible for registering, storing, and retrieving Model Context Protocol (MCP) resources and resource templates.

    • Resources: Static entities identified by a unique uri.
    • Templates: Dynamic entities identified by a uriTemplate that can resolve to specific resources by extracting parameters from a URI.

    When you call get(uri), the manager first checks for a direct resource match. If none is found, it attempts to match the URI against registered templates. If a template matches, the manager automatically creates and caches a new resource for that specific URI using the template's logic and extracted parameters.

    import ResourceManager from "easy-mcp/lib/ResourceManager";
    
    const manager = ResourceManager.create();
    
    // Register a static resource
    manager.addResource({
      uri: "file:///logs/app.log",
      name: "App Log",
      mimeType: "text/plain",
      description: "The main application log file",
      fn: async () => "log content..."
    });
    
    // Register a template
    manager.addTemplate({
      uriTemplate: "file:///users/{user}/profile",
      name: "User Profile",
      mimeType: "application/json",
      description: "User profile data",
      fn: async (params) => JSON.stringify({ user: params.user, status: "active" })
    });
    
    // Retrieve a resource (direct or via template)
    const resource = await manager.get("file:///users/jdoe/profile");
  6. Decorator API Reference

    main

    The Decorator API allows for a declarative way to define MCP components within a class extending EasyMCP.

    @Tool(config?: ToolConfig)

    Defines a method as a tool. Arguments are inferred from TS annotations. An optional context: Context can be added as the last argument.

    • description: Optional description.
    • optionals: Array of parameter names to be marked as optional to the client.
    • parameters: Explicit array of parameter definitions for full control.

    @Resource(uri: string, config?: Partial<ResourceDefinition>)

    Defines a method as a resource or resource template (if the URI contains handlebars).

    • name: Optional name.
    • description: Optional description.
    • mimeType: Optional MIME type.

    @Prompt(config?: PromptDefinition)

    Defines a method as a prompt.

    • name: Optional name (defaults to method name).
    • description: Optional description.
    • args: Optional array of argument definitions.

    @Root(uri: string, config?: { name?: string })

    Applied to the class to define a root directory.

    • name: Optional name for the root.
  7. Express-like API Reference

    main

    The Express-like API provides explicit methods for defining MCP server components.

    • 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.
  8. Handle Resource errors

    main

    When interacting with the ResourceManager, you may encounter the following error types:

    • ResourceError: The base error class for resource-related issues.
    • ResourceNotFoundError: Thrown when a requested uri does not match any registered resource or template.
  9. Register resources with `mcp.resource()`

    main

    Use the resource() method to register a static resource. A resource is identified by a unique uri and can provide data via a function fn.

    mcp.resource({
      uri: 'memo://notes',
      name: 'System Notes',
      description: 'A collection of system notes',
      mimeType: 'text/plain',
      fn: async () => 'This is a note content.'
    });
  10. Register an MCP resource

    main

    Use addResource(config: ResourceConfig) to register a static resource in the manager. If a resource with the same uri already exists, it will be overwritten and a warning will be logged.

    manager.addResource({
      uri: "string",
      name: "string",
      mimeType: "string",
      description: "string",
      fn: async () => "string" // The function that returns the resource content
    });