wreiske:meteor-wormhole

v0.2.1Published 3 weeks ago

meteor-wormhole

A cosmic bridge connecting Meteor methods to AI agents through MCP

Meteor Package

meteor-wormhole is a server-only Meteor 3 package that exposes your Meteor.methods as MCP (Model Context Protocol) tools so AI agents (Claude, GPT, Copilot, etc.) can discover and invoke them. It also optionally exposes them as REST endpoints with an auto-generated OpenAPI 3.1 spec and Swagger UI.

Features

  • All-In Mode — Automatically expose every Meteor method as an MCP tool
  • Opt-In Mode — Selectively expose methods with rich metadata and schemas
  • Streamable HTTP Transport — MCP server embedded in your Meteor app via WebApp
  • REST API — Optional REST endpoints for every exposed method (POST /api/<method>)
  • OpenAPI 3.1 Spec — Auto-generated from your method registry
  • Swagger UI — Built-in API docs browser at /api/docs
  • API Key Authentication — Optional bearer token auth for MCP and REST endpoints
  • Input & Output Schemas — Define JSON Schema for method parameters and return values
  • Smart Defaults — Auto-excludes internal Meteor, DDP, and Accounts methods

Requirements

  • Meteor 3.4+
  • Server-only (this package does not run on the client)

Install

meteor add wreiske:meteor-wormhole

Quick Start

1import { Wormhole } from 'meteor/wreiske:meteor-wormhole';
2
3// Expose all Meteor methods as MCP tools (default mode)
4Wormhole.init();

That's it. Every method defined via Meteor.methods() is now available to MCP clients at http://localhost:3000/mcp.

Configuration

Wormhole.init(options)

Initialize the MCP bridge and optionally the REST API. Must be called once at server startup.

1Wormhole.init({
2  mode: 'all',
3  path: '/mcp',
4  name: 'my-app',
5  version: '1.0.0',
6  apiKey: 'my-secret-key',
7  exclude: [/^admin\./, 'internalMethod'],
8  rest: {
9    enabled: true,
10    path: '/api',
11    docs: true,
12  },
13});

Options

OptionTypeDefaultDescription
mode'all' | 'opt-in''all''all' auto-registers all methods. 'opt-in' requires explicit Wormhole.expose() calls.
pathstring'/mcp'HTTP path where the MCP server listens.
namestring'meteor-wormhole'Display name for the MCP server.
versionstring'1.0.0'Semantic version of the MCP server.
apiKeystring | nullnullBearer token for authentication. If set, all requests must include Authorization: Bearer <apiKey>.
exclude(string | RegExp)[][]Method name patterns to exclude in 'all' mode. Supports exact strings and RegExp.
restobject | booleanfalseREST API configuration (see below). Pass true to enable with defaults.

REST Options (options.rest)

OptionTypeDefaultDescription
enabledbooleanfalseEnable the REST API (opt-in).
pathstring'/api'Base path for REST endpoints.
docsbooleantrueServe Swagger UI at <path>/docs.
apiKeystring | null(inherits from parent)Override the API key for REST only.

Default Exclusions (All Mode)

In 'all' mode, the following methods are always excluded automatically:

  • Methods starting with / (DDP internal)
  • Methods starting with _ (private convention)
  • Accounts methods: login, logout, getNewToken, removeOtherTokens, configureLoginService, changePassword, forgotPassword, resetPassword, verifyEmail, createUser, ATRemoveToken, ATCreateUserServer

Use the exclude option to add your own patterns on top of these defaults.

Exposing Methods

Wormhole.expose(methodName, options)

Explicitly register a Meteor method as an MCP tool. Required in 'opt-in' mode, but also works in 'all' mode to enrich auto-registered methods with descriptions and schemas.

1Wormhole.expose('todos.add', {
2  description: 'Add a new todo item to the list',
3  inputSchema: {
4    type: 'object',
5    properties: {
6      title: { type: 'string', description: 'The todo title' },
7      priority: {
8        type: 'number',
9        description: 'Priority level (1=low, 5=high)',
10      },
11    },
12    required: ['title'],
13  },
14  outputSchema: {
15    type: 'object',
16    properties: {
17      _id: { type: 'string' },
18      title: { type: 'string' },
19      done: { type: 'boolean' },
20    },
21  },
22});

Parameters

ParameterTypeDescription
methodNamestringThe exact Meteor method name (e.g., 'todos.add').
options.descriptionstringHuman-readable description. Used in MCP tool listings and OpenAPI spec.
options.inputSchemaobjectJSON Schema defining expected input parameters.
options.outputSchemaobjectJSON Schema for the return value. Used in OpenAPI spec. REST wraps this in { result: <value> }.

Wormhole.unexpose(methodName)

Remove a method from MCP/REST exposure.

1Wormhole.unexpose('todos.add'); // returns true if it was registered

Tool Name Mapping

Method names are sanitized for MCP and REST: special characters (., -, /) are replaced with underscores.

Meteor MethodMCP Tool / REST Route
todos.addtodos_add
user-service.getUseruser_service_getUser

MCP Transport

