Skip to main content
Glama

Levitate runs near local tools, launches one configured stdio MCP server, connects as an MCP client, then exposes a Streamable HTTP endpoint for Claude, ChatGPT, and other remote MCP hosts.

The project is Levitate. The package, CLI, Docker image, and binary artifact are levitate.

Initial target flow:

Claude.ai / ChatGPT
  -> public HTTPS remote MCP endpoint
  -> Levitate
  -> local stdio MCP server
  -> private tool or data system

Levitate is backend-agnostic. Any stdio MCP server can be exposed through its HTTP endpoint, subject to auth and policy.

Why Promotion Exists

Many useful MCP servers are local stdio servers. They work with Claude Desktop, Claude Code, Cursor, and other local MCP hosts, but cloud-hosted AI apps cannot connect to them directly. Levitate promotes those local capabilities into a remote MCP endpoint while keeping policy and auth at the gateway.

Related MCP server: MCP HTTP Proxy

Security

Do not expose private local tools without authentication.

Levitate requires authentication for the MCP endpoint. Static bearer tokens are available for local/dev/simple deployments. OIDC/JWT validation is available for Auth0 and other RS256 JWKS-backed issuers. MCP servers can read or modify private data, and tunnel-published endpoints are public unless protected. GET /health is unauthenticated for deployment checks; /mcp requires Authorization: Bearer <token>.

Quick Start

Install dependencies:

pnpm install

Set a bearer token:

export LEVITATE_TOKEN="$(openssl rand -hex 32)"

Start levitate with the fake stdio backend profile:

pnpm build
pnpm start -- --config config/fake-stdio.toml

MCP endpoint:

http://127.0.0.1:8790/mcp

Health check:

curl http://127.0.0.1:8787/health

Authenticated MCP clients must send:

Authorization: Bearer <LEVITATE_TOKEN>

Example Backend Profile

config/fake-stdio.toml shows one deterministic local profile for the fake stdio test backend:

[server]
name = "fake"
host = "127.0.0.1"
port = 8790

[stdio]
command = "node"
args = ["test/fixtures/fake-stdio-server.mjs"]

[auth]
mode = "bearer"
token_env = "LEVITATE_TOKEN"

Real deployments can point [stdio] at any stdio MCP server and then use tool policy to filter or block exposed tools.

Auth Configuration

Static bearer tokens

Static bearer mode reads a token from config or an environment variable:

[auth]
mode = "bearer"
token_env = "LEVITATE_TOKEN"

Authenticated clients must send:

Authorization: Bearer <LEVITATE_TOKEN>

OIDC/JWT validation

OIDC mode validates incoming bearer JWTs against the configured issuer, audience, expiration, and JWKS signature:

[auth]
mode = "oidc"
issuer = "https://YOUR_TENANT.auth0.com/"
audience = "https://levitate.example.com"
jwks_uri = "https://YOUR_TENANT.auth0.com/.well-known/jwks.json"

jwks_uri is optional when the issuer's standard /.well-known/jwks.json path is correct.

Auth0 setup:

  • Create an Auth0 Machine to Machine application for clients that need tokens.

  • Create an Auth0 API with identifier https://levitate.example.com.

  • Use RS256 signing.

  • Configure Levitate with issuer https://YOUR_TENANT.auth0.com/ and audience https://levitate.example.com.

Levitate only needs issuer, audience, and optionally JWKS URI to validate incoming tokens. Auth0 client credentials are for clients or smoke scripts that obtain tokens; do not store client secrets in Levitate config.

Manual token acquisition for local smoke tests can use environment variables:

export AUTH0_DOMAIN=YOUR_TENANT.auth0.com
export AUTH0_AUDIENCE=https://levitate.example.com
export AUTH0_CLIENT_ID=...
export AUTH0_CLIENT_SECRET=...

Then request a token from:

https://${AUTH0_DOMAIN}/oauth/token

Tool Policy

Levitate filters backend tools before advertising them to remote clients.

Rules:

  • If tools.allow is configured, only listed tools are advertised and callable.

  • tools.deny is always enforced as an extra guard.

  • Direct calls to denied tools return an MCP tool error and are logged.

This lets a private backend expose read-only or append-only tools while hiding destructive tools.

Server Instructions

Instructions can be configured inline or loaded from a file:

[instructions]
file = "/path/to/SKILL.md"

Levitate passes these instructions through the MCP server initialization result using the official TypeScript SDK Server instructions option.

Multi-backend Routing Model

Levitate is intended to host multiple MCP backends by assigning each backend its own HTTP MCP endpoint:

  • /notes/mcp

  • /ingest/mcp

  • /tools/mcp

  • /example/mcp

Each endpoint should behave as an independent MCP server backed by one stdio MCP backend.

Levitate does not merge multiple backend tool namespaces into a single /mcp endpoint by default. MCP already provides tool discovery through tools/list, so Levitate should preserve backend tool names and schemas unless an explicit policy filters or blocks them.

This keeps Levitate transport-transparent and avoids tool-name collisions, namespace rewriting, ambiguous routing, and policy mistakes. If an aggregate MCP endpoint is ever needed, it should be treated as a separate explicit feature, not the default multi-backend model.

Tunnel Deployment

Run levitate locally, then expose it with Cloudflare Tunnel, ngrok, or another HTTPS tunnel:

cloudflared tunnel --url http://127.0.0.1:8787

