@longrun/turtle
v0.2.7
Published
Lightweight HTTP MCP Server framework
Keywords
Readme
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
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 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.
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 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:
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:
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:
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 /mcpAny 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:
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.
