Content
<p align="center">
```
████████ ██████ ████████ ████████ ████████ ████████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
████████ ██ ██ ████████ ██ ██████ ████████
██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██████ ██ ██ ██ ████████ ██ ██
```
</p>
<h3 align="center">Standalone MCP Gateway</h3>
<p align="center"><em>Part of the Nimbus Ecosystem</em></p>
---
Porter aggregates multiple MCP servers into a single unified MCP endpoint. Use it standalone with `porter serve` or `porter stdio`, or as part of the [Nimbus](https://github.com/Layer-Prime-Solutions/nimbus) platform.
## What is Porter?
Porter is a library and binary that aggregates multiple MCP servers behind a single MCP interface:
- **MCP servers**: Manages external MCP servers over STDIO or Streamable HTTP transports, namespace-isolating their tools to prevent name collisions
Works standalone without any Nimbus infrastructure. Connect any MCP client directly.
## Install
```bash
cargo install nimbus-porter-cli
```
Build from source:
```bash
git clone https://github.com/Layer-Prime-Solutions/porter
cd porter
cargo build --release
# Binary is at ./target/release/porter
```
## Quick Start
**1. Create a `porter.toml` config file** (or copy the example: `cp porter.example.toml porter.toml`):
```toml
# MCP servers (STDIO or HTTP transports)
[servers.github-mcp]
slug = "gh-mcp"
transport = "stdio"
command = "gh-mcp"
[servers.context7]
slug = "c7"
transport = "http"
url = "https://mcp.context7.com/mcp"
```
**2. Start Porter:**
```bash
porter serve
```
Output:
```
Porter HTTP server listening host=127.0.0.1 port=3000
Connect your MCP client to http://127.0.0.1:3000/mcp
```
**3. Connect your MCP client** to `http://127.0.0.1:3000/mcp`
All tools from all configured servers are available at that single endpoint.
## Configuration
Porter config uses `porter.toml` (or a custom path via `--config`).
### Listen
Set default bind address and port for `porter serve`. CLI flags `--host` and `--port` override these when provided.
```toml
[listen]
host = "127.0.0.1" # Default: "127.0.0.1"
port = 3000 # Default: 3000
```
### MCP Servers
```toml
[servers.<name>]
slug = "unique-id" # Required: identifier used as tool namespace prefix
transport = "stdio" # "stdio" or "http"
enabled = true # Optional: default true
# For stdio transport:
command = "my-mcp-server" # Required for stdio
args = ["--verbose"] # Optional extra args
env.MY_VAR = "${MY_VAR}" # Optional env vars (must use ${VAR} syntax)
cwd = "/path/to/dir" # Optional working directory
# For http transport:
url = "https://mcp.example.com/mcp" # Required for http
# Tool access gate (optional, applies to any transport):
allow = ["get_*", "search_*"] # If present, ONLY matching tools are exposed
deny = ["*delete*", "merge_*"] # Matching tools are always blocked
```
### Tool Access Gate
Each server accepts optional `allow` and `deny` lists that gate which of its
tools Porter exposes. The gate is enforced in **both** directions: denied tools
are hidden from the aggregated tool listing **and** rejected before any call is
forwarded to the downstream server (Porter never proxies-then-filters).
Matching is applied to the downstream (un-namespaced) tool name. Each entry is
an exact name or a simple glob:
| Pattern | Matches | Example |
|------------|--------------------|----------------------|
| `name` | exact match | `merge_pull_request` |
| `prefix*` | prefix match | `jira_get_*` |
| `*suffix` | suffix match | `*_issue` |
| `*inner*` | substring match | `*delete*` |
The `*` wildcard is honored **only** as a single leading and/or trailing
character (the four forms above). A `*` anywhere else — an interior star like
`delete*confirm`, or a doubled `**abc` — would be treated as a literal and
silently match nothing, so Porter **rejects such patterns at startup** rather
than fail open.
A tool is permitted **iff** (`allow` is omitted **or** it matches an `allow`
entry) **and** it matches **no** `deny` entry. **Deny always wins over allow** —
a tool listed in both is blocked (Porter logs a warning at startup). With no
`allow`/`deny` configured, every backend tool is proxied. An explicit empty
list (`allow = []`) blocks every tool. A bare `*` matches everything.
Blocked tools never reach the backend: a call to one is rejected by Porter
itself with an access-policy error naming the tool, server, and reason.
> **Generic executors**: Some servers front an entire API behind a single
> "run this command" tool (e.g. `awslabs.aws-api-mcp-server`'s `call_aws`).
> Porter's gate matches tool *names*, so it cannot tell a read from a write when
> both ride inside one tool — for those servers, enforce read-only with the
> server's own switch (e.g. `READ_OPERATIONS_ONLY=true`).
### Full Example
```toml
[servers.filesystem]
slug = "fs"
transport = "stdio"
command = "mcp-server-filesystem"
args = ["/home/user/projects"]
[servers.github-mcp]
slug = "gh-mcp"
transport = "stdio"
command = "gh-mcp"
env.GITHUB_TOKEN = "${GITHUB_TOKEN}"
```
## Usage
### porter serve
Start a Streamable HTTP MCP server:
```bash
# Default: localhost:3000, config from porter.toml
porter serve
# Custom config, port, and host
porter serve --config my-tools.toml --port 8080 --host 0.0.0.0
```
Options:
- `--config` / `-c`: Path to config file (default: `./porter.toml` or `~/.config/porter/porter.toml`)
- `--port` / `-p`: HTTP port (overrides `[listen].port` from config; default: `3000`)
- `--host`: Bind address (overrides `[listen].host` from config; default: `127.0.0.1`)
**Hot-reload**: Porter watches the config file for changes. When you edit `porter.toml`, Porter automatically reloads the tool surface and sends a `tools/list_changed` notification to all connected MCP clients — no restart required.
MCP endpoint: `http://<host>:<port>/mcp`
### porter stdio
Bridge all configured tools over STDIO for Claude Desktop and other STDIO-based MCP clients:
```bash
porter stdio
porter stdio --config /path/to/porter.toml
```
Options:
- `--config` / `-c`: Path to config file (default: `./porter.toml` or `~/.config/porter/porter.toml`)
**Hot-reload**: Same as `porter serve` — Porter watches the config file for changes and sends a `tools/list_changed` notification to the connected client automatically. No restart required.
## Client Configuration
Porter searches for config in order: `./porter.toml` then `~/.config/porter/porter.toml`. If you place your config at `~/.config/porter/porter.toml`, the examples below work without `--config`.
### Claude Code
```bash
claude mcp add porter -- porter stdio
```
Or with an explicit config path:
```bash
claude mcp add porter -- porter stdio --config ~/.config/porter/porter.toml
```
### Claude Desktop
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"porter": {
"command": "porter",
"args": ["stdio"]
}
}
}
```
### Cursor
Add to Cursor MCP settings (`.cursor/mcp.json`):
```json
{
"mcpServers": {
"porter": {
"command": "porter",
"args": ["stdio"]
}
}
}
```
### VS Code / Copilot
Add to `.vscode/mcp.json`:
```json
{
"servers": {
"porter": {
"type": "stdio",
"command": "porter",
"args": ["stdio"]
}
}
}
```
### Windsurf
Add to `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"porter": {
"command": "porter",
"args": ["stdio"]
}
}
}
```
### Any Streamable HTTP client
Start `porter serve` and connect to the endpoint:
```
http://127.0.0.1:3000/mcp
```
## Use-Case Recipes
`porter.example.toml` includes ready-to-uncomment config blocks for connecting AI agents to popular project management, code review, and cloud infrastructure platforms via MCP servers. Each recipe documents every exposed tool by access tier and ships a read-only-by-default `deny` list you can loosen.
### Access Tiers
| Tier | Default | Description | Examples |
|------|---------|-------------|----------|
| **Read** | Enabled | Passive observation — no side effects | search, list, get, view, diff |
| **Feedback** | Enabled | Participate in workflows without destructive side effects | create issues, add comments, update labels/status |
| **Destructive** | Denied | Irreversible or high-impact actions | delete, merge, close, archive |
| **Code Write** | Denied | Significant mutations to source code | push files, create branches, fork repos |
Each recipe ships a `deny` list (see [Tool Access Gate](#tool-access-gate)) that blocks its **Destructive** and **Code Write** tools out of the box, so **Read** and **Feedback** tools are the only ones exposed by default. To opt a blocked tool back in, delete its line from that server's `deny` list after reviewing the implications.
### Available Recipes
| Platform | Package | Transport | Auth |
|----------|---------|-----------|------|
| GitHub | `@modelcontextprotocol/server-github` | STDIO | `GITHUB_TOKEN` |
| GitLab | `@modelcontextprotocol/server-gitlab` | STDIO | `GITLAB_TOKEN` (+ optional `GITLAB_API_URL`) |
| Linear | `mcp-remote` → `https://mcp.linear.app/sse` | STDIO (remote bridge) | OAuth (browser flow) |
| Jira | `mcp-atlassian` (via `uvx`) | STDIO | `JIRA_URL` + `JIRA_USERNAME` + `JIRA_API_TOKEN` |
| ClickUp | `clickup-mcp-server` | STDIO | `CLICKUP_API_KEY` + `CLICKUP_TEAM_ID` |
| AWS | `awslabs.aws-api-mcp-server` (via `uvx`) | STDIO | `AWS_PROFILE` (+ `READ_OPERATIONS_ONLY=true` for read-only) |
To use a recipe: open `porter.example.toml`, find the platform block, uncomment it, set the required environment variables, and copy it into your `porter.toml`.
> **Note**: These are third-party MCP servers maintained by their respective communities. Review each server's documentation and security posture before exposing it to AI agents in production environments.
## Safety
- **Tool namespacing**: Each server's tools are prefixed with its slug to prevent name collisions
- **Tool access gate**: Per-server `allow`/`deny` lists hide blocked tools from listings and reject them before they reach the downstream server ([details](#tool-access-gate))
- **Environment isolation**: Child STDIO servers do **not** inherit Porter's environment. Each child starts from a cleared environment with only a minimal baseline (`PATH`, `HOME`, `LANG`) plus the vars declared in its `env` block — Porter's own secrets are never leaked downstream
- **Health tracking**: Unhealthy servers are automatically excluded from tool listings and calls
- **Hot-reload**: Config changes are picked up automatically without restart
## License
Licensed under MIT OR Apache-2.0 at your option.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available