Skip to content

cdavis-code/easy_api_workspace

Repository files navigation

easy_api

A Dart code generator that transforms annotated functions into MCP servers and REST APIs.

Overview

Easy API allows you to expose Dart library functions as MCP tools and REST API endpoints using simple annotations. The generator produces ready-to-run stdio/HTTP MCP servers and/or REST API servers that comply with the MCP specification and OpenAPI 3.0 standard.

What You Can Build

  • MCP Servers - Create AI-powered tools callable by Claude Desktop, Cursor, and other MCP clients via stdio or HTTP transport
  • REST APIs - Generate traditional HTTP REST endpoints with full OpenAPI 3.0 documentation for web/mobile applications
  • Hybrid Solutions - Serve both AI agents and traditional HTTP clients from the same annotated Dart code
  • Code Mode Orchestration - Enable LLM-driven batch tool execution via sandboxed JavaScript for complex multi-step workflows

Uses of Generated .openapi.json

The generated OpenAPI 3.0 specification file is a powerful artifact that enables:

  • Interactive API Documentation - Import into Swagger UI, Redoc, or Stoplight for browsable, interactive documentation
  • Client SDK Generation - Auto-generate type-safe client libraries in 50+ languages using OpenAPI Generator, Swagger Codegen, or NSwag
  • API Testing & Mocking - Create mock servers (Prism, WireMock) and automated tests without writing implementation code
  • API Gateway Integration - Configure Kong, AWS API Gateway, Apigee, or Azure API Management with ready-to-import specs
  • Contract-First Development - Share API contracts with frontend/mobile teams before implementation begins
  • Automated Validation - Validate requests/responses against the spec using tools like Dredd or Schemathesis
  • Developer Portals - Power documentation sites with ReadMe, Postman, or GitBook integrations
  • Load Testing - Generate realistic test scenarios with k6 or Apache JMeter from the spec

Generated Files and Artifacts

The code generator can produce several different output files depending on your @Server configuration:

File Generated When Purpose
.mcp.dart generateMcp: true (default) Complete MCP server implementation with stdio or HTTP transport. Contains all tool handlers, JSON-RPC routing, and server lifecycle management. Do not edit manually — regenerate via build_runner.
.mcp.json generateJson: true MCP tool metadata file describing available tools, their parameters, and schemas. Used by MCP clients to discover and understand tool capabilities without connecting to the server.
.openapi.json generateRest: true OpenAPI 3.0 specification for RESTful API endpoints. Includes resource-based URLs, request/response schemas, proper HTTP status codes, and can be used with Swagger UI, API gateways, or client code generators.
.openapi.dart generateRest: true Complete REST API server implementation using the Shelf web framework. Serves the REST endpoints defined in the OpenAPI spec. Runs as a standard HTTP server on the configured port.

Generation Flags on @Server:

@Server(
  transport: McpTransport.stdio,
  generateMcp: true,      // Generate .mcp.dart server (default: true)
  generateJson: false,    // Generate .mcp.json metadata (default: false)
  generateRest: false,    // Generate .openapi.json + .openapi.dart (default: false)
)

Example Output Files:

example/
├── bin/
│   ├── example.dart                    # Annotated source file (you write this)
│   ├── example.mcp.dart                # Generated MCP server (stdio)
│   ├── example.mcp.json                # Generated tool metadata (optional)
│   ├── example.openapi.dart            # Generated REST API server (optional)
│   └── example.openapi.json            # Generated OpenAPI 3.0 spec (optional)
└── lib/src/
    ├── user.dart                       # Domain models
    ├── user_store.dart                 # Business logic
    ├── todo.dart
    └── todo_store.dart

Use Cases:

  • MCP Server Only (generateMcp: true): Build AI-powered applications that integrate with Claude Desktop, Cursor, or other MCP clients
  • REST API Only (generateRest: true): Create traditional HTTP APIs for web/mobile apps with full OpenAPI documentation
  • Both MCP + REST (generateMcp: true, generateRest: true): Serve both AI agents and traditional clients from the same annotated code
  • Metadata Export (generateJson: true): Share tool specifications with team members or use in CI/CD pipelines