or:

ngrok http 8787

Configure the AI app connector to use the public HTTPS /mcp URL and bearer token.

Smoke Tests

Fake stdio backend

Use the fake stdio backend for deterministic local checks of Levitate's HTTP proxy and policy behavior:

export LEVITATE_TOKEN="dev-secret"
pnpm build
pnpm start -- --config config/fake-stdio.toml

In another terminal, connect MCP Inspector over Streamable HTTP with bearer auth:

npx -y @modelcontextprotocol/inspector@0.22.0 \
  --cli \
  --transport http \
  --header "Authorization: Bearer ${LEVITATE_TOKEN}" \
  -- http://127.0.0.1:8790/mcp \
  --method tools/list

Call the allowed tool:

npx -y @modelcontextprotocol/inspector@0.22.0 \
  --cli \
  --transport http \
  --header "Authorization: Bearer ${LEVITATE_TOKEN}" \
  -- http://127.0.0.1:8790/mcp \
  --method tools/call \
  --tool-name fake_allowed \
  --tool-arg message=hello

Call the denied tool directly:

npx -y @modelcontextprotocol/inspector@0.22.0 \
  --cli \
  --transport http \
  --header "Authorization: Bearer ${LEVITATE_TOKEN}" \
  -- http://127.0.0.1:8790/mcp \
  --method tools/call \
  --tool-name fake_denied

Expected result:

  • initialize succeeds

  • tools/list advertises fake_allowed

  • fake_denied is not advertised

  • calling fake_allowed returns fixture JSON

  • directly calling fake_denied returns an MCP tool error from Levitate

The automated version is covered by:

pnpm test test/mcp.test.ts

Optional local real-backend smoke test

You can test Levitate against any real stdio MCP backend using a local config. This is not required for normal development or CI. Create a local config that points to your backend, then choose one safe allowed tool and one denied tool for policy testing.

export LEVITATE_TOKEN="$(openssl rand -hex 32)"
export LEVITATE_CONFIG="config/example.local.toml"
export LEVITATE_SAFE_TOOL="example_safe_tool"
export LEVITATE_DENIED_TOOL="example_denied_tool"
pnpm build
pnpm start -- --config "$LEVITATE_CONFIG"

In another terminal, connect MCP Inspector:

npx -y @modelcontextprotocol/inspector@0.22.0 \
  --cli \
  --transport http \
  --header "Authorization: Bearer ${LEVITATE_TOKEN}" \
  -- http://127.0.0.1:8787/mcp \
  --method tools/list

Call a safe read-only tool:

npx -y @modelcontextprotocol/inspector@0.22.0 \
  --cli \
  --transport http \
  --header "Authorization: Bearer ${LEVITATE_TOKEN}" \
  -- http://127.0.0.1:8787/mcp \
  --method tools/call \
  --tool-name "$LEVITATE_SAFE_TOOL"

Call a denied tool directly:

npx -y @modelcontextprotocol/inspector@0.22.0 \
  --cli \
  --transport http \
  --header "Authorization: Bearer ${LEVITATE_TOKEN}" \
  -- http://127.0.0.1:8787/mcp \
  --method tools/call \
  --tool-name "$LEVITATE_DENIED_TOOL"

If a tool requires arguments, add --tool-arg key=value entries according to the backend's advertised input schema.

Verify in Inspector:

  • initialize succeeds

  • tools/list shows allowed backend tools

  • allowed tool calls work through Levitate

  • denied direct calls return an MCP tool error instead of an HTTP error or server crash

MCP Inspector 0.22.0 CLI supports HTTP headers with --header, so the smoke test keeps bearer-token auth enabled. The browser UI path may require entering headers in the UI; use the CLI commands above as the reproducible smoke path.

Docker

Build:

docker build -t levitate .

Run:

docker run --rm -p 8787:8787 \
  -e LEVITATE_TOKEN="$LEVITATE_TOKEN" \
  -v "$PWD/config:/app/config:ro" \
  levitate

For local stdio servers that need host files, mount required vault/tool paths and adjust config paths for the container.

Auth Notes

OIDC/JWT validation runs behind the same Authenticator interface as static bearer tokens.

OIDC validation checks:

  • JWKS signature

  • issuer

  • audience

  • expiration

  • not-before when present

  • subject or email allowlists when configured

Only RS256 JWTs are accepted. Static bearer auth remains available for local/dev/simple deployments.

MCP Transport Choice

Levitate uses the official @modelcontextprotocol/sdk v1 Streamable HTTP implementation:

  • backend: StdioClientTransport

  • remote endpoint: WebStandardStreamableHTTPServerTransport

  • HTTP framework: Hono, following the SDK Hono example

The remote endpoint is /mcp and uses JSON responses from Streamable HTTP for straightforward request/response behavior. Compatibility should be validated against each target remote MCP host because Claude, ChatGPT, and other hosts may differ in connector rollout details.

Non-Goals

  • No web UI

  • No Chrome extension

  • No WebRTC mode

  • No OAuth login UI

  • No approval UI

  • No multi-user management

  • No multi-profile routing

  • No persistent audit database

  • No backend-specific wrapper behavior

  • No Go or Rust rewrite plan

Validation

pnpm test
pnpm typecheck
pnpm build
A
license - permissive license
-
quality - not tested
D
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/iomz/levitate'

If you have feedback or need assistance with the MCP directory API, please join our Discord server