MCP tunnels
An MCP tunnel connects Syntic to an MCP server that lives inside your private network. You run a small connector process next to the server. The connector opens an outbound TLS connection to Syntic on port 443 and keeps it open. When a request or an agent session calls a tool on that server, Syntic sends the call down the tunnel and the connector forwards it to the server. Nothing in your network has to accept inbound traffic, and the server never needs a public address.
Preview flag: syntic-beta: mcp-tunnels-2026-12-01. Tunnels belong to a workspace and are managed with that workspace’s inference keys.
How a call travels
- Your request lists an MCP server with a
tunnel_id(see MCP connector). - Syntic checks the organization’s MCP policy, then picks one of the tunnel’s online connectors.
- The connector checks the target URL against the tunnel’s
allowed_targets. It refuses anything else, even if Syntic asked for it. - The connector forwards the MCP request, streams the reply back, and Syntic hands the tool result to the model.
Using a tunnel in a request
{
"model": "Amara S1.2",
"messages": [{ "role": "user", "content": "What is the stock level of part 7731-B?" }],
"mcp_servers": [
{
"name": "erp",
"url": "http://erp-mcp.internal:8080/mcp",
"tunnel_id": "tnl_01JCE4G6J8L0N2Q4S6U8",
"allowed_tools": ["get_stock_level"]
}
]
}The url is the address as the connector sees it inside your network. It must exactly match one of the tunnel’s allowed_targets.
Running the connector
docker run -d --name syntic-tunnel --restart unless-stopped \
-e SYNTIC_TUNNEL_TOKEN="$SYNTIC_TUNNEL_TOKEN" \
syntic/tunnel-connector:0.9| Setting | Default | Meaning |
|---|---|---|
SYNTIC_TUNNEL_TOKEN | required in token mode | The stn_ token from create or rotate. |
SYNTIC_TUNNEL_CERT, SYNTIC_TUNNEL_KEY | required in mtls mode | Paths to the certificate from Issue a certificate and its private key. |
SYNTIC_TUNNEL_MAX_INFLIGHT | 64 | Concurrent calls this connector accepts. |
SYNTIC_TUNNEL_LOG | info | debug logs every forwarded call without bodies. |
Run two connectors on different hosts for redundancy. Calls are spread across all online connectors, and a connector that drops has its in-flight calls retried once on another one.
Limits
| Limit | Value |
|---|---|
| Tunnels per workspace | 25 |
| Connectors per tunnel | 10 |
allowed_targets per tunnel | 20 |
| Largest single MCP message | 10 MB |
| Call timeout | 120 seconds, the same as public MCP servers |
Errors seen by requests
| Status | code | Meaning |
|---|---|---|
| 404 | tunnel_not_found | The tunnel_id does not exist in the request’s workspace. |
| 403 | mcp_server_not_allowed | The organization’s MCP policy does not list this tunnel. |
| 502 | tunnel_unavailable | No connector is online, or the tunnel is disabled. |
| 502 | tunnel_target_refused | The connector refused the URL because it is not in allowed_targets. |
| 504 | tunnel_timeout | The server behind the tunnel did not answer within 120 seconds. |
Endpoints
| Operation | Endpoint |
|---|---|
| Create a tunnel | POST /v1/tunnels |
| List tunnels | GET /v1/tunnels |
| Get a tunnel | GET /v1/tunnels/{tunnel_id} |
| Update a tunnel | PATCH /v1/tunnels/{tunnel_id} |
| Delete a tunnel | DELETE /v1/tunnels/{tunnel_id} |
| Rotate token | POST /v1/tunnels/{tunnel_id}/rotate_token |
| Connections | GET /v1/tunnels/{tunnel_id}/connections |
| Issue a certificate | POST /v1/tunnels/{tunnel_id}/certificates |
| List certificates | GET /v1/tunnels/{tunnel_id}/certificates |
| Get a certificate | GET /v1/tunnels/{tunnel_id}/certificates/{certificate_id} |
| Revoke a certificate | POST /v1/tunnels/{tunnel_id}/certificates/{certificate_id}/revoke |