Known Caveats

  • HTTP MCP transport — SSE keepalive buffering. When @Server(transport: McpTransport.http) is used, the generated .mcp.dart exposes a Streamable-HTTP compliant endpoint (GET opens an SSE channel, POST handles JSON-RPC requests, DELETE terminates a session, OPTIONS returns CORS preflight). The HTTP response headers (200 OK + Content-Type: text/event-stream) flush immediately, which is what MCP clients rely on to accept the transport during the handshake. However, dart:io's HttpResponse buffers small chunked writes, so the periodic keepalive comments (: keepalive\n\n, every 15s) may not be visible to raw tools like curl -N for several seconds. This does not affect correctness — all JSON-RPC request/response traffic flows normally over POST. If you need faster server-push flushing (for example, server-initiated tool notifications), pad the first SSE event with ~4 KiB of comment bytes or drop down to a raw dart:io HttpServer that calls response.flush() after each write.

Table of Contents

Packages

Package Description Version
easy_api_annotations Annotation definitions (@Server, @Tool, @Parameter) pub package
easy_api_generator Build runner generator that produces MCP server code pub package

Quick Start

1. Add Dependencies

dependencies:
  easy_api_annotations: ^0.6.0

dev_dependencies:
  build_runner: ^2.4.0
  easy_api_generator: ^0.6.1

2. Annotate Your Functions

import 'package:easy_api_annotations/mcp_annotations.dart';

@Server(transport: McpTransport.stdio)
class UserServer {
  @Tool(description: 'Get user by ID')
  Future<User> getUser(
    @Parameter(
      title: 'User ID',
      description: 'The unique identifier for the user',
      example: 42,
    )
    int id,
  ) async {
    // ...
  }
  
  @Tool(description: 'Create a new user')
  Future<User> createUser({
    @Parameter(
      title: 'Name',
      description: 'The user\'s full name',
      example: 'Jane Doe',
    )
    required String name,
    
    @Parameter(
      title: 'Email',
      description: 'A valid email address',
      example: 'jane@example.com',
      pattern: r'^[\w\.-]+@[\w\.-]+\.\w+$',
    )
    required String email,
  }) async {
    // ...
  }
}

3. Run the Generator

dart run build_runner build

4. Run the Server

dart run bin/my_server.mcp.dart

Annotations

@Server

Controls the transport type and configuration for the generated server.

// Stdio transport (default)
@Server(transport: McpTransport.stdio)

// HTTP transport with custom port and address
@Server(
  transport: McpTransport.http,
  port: 8080,                    // Default: 3000
  address: '0.0.0.0',            // Default: '127.0.0.1'
  generateJson: true,            // Optional: generate .mcp.json metadata
  generateMcp: true,             // Default: true — generate .mcp.dart server
  generateRest: false,           // Default: false — generate .openapi.json REST spec
  toolPrefix: 'user_service_',   // Optional: prefix all tool names
  autoClassPrefix: true,         // Optional: prefix with class name
  annotationsDefault: ToolAnnotations(  // Optional: server-wide defaults for tool hints
    openWorldHint: false,
  ),
)

Migration note: @Mcp is still available as a deprecated typedef for backward compatibility. New code should use @Server.

@Tool

Marks a method as an MCP tool and provides metadata.

@Tool(description: 'Create a new user')
Future<User> createUser(String name, String email) async { ... }

// With custom tool name
@Tool(
  name: 'user_create',  // Custom name instead of method name
  description: 'Creates a new user',
)
Future<User> createUser(String name, String email) async { ... }

// Disable code mode for destructive operations
@Tool(
  description: 'Delete a user',
  codeMode: false,  // Not available in batch orchestration
)
Future<bool> deleteUser(int id) async { ... }

If description is omitted, the function's doc comment is used. Use name to customize the tool name for avoiding collisions or better organization. Set codeMode to false for tools that should not be available in batch orchestration (e.g., destructive operations).

