# McpNet

> 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.

- **Type:** MCP server
- **Install:** `agentstack add mcp-localtonet-mcpnet`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [localtonet](https://agentstack.voostack.com/s/localtonet)
- **Installs:** 0
- **Category:** [Data & Analytics](https://agentstack.voostack.com/c/data-and-analytics)
- **Latest version:** 0.1.0
- **License:** Unlicense
- **Upstream author:** [localtonet](https://github.com/localtonet)
- **Source:** https://github.com/localtonet/McpNet
- **Website:** https://github.com/localtonet/McpNet

## Install

```sh
agentstack add mcp-localtonet-mcpnet
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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

1. [Overview](#overview)
2. [Architecture](#architecture)
3. [Project Layout](#project-layout)
4. [Quick Start](#quick-start)
5. [Configuration Reference](#configuration-reference)
6. [Authentication Modes](#authentication-modes)
7. [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)
8. [Tool Aggregation](#tool-aggregation)
9. [Tool Groups](#tool-groups)
10. [Client Management & Per-Client Access Control](#client-management--per-client-access-control)
11. [Rate Limiting](#rate-limiting)
12. [Server Health Checks](#server-health-checks)
13. [Audit Log](#audit-log)
14. [MCP Catalog](#mcp-catalog)
15. [Web Dashboard](#web-dashboard)
16. [Management REST API](#management-rest-api)
17. [Management CLI - `mcpnet`](#management-cli--mcpnet)
18. [Meta-Tools (gateway self-management via MCP)](#meta-tools-gateway-self-management-via-mcp)
19. [Secrets at Rest](#secrets-at-rest)
20. [Persistence Backends](#persistence-backends)
21. [OpenTelemetry](#opentelemetry)
22. [Session Management](#session-management)
23. [Docker](#docker)
24. [Import / Export](#import--export)
25. [Testing](#testing)
26. [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 as `serverName__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

```bash
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)

```bash
# 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`):

```jsonc
{
  "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

```jsonc
{
  "Name": "my-remote-server",
  "Url": "https://mcp.example.com/mcp",
  "TransportType": "StreamableHttp",  // or "Sse"
  "BearerToken": "optional-static-token"
}
```

### Stdio (npx / local process)

```jsonc
{
  "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:**
```jsonc
{ "BearerToken": "upstream-token" }
```

**OAuth 2.0 Client Credentials:**
```jsonc
{
  "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 `401` from 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 (`CustomHeaders` dict).

---

## 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 `totalTools` counter in `/api/tools/status` increases 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

```jsonc
{ "RateLimitPerMinute": 60 }   // all tool calls across all servers combined
```

### Per-server override

```jsonc
{
  "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:

```jsonc
{
  "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.json` file
- **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](https://github.com/localtonet)
- **Source:** [localtonet/McpNet](https://github.com/localtonet/McpNet)
- **License:** Unlicense
- **Homepage:** https://github.com/localtonet/McpNet

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-localtonet-mcpnet
- Seller: https://agentstack.voostack.com/s/localtonet
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