The MCP server uses Streamable HTTP transport, mounted at the configured path (default /mcp).

Connecting an MCP Client

Configure your MCP client (Claude Desktop, VS Code, etc.) to connect:

1{
2  "mcpServers": {
3    "my-meteor-app": {
4      "type": "streamablehttp",
5      "url": "http://localhost:3000/mcp"
6    }
7  }
8}

With API key authentication:

1{
2  "mcpServers": {
3    "my-meteor-app": {
4      "type": "streamablehttp",
5      "url": "http://localhost:3000/mcp",
6      "headers": {
7        "Authorization": "Bearer my-secret-key"
8      }
9    }
10  }
11}

How It Works

  • POST /mcp — Send JSON-RPC 2.0 requests. First request creates a session; subsequent requests include the mcp-session-id header.
  • GET /mcp — Long-poll/stream events for an existing session.
  • DELETE /mcp — Close a session.

Tool Invocation

When an MCP client calls a tool:

  • With inputSchema: The entire params object is passed as a single argument to the Meteor method.
  • Without inputSchema: The args array is spread as positional arguments.

Errors from Meteor methods are returned as MCP error content:

1{
2  "content": [{ "type": "text", "text": "not-found: Todo not found" }],
3  "isError": true
4}

REST API

Enable the REST API to call exposed methods via standard HTTP.

1Wormhole.init({
2  mode: 'all',
3  rest: { enabled: true },
4});

Endpoints

MethodPathDescriptionAuth Required
POST/api/<tool_name>Invoke a methodYes (if apiKey set)
GET/api/List all endpointsYes (if apiKey set)
GET/api/openapi.jsonOpenAPI 3.1 specNo
GET/api/docsSwagger UINo

Calling a Method

curl -X POST http://localhost:3000/api/todos_add \
  -H 'Content-Type: application/json' \
  -d '{"title": "Buy milk", "priority": 3}'

Success response:

1{
2  "result": { "_id": "abc123", "title": "Buy milk", "done": false }
3}

Error response:

1{
2  "error": "not-found",
3  "reason": "Todo not found",
4  "message": "Todo not found [not-found]"
5}

Request Limits

  • Max body size: 1 MB
  • Content-Type: Must be application/json

Authentication

When apiKey is set, all MCP and REST requests (except Swagger UI and OpenAPI spec) require a bearer token:

Authorization: Bearer <your-api-key>

Unauthorized requests receive a 401 response.

Additional API

Wormhole.registry

Read-only access to the internal method registry.

1Wormhole.registry.names();   // ['todos.add', 'todos.list', ...]
2Wormhole.registry.size();    // 5
3Wormhole.registry.has('todos.add'); // true
4Wormhole.registry.get('todos.add');
5// { description: '...', inputSchema: {...}, outputSchema: {...}, registeredAt: 1709... }

Wormhole.initialized

boolean — Whether Wormhole.init() has been called.

Wormhole.options

Returns a copy of the resolved configuration options.

generateOpenApiSpec(registry, options)

Generate an OpenAPI 3.1.0 spec object from a registry. Exported for advanced use cases.

1import { generateOpenApiSpec } from 'meteor/wreiske:meteor-wormhole';
2
3const spec = generateOpenApiSpec(Wormhole.registry, {
4  name: 'My API',
5  version: '2.0.0',
6  restPath: '/api',
7  apiKey: 'secret',
8  description: 'My custom API',
9});

Full Example

1import { Meteor } from 'meteor/meteor';
2import { Wormhole } from 'meteor/wreiske:meteor-wormhole';
3
4// Initialize with all features
5Wormhole.init({
6  mode: 'all',
7  path: '/mcp',
8  name: 'my-app',
9  version: '1.0.0',
10  apiKey: process.env.MCP_API_KEY || null,
11  exclude: [/^admin\./],
12  rest: {
13    enabled: true,
14    path: '/api',
15    docs: true,
16  },
17});
18
19// Enrich specific methods with schemas
20Wormhole.expose('todos.add', {
21  description: 'Add a new todo item',
22  inputSchema: {
23    type: 'object',
24    properties: {
25      title: { type: 'string' },
26    },
27    required: ['title'],
28  },
29  outputSchema: {
30    type: 'object',
31    properties: {
32      _id: { type: 'string' },
33      title: { type: 'string' },
34      done: { type: 'boolean' },
35    },
36  },
37});
38
39// Define the actual Meteor method
40Meteor.methods({
41  'todos.add'({ title }) {
42    const todoId = TodosCollection.insertAsync({ title, done: false });
43    return TodosCollection.findOneAsync(todoId);
44  },
45});

Now available:

  • MCP: http://localhost:3000/mcp — AI agents can discover and call todos_add
  • REST: POST http://localhost:3000/api/todos_add — HTTP clients can call the method
  • Docs: http://localhost:3000/api/docs — Interactive Swagger UI
  • Spec: http://localhost:3000/api/openapi.json — OpenAPI 3.1 spec

Testing

# Run package unit tests (Tinytest)
cd apps/test-app && meteor test-packages ../../packages/meteor-wormhole

# Run MCP integration test client
meteor npm run test:client

License

MIT