Tool Annotations

Tools can carry behavioral hints via ToolAnnotations that inform MCP clients how they function:

  • title — Human-readable display title.
  • readOnlyHint — If true, the tool does not modify its environment (safe for auto-approval).
  • destructiveHint — If true, the tool may perform destructive updates (clients should prompt for confirmation).
  • idempotentHint — If true, repeated calls with the same arguments have no additional effect (safe to retry).
  • openWorldHint — If true, the tool interacts with external entities like APIs or the internet. If false, it operates within a closed system.

Set server-wide defaults with @Server(annotationsDefault: ...) so all tools inherit the same hints unless overridden. If neither server defaults nor per-tool annotations are set, no annotations are emitted in the generated output.

@Tool(
  description: 'Get user by ID',
  annotations: ToolAnnotations(
    title: 'Get User',
    readOnlyHint: true,
    openWorldHint: false,
  ),
)
Future<User?> getUser(int id) async { ... }

@Parameter (Optional)

Provides rich metadata for individual parameters. Use when you need custom titles, descriptions, examples, or validation constraints.

@Tool(description: 'Create a new user')
Future<User> createUser({
  @Parameter(
    title: 'Full Name',
    description: 'The user\'s full name',
    example: 'John Doe',
  )
  required String name,
  
  @Parameter(
    title: 'Email Address',
    description: 'A valid email address',
    example: 'john@example.com',
    pattern: r'^[\w\.-]+@[\w\.-]+\.\w+$',
  )
  required String email,
  
  @Parameter(
    title: 'Age',
    minimum: 0,
    maximum: 150,
    example: 25,
  )
  int? age,
}) async { ... }

Full @Parameter fields:

Field Type Description
title String? Human-readable label displayed in MCP clients
description String? Detailed description of the parameter
example Object? Example value shown as a hint
minimum num? Minimum value for numeric parameters
maximum num? Maximum value for numeric parameters
pattern String? Regex pattern for string validation
sensitive bool Mark as sensitive (e.g., passwords, API keys). Default: false
enumValues List<Object?>? Restrict to specific allowed values

Note: @Parameter is optional. By default, the generator extracts parameter information from Dart types and method signatures.

Code Mode

Enable batch tool orchestration via sandboxed JavaScript execution. Reduces latency by replacing N sequential round-trips with a single call.

@Server(
  codeMode: true,           // Enable the execute tool
  codeModeTimeout: 60,      // Optional: max execution time (default: 30s)
)

When enabled, an execute tool is generated that spawns a sandboxed Node.js subprocess where all code-mode-enabled tools are available as external_* async functions. The LLM can use Promise.all() for parallel calls and await for sequential logic.

Benefits of Code Mode:

  • Progressive Tool Discovery - Instead of loading all tool definitions upfront (which can consume 100,000+ tokens), the agent discovers tools on-demand through the filesystem, reducing context usage by up to 98.7%
  • Context-Efficient Data Processing - Filter, aggregate, and transform large datasets in the execution environment before returning results. Process 10,000 rows but return only the 5 that matter
  • Powerful Control Flow - Use loops, conditionals, and error handling in code rather than chaining individual tool calls through the agent loop, saving both time and tokens
  • Privacy-Preserving Operations - Intermediate results stay in the execution environment by default. Sensitive data flows through your workflow without entering the model's context unless explicitly logged
  • Parallel Execution - Use Promise.all() to execute multiple independent tools simultaneously, dramatically reducing latency compared to sequential calls
  • Reduced Token Costs - By writing code instead of making sequential tool calls, agents avoid loading intermediate results into context multiple times, saving significant tokens on complex workflows

Requirements: Node.js must be installed on the system.

Learn More: Read Anthropic's comprehensive guide on Code Execution with MCP to understand the efficiency gains and architectural patterns.

