Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d9018d1
:hamister: :new: add init and server cmd
marcellodesales Jun 1, 2026
5a5e192
:hamister: :recycle: add client and oauth support
marcellodesales Jun 1, 2026
bce33b6
:whale: :new: add docker-compose.yaml
marcellodesales Jun 1, 2026
2a56481
:books: :recycle: update README.md docs
marcellodesales Jun 1, 2026
08b26ac
:see_no_evil: add this
marcellodesales Jun 25, 2026
8ded7a6
:hamster: :recycle: support oauth authentication from browser
marcellodesales Jun 26, 2026
e466ea9
:whale: :recycle: update docker compose with general settings
marcellodesales Jun 30, 2026
e6bfb1a
:whale: :hamster: update vault server to not fetch certs
marcellodesales Jul 2, 2026
acf1970
:hamster: :whale: add fetch certs script example
marcellodesales Jul 2, 2026
f9bdb04
Merge branch 'feature/split-cert-puller' into feature/support-vault-m…
marcellodesales Jul 2, 2026
243b612
:hamister: :recycle: update OIDC support enablement
marcellodesales Jul 3, 2026
ead8de0
:wrench: :recycle: fix certs impl from sidecard
marcellodesales Jul 3, 2026
d50f6e6
:whale: :recycle: update to make it run
marcellodesales Jul 3, 2026
c370cf7
:books: README: update with oidc localhost support
marcellodesales Jul 3, 2026
3f8b784
Potential fix for pull request finding
marcellodesales Jul 3, 2026
5bb931a
:hamster: :recycle: add client renewal, error handling
marcellodesales Jul 7, 2026
490ffd2
:hamster: :bug: fix secrets read from the service
marcellodesales Jul 7, 2026
3677000
fix(oauth): make login page form actions base-path aware
marcellodesales Jul 10, 2026
7ce384f
ci: retrigger docker-multiarch after digest-step fix in github-platfo…
marcellodesales Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

# Test binary, built with `go test -c`
*.test
data/

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
Expand Down
109 changes: 84 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ and other MCP clients.
The server can be configured using environment variables:

- `VAULT_ADDR`: Vault server address (default: `http://127.0.0.1:8200`)
- `VAULT_TOKEN`: Vault authentication token (required)
- `VAULT_TOKEN`: Vault authentication token (required in stdio mode; optional in HTTP mode — if set it bypasses browser OAuth and is used for requests)
- `VAULT_NAMESPACE`: Vault namespace (optional)
- `TRANSPORT_MODE`: Set to `http` to enable HTTP mode
- `TRANSPORT_HOST`: Host to bind to for HTTP mode (default: `127.0.0.1`)
Expand All @@ -80,54 +80,109 @@ The server can be configured using environment variables:
- `MCP_RATE_LIMIT_GLOBAL`: Global rate limit (format: `rps:burst`) (default: `10:20`)
- `MCP_RATE_LIMIT_SESSION`: Per-session rate limit (format: `rps:burst`) (default: `5:10`)

