Skip to main content
Glama
mithung-vunet

VuNet MCP HTTP Server

README.md
# VuNet MCP HTTP Server

A **Model Context Protocol (MCP) server** for [VuNet vuSmartMaps](https://www.vunetsystems.com/) using the **Streamable HTTP transport**. Connects AI assistants (VS Code Copilot, ChatGPT, Claude Desktop, etc.) directly to live VuNet observability data.

---

## Features

- **HTTP-based MCP** — works with any MCP client that supports Streamable HTTP (VS Code, ChatGPT, Claude)
- **Multi-environment** — run one server process per VuNet tenant (UAT, production, sandbox) each on its own port
- **Auto-authentication** — logs in to VuNet automatically, caches session for 1 hour
- **HTTPS-ready** — pass cert/key in config for direct TLS, or put NGINX in front

## Tools Exposed

| Tool | Description |
|------|-------------|
| `vunet_get_status` | Check connection status and VuNet tenant info |
| `vunet_query_metric` | Query any VuNet data model/metric with time range and filters |
| `vunet_fetch_datamodels` | Browse/search available signals (Metric, Event, Log) from the live tenant |
| `vunet_list_data_models` | Static reference list of common signal categories |

---

## Prerequisites

- **Node.js 18+** — [download](https://nodejs.org/)
- A running **VuNet vuSmartMaps** instance with API access
- (For HTTPS) A valid TLS certificate for your domain

---

## Quick Start

### 1. Get the package

**Option A — Clone the repo:**
```bash
git clone https://github.com/mithung-vunet/vunet-mcp-http.git
cd vunet-mcp-http
npm install
```

**Option B — Extract the release zip:**
```bash
unzip vunet-mcp-deploy.zip
cd vunet-mcp-deploy
npm install
```

### 2. Configure environments

Copy the example config and fill in your VuNet credentials:

```bash
cp config.example.json config.json
```

Edit `config.json`:

```json
{
  "environments": {
    "production": {
      "label":      "VuNet Production",
      "tenant_url": "https://your-vunet-host.example.com",
      "username":   "your-username",
      "password":   "your-password",
      "bu_id":      "1",
      "verify_ssl": true,
      "port":       3001,
      "ssl_cert":   "",
      "ssl_key":    ""
    },
    "uat": {
      "label":      "VuNet UAT",
      "tenant_url": "https://your-vunet-uat-host.example.com",
      "username":   "your-username",
      "password":   "your-password",
      "bu_id":      "1",
      "verify_ssl": true,
      "port":       3002,
      "ssl_cert":   "",
      "ssl_key":    ""
    }
  }
}
```

> **Note:** `config.json` is git-ignored — never commit real credentials.

### 3. Start the server

**Single environment:**
```bash
VUNET_ENV=production node index.js
# Windows:
set VUNET_ENV=production && node index.js
```

**All environments at once (one process per env):**
```bash
node start-all.js
```

Output:
```
[Vunet MCP] VuNet Production
[Vunet MCP] Listening on http://127.0.0.1:3001/mcp
[Vunet MCP] Tenant: https://your-vunet-host.example.com
[Vunet MCP] Mode: HTTP (no TLS)
```

---

## Configuration Options

### `config.json` fields

| Field | Required | Description |
|-------|----------|-------------|
| `label` | No | Human-readable name shown in logs |
| `tenant_url` | **Yes** | Base URL of your VuNet vuSmartMaps instance |
| `username` | **Yes** | VuNet login username |
| `password` | **Yes** | VuNet login password |
| `bu_id` | No | Business Unit ID (default: `"1"`) |
| `verify_ssl` | No | Set `false` to skip SSL verification for self-signed certs |
| `port` | No | Port to listen on (default: `3000`) |
| `ssl_cert` | No | Path to TLS certificate (enables HTTPS if set with `ssl_key`) |
| `ssl_key` | No | Path to TLS private key |

### Environment variable overrides

All settings can be overridden via environment variables (useful for Docker/CI):

| Variable | Override |
|----------|----------|
| `VUNET_ENV` | Which environment block to load from `config.json` |
| `VUNET_TENANT_URL` | `tenant_url` |
| `VUNET_USERNAME` | `username` |
| `VUNET_PASSWORD` | `password` |
| `VUNET_BU_ID` | `bu_id` |
| `VUNET_VERIFY_SSL` | `verify_ssl` (`false` to disable) |
| `VUNET_BIND_HOST` | Bind address (default `127.0.0.1`, use `0.0.0.0` for external) |
| `VUNET_CONFIG` | Path to a custom config file |
| `PORT` | Override port |

**Pure env-var mode (no config.json):**
```bash
VUNET_TENANT_URL=https://vunet.example.com \
VUNET_USERNAME=admin \
VUNET_PASSWORD=secret \
VUNET_BIND_HOST=0.0.0.0 \
node index.js
```

---

## Production Deployment (Linux + NGINX)

### Run as a background service

```bash
# Start all environments in background
VUNET_ENV=production VUNET_BIND_HOST=0.0.0.0 nohup node index.js > prod.log 2>&1 &
VUNET_ENV=uat        VUNET_BIND_HOST=0.0.0.0 nohup node index.js > uat.log  2>&1 &
```

### NGINX reverse proxy with HTTPS

Install NGINX and configure it to terminate TLS and proxy to Node:

```nginx
server {
    listen 443 ssl;
    server_name mcp.your-domain.com;

    ssl_certificate     /path/to/fullchain.crt;   # leaf + intermediate concatenated
    ssl_certificate_key /path/to/server.key;

    # Route /production/ → Node port 3001
    location /production/ {
        proxy_pass http://127.0.0.1:3001/mcp;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;
    }

    # Route /uat/ → Node port 3002
    location /uat/ {
        proxy_pass http://127.0.0.1:3002/mcp;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;
    }
}
```

> **Important:** Include the full certificate chain (leaf + intermediate) in your `ssl_certificate` file. Missing intermediates will cause ChatGPT and other clients to reject the TLS connection.
>
> Concatenate them:
> ```bash
> cat server.crt intermediate.crt > fullchain.crt
> ```

Enable and reload:
```bash
sudo ln -s /etc/nginx/sites-available/mcp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```

---

## MCP Client Configuration

### VS Code (GitHub Copilot)

Add to your workspace `.vscode/mcp.json`:

```json
{
  "servers": {
    "vunet-production": {
      "type": "http",
      "url": "https://mcp.your-domain.com/production/"
    },
    "vunet-uat": {
      "type": "http",
      "url": "https://mcp.your-domain.com/uat/"
    }
  }
}
```

Then use **Ctrl+Shift+P → MCP: List Servers** to connect.

### ChatGPT

1. Go to **chatgpt.com → Apps (beta) → New App**
2. Set **MCP Server URL** to `https://mcp.your-domain.com/uat/`
3. Set **Authentication** to `No Auth`
4. Check the acknowledgment and click **Create**

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "vunet": {
      "type": "http",
      "url": "https://mcp.your-domain.com/production/"
    }
  }
}
```

---

## Verify the Server

Test the MCP handshake with curl:

```bash
curl https://mcp.your-domain.com/uat/ \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
```

Expected response:
```
event: message
data: {"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"vunet-mcp-server","version":"1.0.0"}},"jsonrpc":"2.0","id":1}
```

---

## Troubleshooting

| Symptom | Cause | Fix |
|---------|-------|-----|
| `Not Acceptable: Client must accept text/event-stream` | Missing `Accept` header | Normal in browser — MCP clients send the right headers automatically |
| `Already connected to a transport` | Old server version | Upgrade to v1.1+ which creates a fresh Server per connection |
| `Error creating connector` (ChatGPT) | Certificate chain incomplete | Concatenate leaf + intermediate into `fullchain.crt` |
| `502 Bad Gateway` | Server can't reach VuNet tenant | Check `tenant_url`, network, and `verify_ssl` setting |
| `Verify return code: 21` | Missing intermediate cert | Download and concatenate the CA intermediate cert |
| `Unable to verify certificate` | Self-signed cert | Set `"verify_ssl": false` in config |

---

## Security Notes

- `config.json` is **git-ignored** — never commit it
- Use environment variables instead of config files in containerized deployments
- Restrict `VUNET_BIND_HOST` to `127.0.0.1` when running behind NGINX (don't expose Node directly)
- Use a reverse proxy (NGINX) to handle TLS — avoid putting private keys in app config where possible

---

## License

MIT — © VuNet Systems