Security note — Code Mode is an orchestration primitive, not a security sandbox. The spawned Node.js subprocess runs with a 64 MB heap cap and a wall-clock timeout, but it has full access to the host filesystem, network, and require('child_process'). Treat any code that reaches execute with the same trust level as code you run locally, and enable Code Mode only for trusted LLMs / operators. For stricter isolation, consider running your MCP server inside a container or enabling Node.js --permission flags (Node ≥ 20).

REST API Specification Generation

Generate RESTful OpenAPI 3.0 specifications and a ready-to-run REST server alongside your MCP tools by setting generateRest: true on the @Server annotation:

@Server(
  transport: McpTransport.http,
  port: 8080,
  generateRest: true,  // Enable OpenAPI spec + REST server generation
)
class UserService {
  @Tool(description: 'Get user by ID')
  Future<User> getUser(int id) async { ... }
  
  @Tool(description: 'Create a new user')
  Future<User> createUser(String name, String email) async { ... }
}

This generates a .openapi.json file with:

  • RESTful endpoints - Tools mapped to standard HTTP methods (GET, POST, PATCH, DELETE)
  • Resource-based URLs - e.g., /users, /users/{id} instead of /tools/createUser
  • Request/response schemas - Full type information with validation
  • Proper status codes - 200, 201, 204, 400, 404 as appropriate
  • Tags and operation IDs - For API organization and client generation

A companion .openapi.dart file is also generated, providing a complete REST API server implementation built on the Shelf web framework. The generated spec follows Swagger API design best practices and can be used with Swagger UI, API gateways, and client code generation tools.

Features

  • AST-based parsing - Uses dart:analyzer for reliable code extraction
  • Two transport modes - stdio (JSON-RPC) and HTTP (Shelf-based)
  • Configurable HTTP server - Customize port and bind address
  • Rich parameter metadata - Optional @Parameter annotation for titles, descriptions, validation, sensitive flags, and enum values
  • Custom tool names - Use name parameter on @Tool, toolPrefix or autoClassPrefix on @Server to avoid collisions
  • Automatic schema generation - Dart types mapped to JSON Schema
  • Optional parameter support - Named and optional positional parameters
  • Doc comment extraction - Falls back to doc comments when description not provided
  • Code Mode - Batch tool orchestration via sandboxed Node.js execution with external_* functions and Promise.all support
  • OpenAPI 3.0 specification - Auto-generate RESTful API documentation from MCP tools
  • Tool icons - Optional icon URLs for visual identification in MCP clients
  • Many-to-many relationships - Full example with User/Todo cross-store operations
  • Generated metadata - .mcp.json tool metadata and .openapi.json RPC-to-REST mapping

Development

Prerequisites

  • Dart SDK ^3.11.0
  • Melos (for workspace management)

AI Agent Skills

This project includes specialized skills for AI agents to assist with annotation and code generation:

  • Add Server Annotations Skill: Located at packages/easy_api_annotations/skills/easy_mcp_add-server-annotations/SKILL.md
    • Helps AI agents automatically add @Server, @Tool, and @Parameter annotations to existing Dart code
    • Provides step-by-step workflow guidance for converting Dart libraries into MCP/REST servers
    • Includes best practices, common patterns (CRUD, API wrappers, utilities), and troubleshooting tips
    • How to use: Share this skill file with your AI assistant (Claude, Cursor, etc.) to guide it through the annotation process with expert-level knowledge of the Easy API framework

Commands

# Install dependencies
melos bootstrap

# Run all tests
melos run test

# Analyze code
melos run analyze

# Format code
melos run format

# Rebuild generated code
melos run build

Contributing

Contributions are welcome! Whether you're reporting a bug, improving docs, or submitting a pull request, please read CONTRIBUTING.md first. It covers local setup, the Melos-based workflow, coding standards, testing, and the release process.

Quick links:

License

MIT License - see LICENSE for details.

Support

If you find this project useful, consider supporting its development:

Buy Me A Coffee

About

A Dart code generator that transforms annotated functions into Model Context Protocol (MCP) servers

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors