Install
$ agentstack add mcp-localtonet-mcpnet ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
| Package | Version | Downloads | |---|---|---| | McpNet.Gateway.Standalone | [](https://www.nuget.org/packages/McpNet.Gateway.Standalone) | [](https://www.nuget.org/packages/McpNet.Gateway.Standalone) | | McpNet.Gateway | [](https://www.nuget.org/packages/McpNet.Gateway) | [](https://www.nuget.org/packages/McpNet.Gateway) | | McpNet.Core | [](https://www.nuget.org/packages/McpNet.Core) | [](https://www.nuget.org/packages/McpNet.Core) | | McpNet.Transport.Http | [](https://www.nuget.org/packages/McpNet.Transport.Http) | [](https://www.nuget.org/packages/McpNet.Transport.Http) |
> Note: McpNet.Gateway.Standalone already includes all other packages above as dependencies. You don't need to install them separately.
McpNet Gateway
> A high-performance, enterprise-ready Model Context Protocol (MCP) gateway for .NET 8. > Aggregate any number of upstream MCP servers behind a single authenticated endpoint - with a web dashboard, management CLI, per-client access control, rate limiting, audit logging, and optional OpenTelemetry observability.
Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Project Layout](#project-layout)
- [Quick Start](#quick-start)
- [Configuration Reference](#configuration-reference)
- [Authentication Modes](#authentication-modes)
- [Upstream Servers](#upstream-servers)
- [StreamableHTTP / SSE](#streamablehttp--sse)
- [Stdio (npx / local process)](#stdio-npx--local-process)
- [Upstream Bearer & OAuth 2.0](#upstream-bearer--oauth-20)
- [Tool Aggregation](#tool-aggregation)
- [Tool Groups](#tool-groups)
- [Client Management & Per-Client Access Control](#client-management--per-client-access-control)
- [Rate Limiting](#rate-limiting)
- [Server Health Checks](#server-health-checks)
- [Audit Log](#audit-log)
- [MCP Catalog](#mcp-catalog)
- [Web Dashboard](#web-dashboard)
- [Management REST API](#management-rest-api)
- [Management CLI -
mcpnet](#management-cli--mcpnet) - [Meta-Tools (gateway self-management via MCP)](#meta-tools-gateway-self-management-via-mcp)
- [Secrets at Rest](#secrets-at-rest)
- [Persistence Backends](#persistence-backends)
- [OpenTelemetry](#opentelemetry)
- [Session Management](#session-management)
- [Docker](#docker)
- [Import / Export](#import--export)
- [Testing](#testing)
- [Security Notes](#security-notes)
Overview
McpNet Gateway sits between your AI agents (Claude, Cursor, any MCP client) and one or more upstream MCP servers. Instead of configuring every agent to know about every server, agents connect to the gateway once and see a unified, namespaced tool catalogue.
AI Agent (MCP client)
│ Bearer token
▼
┌─────────────────────────────────────┐
│ McpNet Gateway │
│ ┌──────────────────────────────┐ │
│ │ Auth · Rate Limit · Audit │ │
│ └──────────────┬───────────────┘ │
│ ┌──────────────▼───────────────┐ │
│ │ Tool Aggregator │ │
│ │ (parallel refresh cache) │ │
│ └──┬──────────┬──────────┬─────┘ │
└─────│──────────│──────────│─────────┘
│ │ │
▼ ▼ ▼
Server A Server B Server C
(HTTP/MCP) (stdio/npx) (OAuth API)
Key properties:
- Single MCP endpoint at
/mcp- all upstream tools namespaced asserverName__toolName - Parallel refresh - each upstream is probed concurrently; a slow or unresponsive server never blocks others
- Incremental cache - tools from fast servers appear in the catalogue immediately, without waiting for slow ones
- Zero-dependency default - JSON file persistence, no database required
- Enterprise-ready - token-authenticated clients, per-client ACL, per-server rate limits, full audit trail
Architecture
src/
├── McpNet.Core Protocol models, JSON-RPC, McpJsonOptions
├── McpNet.Gateway Routing, aggregation, upstream clients, OAuth, telemetry
├── McpNet.Gateway.Persistence EF Core repositories (SQLite / PostgreSQL)
├── McpNet.Transport.AspNetCore ASP.NET Core MCP transport middleware
├── McpNet.Transport.Http Streamable-HTTP & SSE upstream client
├── McpNet.Transport.Stdio Stdio (child-process) upstream client
├── McpNet.Dashboard Web dashboard + REST management API (embedded resources)
├── McpNet.Host Composition root - entry point
└── McpNet.Cli `mcpnet` management CLI
Project Layout
| Project | Description | |---|---| | McpNet.Core | MCP protocol types, JSON-RPC request/response, McpJsonOptions (System.Text.Json) | | McpNet.Gateway | Request router, tool aggregator, server registry, session manager, OAuth token provider, OpenTelemetry | | McpNet.Gateway.Persistence | EF Core DbContext + repositories for SQLite and PostgreSQL | | McpNet.Transport.AspNetCore | Maps /mcp using ASP.NET Core minimal APIs | | McpNet.Transport.Http | HTTP upstream client for StreamableHTTP and SSE transport | | McpNet.Transport.Stdio | Stdio upstream client - spawns child processes (npx, uvx, executables) | | McpNet.Dashboard | Embedded web dashboard (dashboard.html, dashboard.js) + REST API endpoints | | McpNet.Host | Program.cs - wires all services, starts Kestrel | | McpNet.Cli | .NET global tool - mcpnet CLI for scripted management |
Quick Start
From source
git clone https://github.com/localtonet/McpNet
cd McpNet
# Build
dotnet build src/McpNet.Host/McpNet.Host.csproj -c Release
# Run (from binary directory so it finds mcp-data/)
cd src/McpNet.Host/bin/Release/net8.0/
./mcpnet-gateway
Gateway starts on port 5050. Open http://localhost:5050/dashboard to access the web UI.
Docker (recommended)
# Default - JSON file persistence, no database
docker compose up -d
# With PostgreSQL
docker compose --profile postgres up -d
Connect an MCP client
Point your AI agent at:
http://localhost:5050/mcp
Authorization: Bearer
In Dev mode (default) the Authorization header is optional - all requests are allowed. In Enterprise mode a valid client bearer token is required.
Configuration Reference
Configuration is read from appsettings.json under the McpGateway section, with environment variable overrides (McpGateway__Key=value):
{
"McpGateway": {
// Authentication mode: Dev (open) | Enterprise (token-required)
"Mode": "Dev",
// Admin token for the management API and dashboard
// Override with env: MCPGATEWAY_ADMIN_TOKEN
"AdminToken": "change-me-in-production",
// Listening port. Override with env: MCPGATEWAY_PORT
"Port": 5050,
// Directory for JSON persistence files and custom catalog
"DataDirectory": "mcp-data",
// Persistence backend: Json (default) | sqlite | postgres
"Persistence": "Json",
// Connection strings (only needed for sqlite/postgres)
"ConnectionStrings": {
"Sqlite": "Data Source=mcp-data/mcpnet.db",
"Postgres": "Host=localhost;Database=mcpnet;Username=mcpnet;Password=secret"
},
// Enable gateway self-management via MCP tools (default: false)
"EnableMetaTools": false,
// OpenTelemetry (default: disabled)
"Telemetry": {
"Enabled": false,
"OtlpEndpoint": "" // e.g. http://localhost:4317
}
}
}
All McpGateway__* keys can be set as environment variables, which take precedence over appsettings.json.
Authentication Modes
| Mode | Behaviour | |---|---| | Dev | No authentication required. All MCP and API requests are allowed. Suitable for local development. | | Enterprise | The management API requires X-Admin-Token: . MCP endpoint requires Authorization: Bearer . |
Switch mode via config: McpGateway__Mode=Enterprise.
Admin token
The admin token protects all /api/* endpoints and the dashboard. Set via MCPGATEWAY_ADMIN_TOKEN environment variable or McpGateway.AdminToken in appsettings.json.
Upstream Servers
StreamableHTTP / SSE
{
"Name": "my-remote-server",
"Url": "https://mcp.example.com/mcp",
"TransportType": "StreamableHttp", // or "Sse"
"BearerToken": "optional-static-token"
}
Stdio (npx / local process)
{
"Name": "filesystem",
"TransportType": "Stdio",
"StdioCommand": "npx",
"StdioArgs": ["-y", "@modelcontextprotocol/server-filesystem", "C:\\Users\\me\\Documents"],
"StdioWorkingDirectory": null,
"StdioEnvVars": {
"API_KEY": "secret" // injected into the child process environment
}
}
StdioEnvVars lets you pass secrets to the child process without embedding them in StdioArgs.
The gateway probes the runtime automatically (node --version / python --version etc.) and reports this in the health endpoint response.
> First-run note: npx packages are downloaded on first connect. This can take 60–120 s. The gateway uses a 120 s per-server timeout and will succeed on the second background refresh (~60 s later) once the package is cached.
Upstream Bearer & OAuth 2.0
Each registered server can authenticate to its upstream independently:
Static Bearer token:
{ "BearerToken": "upstream-token" }
OAuth 2.0 Client Credentials:
{
"OAuth": {
"Enabled": true,
"TokenUrl": "https://auth.example.com/oauth/token",
"ClientId": "my-client-id",
"ClientSecret": "my-client-secret",
"Scopes": ["mcp.read", "mcp.write"]
}
}
OAuth behaviour:
- Access tokens are cached in memory and refreshed automatically ~30 s before expiry.
- On a
401from the upstream the cached token is invalidated and the request is retried once with a fresh token. - Custom HTTP headers per server are also supported (
CustomHeadersdict).
Tool Aggregation
When the gateway starts and periodically (every 60 s via ToolRefreshBackgroundService), it connects to each enabled upstream server and discovers its tools. All tools are merged into a single in-memory catalogue namespaced as:
{serverName}__{toolName}
Parallel refresh with incremental cache
Each upstream server is refreshed concurrently (Task.WhenAll). When a server responds, its tools are written to the live cache immediately - without waiting for other servers to finish. This means:
- Fast HTTP servers (typically 1–3 s) are available right away.
- A slow or unresponsive stdio server (e.g. 120 s npx download timeout) does not block fast servers.
- The
totalToolscounter in/api/tools/statusincreases incrementally as each server responds.
Individual tool enable/disable
Any tool can be disabled without removing the upstream server. Disabled tools are hidden from the tool list and return an error if called directly.
Tool Groups
Tool groups let you bundle related tools and assign them to clients as a unit.
POST /api/groups - create group
POST /api/groups/{id}/tools - add tool to group
DELETE /api/groups/{id}/tools/{toolName} - remove tool from group
A client assigned a group only sees (and can call) tools in that group. Groups can be managed from the dashboard Tool Groups tab or via the CLI.
Client Management & Per-Client Access Control
In Enterprise mode every external MCP consumer is a McpClient with its own bearer token. Access is controlled at three levels:
| Level | Field | Behaviour | |---|---|---| | Global | Enabled | When false, all requests from this client are rejected | | Server ACL | AllowedServerIds | If non-empty, client only sees tools from listed servers | | Group ACL | AllowedGroupIds | If non-empty, client only sees tools in listed groups |
Leave both AllowedServerIds and AllowedGroupIds empty to grant access to everything.
Client tokens are 32-byte cryptographically random values (URL-safe Base64). Tokens can be regenerated at any time from the dashboard.
Rate Limiting
Rate limiting is evaluated per client, per tool call, after tool and server resolution.
Global limit
{ "RateLimitPerMinute": 60 } // all tool calls across all servers combined
Per-server override
{
"RateLimitPerMinute": 200,
"ServerRateLimits": [
{ "ServerId": "", "LimitPerMinute": 10 }, // tighter limit for expensive API
{ "ServerId": "", "LimitPerMinute": 500 } // looser limit for local server
]
}
Precedence: per-server limit (if defined) > global limit. A value of 0 means unlimited.
When the limit is exceeded the gateway returns JSON-RPC error code -32029.
Server Health Checks
GET /api/servers/{id}/health
Returns live health status for any registered server. For stdio servers, also probes the runtime:
{
"id": "535fc449-...",
"status": "healthy", // healthy | warning | unhealthy
"latencyMs": 18,
"toolCount": 14,
"note": null,
"command": "npx",
"runtimeInfo": "node v18.20.4 / npm 10.7.0"
}
The Ping button in the dashboard triggers this endpoint and updates the health chip inline.
Audit Log
Every tool call is recorded in the audit log:
| Field | Description | |---|---| | clientId / clientName | Which client made the call | | method | MCP method (tools/call) | | toolName | Full tool name (server__tool) | | serverName | Upstream server | | success | Whether the call succeeded | | durationMs | End-to-end latency | | timestamp | UTC timestamp |
The audit log is viewable from the dashboard Activity tab and via GET /api/audit. It is also available through the mcpnet audit CLI command.
MCP Catalog
The gateway includes a built-in catalog of 61 popular MCP servers (embedded in the binary, no internet required). You can:
- Browse the catalog from the dashboard Catalog tab
- Search by name, category, or description
- Add any catalog entry to the gateway as a new upstream server in one click
- Add custom entries to a local
mcp-data/custom-catalog.jsonfile - Remove custom entries
The catalog is served from /api/catalog and searched via /api/catalog/search.
Web Dashboard
Access at http://localhost:5050/dashboard (or /).
Tabs
| Tab | Description | |---|---| | Dashboard | Overview: server count, tool count, client count, recent activity feed | | Servers | Register, edit, enable/disable, delete upstream servers; inline health chips; Ping button | | Catalog | Browse / search 61+ built-in MCP servers; add custom entries; one-click install | | Tools | Browse all aggregated tools; enable/disable individual tools; refresh trigger with live progress | | Tool Groups | Create groups, manage tool membership | | Clients | Create clients, manage permissions (server ACL, group ACL, rate limits, per-server rate limits), regenerate tokens | | Activity | Filterable audit log table | | Settings | Import / export gateway config; gateway info |
Background auto-refresh
The dashboard polls /api/tools/status every 60 seconds in the background and automatically updates tool counts and health chips without requiring a manual refresh.
Incremental tool loading
When a manual Refresh Tools is triggered, the dashboard polls for status every 2 s and loads tools incrementally as fast servers respond - the tool list updates in real-time without waiting for slow servers.
Management REST API
All endpoints are under /api and require X-Admin-Token: in Enterprise mode.
Servers
| Method | Path | Description | |---|---|---| | GET | /api/servers | List all registered servers | | GET | /api/servers/{id} | Get server details | | POST | /api/servers | Register a new server | | PUT | /api/servers/{id} | Update server | | DELETE | /api/servers/{id} | Remove server | | PATCH | `/api/servers/{id}/toggle
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: localtonet
- Source: localtonet/McpNet
- License: Unlicense
- Homepage: https://github.com/localtonet/McpNet
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.