A minimal MCP (Model Context Protocol) server demonstrating Tavily web search via SSE transport, built on the Lunet framework.
MCP servers are often deployed as sidecar processes or in resource-constrained environments. This implementation prioritizes:
- Minimal footprint - 7 MB runtime memory vs 14-28 MB for Node/Bun/Python
- Small image size - 171 MB Docker image vs 367-420 MB for alternatives
- Stable dependencies - libuv and LuaJIT are mature, battle-tested libraries with Debian LTS support
- No dependency churn - No npm/pip ecosystem overhead or constant security patches
Run ./compare.sh to reproduce these benchmarks:
| Implementation | Docker Image | Runtime Memory |
|---|---|---|
| lunet-mcp-sse | 171 MB | 7 MB |
| tavily-mcp (Node.js) | 420 MB | 18 MB |
| tavily-mcp (Bun) | 382 MB | 14 MB |
| FastMCP (Python) | 367 MB | 28 MB |
lunet-mcp-sse uses 2.5x less memory than Node.js, 2x less than Bun, and 4x less than Python.
- MCP 2024-11-05 protocol support
- SSE transport for real-time communication
- Tavily search integration
- Memory-efficient (~7 MB RSS in Docker, ~2 MB native)
- Zero-cost tracing for debugging (no overhead when disabled)
- Cross-platform binaries (Linux amd64/arm64, macOS arm64, Windows amd64)
Pre-built binaries are available from GitHub Releases:
| Platform | Archive |
|---|---|
| Linux (amd64) | lunet-mcp-sse-linux-amd64.tar.gz |
| Linux (arm64) | lunet-mcp-sse-linux-arm64.tar.gz |
| macOS (arm64) | lunet-mcp-sse-macos.tar.gz |
| Windows (amd64) | lunet-mcp-sse-windows-amd64.zip |
# Install dependencies
brew install luajit libuv zlib curl
# Download and extract
curl -L -o lunet-mcp-sse-macos.tar.gz \
https://github.com/lua-lunet/lunet-mcp-sse/releases/download/nightly/lunet-mcp-sse-macos.tar.gz
tar -xzf lunet-mcp-sse-macos.tar.gz
# Set up API key
echo "TAVILY_API_KEY=your_key_here" > .env
# Run
./run.sh# Install dependencies
sudo apt-get update
sudo apt-get install -y libuv1 libluajit-5.1-2 libcurl4 zlib1g curl
# Download and extract (use arm64 or amd64 as appropriate)
curl -L -o lunet-mcp-sse-linux-arm64.tar.gz \
https://github.com/lua-lunet/lunet-mcp-sse/releases/download/nightly/lunet-mcp-sse-linux-arm64.tar.gz
tar -xzf lunet-mcp-sse-linux-arm64.tar.gz
# Set up API key
echo "TAVILY_API_KEY=your_key_here" > .env
# Run
./run.sh# Download and extract the zip from GitHub releases
# Then in the extracted directory:
# Set up API key
echo TAVILY_API_KEY=your_key_here > .env
# Run
.\run.batYou can run the Linux binary in a Docker container. This is useful for testing on macOS or other platforms.
# Start a container with the binary mounted
docker run -d --name mcp-server -p 8080:8080 \
-v /path/to/extracted/linux-app:/app -w /app \
debian:trixie-slim \
/bin/bash -c 'apt-get update -qq && apt-get install -y -qq libuv1 libluajit-5.1-2 libcurl4 zlib1g curl >/dev/null 2>&1 && export $(cat .env | xargs) && HOST=0.0.0.0 ./bin/lunet-run --dangerously-skip-loopback-restriction app/main.lua'
# Test
curl http://localhost:8080/
# Stop
docker stop mcp-server && docker rm mcp-serverOn macOS, use Colima to run Linux arm64 containers:
# Start Colima
colima start
# Download and extract the Linux arm64 build
curl -L -o lunet-mcp-sse-linux-arm64.tar.gz \
https://github.com/lua-lunet/lunet-mcp-sse/releases/download/nightly/lunet-mcp-sse-linux-arm64.tar.gz
mkdir -p linux-app
tar -xzf lunet-mcp-sse-linux-arm64.tar.gz -C linux-app
# Copy into Colima VM
colima ssh -- mkdir -p /tmp/testapp
tar -czf - -C linux-app . | colima ssh -- tar -xzf - -C /tmp/testapp
# Copy your .env
echo "TAVILY_API_KEY=your_key_here" | colima ssh -- tee /tmp/testapp/.env
# Run the server
docker run -d --name mcp-server -p 8080:8080 \
-v /tmp/testapp:/app -w /app \
debian:trixie-slim \
/bin/bash -c 'apt-get update -qq && apt-get install -y -qq libuv1 libluajit-5.1-2 libcurl4 zlib1g curl >/dev/null 2>&1 && export $(cat .env | xargs) && HOST=0.0.0.0 ./bin/lunet-run --dangerously-skip-loopback-restriction app/main.lua'
# Test from macOS
curl http://localhost:8080/
./test_tavily.sh
# Cleanup
docker stop mcp-server && docker rm mcp-server
colima stop| Method | Path | Description |
|---|---|---|
| GET | / |
Server info (JSON) |
| GET | /sse |
SSE stream (creates session) |
| POST | /message?session=<id> |
Send JSON-RPC message |
# Check server info
curl http://localhost:8080/
# Run Tavily search test
./test_tavily.sh
# Connect to SSE stream manually
curl -N http://localhost:8080/sse- Client GETs
/sseto establish SSE connection - Server sends
endpointevent with session ID - Client POSTs JSON-RPC messages to
/message?session=<id> - Server responds via SSE events on the GET connection
There is no HTTPS termination support in this repo by design. If you want to run a public-facing MCP SSE you can trivially deploy this code with nginx as a sidecar else on a private cloud network behind a cloud load balancer. That is The Unix Way of piping data between specialist tools. It gives you the “QMail Security Model” where dealing protocol attacks is process segregated from any business logic.
Search the web using Tavily AI search engine.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "tavily-search",
"arguments": {
"query": "latest AI news",
"max_results": 5
}
}
}| Variable | Default | Description |
|---|---|---|
TAVILY_API_KEY |
- | Tavily API key (required) |
PORT |
8080 | Server port |
HOST |
127.0.0.1 | Bind address (use 0.0.0.0 for Docker) |
MCP_TRACE |
off | Trace level (off/error/warn/info/debug/trace) |
MCP_TRACE_FILE |
stderr | Output file for traces |
Consumes official pre-built Lunet v0.9.2 release archives via the vendored fetcher script.
- Host tool:
lua(5.1+ or LuaJIT) for running the fetcher - Runtime libraries:
- macOS:
brew install luajit libuv zlib curl - Linux (Debian/Ubuntu):
sudo apt-get install -y libuv1 libluajit-5.1-2 libcurl4 zlib1g
- macOS:
# Clone the repository
git clone https://github.com/lua-lunet/lunet-mcp-sse.git
cd lunet-mcp-sse
# Fetch pre-built Lunet v0.9.2 runtime and type definitions
make build
# Configure API key
echo "TAVILY_API_KEY=your_key_here" > .env
# Run the MCP server
make runThe fetcher populates types/ with type definitions. Configure .luarc.json for LuaCATS and Teal editor navigation and type checking:
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"workspace.library": ["types"]
}The server includes a zero-cost tracing system that has no overhead when disabled.
# No tracing (production, zero overhead)
./run.sh
# Verbose startup - show server banner
./run.sh -v
# or: ./run.sh --verbose
# Info level - see sessions and tool calls
MCP_TRACE=info ./run.sh
# Debug level - see HTTP requests and MCP methods
MCP_TRACE=debug ./run.shCompare memory and image size against other MCP implementations:
./compare.shThis tests against:
tavily-mcp(Node.js via npx)tavily-mcp(Bun)- FastMCP (Python)
MIT
This project uses Lunet, which is based on xialeistudio/lunet by 夏磊 (Xia Lei). See also his excellent write-up: Lunet: Design and Implementation of a High-Performance Coroutine Network Library.