Skip to content

A TypeScript runtime that materializes MCP server tools as typed functions, then evaluates TypeScript snippets that compose those tools — keeping intermediate data in-memory and out of the model's context window.

Notifications You must be signed in to change notification settings

splusq/mcp-compose

Repository files navigation

mcp-compose

A TypeScript runtime that materializes MCP server tools as typed functions, then evaluates TypeScript snippets that compose those tools — keeping intermediate data in-memory and out of the model's context window.

Instead of the model shuttling data between tool calls, it declares intent:

const doc = await getDoc({ documentId: "doc-001" });
await emailDoc({ to: "boss@example.com", subject: doc.title, body: doc.body });

The runtime handles data flow. The model never sees the document body.

Architecture

                          mcp-compose
 ┌──────────────────────────────────────────────────────┐
 │                                                      │
 │   TS snippet ──► esbuild ──► node:vm sandbox         │
 │                                 │                    │
 │                    ┌────────────┼────────────┐       │
 │                    ▼            ▼            ▼       │
 │               getDoc()    listDocs()   emailDoc()    │
 │                    │            │            │       │
 │              ┌─────┘            │            └────┐  │
 │              ▼                  ▼                 ▼  │
 │        ┌──────────┐     ┌──────────┐     ┌──────────┐│
 │        │doc-server│     │doc-server│     │email-srvr││
 │        │(stdio)   │     │(stdio)   │     │(stdio)   ││
 │        └──────────┘     └──────────┘     └──────────┘│
 └──────────────────────────────────────────────────────┘

Data flow: User code runs in a node:vm sandbox. Tool functions are injected as globals. Each call dispatches over stdio to the backing MCP server via a connection pool. Results stay in the sandbox — only a summary is returned.

Module layout

src/
├── config/          # Zod-validated config loader (mcp-compose.json)
├── materializer/    # Introspects MCP servers, generates .d.ts declarations
├── transport/       # Connection pool + tool call dispatcher
├── runtime/         # esbuild transpile → vm sandbox execution
├── interface/       # CLI and MCP server frontends
└── index.ts         # Public API

Commands

Command Description Example
just build Compile TypeScript (tsgo) just build
just test Build + run all tests (node:test) just test
just clean Remove dist/ and generated/ just clean
just install Install npm dependencies just install
just materialize Generate .d.ts files for all configured servers just materialize
just serve Start mcp-compose as an MCP server just serve
just eval CODE Evaluate a TypeScript expression just eval 'return await listDocs()'
just run FILE Run a TypeScript file in the sandbox just run script.ts
just inspect [SERVER] Open MCP inspector (default: compose) just inspect doc-server

Quick start

just install
just test          # 24 tests, ~0.4s
just materialize   # generates typed declarations in generated/

Evaluate expressions

# List available documents
just eval 'return await listDocs()'

# Fetch a document
just eval 'return await getDoc({ documentId: "doc-001" })'

# Compose: fetch doc then email it
just eval '
  const doc = await getDoc({ documentId: "doc-001" });
  return await emailDoc({ to: "a@b.com", subject: doc.title, body: doc.body })
'

Use as an MCP server

Add to your MCP client config:

{
  "mcpServers": {
    "mcp-compose": {
      "command": "node",
      "args": ["dist/src/interface/mcp-server.js"],
      "cwd": "/path/to/mcp-compose"
    }
  }
}

This exposes two tools: compose (execute TS code) and listAvailableTools (show available tool signatures).

Programmatic API

import { compose } from "mcp-compose";

const result = await compose(`
  const doc = await getDoc({ documentId: "doc-001" });
  return doc.title;
`);

console.log(result.value);    // "Q4 Revenue Report"
console.log(result.callLog);  // [{ serverId: "doc-server", toolName: "getDoc", ... }]
console.log(result.totalBytes); // bytes kept out of context window

Configuration

mcp-compose.json declares which MCP servers to connect to:

{
  "servers": {
    "doc-server": {
      "command": "node",
      "args": ["--experimental-strip-types", "demo/doc-server/index.ts"]
    },
    "email-server": {
      "command": "node",
      "args": ["--experimental-strip-types", "demo/email-server/index.ts"]
    }
  }
}

Environment variables can be referenced with ${VAR_NAME} syntax in command, args, and env values.

About

A TypeScript runtime that materializes MCP server tools as typed functions, then evaluates TypeScript snippets that compose those tools — keeping intermediate data in-memory and out of the model's context window.

Resources

Stars

Watchers

Forks