npm package: @longrun/turtle
Description: Lightweight HTTP MCP Server framework
Latest version: 0.2.7

# Turtle

A lightweight Model Context Protocol HTTP server framework

## Overview

Turtle makes it easy to create internet-accessible Model Context Protocol servers. It supports:

- Multi-tenant operation with saved configuration per user
- Horizontal scaling with shared database support
- Multiple MCP servers on different URLs
- Built-in security and safety features

There's also experimental support for long-running asynchronous MCP tools that's backwards-compatible with existing MCP clients.

## Quick Start

```typescript
import { defineTool, startHttpServer } from '@longrun/turtle';
import { z } from 'zod';

const hello = defineTool(
  'hello',
  'Say hello to the world',
  z.object({
    name: z.string(),
  }),
  async ({ name }) => `Hello ${name}!`
);

// Basic server
startHttpServer(3001, [
  {
    tools: [hello],
  },
]);

// With middleware (optional)
import cors from 'cors';
startHttpServer(
  3001,
  [
    {
      tools: [hello],
    },
  ],
  cors()
);
```

Then start the server and connect to http://localhost:3001 using any MCP client.

## Tool Definition

Tools are defined using `defineTool` with:

- name
- description
- [Zod](https://zod.dev/) schema for parameters
- handler function returning a string

The name and description are crucial as they expose your tool to models. The schema validates parameters, and TypeScript ensures handler parameters match the schema.

The schema defines the parameters for your tool, and the handler function is the implementation. If you're using Typescript, `defineTool` will enforce that the schema and handler function parameters match.

Handler functions can take a second parameter to access `userId` and per-user configuration values. See "Authentication" and "User configuration" sections for details.

```typescript
async ({ name }, ctx) => {
  // Get an optional value (could be undefined)
  const customGreeting = await ctx.getConfig('customGreeting');
  // Get a required value (will throw an error if not found)
  const company = await ctx.requireConfig('company');
  return `Welcome to ${company}, ${name}! Your username is ${ctx.userId}.${customGreeting}`;
};
```

Tools return strings - Turtle automatically converts them to MCP response objects.

## Hosting

Turtle uses [Express](https://expressjs.com/) as an HTTP server.

`startHttpServer` takes a list of servers as input - each can have a separate list of tools and sub-URL under the root:

```typescript
startHttpServer(3001, [
  {
    mountpath: '/en',
    name: 'English',
    version: '1.0.0',
    tools: [hello, goodbye],
  },
  {
    mountpath: '/fr',
    name: 'French',
    version: '1.0.0',
    tools: [bonjour, auRevoir],
  },
]);
```

A single server without a path mounts at root. Servers can define name and version. Defaults are "1.0.0" for version and first tool name for name.

### Adding Express Middlewares

You can add Express middlewares to your Turtle server by passing them as an optional third parameter to `startHttpServer`. This allows you to add security, logging, CORS, and other middleware before the MCP servers are mounted:

```typescript
import cors from 'cors';
import helmet from 'helmet';

// Single middleware
startHttpServer(3001, [mcpConfig], cors());

// Multiple middlewares
startHttpServer(
  3001,
  [mcpConfig],
  [
    cors(),
    helmet(),
    (req, res, next) => {
      console.log(`${req.method} ${req.path}`);
      next();
    },
  ]
);
```

The middlewares are applied in order before the MCP servers are mounted, ensuring they run for all requests.

### Using Your Own Express Server

You can use your own Express server or mount alongside other routes. Each Turtle MCP server is an Express Router, so you can `use` it just like any other Router:

```typescript
import express from 'express';
import { createHttpServer } from '@longrun/turtle';

const app = express();
app.get('/', (req, res) => {
  res.send('Hello World');
});

// Add your own middlewares first
app.use(cors());
app.use(helmet());

// Create and mount the MCP server
const mcpServer = createHttpServer(
  [
    {
      tools: [hello, goodbye],
      mountpath: '/mcp',
    },
  ],
  [
    // Additional middlewares specific to MCP routes
    (req, res, next) => {
      console.log('MCP request:', req.path);
      next();
    },
  ]
);
app.use(mcpServer); // Clients connect at /mcp
```

Any Express middleware can be applied to your server or the one returned by `startHttpServer`, enabling standard security and safety solutions from the Express ecosystem.

## Long-running asynchronous tools

Turtle supports long-running tools (experimental) for operations that run longer than the typical MCP tool timeout. The developer is responsible for creating the actual system that runs and persists these operations - Turtle provides the MCP interface.

Define these with two functions:

```typescript
const writeReport = defineLongRunningTool(
  'writeQuartelyReport',
  'Generate a quarterly report - this could take up to an hour',
  z.object({
    year: z.number(),
    quarter: z.enum(['Q1', 'Q2', 'Q3', 'Q4']),
  }),
  // The start function
  async (args) => {
    const id = `quarterly-report-${args.year}-${args.quarter}`;
    const existingReport = _getReport(id);
    if (existingReport) {
      throw new Error(
        `There's already a report for ${args.year} ${args.quarter}. Please call writeQuarterlyReport-results with executionId ${id} to get it.`
      );
    } else {
      _enqueue_report(id, args.year, args.quarter);
      return {
        executionId: id,
        etaSeconds: 60 * 60, // optional
      };
    }
  },
  // The results function
  async (executionId) => {
    const report = _getReport(executionId);
    if (report.completed) {
      return report.url;
    } else {
      throw new Error('Report is not complete. Please check back later');
    }
  }
);
```

Turtle creates two tools: one to start the process and one to get results. The start function returns an `executionId` and optional `etaSeconds`. The results function checks completion status. Turtle automatically generates instructions in the tool descriptions for the calling model to understand how to use these.

## FAQ

- **Does Turtle support non-text return types from tools?** Not today.