OAuth / browser login (see [Browser OAuth](#browser-oauth-vault-login)):

- `MCP_AUTH_SECRET`: base64url (32-byte) key that seals OAuth tokens. Optional; if unset the server generates one at startup and logs it at INFO. Set it to keep bearer tokens valid across restarts.
- `MCP_SERVER_URL`: public base URL advertised in OAuth metadata/redirects (default: derived per-request from the `Host` / `X-Forwarded-*` headers)
- `MCP_AUTH_CODE_TTL`: lifetime of authorization codes / login state (default: `5m`)
- `MCP_AUTH_ACCESS_TTL`: lifetime of bearer access tokens (default: `12h`)
- `VAULT_CACERT`: PEM CA bundle path used to verify the upstream Vault TLS cert (e.g. `/viasat/certs/viasat.io.pem`)
- `VIASAT_IO_CACERT_FILE` / `VIASAT_IO_CACERT_URL`: location and source URL of the Viasat private CA bundle; bootstrap it out-of-process (see `scripts/fetch-secrets/` or `docker-compose-viasat.yaml`)
- `VAULT_AUTH_LDAP_MOUNT` (default `ldap`), `VAULT_AUTH_USERPASS_MOUNT` (default `userpass`), `VAULT_OIDC_MOUNT` (default `oidc`), `VAULT_OIDC_ROLE` (optional): Vault auth method mounts used by the login page

## HTTP Mode Configuration

In HTTP mode, Vault configuration can be provided through multiple methods (in order of precedence):
In HTTP mode, the `/mcp` endpoint always requires authentication. You can satisfy this requirement in one of two ways:

- **Browser OAuth** (recommended for interactive MCP clients): the client is redirected to `/vault/login` and then calls `/mcp` with `Authorization: Bearer <token>` minted by this server.
- **Token bypass** (for clients that cannot do OAuth): provide a Vault token externally via `VAULT_TOKEN` (env) or the `X-Vault-Token` request header.

- **HTTP Query**: `VAULT_ADDR`
- **HTTP Headers**: `VAULT_ADDR`, `X-Vault-Token`, and `X-Vault-Namespace`
- **Environment Variables**: Standard `VAULT_ADDR`, `VAULT_TOKEN`, and `VAULT_NAMESPACE` env vars
Bearer tokens are sealed with `MCP_AUTH_SECRET`. If you let the server auto-generate it on each start, previously issued bearer tokens stop working after restart (clients will re-authenticate).

Upstream Vault connection details used for the login flow are configured via environment variables (`VAULT_ADDR`, `VAULT_NAMESPACE`, `VAULT_CACERT`, etc.).

### Middleware Stack

The HTTP server includes a comprehensive middleware stack:

- **CORS Middleware**: Enables cross-origin requests with appropriate headers
- **Bearer (OAuth) Middleware**: Unseals the bearer token and injects the Vault credentials into the request context (or bypasses OAuth when a Vault token is supplied externally)
- **Vault Context Middleware**: Extracts Vault configuration and adds to request context
- **Logging Middleware**: Structured HTTP request logging

## Browser OAuth (Vault login)

In HTTP mode the server can act as its own **OAuth 2.1 Authorization Server** so an
interactive MCP client (Claude, an agentic CLI, etc.) is handed a URL to authenticate
against your Vault in the browser — no token copy/paste required. The Vault token obtained
during login is encrypted (AES-256-GCM) into the OAuth bearer token; nothing is stored
server-side (the flow is fully stateless).

`MCP_AUTH_SECRET` seals the issued bearer tokens. It is optional; if unset the server generates one at
startup and logs it at INFO (set it to keep bearer tokens valid across restarts).

If you cannot do OAuth, you can bypass the browser flow by providing a Vault token externally
(`VAULT_TOKEN` env var or `X-Vault-Token` request header).

Token bypass (no browser OAuth):

```bash
export VAULT_ADDR=https://vault.seceng-iam.viasat.io
export VAULT_TOKEN=hvs....
docker compose up --build
```

Browser OAuth:

```bash
export VAULT_ADDR=https://vault.seceng-iam.viasat.io
# Optional: set to keep bearer tokens valid across restarts
export MCP_AUTH_SECRET=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')
docker compose up --build
```

### How it works

1. The MCP client discovers `/.well-known/oauth-protected-resource/mcp` and
`/.well-known/oauth-authorization-server`, dynamically registers (`/register`), and opens
`/authorize` (authorization code + PKCE).
2. `/authorize` redirects the browser to `/vault/login`, which offers three ways to authenticate:
- **LDAP** — `auth/<ldap mount>/login/<user>`
- **Userpass** — `auth/<userpass mount>/login/<user>`
- **OIDC (SSO)** — Vault `auth/<oidc mount>/oidc/auth_url`; the browser is sent to your IdP
and returns to `/vault/oidc/callback`. The callback URL is computed **dynamically from the
current host**, so it works behind any hostname/reverse proxy.
3. On success the client receives an authorization code, exchanges it at `/token`, and uses the
returned `Bearer` token on `/mcp`. An invalid/expired token yields `401` with a
`WWW-Authenticate` challenge so the client re-authenticates.

> **OIDC note:** the dynamic callback `https://<host>/vault/oidc/callback` must be present in the
> Vault OIDC role's `allowed_redirect_uris`. Set the role via `VAULT_OIDC_ROLE`.

### TLS to a private Vault (the Viasat CA)

To trust `https://vault.seceng-iam.viasat.io`, mount the Viasat private CA bundle and point
`VAULT_CACERT` at it. Bootstrap the CA bundle out-of-process (for example via `scripts/fetch-certs/`
or the `certs-puller` service in `docker-compose-viasat.yaml`). The Vault MCP server will fail fast
if a CA bundle path is configured but missing on disk.


## Integration with Visual Studio Code

1. In your project workspace root, create or open the `.vscode/mcp.json` configuration file. Alternatively, to add an MCP to your user configuration, run the `MCP: Open User Configuration` command, which opens the mcp.json file in your user profile. If the file does not exist, VS Code creates it for you.

Streamable HTTP mode supports browser OAuth; OAuth-capable MCP clients will be directed to `/vault/login` to authenticate. If you run the server with `VAULT_TOKEN`, clients can skip OAuth.

<table>
<tr><th>Streamable HTTP mode</th><th>Stdio mode</th></tr>
<tr valign=top>
<td>

```json
{
"inputs": [
{
"type": "promptString",
"id": "vault_token",
"description": "Vault Token",
"password": true
},
{
"type": "promptString",
"id": "vault_namespace",
"description": "Vault Namespace (optional)",
"password": false
}
],
"servers": {
"vault-mcp-server": {
"url": "http://localhost:8080/mcp?VAULT_ADDR=http://127.0.0.1:8200",
"headers": {
"X-Vault-Token": "${input:vault_token}",
"X-Vault-Namespace": "${input:vault_namespace}"
}
"url": "http://localhost:8080/mcp"
}
}
}
Expand Down Expand Up @@ -234,7 +289,11 @@ docker logs vault-dev
Run the Vault MCP server:

```bash
docker run --network=mcp -p 8080:8080 -e VAULT_ADDR='http://vault-dev:8200' -e VAULT_TOKEN='<your-token-from-last-step>' -e TRANSPORT_MODE='http' vault-mcp-server:dev
# Option A: token bypass (no browser OAuth)
docker run --network=mcp -p 8080:8080 -e VAULT_ADDR='http://vault-dev:8200' -e VAULT_TOKEN='hvs....' -e TRANSPORT_MODE='http' vault-mcp-server:dev

# Option B: browser OAuth (MCP_AUTH_SECRET optional)
docker run --network=mcp -p 8080:8080 -e VAULT_ADDR='http://vault-dev:8200' -e MCP_AUTH_SECRET="$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" -e TRANSPORT_MODE='http' vault-mcp-server:dev
```

## Available Tools
Expand Down
19 changes: 18 additions & 1 deletion cmd/vault-mcp-server/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"io"
stdlog "log"
"os"
"strings"
"time"

"github.com/mark3labs/mcp-go/server"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -42,9 +44,24 @@ func initConfig() {

func initLogger(outPath string) (*log.Logger, error) {
logger := log.New()
logger.SetLevel(log.DebugLevel)

// JSON format + stdout so `docker compose logs` shows structured output
// matching the other Viasat MCP tools (delinea, codedx, tenable, etc.).
logger.SetFormatter(&log.JSONFormatter{
TimestampFormat: time.RFC3339Nano,
})

// Default INFO; honour LOG_LEVEL env var (debug / info / warn / error).
level := log.InfoLevel
if v := strings.ToLower(strings.TrimSpace(os.Getenv("LOG_LEVEL"))); v != "" {
if parsed, err := log.ParseLevel(v); err == nil {
level = parsed
}
}
logger.SetLevel(level)

if outPath == "" {
logger.SetOutput(os.Stdout)
return logger, nil
}

Expand Down
Loading