需自行编写 nexus.toml 配置,且每个 LLM 供应商至少配置一个模型;STDIO 服务器必须在 stdout 输出合法 JSON-RPC 消息;默认监听 127.0.0.1:8000,Docker 部署需映射端口并挂载配置文件
curl -fsSL https://nexusrouter.com/install | bash
docker run -p 8000:8000 -v /path/to/config:/etc/nexus.toml ghcr.io/grafbase/nexus:latestREADME 未说明
把多个 MCP 服务器和 LLM 供应商统一到一个端点,集中治理与管控 AI 调用
面向 LLM 应用的统一网关,集中治理与保护各家大模型 API 调用,支持 MCP 协议接入。
核心能力
- 聚合 STDIO、SSE、HTTP 三类 MCP 服务器到单一端点
- 统一路由 OpenAI、Anthropic、Google、AWS Bedrock 并支持工具调用
- 用自然语言模糊搜索所有已连接工具
- 内置 CORS、CSRF、OAuth2 与 TLS 安全机制
- 支持全局、按 IP、按服务器、按工具的多级限流
容易踩的坑
- 0.6.0Nexus 0.6.0最新正式版2025-09-29 · 2025-09-29 17:14 UTC
Nexus 0.6.0 - September 29, 2025
Summary
This maintenance release focuses on improving client IP extraction for better rate limiting support, enhancing telemetry metrics with proper units, and reorganizing the test infrastructure for better maintainability.
Breaking Changes
Telemetry Metrics Unit Change
Impact: OpenTelemetry metrics are now reported with proper unit annotations.
Change: Histogram metrics now include the
s(seconds) unit annotation, aligning with OpenTelemetry specifications for proper metric interpretation in observability platforms. Previously, metrics were reported in milliseconds.New Features
Configurable Client IP Extraction
User Impact: Administrators can now configure how Nexus determines client IP addresses for rate limiting and logging, essential for deployments behind proxies or load balancers.
Configuration:
[server.client_ip] # Use X-Real-Ip header to retrieve the client IP address (default: false) x_real_ip = true # Use X-Forwarded-For header with trusted hop count # Absent by default, which deactivates this method x_forwarded_for_trusted_hops = 1Resolution Order:
X-Real-Ipheader (if enabled)X-Forwarded-Forheader at specified hop (if configured)- Connection source IP (fallback)
Enhancements
HTTP Client Optimization for LLM Providers
User Impact: Improved reliability and performance when communicating with LLM providers.
Technical Details:
- Standardized HTTP client configuration across all LLM providers
- Applied common defaults from Grafbase Gateway for consistency
- Modified:
crates/llm/src/provider/http_client.rs - Affects: OpenAI, Anthropic, Google, and Bedrock providers
Dependency Updates
- Updated OpenTelemetry Rust monorepo to v0.31
- Updated regex crate to v1.11.3
- Updated various GitHub Actions to latest versions
- Switched to forked fastrace for compatibility fixes
Deployment Notes
Configuration Changes
If you're running Nexus behind a proxy or load balancer, you should configure client IP extraction:
- For single proxy (nginx, Cloudflare, etc.):
[server.client_ip] x_real_ip = true- For proxy chain:
[server.client_ip] x_forwarded_for_trusted_hops = 2 # Adjust based on your proxy countObservability Updates
Metrics were previously reported in milliseconds without any unit, now they're reported in seconds (
s) - 0.5.3Nexus 0.5.32025-09-24 · 2025-09-24 15:47 UTC
查看更新说明
Nexus 0.5.3 - September 24, 2025
Summary
This patch release focuses on improving observability and telemetry, fixing a critical issue with Anthropic Claude's tool result handling, and enhancing the startup logging experience. The release also includes a dependency update for the MCP client library.
Bug Fixes
Fixed Duplicate Tool Results in Anthropic Claude Responses
Issue: When using Claude models through the Anthropic provider, tool call results followed by text content were being duplicated in the response stream, causing confusing output for users.
Resolution: Improved the message conversion logic in the Anthropic provider to correctly handle mixed tool results and text content without duplication.
Technical Details:
- Modified the unified-to-Anthropic message conversion in
crates/llm/src/messages/unified/to_anthropic.rs - Updated input processing in
crates/llm/src/provider/anthropic/input.rs
Fixed Telemetry Child Span Sampling Issue
Issue: Child spans were being sent to the telemetry collector even when their parent span was not sampled, causing unnecessary overhead and confusing trace data.
Resolution: Implemented proper span sampling inheritance to ensure child spans respect parent sampling decisions.
Technical Details:
- Enhanced tracing logic in multiple components to check parent span sampling status
- Added new utility function in
crates/telemetry/src/tracing.rsfor consistent sampling behavior - Updated tracing implementations across LLM, MCP, and rate-limit crates
Restored Nexus Version Logging at Startup
Issue: The Nexus version was not being logged at startup, making it difficult to identify which version was running in production environments.
Resolution: Moved version logging to immediately after logger initialization in the server crate, ensuring it's the first informational message users see.
Technical Details:
- Moved version logging from
nexus/src/main.rstocrates/server/src/lib.rs - Added
versionfield toServeConfigstruct for proper version passing - Updated integration tests to handle the new version parameter
- Ensures consistent version logging across all deployment scenarios
Dependency Updates
Updated RMCP Client Library
Change: Updated the
rmcpcrate from version 0.6.x to 0.7.0Impact: This update brings improvements to the MCP (Model Context Protocol) client implementation, potentially including bug fixes and performance enhancements for downstream MCP server connections.
Deployment Notes
This is a patch release with no breaking changes. The update can be deployed directly without any configuration changes or migration steps.
- Modified the unified-to-Anthropic message conversion in
- 0.5.2Nexus 0.5.22025-09-24 · 2025-09-24 11:09 UTC
查看更新说明
Nexus 0.5.2 - September 24, 2025
Summary
This release introduces Role-Based Access Control (RBAC) for MCP tools, allowing fine-grained control over which users can access specific MCP servers and tools based on group membership. The implementation supports allow/deny lists at both server and tool levels, providing flexible security policies for enterprise deployments. Additionally, this release includes a fix for unconnected LLM spans in telemetry and various dependency updates.
New Features
Role-Based Access Control for MCP Tools
User Impact: Administrators can now restrict access to MCP servers and individual tools based on user group membership. This enables scenarios like limiting expensive or sensitive tools to specific user groups, blocking suspended users, or creating tiered access levels for different subscription plans.
Technical Details:
- Implemented hierarchical access control with server-level and tool-level rules
- Added support for allow lists and deny lists
- Tool-level rules override server-level rules for fine-grained control
- Empty allow list (
allow = []) blocks all access without requiring client identification - Deny rules take priority over allow rules when both are specified
- Files modified:
crates/mcp/src/access.rs- Core access control logic implementationcrates/config/src/mcp.rs- Configuration structures for allow/deny listscrates/config/src/loader.rs- Configuration validation and loadingcrates/mcp/src/server.rs- Integration of access control into MCP servercrates/mcp/src/server/search.rs- Tool filtering in search resultscrates/mcp/src/server/search/grouped.rs- Grouped search with access filteringcrates/mcp/src/cache.rs- Cache key updates for access control
Configuration Example:
# Server-level access control [mcp.servers.premium_tools] cmd = ["premium-server"] allow = ["premium", "enterprise"] # Only these groups can access deny = ["suspended"] # Block specific groups # Tool-level override (more specific than server-level) [mcp.servers.premium_tools.tools.expensive_feature] allow = ["enterprise"] # Only enterprise can use this tool [mcp.servers.premium_tools.tools.deprecated_tool] allow = [] # Empty allow list blocks all accessBug Fixes
Fixed Unconnected LLM Spans in Telemetry
Issue: LLM operations were creating orphaned trace spans that weren't properly connected to the parent trace context, making it difficult to track request flows in distributed tracing systems.
Resolution: Updated span creation logic to properly attach LLM spans to their parent context, ensuring complete trace hierarchies.
Technical Details:
- Modified span parent assignment in LLM telemetry layer
- Files modified:
crates/server/src/tracing.rs, telemetry tracing tests - Ensures proper correlation of LLM requests with their originating API calls
Infrastructure/DevOps Changes
Dependency Updates
User Impact: Improved security, performance, and compatibility through updated dependencies.
Technical Details:
- Updated
taiki-e/install-actionfrom v2.61.12 to v2.62.5 - CI/CD improvements - Updated
opentelemetry-collector-contribDocker image to v0.136.0 - Enhanced telemetry capabilities - Updated
tempfilecrate to v3.23.0 - Improved temporary file handling - Updated
serdeto v1.0.226 - Serialization improvements - Updated
clapto v4.5.48 - CLI argument parsing enhancements - Regular
cargo updatefor transitive dependency updates
Deployment Notes
Configuration Changes
For users upgrading from 0.5.1, the following new RBAC configuration options are available:
# MCP server-level access control [mcp.servers.your_server] allow = ["group1", "group2"] # Optional: whitelist groups deny = ["blocked_group"] # Optional: blacklist groups # Tool-level access control (overrides server-level) [mcp.servers.your_server.tools.specific_tool] allow = ["special - 0.5.1Nexus 0.5.12025-09-19 · 2025-09-19 19:09 UTC
查看更新说明
Nexus 0.5.1 - September 19, 2025
Summary
This release enhances the telemetry capabilities with advanced OTLP (OpenTelemetry Protocol) exporter configuration, adds support for OpenAI Codex CLI for the Anthropic protocl, improves logging with UTC timestamps, and includes dependency updates for improved security and performance.
New Features
Enhanced OTLP Telemetry Support
User Impact: Users can now configure OTLP exporters with TLS support for secure telemetry data transmission and custom headers for authentication with observability platforms.
Technical Details:
- Added TLS configuration support for gRPC OTLP exporters
- Implemented custom header support for both gRPC and HTTP OTLP protocols
- Files modified:
crates/config/src/telemetry/exporters.rs,crates/telemetry/src/metadata.rs - New configuration options:
telemetry.exporters.otlp.grpc.tls.domain_name- SNI domain for TLS verificationtelemetry.exporters.otlp.grpc.tls.ca_certificate- Custom CA certificate pathtelemetry.exporters.otlp.grpc.tls.client_certificate- Client certificate for mTLStelemetry.exporters.otlp.grpc.tls.client_key- Client private key for mTLStelemetry.exporters.otlp.grpc.headers- Custom gRPC metadata headerstelemetry.exporters.otlp.http.headers- Custom HTTP headers
OpenAI Codex CLI Support
User Impact: Users can now use the OpenAI Codex CLI with Nexus. The provider fully supports OpenAI-compatible formats including function calling and tool usage, enabling seamless integration with the Codex CLI and other OpenAI-compatible tools.
Technical Details:
- Added support for function calling and tool use, e.g. fixed a few bugs so Anthrpic provider works well with Codex.
- Files modified:
crates/llm/src/provider/anthropic/input.rs,crates/llm/src/messages/unified.rs
Enhancements/Improvements
Improved Logging with UTC Timestamps
User Impact: All log timestamps are now consistently formatted in UTC with microsecond precision, making it easier to correlate logs across distributed systems and time zones.
Technical Details:
- Replaced local timezone logging with UTC timestamps
- Added microsecond precision to timestamps (format:
YYYY-MM-DDTHH:MM:SS.ffffffZ) - Files modified:
crates/server/src/logger.rs,crates/telemetry/src/logs.rs - Removed legacy logger implementation from
nexus/src/logger.rs - Improved terminal detection for color output control
Infrastructure/DevOps Changes
Dependency Updates
User Impact: Improved security and performance through updated dependencies.
Technical Details:
- Updated
anyhowfrom 1.0.99 to 1.0.100 - Enhanced error handling - Updated
rustlsfrom 0.23.31 to 0.23.32 - Security improvements for TLS - Updated various transitive dependencies in
Cargo.lock
Deployment Notes
Configuration Changes
For users upgrading from 0.5.0, the following new configuration options are available but not required:
# OTLP TLS Configuration (optional) [telemetry.exporters.otlp.grpc.tls] domain_name = "telemetry.example.com" ca_certificate = "/path/to/ca.pem" client_certificate = "/path/to/client.pem" client_key = "/path/to/client.key" # OTLP Headers (optional) [telemetry.exporters.otlp.grpc.headers] authorization = "Bearer your-token" x-custom-header = "value"Breaking Changes
None - this release maintains full backward compatibility with 0.5.0.
Migration Steps
No migration required. The new features are opt-in and existing configurations will continue to work without modification.
Performance Considerations
- TLS configuration for OTLP may add slight latency to telemetry exports but improves security
- 0.5.0Nexus 0.5.02025-09-19 · 2025-09-19 12:46 UTC
查看更新说明
Nexus 0.5.0 - September 18, 2025
Summary
This release introduces native Anthropic API support, multi-endpoint LLM configuration, and security through the adoption of Chainguard's Wolfi base image for Docker deployments.
New Features
Native Anthropic API Support
User Impact: Full native support for the Anthropic API protocol. Users can now interact with Claude using Anthropic's native message format, tool calling conventions, and streaming patterns. The implementation is tested with Claude Code, which works fluently with many of the models supported by Nexus, excluding the ones with no tool calling support.
Technical Details:
- Protocol Support: Complete Anthropic Messages API implementation
- Files Added:
crates/llm/src/messages/anthropic.rs- Anthropic message typescrates/integration-tests/tests/llm/anthropic/- Test suite
- Features:
- Native tool calling with Anthropic's specific format
- Streaming support with proper event handling
- Claude Code compatibility
Enhanced Security with Chainguard Wolfi Base Image
User Impact: Improved security for Docker deployments. The new Chainguard Wolfi-based container image reduces attack surface and eliminates known vulnerabilities present in the previous Debian-based image.
Security Improvements:
- Previous Debian Image: 48 known vulnerabilities (2 Critical, 16 High, 25 Medium, 5 Low)
- New Wolfi Image: Minimal attack surface with distroless design
- Benefits:
- Reduced container size (smaller attack surface)
- Regular security updates from Chainguard
- Hardened by default with minimal packages
- Compliance-ready for security-conscious deployments
Technical Details:
- Base Image:
cgr.dev/chainguard/wolfi-base:latest - Migration: Seamless upgrade - no configuration changes required
- Compatibility: Full backward compatibility maintained
- File Modified:
Dockerfile- Complete rebuild with multi-stage optimization
Multi-Endpoint LLM Configuration
User Impact: Configure multiple LLM endpoints with different protocols on the same Nexus instance. This allows serving both OpenAI and Anthropic compatible endpoints simultaneously, making it easier to support diverse client applications.
Technical Details:
- Configuration: New
[llm.protocols]section in configuration - Files Modified:
crates/config/src/llm.rs- Enhanced endpoint configuration parsingcrates/server/src/lib.rs- Multi-endpoint routing setup
- Example Configuration:
[llm.protocols.openai] enabled = true path = "/llm/openai" [llm.protocols.anthropic] enabled = true path = "/llm/anthropic" - Routing: Dynamic endpoint registration based on configuration
- Default Behavior: OpenAI endpoint enabled by default at
/llm/openai, Anthropic disabled by default
Dependency Updates
User Impact: Improved stability, performance, and security through regular dependency maintenance.
Key Updates:
- Rust Crates:
rmcpto 0.6.4 - MCP protocol improvements and bug fixesaws-sdk-bedrockruntimeto 1.106.0 - Latest AWS Bedrock supporttonicto 0.14.2 - gRPC/OpenTelemetry communication improvements- Various internal dependencies updated for stability
- CI/CD:
- GitHub Actions tooling kept up-to-date
- Security: Regular updates to maintain security posture, no critical CVEs addressed
Breaking Changes
LLM Endpoint Configuration
The LLM endpoint configuration has changed from a single path to protocol-specific paths:
Previous (0.4.x):
[llm] enabled = true path = "/llm" # Single endpoint pathNew (0.5.0):
[llm] enabled = true [llm.protocols.openai] enabled = true path = "/llm" # OpenAI protocol endpoint [llm.protocols.anthropic] enabled = false #
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Features
- MCP Server Aggregation: Connect multiple MCP servers (STDIO, SSE, HTTP) through a single endpoint
- LLM Provider Routing: Unified interface for OpenAI, Anthropic, Google, and AWS Bedrock LLM providers with full tool calling support
- Context-Aware Tool Search: Intelligent fuzzy search across all connected tools using natural language queries
- Protocol Support: Supports STDIO (subprocess), SSE (Server-Sent Events), and streamable HTTP MCP servers
- Flexible Configuration: TOML-based configuration with environment variable substitution
- Security: Built-in CORS, CSRF protection, OAuth2, and TLS support
- Rate Limiting: Multi-level rate limiting with in-memory or Redis backends
- Docker Ready: Available as a container image with minimal configuration needed
Installation
Quick Install (Linux/Windows (WSL)/macOS)
curl -fsSL https://nexusrouter.com/install | bash
Docker
Pull the latest image:
docker pull ghcr.io/grafbase/nexus:latest
Or use the stable version:
docker pull ghcr.io/grafbase/nexus:stable
Or use a specific version:
docker pull ghcr.io/grafbase/nexus:X.Y.Z
Build from Source
git clone https://github.com/grafbase/nexus
cd nexus
cargo build --release
Running Nexus
Using the Binary
nexus
Using Docker
docker run -p 8000:8000 -v /path/to/config:/etc/nexus.toml ghcr.io/grafbase/nexus:latest
Docker Compose Example
services:
nexus:
image: ghcr.io/grafbase/nexus:latest
ports:
- "8000:8000"
volumes:
- ./nexus.toml:/etc/nexus.toml
environment:
- GITHUB_TOKEN=${GITHUB_TOKEN}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
Configuration
Create a nexus.toml file to configure Nexus:
# LLM Provider configuration
[llm.providers.openai]
type = "openai"
api_key = "{{ env.OPENAI_API_KEY }}"
forward_token = true
# Model configuration (at least one model required per provider)
[llm.providers.openai.models.gpt-4]
[llm.providers.openai.models.gpt-3-5-turbo]
[llm.providers.anthropic]
type = "anthropic"
api_key = "{{ env.ANTHROPIC_API_KEY }}"
[llm.providers.anthropic.models.claude-3-5-sonnet-20241022]
# MCP Server configuration
[mcp.servers.github]
url = "https://api.githubcopilot.com/mcp/"
auth.token = "{{ env.GITHUB_TOKEN }}"
[mcp.servers.filesystem]
cmd = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/home/YOUR_USERNAME/Desktop"]
[mcp.servers.python_server]
cmd = ["python", "-m", "mcp_server"]
env = { PYTHONPATH = "/opt/mcp" }
cwd = "/workspace"
Configuration Options
Server Configuration
server.listen_address: The address and port Nexus will listen on (default:127.0.0.1:8000)server.health.enabled: Enable health endpoint (default:true)server.health.path: Health check endpoint path (default:/health)
LLM Configuration
llm.enabled: Enable LLM functionality (default:true)llm.protocols.openai.enabled: Enable OpenAI protocol endpoint (default:true)llm.protocols.openai.path: OpenAI endpoint path (default:/llm/openai)llm.protocols.anthropic.enabled: Enable Anthropic protocol endpoint (default:false)llm.protocols.anthropic.path: Anthropic endpoint path (default:/llm/anthropic)
For detailed LLM provider configuration, see the LLM Provider Configuration section below.
MCP Configuration
mcp.enabled: Enable MCP functionality (default:true)mcp.path: MCP endpoint path (default:/mcp)mcp.enable_structured_content: Control MCP search tool response format (default:true)- When
true: Uses modernstructuredContentfield for better performance and type safety - When
false: Uses legacycontentfield withContent::jsonobjects for compatibility with older MCP clients
- When
MCP Server Types
-
STDIO Servers: Launch local processes that communicate via standard input/output
[mcp.servers.my_tool] cmd = ["path/to/executable", "--arg1", "--arg2"] # Optional: Set environment variables env = { DEBUG = "1", API_KEY = "{{ env.MY_API_KEY }}" } # Optional: Set working directory cwd = "/path/to/working/directory" # Optional: Configure stderr handling (default: "null") stderr = "inherit" # Show in console # or stderr = { file = "/var/log/mcp/server.log" } # Log to fileNote: STDIO servers must output valid JSON-RPC messages on stdout. The
cmdarray must have at least one element (the executable). -
SSE Servers: Connect to Server-Sent Events endpoints
[mcp.servers.my_sse_server] protocol = "sse" url = "http://example.com/sse" message_url = "http://example.com/messages" # Optional -
HTTP Servers: Connect to streamable HTTP endpoints
[mcp.servers.my_http_server] protocol = "streamable-http" url = "https://api.example.com/mcp"
For remote MCP servers, if you omit the protocol Nexus will first try streamable HTTP and then SSE.
Authentication
Add service token authentication to any server:
[mcp.servers.my_server.auth]
token = "your-token-here"
# Or use environment variables
token = "{{ env.MY_API_TOKEN }}"
If you enable OAuth2 authentication to your server, and your downstream servers all use the same authentication server, you can configure Nexus to forward the request access token to the downstream server.
[mcp.servers.my_server.auth]
type = "forward"
Header Insertion for MCP Servers
Nexus supports inserting static headers when making requests to MCP servers. Headers can be configured globally (for all MCP servers) or per-server.
Note: MCP currently only supports header insertion with static values. Headers from incoming requests are not forwarded.
Global MCP Headers
Configure headers that apply to all MCP servers:
# Global headers for all MCP servers
[[mcp.headers]]
rule = "insert"
name = "X-Application"
value = "nexus-router"
[[mcp.headers]]
rule = "insert"
name = "X-API-Version"
value = "v1"
Server-Specific Headers
Configure headers for individual HTTP-based MCP servers:
[mcp.servers.my_server]
url = "https://api.example.com/mcp"
# Insert headers for this specific server
[[mcp.servers.my_server.headers]]
rule = "insert"
name = "X-API-Key"
value = "{{ env.MY_API_KEY }}" # Environment variable substitution
[[mcp.servers.my_server.headers]]
rule = "insert"
name = "X-Service-Name"
value = "my-service"
MCP Header Features
- Static Values Only: Headers are set at client initialization time with static values
- Environment Variables: Use
{{ env.VAR_NAME }}syntax for environment variable substitution - HTTP Servers Only: Headers only apply to HTTP-based MCP servers (not STDIO servers)
- Insert Rule: Currently only the
insertrule is supported for MCP
MCP Access Control
Restrict access to MCP servers and tools based on user groups:
# Server-level access control
[mcp.servers.premium_tools]
cmd = ["premium-server"]
allow = ["premium", "enterprise"] # Only these groups can access
deny = ["suspended"] # Block specific groups
# Tool-level override (more specific than server-level)
[mcp.servers.premium_tools.tools.expensive_feature]
allow = ["enterprise"] # Only enterprise can use this tool
[mcp.servers.premium_tools.tools.deprecated_tool]
allow = [] # Empty allow list blocks all access (no client ID needed)
Access control rules:
- If
allowis set, only listed groups can access (requires client identification) - If
denyis set, listed groups are blocked (requires client identification) - Empty
allow = []blocks all access without requiring client identification - Tool-level rules override server-level rules
- Deny takes priority over allow
OAuth2 Authentication
Configure OAuth2 authentication to protect your Nexus endpoints:
[server.oauth]
url = "https://your-oauth-provider.com/.well-known/jwks.json"
poll_interval = "5m"
expected_issuer = "https://your-oauth-provider.com"
expected_audience = "your-service-audience"
[server.oauth.protected_resource]
resource = "https://your-nexus-instance.com"
authorization_servers = ["https://your-oauth-provider.com"]
OAuth2 configuration options:
url: JWKs endpoint URL for token validationpoll_interval: How often to refresh JWKs (optional, default: no polling)expected_issuer: Expectedissclaim in JWT tokens (optional)expected_audience: Expectedaudclaim in JWT tokens (optional)protected_resource.resource: URL of this protected resourceprotected_resource.authorization_servers: List of authorization server URLs
When OAuth2 is enabled, all endpoints except /health and /.well-known/oauth-protected-resource require valid JWT tokens in the Authorization: Bearer <token> header.
Rate Limiting
Nexus supports rate limiting to prevent abuse and ensure fair resource usage:
# Global rate limiting configuration
[server.rate_limits]
enabled = true
# Storage backend configuration
[server.rate_limits.storage]
type = "memory" # or "redis" for distributed rate limiting
# For Redis backend:
# url = "redis://localhost:6379"
# key_prefix = "nexus:rate_limit:"
# Global rate limit (applies to all requests)
[server.rate_limits.global]
limit = 1000
interval = "60s"
# Per-IP rate limit
[server.rate_limits.per_ip]
limit = 100
interval = "60s"
# Per-MCP server rate limits
[mcp.servers.my_server.rate_limits]
limit = 50
interval = "60s"
# Tool-specific rate limits (override server defaults)
[mcp.servers.my_server.rate_limits.tools]
expensive_tool = { limit = 10, interval = "60s" }
cheap_tool = { limit = 100, interval = "60s" }
Rate Limiting Features:
- Multiple levels: Global, per-IP, per-server, and per-tool limits
- Storage backends: In-memory (single instance) or Redis (distributed)
- Flexible intervals: Configure time windows for each limit
- Tool-specific overrides: Set different limits for expensive operations
Redis Backend Configuration:
[server.rate_limits.storage]
type = "redis"
url = "redis://localhost:6379"
key_prefix = "nexus:rate_limit:"
response_timeout = "1s"
connection_timeout = "5s"
# Connection pool settings
[server.rate_limits.storage.pool]
max_size = 16
min_idle = 0
timeout_create = "5s"
timeout_wait = "5s"
timeout_recycle = "300s"
# TLS configuration for Redis (optional)
[server.rate_limits.storage.tls]
enabled = true
ca_cert_path = "/path/to/ca.crt"
client_cert_path = "/path/to/client.crt" # For mutual TLS
client_key_path = "/path/to/client.key"
# insecure = true # WARNING: Only for development/testing, skips certificate validation
Note: When configuring tool-specific rate limits, Nexus will warn if you reference tools that don't exist.
LLM Token Rate Limiting
Nexus provides token-based rate limiting for LLM providers to help control costs and prevent abuse. Unlike request-based rate limits, token rate limits count an estimate of actual tokens consumed.
Prerequisites
IMPORTANT: LLM rate limiting requires client identification to be enabled:
[server.client_identification]
enabled = true
# Choose identification methods (at least one required)
client_id.jwt_claim = "sub" # Extract ID from JWT 'sub' claim
# or
client_id.http_header = "X-Client-ID" # Extract ID from HTTP header
# Optional: Limit groups per user (at most one allowed)
group_id.jwt_claim = "groups" # JWT claim containing user's group
# or
group_id.http_header = "X-Group-ID" # Extract ID from HTTP header
# You must provide a list of allowed groups
[server.client_identification.validation]
group_values = ["free", "pro", "max"]
Without client identification, rate limits cannot be enforced and requests will fail with a configuration error.
Configuration Hierarchy
Token rate limits can be configured at four levels, from most to least specific:
- Model per user + group: Specific model for specific each user in a group
- Model per user: Specific model for each user
- Provider per user + group: All models from provider for each user in a group
- Provider per user: All models from provider for each user
The most specific applicable limit is always used.
Basic Configuration
# Provider-level default rate limit (applies to all models)
[llm.providers.openai.rate_limits.per_user]
input_token_limit = 100000 # 100K input tokens
interval = "1m" # Per minute
# Model-specific rate limit (overrides provider default)
[llm.providers.openai.models.gpt-4.rate_limits.per_user]
input_token_limit = 50000 # More restrictive for expensive model
interval = "30s"
Group-Based Rate Limits
Configure different limits for user groups (requires group_id and group_values in client identification):
# Provider-level group limits
[llm.providers.openai.rate_limits.per_user.groups]
free = { input_token_limit = 10000, interval = "60s" }
pro = { input_token_limit = 100000, interval = "60s" }
enterprise = { input_token_limit = 1000000, interval = "60s" }
# Model-specific group limits (override provider groups)
[llm.providers.openai.models.gpt-4.rate_limits.per_user.groups]
free = { input_token_limit = 5000, interval = "60s" }
pro = { input_token_limit = 50000, interval = "60s" }
enterprise = { input_token_limit = 500000, interval = "60s" }
The limits are per user, but you can define different limits if the user is part of a specific group. If the user does not belong to any group, they will be assigned to the per-user limits.
Complete Example
# Client identification (REQUIRED for rate limiting)
[server.client_identification]
enabled = true
client_id.jwt_claim = "sub"
group_id.jwt_claim = "subscription_tier"
[server.client_identification.validation]
group_values = ["free", "pro", "enterprise"]
# OpenAI provider with rate limiting
[llm.providers.openai]
type = "openai"
api_key = "{{ env.OPENAI_API_KEY }}"
# Provider-level defaults
[llm.providers.openai.rate_limits.per_user]
input_token_limit = 100000
interval = "60s"
[llm.providers.openai.rate_limits.per_user.groups]
free = { input_token_limit = 10000, interval = "60s" }
pro = { input_token_limit = 100000, interval = "60s" }
# GPT-4 specific limits (more restrictive)
[llm.providers.openai.models.gpt-4]
[llm.providers.openai.models.gpt-4.rate_limits.per_user]
input_token_limit = 50000
interval = "60s"
[llm.providers.openai.models.gpt-4.rate_limits.per_user.groups]
free = { input_token_limit = 5000, interval = "60s" }
pro = { input_token_limit = 50000, interval = "60s" }
# GPT-3.5 uses provider defaults
[llm.providers.openai.models.gpt-3-5-turbo]
How Token Counting Works
- Input Tokens Only: Rate limiting is based solely on input tokens counted from the request's messages and system prompts
- No Output Buffering: Output tokens and
max_tokensparameter are NOT considered in rate limit calculations - Pre-check: Input tokens are checked against rate limits before processing
- Token Accumulation: Uses a sliding window algorithm to track usage over time
Note: The rate limiting is designed to be predictable and based only on what the client sends, not on variable output sizes.
Rate Limit Response
When rate limited, the server returns a 429 status code. No Retry-After headers are sent to maintain consistency with downstream LLM provider behavior.
Error Responses
When rate limits are exceeded:
{
"error": {
"message": "Rate limit exceeded: Token rate limit exceeded. Please try again later.",
"type": "rate_limit_error",
"code": 429
}
}
Important Notes
- Per-User Limits: All limits are per individual user/client ID
- No Shared Pools: Currently, there are no shared/global token pools
- Streaming Support: Token counting works with both regular and streaming responses
- Provider Agnostic: Works consistently across all LLM providers
- Validation: Configuration is validated at startup; invalid group names will cause errors
TLS Configuration
Configure TLS for downstream connections:
[mcp.servers.my_server.tls]
verify_certs = true
accept_invalid_hostnames = false
root_ca_cert_path = "/path/to/ca.pem"
client_cert_path = "/path/to/client.pem"
client_key_path = "/path/to/client.key"
LLM Provider Configuration
Nexus provides a unified interface for multiple LLM providers, allowing you to route chat completions through various services with a consistent API.
Enabling LLM Routing
[llm]
enabled = true # Enable LLM functionality (default: true)
# OpenAI protocol endpoint configuration
[llm.protocols.openai]
enabled = true # Enable OpenAI protocol (default: true)
path = "/llm" # Custom path (default: "/llm/openai")
# Anthropic protocol endpoint configuration
[llm.protocols.anthropic]
enabled = true # Enable Anthropic protocol (default: false)
path = "/claude" # Custom path (default: "/llm/anthropic")
Supported Providers
Nexus currently supports four major LLM providers with full tool calling capabilities:
- OpenAI (including OpenAI-compatible APIs) - Full tool calling and parallel execution support
- Anthropic (Claude models) - Tool calling with function definitions and tool choice controls
- Google (Gemini models) - Function calling with parameter schemas and tool selection
- AWS Bedrock (Multiple model families via AWS) - Tool calling support across all supported model families
Provider Configuration
Configure one or more LLM providers in your nexus.toml:
OpenAI Provider
[llm.providers.openai]
type = "openai"
api_key = "{{ env.OPENAI_API_KEY }}"
# Optional: Use a custom base URL (for Azure OpenAI, proxies, or compatible APIs)
base_url = "https://api.openai.com/v1" # Default
# Model Configuration (REQUIRED - at least one model must be configured)
[llm.providers.openai.models.gpt-4]
# Optional: Rename the model for your users
# rename = "smart-model" # Users will see "openai/smart-model"
[llm.providers.openai.models.gpt-3-5-turbo]
# Models without rename use their original ID
Anthropic Provider
[llm.providers.anthropic]
type = "anthropic"
api_key = "{{ env.ANTHROPIC_API_KEY }}"
# Optional: Use a custom base URL
base_url = "https://api.anthropic.com/v1" # Default
# Model Configuration (REQUIRED - at least one model must be configured)
[llm.providers.anthropic.models.claude-3-opus-20240229]
[llm.providers.anthropic.models.claude-3-5-sonnet-20241022]
Google Provider
[llm.providers.google]
type = "google"
api_key = "{{ env.GOOGLE_API_KEY }}"
# Optional: Use a custom base URL
base_url = "https://generativelanguage.googleapis.com/v1beta" # Default
# Model Configuration (REQUIRED - at least one model must be configured)
# Note: Model IDs with dots must be quoted in TOML
[llm.providers.google.models."gemini-1.5-flash"]
[llm.providers.google.models.gemini-pro]
AWS Bedrock Provider
[llm.providers.bedrock]
type = "bedrock"
# Optional: AWS profile to use (defaults to environment settings)
profile = "{{ env.AWS_PROFILE }}"
# Optional: AWS region (defaults to environment or us-east-1)
region = "us-west-2"
# Model Configuration (REQUIRED - at least one model must be configured)
# Bedrock uses model IDs with dots, so they must be quoted
[llm.providers.bedrock.models."anthropic.claude-3-5-sonnet-20241022-v2:0"]
[llm.providers.bedrock.models."anthropic.claude-3-opus-20240229-v1:0"]
[llm.providers.bedrock.models."amazon.nova-micro-v1:0"]
[llm.providers.bedrock.models."meta.llama3-8b-instruct-v1:0"]
[llm.providers.bedrock.models."ai21.jamba-1.5-mini-v1:0"]
# Rename models for simpler access
[llm.providers.bedrock.models.claude-haiku]
rename = "anthropic.claude-3-5-haiku-20241022-v1:0" # Users will access as "bedrock/claude-haiku"
[llm.providers.bedrock.models.jamba-mini]
rename = "ai21.jamba-1.5-mini-v1:0" # Users will access as "bedrock/jamba-mini"
AWS Bedrock provides access to multiple foundation models through a single API. Key features:
- Unified Access: Use models from Anthropic, Amazon, Meta, Cohere, and more through one interface
- AWS Integration: Leverages AWS credentials and IAM for authentication
- Regional Availability: Models may vary by AWS region
- Native Streaming: Full streaming support for all compatible models
Authentication: Bedrock uses standard AWS credential chain:
- Environment variables (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) - AWS profile (specified in config or via
AWS_PROFILEenvironment variable) - IAM role (when running on EC2/ECS/Lambda)
- AWS SSO credentials
Supported Model Families:
- AI21 Jamba: Jamba 1.5 Mini and Large models with 256K context window
- Anthropic Claude: All Claude 3 models (Opus, Sonnet, Haiku) and Claude Instant
- Amazon Nova: Nova Micro, Lite, Pro models
- Amazon Titan: Titan Text and Embeddings models
- Meta Llama: Llama 2 and Llama 3 models
- Cohere Command: Command and Command R models
- DeepSeek: DeepSeek R1 reasoning models
- Mistral: Mistral 7B and Mixtral models
Model ID Format: Bedrock model IDs follow the pattern provider.model-name-version:revision, for example:
ai21.jamba-1.5-mini-v1:0anthropic.claude-3-5-sonnet-20241022-v2:0amazon.nova-micro-v1:0meta.llama3-8b-instruct-v1:0
Model Configuration
Nexus automatically discovers models from every provider. At startup the server performs an initial discovery; a background task then refreshes provider inventories every five minutes and publishes the results through a watch channel for lock-free lookups.
Automatic Discovery (default)
Simply configure a provider—no model list is required. Nexus fetches the provider's models and exposes them as bare model names:
[llm.providers.openai]
type = "openai"
api_key = "{{ env.OPENAI_API_KEY }}"
Discovered models appear in /v1/models (e.g., gpt-4, gpt-4o-mini). Requests using these bare names route automatically.
Restrict Discovery with model_filter
Use model_filter to limit which discovered models are exposed. The regex is case-insensitive, must not be empty, and follows the regex crate syntax.
[llm.providers.openai]
type = "openai"
api_key = "{{ env.OPENAI_API_KEY }}"
model_filter = "^gpt-4" # Only expose GPT-4-family models from discovery
[llm.providers.anthropic]
type = "anthropic"
api_key = "{{ env.ANTHROPIC_API_KEY }}"
model_filter = "^claude-"
If discovery fails for any provider at startup, Nexus exits so you can fix the configuration. Refresh failures log errors but keep the last successful snapshot.
Combine Discovery with Explicit Models
Explicit models remain useful for renames, rate limits, and legacy prefixes. They always appear with provider/model even when a filter would exclude them.
[llm.providers.openai]
type = "openai"
api_key = "{{ env.OPENAI_API_KEY }}"
model_filter = "^gpt-4"
[llm.providers.openai.models.gpt-3-5-turbo]
rename = "fast-model"
[llm.providers.openai.models.gpt-4]
[llm.providers.openai.models.gpt-4.rate_limits.per_user]
input_token_limit = 50000
interval = "60s"
Discovery guarantees:
- Provider order in the TOML determines duplicate resolution (first provider wins); skipped duplicates emit warn-level logs
/v1/modelslists bare discovered models first, followed by explicitprovider/modelentries- The watch channel keeps request handlers in sync without locking
How model resolution works:
- If the model name contains
/, route to the specified provider (openai/gpt-4) - Otherwise look up the bare name in the discovered model map (
gpt-4→ provider) - Return "model not found" if the name is missing from both
Accessing models:
- Discovered models: Use bare names (e.g.,
gpt-4,claude-3-opus) - Explicit models: Use
provider/modelformat (e.g.,openai/fast-model) - Both formats share rate limits and telemetry accounting
Model Renaming
You can rename models to provide custom identifiers for your users:
[llm.providers.openai.models.gpt-4]
rename = "smart-model" # Users will access this as "openai/smart-model"
[llm.providers.openai.models.gpt-3-5-turbo]
rename = "fast-model" # Users will access this as "openai/fast-model"
This is useful for:
- Creating business-friendly model names
- Abstracting away provider-specific model names
- Providing consistent naming across different providers
TOML Syntax for Model IDs
Model IDs that contain dots must be quoted in TOML:
# Correct - dots in model IDs require quotes
[llm.providers.google.models."gemini-1.5-flash"]
[llm.providers.google.models."gemini-1.5-pro"]
# Also correct - no dots, no quotes needed
[llm.providers.google.models.gemini-pro]
Multiple Provider Configuration
You can configure multiple instances of the same provider type with different names:
# Primary OpenAI account
[llm.providers.openai_primary]
type = "openai"
api_key = "{{ env.OPENAI_PRIMARY_KEY }}"
[llm.providers.openai_primary.models.gpt-4]
[llm.providers.openai_primary.models.gpt-3-5-turbo]
# Secondary OpenAI account or Azure OpenAI
[llm.providers.openai_secondary]
type = "openai"
api_key = "{{ env.OPENAI_SECONDARY_KEY }}"
base_url = "https://my-azure-instance.openai.azure.com/v1"
[llm.providers.openai_secondary.models.gpt-4]
rename = "azure-gpt-4" # Distinguish from primary account
# Anthropic
[llm.providers.claude]
type = "anthropic"
api_key = "{{ env.ANTHROPIC_API_KEY }}"
[llm.providers.claude.models.claude-3-opus-20240229]
# Google Gemini
[llm.providers.gemini]
type = "google"
api_key = "{{ env.GOOGLE_API_KEY }}"
[llm.providers.gemini.models."gemini-1.5-flash"]
Token Forwarding
Nexus supports token forwarding, allowing users to provide their own API keys at request time instead of using the configured keys. This feature is opt-in and disabled by default.
Configuring Token Forwarding
Enable token forwarding for any provider by setting forward_token = true:
[llm.providers.openai]
type = "openai"
api_key = "{{ env.OPENAI_API_KEY }}" # Fallback key (optional with token forwarding)
forward_token = true # Enable token forwarding for this provider
[llm.providers.openai.models.gpt-4]
[llm.providers.openai.models.gpt-3-5-turbo]
[llm.providers.anthropic]
type = "anthropic"
# No api_key required when token forwarding is enabled
forward_token = true
[llm.providers.anthropic.models.claude-3-5-sonnet-20241022]
[llm.providers.google]
type = "google"
api_key = "{{ env.GOOGLE_API_KEY }}"
forward_token = false # Explicitly disabled (default)
[llm.providers.google.models."gemini-1.5-flash"]
Using Token Forwarding
When token forwarding is enabled for a provider, users can pass their own API key using the X-Provider-API-Key header:
# Using your own OpenAI key
curl -X POST http://localhost:8000/llm/v1/chat/completions \
-H "Content-Type: application/json" \
-H "X-Provider-API-Key: sk-your-openai-key" \
-d '{
"model": "openai/gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
}'
# Using your own Anthropic key
curl -X POST http://localhost:8000/llm/v1/chat/completions \
-H "Content-Type: application/json" \
-H "X-Provider-API-Key: sk-ant-your-anthropic-key" \
-d '{
"model": "anthropic/claude-3-opus-20240229",
"messages": [{"role": "user", "content": "Hello"}]
}'
Token Forwarding Behavior
-
When token forwarding is enabled (
forward_token = true):- User-provided keys (via header) take priority
- Falls back to configured key if no header is provided
- Returns 401 error if neither key is available
-
When token forwarding is disabled (
forward_token = false, default):- Always uses the configured API key
- Ignores the
X-Provider-API-Keyheader - Returns 401 error if no configured key exists
Security Considerations
- OAuth2 Integration: When OAuth2 is enabled, users must still authenticate with Nexus even when using token forwarding
- Key Validation: API keys are validated by the provider's API
- No Logging: User-provided keys are never logged
- HTTPS Recommended: Always use HTTPS in production to protect API keys in transit
Provider Limitations
- AWS Bedrock: Token forwarding is not supported for Bedrock providers. Bedrock uses AWS IAM credentials and request signing, which cannot be provided via simple API key headers. You must configure AWS credentials at the provider level (via environment variables, AWS profile, or explicit credentials in configuration).
Header Transformation for LLM Providers
Nexus supports header transformation for LLM providers, allowing you to forward, insert, remove, or rename headers when making requests to LLM APIs.
Provider-Level Headers
Configure header rules at the provider level:
[llm.providers.openai]
type = "openai"
api_key = "{{ env.OPENAI_API_KEY }}"
# Forward headers from incoming requests
[[llm.providers.openai.headers]]
rule = "forward"
name = "X-Request-ID"
# Forward with a default value if not present
[[llm.providers.openai.headers]]
rule = "forward"
name = "X-Trace-ID"
default = "generated-trace-id"
# Forward and rename
[[llm.providers.openai.headers]]
rule = "forward"
name = "X-Custom-Header"
rename = "X-OpenAI-Custom"
# Insert static headers
[[llm.providers.openai.headers]]
rule = "insert"
name = "X-OpenAI-Beta"
value = "assistants=v2"
# Remove headers
[[llm.providers.openai.headers]]
rule = "remove"
name = "X-Internal-Secret"
# Pattern-based forwarding (regex)
[[llm.providers.openai.headers]]
rule = "forward"
pattern = "X-Debug-.*"
# Pattern-based removal
[[llm.providers.openai.headers]]
rule = "remove"
pattern = "X-Internal-.*"
# Rename and duplicate (keeps original and adds renamed copy)
[[llm.providers.openai.headers]]
rule = "rename_duplicate"
name = "X-User-ID"
rename = "X-OpenAI-User"
Model-Level Headers
Configure headers for specific models (overrides provider-level rules):
[llm.providers.openai.models.gpt-4]
# Model-specific headers override provider headers
[[llm.providers.openai.models.gpt-4.headers]]
rule = "insert"
name = "X-Model-Config"
value = "premium"
[[llm.providers.openai.models.gpt-4.headers]]
rule = "forward"
pattern = "X-Premium-.*"
Header Rule Types
-
forward: Pass headers from incoming requests to the LLM provider
name: Single header name to forwardpattern: Regex pattern to match multiple headersdefault: Optional default value if header is missingrename: Optional new name for the forwarded header
-
insert: Add static headers to requests
name: Header namevalue: Static value (supports{{ env.VAR }}substitution)
-
remove: Remove headers before sending to provider
name: Single header name to removepattern: Regex pattern to match headers to remove
-
rename_duplicate: Forward header with both original and new name
name: Original header namerename: New header name for the duplicatedefault: Optional default if header is missing
Important Notes
- AWS Bedrock: Does not support custom headers due to SigV4 signing requirements
- Priority: Model-level rules override provider-level rules
- Token Forwarding: The
X-Provider-API-Keyheader is handled separately for token forwarding - Pattern Matching: Patterns are case-insensitive regex expressions
Using the LLM API
Once configured, you can interact with LLM providers through Nexus's unified API:
List Available Models
curl http://localhost:8000/llm/models
Response:
{
"object": "list",
"data": [
{
"id": "openai_primary/gpt-4-turbo",
"object": "model",
"created": 1677651200,
"owned_by": "openai"
},
{
"id": "claude/claude-3-5-sonnet-20241022",
"object": "model",
"created": 1709164800,
"owned_by": "anthropic"
},
{
"id": "gemini/gemini-1.5-pro",
"object": "model",
"created": 1710000000,
"owned_by": "google"
}
]
}
Tool Calling (Function Calling)
Nexus supports advanced tool calling across all LLM providers, allowing models to invoke external functions with structured parameters. All providers use the standardized OpenAI tool calling format for consistency.
Basic Tool Calling Example:
curl -X POST http://localhost:8000/llm/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-3-5-sonnet-20241022",
"messages": [
{"role": "user", "content": "What'\''s the weather in San Francisco?"}
],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City and state"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["location"]
}
}
}],
"tool_choice": "auto"
}'
Tool Choice Options:
"auto"(default): Model decides whether to call tools"none": Model won't call any tools"required": Model must call at least one tool{"type": "function", "function": {"name": "specific_function"}}: Force a specific tool
Parallel Tool Calls:
When supported by the provider, models can call multiple tools simultaneously:
{
"model": "openai/gpt-4",
"messages": [{"role": "user", "content": "Get weather for NYC and LA"}],
"tools": [/* tool definitions */],
"parallel_tool_calls": true
}
Tool Conversation Flow:
Tool calling creates a multi-turn conversation:
- User message → asks a question requiring tool use
- Assistant message → responds with tool calls (no content)
- Tool messages → provide results from tool execution
- Assistant message → synthesizes final response
{
"messages": [
{"role": "user", "content": "What's the weather in Paris?"},
{
"role": "assistant",
"tool_calls": [{
"id": "call_123",
"type": "function",
"function": {"name": "get_weather", "arguments": "{\"location\": \"Paris\"}"}
}]
},
{
"role": "tool",
"tool_call_id": "call_123",
"content": "Weather in Paris: 22°C, sunny"
},
{
"role": "assistant",
"content": "The weather in Paris is currently 22°C and sunny!"
}
]
}
Provider-Specific Tool Support:
All providers support the standardized format, but have different capabilities:
- OpenAI: Full support including parallel calls and streaming tool calls
- Anthropic: Tool calling with robust function definitions and tool choice controls
- Google: Function calling with JSON schema validation and tool selection
- AWS Bedrock: Tool calling support varies by model family (Claude, Nova, etc.)
Streaming Tool Calls:
Tool calls can be streamed just like regular responses:
curl -X POST http://localhost:8000/llm/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4",
"messages": [{"role": "user", "content": "Search for Python tutorials"}],
"tools": [/* tool definitions */],
"stream": true
}'
The stream will include tool call chunks as they're generated, followed by the tool execution results.
Chat Completions
Send a chat completion request using the OpenAI-compatible format:
curl -X POST http://localhost:8000/llm/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "openai_primary/gpt-4-turbo",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello, how are you?"}
],
"temperature": 0.7,
"max_tokens": 150
}'
The model name format is <provider_name>/<model_id>. Nexus automatically routes the request to the appropriate provider and transforms the request/response as needed.
Streaming Responses
Nexus supports streaming responses for all LLM providers using Server-Sent Events (SSE):
curl -X POST http://localhost:8000/llm/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-3-5-sonnet-20241022",
"messages": [
{"role": "user", "content": "Write a short poem"}
],
"stream": true,
"max_tokens": 100
}'
When stream: true is set, the response will be streamed as Server-Sent Events with the following format:
data: {"id":"msg_123","object":"chat.completion.chunk","created":1234567890,"model":"anthropic/claude-3-5-sonnet-20241022","choices":[{"index":0,"delta":{"role":"assistant","content":"Here"}}]}
data: {"id":"msg_123","object":"chat.completion.chunk","created":1234567890,"model":"anthropic/claude-3-5-sonnet-20241022","choices":[{"index":0,"delta":{"content":" is"}}]}
data: {"id":"msg_123","object":"chat.completion.chunk","created":1234567890,"model":"anthropic/claude-3-5-sonnet-20241022","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":25,"total_tokens":35}}
data: [DONE]
Streaming is supported for all providers (OpenAI, Anthropic, Google) and provides:
- Real-time token delivery as they're generated
- Consistent chunk format across all providers
- Usage statistics in the final chunk
- Standard SSE format compatible with OpenAI SDKs
Provider-Specific Considerations
OpenAI
- Supports all standard OpenAI models (GPT-3.5, GPT-4, etc.)
- Compatible with Azure OpenAI endpoints
- Tool Calling: Full support including parallel tool calls and streaming tool calls
- Function Definitions: JSON schema support for parameters
- Tool Choice: Supports all tool choice modes including specific function forcing
- Supports streaming responses with Server-Sent Events (SSE)
Anthropic
- System messages are automatically extracted and placed in the
systemfield - Messages must alternate between user and assistant roles
- Requires explicit
max_tokensparameter (defaults to 4096 if not specified) - Tool Calling: Robust tool calling with function definitions and tool choice controls
- Tool Use: Supports tool_use blocks with structured parameter validation
- Streaming Tools: Tool calls can be streamed incrementally
- Supports all Claude models (Opus, Sonnet, Haiku)
- Supports streaming responses with Server-Sent Events (SSE)
- Assistant role is automatically mapped to "model" role
- System messages are placed in the
systemInstructionfield - Function Calling: Native function calling with JSON schema validation
- Tool Selection: Supports automatic tool selection and forced function calls
- Parameter Validation: Strict parameter schema enforcement
- Returns appropriate safety ratings when available
- Supports streaming responses with Server-Sent Events (SSE)
AWS Bedrock
- Uses the Bedrock conversation API for seamless integration
- We have tested and verified Anthropic Claude, Amazon Nova, Meta Llama, Cohere Command, Mistral, and DeepSeek models
- Other models may work, but we have not tested them. Please report any issues or successes.
- Tool Calling: Support varies by model family:
- Anthropic Claude: Full tool calling support through Bedrock
- Amazon Nova: Native tool calling capabilities
- Meta Llama: Function calling has issues, avoid using
- Cohere Command: Tool use support
- Mistral: Tools work well with Bedrock
- DeepSeek: Tools do not work with Bedrock
- Uses AWS SDK for authentication and request signing
- Supports all Bedrock features including streaming and model-specific parameters
- Regional endpoint selection based on configuration or AWS defaults
- Model availability varies by AWS region
Using Nexus with LLM SDKs
Nexus provides an OpenAI-compatible API, making it easy to use with existing LLM SDKs and libraries. Simply point the SDK to your Nexus instance instead of the provider's API.
OpenAI SDK (Python)
from openai import OpenAI
# Point to your Nexus instance
client = OpenAI(
base_url="http://localhost:8000/llm",
api_key="your-service-token" # Use a JWT token if OAuth2 is enabled, or any string if not
)
# Use any configured provider/model
response = client.chat.completions.create(
model="anthropic/claude-3-5-sonnet-20241022",
messages=[
{"role": "user", "content": "Hello!"}
]
)
# Streaming works seamlessly
stream = client.chat.completions.create(
model="openai/gpt-4-turbo",
messages=[
{"role": "user", "content": "Write a poem"}
],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
OpenAI SDK (Node.js/TypeScript)
import OpenAI from 'openai';
// Configure to use Nexus
const openai = new OpenAI({
baseURL: 'http://localhost:8000/llm',
apiKey: 'your-service-token', // Use a JWT token if OAuth2 is enabled, or any string if not
});
// Use any provider through Nexus
const response = await openai.chat.completions.create({
model: 'google/gemini-1.5-pro',
messages: [
{ role: 'user', content: 'Explain quantum computing' }
],
});
// Streaming with any provider
const stream = await openai.chat.completions.create({
model: 'anthropic/claude-3-opus-20240229',
messages: [
{ role: 'user', content: 'Write a story' }
],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || '');
}
LangChain Integration
from langchain_openai import ChatOpenAI
# Use Nexus as the LLM provider
llm = ChatOpenAI(
base_url="http://localhost:8000/llm",
api_key="your-service-token", # Use a JWT token if OAuth2 is enabled
model="openai/gpt-4-turbo"
)
# Works with any configured provider
claude = ChatOpenAI(
base_url="http://localhost:8000/llm",
api_key="your-service-token", # Use a JWT token if OAuth2 is enabled
model="anthropic/claude-3-5-sonnet-20241022"
)
cURL with jq for Command Line
# Regular completion (with OAuth2 authentication if enabled)
curl -s http://localhost:8000/llm/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-jwt-token" \
-d '{
"model": "openai/gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
}' | jq -r '.choices[0].message.content'
# Streaming with SSE parsing
curl -s http://localhost:8000/llm/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-jwt-token" \
-d '{
"model": "anthropic/claude-3-5-sonnet-20241022",
"messages": [{"role": "user", "content": "Write a haiku"}],
"stream": true
}' | grep "^data: " | sed 's/^data: //' | jq -r 'select(.choices != null) | .choices[0].delta.content // empty'
Authentication with OAuth2
When OAuth2 is enabled in your Nexus configuration, you must provide a valid JWT token:
# With OAuth2 enabled
client = OpenAI(
base_url="http://localhost:8000/llm",
api_key="eyJhbGciOiJSUzI1NiIs..." # Your JWT token
)
Without OAuth2, the api_key field is still required by most SDKs but can be any non-empty string:
# Without OAuth2
client = OpenAI(
base_url="http://localhost:8000/llm",
api_key="dummy" # Any non-empty string works
)
Error Handling
Nexus provides consistent error responses across all providers:
- 400 Bad Request: Invalid request format or parameters
- 401 Unauthorized: Missing or invalid API key
- 429 Too Many Requests: Rate limit exceeded
- 500 Internal Server Error: Provider API error or network issues
Example error response:
{
"error": {
"message": "Invalid model format: expected 'provider/model', got 'invalid-format'",
"type": "invalid_request_error",
"code": 400
}
}
Telemetry
Nexus provides comprehensive observability through OpenTelemetry metrics, distributed tracing, and logs export. All telemetry features have zero overhead when the [telemetry] section is absent from configuration.
Telemetry activation is controlled by exporters configuration:
- Metrics: Enabled when OTLP exporters are configured
- Tracing: Enabled when OTLP exporters are configured
- Logs: Enabled when OTLP exporters are configured
Each telemetry signal (metrics, tracing, logs) can override the global exporters configuration with their own specific exporters.
Metrics Configuration
[telemetry]
service_name = "nexus-production" # Optional, defaults to "nexus"
# Resource attributes for all telemetry (optional)
[telemetry.resource_attributes]
environment = "production"
region = "us-east-1"
# OTLP exporter for metrics
[telemetry.exporters.otlp]
enabled = true # Default: false
endpoint = "http://localhost:4317" # Default: http://localhost:4317
protocol = "grpc" # Default: grpc (options: grpc, http)
timeout = "60s" # Default: 60s
# Protocol-specific configuration
# For gRPC protocol (headers must be lowercase, cannot start with "grpc-"):
[telemetry.exporters.otlp.grpc.headers]
authorization = "Bearer {{ env.OTLP_TOKEN }}"
x-nexus-shard = "primary"
# TLS configuration for secure gRPC connections (all fields optional):
[telemetry.exporters.otlp.grpc.tls]
domain_name = "custom_name" # Domain name for TLS verification (SNI)
key = "/path/to/key.pem" # Path to client private key PEM file
cert = "/path/to/cert.pem" # Path to client certificate PEM file
ca = "/path/to/ca.pem" # Path to CA certificate PEM file
# For HTTP protocol (standard HTTP header names):
[telemetry.exporters.otlp.http.headers]
Authorization = "Bearer {{ env.OTLP_TOKEN }}"
X-Nexus-Shard = "primary"
# Batch export configuration (all optional with defaults)
[telemetry.exporters.otlp.batch_export]
scheduled_delay = "5s" # Default: 5s
max_queue_size = 2048 # Default: 2048
max_export_batch_size = 512 # Default: 512
max_concurrent_exports = 1 # Default: 1
See Available Metrics for detailed metric definitions.
Tracing Configuration
[telemetry.tracing]
sampling = 0.15 # Sample 15% of requests (default: 0.15)
parent_based_sampler = false # Respect parent trace sampling decision (default: false)
# Collection limits (per span)
[telemetry.tracing.collect]
max_events_per_span = 128
max_attributes_per_span = 128
max_links_per_span = 128
max_attributes_per_event = 128
max_attributes_per_link = 128
# Trace context propagation formats
[telemetry.tracing.propagation]
trace_context = true # W3C Trace Context
aws_xray = false # AWS X-Ray format
# Optional: Override global OTLP exporter for traces
[telemetry.tracing.exporters.otlp]
enabled = true
endpoint = "http://localhost:4317"
protocol = "grpc" # or "http"
timeout = "60s"
# Protocol-specific headers
[telemetry.tracing.exporters.otlp.grpc.headers]
authorization = "Bearer {{ env.OTLP_TOKEN }}"
# TLS configuration (same format as global OTLP):
# [telemetry.tracing.exporters.otlp.grpc.tls]
# domain_name = "custom_name"
# key = "/path/to/key.pem"
# cert = "/path/to/cert.pem"
# ca = "/path/to/ca.pem"
# Or for HTTP:
# [telemetry.tracing.exporters.otlp.http.headers]
# Authorization = "Bearer {{ env.OTLP_TOKEN }}"
See Distributed Tracing for span hierarchy and attributes.
Logs Configuration
Nexus can export application logs to OpenTelemetry collectors, automatically correlating them with distributed traces. Logs are enabled when OTLP exporters are configured (either globally or specifically for logs).
# Option 1: Use global OTLP exporter for all telemetry signals
# (logs will use the same endpoint as metrics and traces)
# Option 2: Configure a separate OTLP endpoint specifically for logs
[telemetry.logs.exporters.otlp]
enabled = true
endpoint = "http://logs-collector:4317" # Different endpoint for logs
protocol = "grpc" # or "http"
timeout = "30s"
# Protocol-specific headers
[telemetry.logs.exporters.otlp.grpc.headers]
authorization = "Bearer {{ env.OTLP_TOKEN }}"
# TLS configuration (same format as global OTLP):
# [telemetry.logs.exporters.otlp.grpc.tls]
# domain_name = "custom_name"
# key = "/path/to/key.pem"
# cert = "/path/to/cert.pem"
# ca = "/path/to/ca.pem"
# Or for HTTP:
# [telemetry.logs.exporters.otlp.http.headers]
# Authorization = "Bearer {{ env.OTLP_TOKEN }}"
# Logs are automatically correlated with active trace and span IDs
When logs export is enabled, logs include:
- Automatic trace and span ID correlation for distributed debugging
- Log severity levels mapped from Rust log levels
- Source code location attributes (file, line, module)
- All logs from Nexus and its dependencies
Note: OpenTelemetry's own internal logs are filtered out to prevent recursion.
Available Metrics
All histograms also function as counters (count field tracks number of observations).
HTTP Server Metrics:
http.server.request.duration(histogram)- Attributes:
http.route,http.request.method,http.response.status_code
- Attributes:
MCP Operation Metrics:
-
mcp.tool.call.duration(histogram)- Attributes:
tool_name,tool_type(builtin/downstream),status(success/error),client.id,client.group - Additional for search:
keyword_count,result_count - Additional for execute:
server_name(for downstream tools) - Additional for errors:
error.type:parse_error- Invalid JSON (-32700)invalid_request- Not a valid request (-32600)method_not_found- Method/tool does not exist (-32601)invalid_params- Invalid method parameters (-32602)internal_error- Internal server error (-32603)rate_limit_exceeded- Rate limit hit (-32000)server_error- Other server errors (-32001 to -32099)unknown- Any other error code
- Attributes:
-
mcp.tools.list.duration(histogram)- Attributes:
method(list_tools),status,client.id,client.group
- Attributes:
-
mcp.prompt.request.duration(histogram)- Attributes:
method(list_prompts/get_prompt),status,client.id,client.group - Additional for errors:
error.type(same values as above)
- Attributes:
-
mcp.resource.request.duration(histogram)- Attributes:
method(list_resources/read_resource),status,client.id,client.group - Additional for errors:
error.type(same values as above)
- Attributes:
LLM Operation Metrics:
-
gen_ai.client.operation.duration(histogram)- Tracks the total duration of LLM chat completion operations
- Attributes:
gen_ai.system(always "nexus.llm")gen_ai.operation.name(always "chat.completions")gen_ai.request.model(e.g., "openai/gpt-4")gen_ai.response.finish_reason(stop/length/tool_calls/content_filter)client.id(from x-client-id header)client.group(from x-client-group header)error.type(for failed requests):invalid_request- Malformed requestauthentication_failed- Invalid API keyinsufficient_quota- Quota exceededmodel_not_found- Unknown modelrate_limit_exceeded- Provider or token rate limit hitstreaming_not_supported- Streaming unavailable for modelinvalid_model_format- Incorrect model name formatprovider_not_found- Unknown providerinternal_error- Server errorprovider_api_error- Upstream provider errorconnection_error- Network failure
-
gen_ai.client.time_to_first_token(histogram)- Tracks time until first token in streaming responses
- Attributes:
gen_ai.system,gen_ai.operation.name,gen_ai.request.model,client.id,client.group
-
gen_ai.client.input.token.usage(counter)- Cumulative input token consumption
- Attributes:
gen_ai.system,gen_ai.request.model,client.id,client.group
-
gen_ai.client.output.token.usage(counter)- Cumulative output token consumption
- Attributes:
gen_ai.system,gen_ai.request.model,client.id,client.group
-
gen_ai.client.total.token.usage(counter)- Cumulative total token consumption (input + output)
- Attributes:
gen_ai.system,gen_ai.request.model,client.id,client.group
Redis Storage Metrics (when rate limiting with Redis is enabled):
-
redis.command.duration(histogram)- Tracks Redis command execution times
- Attributes:
operation- The Redis operation type:check_and_consume- HTTP rate limit checkingcheck_and_consume_tokens- Token-based rate limit checking
status(success/error)tokens(for token operations) - Number of tokens checked
-
redis.pool.connections.in_use(gauge)- Current number of connections in use from the pool
- No additional attributes
-
redis.pool.connections.available(gauge)- Current number of available connections in the pool
- No additional attributes
Distributed Tracing
Configuration: See Tracing Configuration in the Telemetry section.
Distributed tracing provides detailed insights into request flows across all components, helping you understand latency, identify bottlenecks, and debug issues in production.
Trace Context Propagation
Nexus supports multiple trace context propagation formats for incoming requests:
- W3C Trace Context: Standard
traceparentandtracestateheaders (enabled by default) - AWS X-Ray:
X-Amzn-Trace-Idheader for AWS services integration
When enabled, Nexus will:
- Extract trace context from incoming HTTP requests
- Continue the trace with the parent context or start a new trace
- Create spans for all internal operations
Note: Nexus is a terminal node for traces - it does not propagate trace context to downstream MCP servers or LLM providers.
Span Hierarchy
Nexus creates a hierarchical span structure for each request:
HTTP Request (root span)
├── Redis Rate Limit Check (redis:check_and_consume:global/ip) - HTTP-level rate limits
├── MCP Operation
│ ├── Redis Rate Limit Check (redis:check_and_consume:server/tool) - MCP-specific rate limits
│ ├── Tool Search (index:search)
│ └── Tool Execution
│ └── Downstream Operations (downstream:execute, downstream:call_tool)
└── LLM Operation (llm:chat_completion or llm:chat_completion_stream)
└── Token Rate Limit Check (redis:check_and_consume_tokens) - Token-based rate limits
Span Attributes
Each span includes semantic attributes following OpenTelemetry conventions:
HTTP Spans:
http.request.method- HTTP method (GET, POST, etc.)http.route- Matched route patternhttp.response.status_code- Response status codeurl.scheme- URL scheme (http/https)server.address- Host header valueurl.full- Full request URLclient.id- Client identifier (if configured)client.group- Client group (if configured)error- Set to "true" if request failed
MCP Spans:
mcp.method- MCP method name (tools/list, tools/call, etc.)mcp.tool.name- Tool being calledmcp.tool.type- Tool type (builtin/downstream)mcp.transport- Transport type (stdio/http)mcp.auth_forwarded- Whether auth was forwardedclient.id- Client identifier (if configured)client.group- Client group (if configured)mcp.search.keywords- Search keywords (for search tool)mcp.search.keyword_count- Number of search keywordsmcp.execute.target_tool- Tool being executedmcp.execute.target_server- Server hosting the toolmcp.error.code- Error code if operation failed
LLM Spans:
gen_ai.request.model- Model identifiergen_ai.request.max_tokens- Max tokens requestedgen_ai.request.temperature- Temperature parametergen_ai.request.has_tools- Whether tools were providedgen_ai.request.tool_count- Number of tools providedgen_ai.response.model- Model used for responsegen_ai.response.finish_reason- Completion reasongen_ai.usage.input_tokens- Input token countgen_ai.usage.output_tokens- Output token countgen_ai.usage.total_tokens- Total token countllm.stream- Whether streaming was usedllm.auth_forwarded- Whether auth was forwardedclient.id- Client identifier (if configured)client.group- Client group (if configured)error.type- Error type if operation failed
Redis Spans:
redis.operation- Operation type (check_and_consume or check_and_consume_tokens)rate_limit.scope- Scope (global/ip/server/tool/token)rate_limit.limit- Request/token limitrate_limit.interval_ms- Time window in millisecondsrate_limit.tokens- Number of tokens (for token operations)rate_limit.allowed- Whether request was allowedrate_limit.retry_after_ms- Retry delay if rate limitedredis.pool.size- Connection pool sizeredis.pool.available- Available connectionsredis.pool.in_use- Connections in useclient.address_hash- Hashed IP for privacy (per-IP limits)llm.provider- Provider name (token limits)llm.model- Model name (token limits)mcp.server- Server name (per-server limits)mcp.tool- Tool name (per-tool limits)
Sampling
Nexus supports two sampling strategies:
Fixed-Rate Sampling (default): Randomly samples a percentage of traces based on the configured rate.
[telemetry.tracing]
sampling = 0.15 # Sample 15% of requests (0.0 to 1.0)
Parent-Based Sampling: When enabled, Nexus respects the sampling decision from parent traces in distributed systems.
[telemetry.tracing]
sampling = 0.15 # Default sampling rate for root traces
parent_based_sampler = true # Respect parent's sampling decision
With parent_based_sampler = true:
- If a parent trace is sampled (W3C traceparent flag=01 or X-Ray Sampled=1), the child trace will be sampled
- If a parent trace is not sampled (flag=00 or Sampled=0), the child trace will not be sampled
- If no parent exists, the
samplingrate is used
This ensures consistent sampling decisions across distributed traces, preventing incomplete trace data where some spans are sampled while others are not.
When a parent trace context is provided in request headers (via W3C Trace Context or AWS X-Ray), Nexus will continue that trace.
Performance Considerations
- **Zero