Skip to content

cloudg mcp serve

Runs cloudg as a Model Context Protocol server, so an AI assistant can query your inventory, graph, findings and compliance data through tools, resources and prompts.

Desktop clients (Claude Desktop, Claude Code, Cursor, VS Code) launch cloudg mcp serve themselves over stdio, and you rarely type it by hand. You do type it when you serve over HTTP, test a policy, or preload datasets. cloudg mcp config writes the client snippet for you. The MCP server section covers the server in depth.

Usage

cloudg mcp serve [OPTIONS]

Options

Flag Value Default Description
--transport stdio http streamable-http sse stdio MCP transport.
--host TEXT 127.0.0.1 HTTP bind address (keep 127.0.0.1 unless you also set --auth-token).
--port INT 8765 HTTP port.
--path TEXT /mcp HTTP endpoint.
--flavor auto native sdk fastmcp auto Server implementation: mcp SDK, standalone fastmcp, or the built-in dependency-free server.
--auth-token TOKEN[:ROLES[:ID]] none Require 'Authorization: Bearer TOKEN' (HTTP). ROLES is a comma list; the principal also gets the 'default' role. TOKEN may be env:VAR. A token containing ':' needs the full form TOKEN:ROLES:ID (fields may be empty). Repeatable. Repeatable. Environment: CLOUDG_MCP_AUTH_TOKENS.
--allowed-origin TEXT none Allowed browser Origin pattern, replacing the default (loopback origins). Same-origin requests are accepted when the Host header is checked (loopback bind or --allowed-host); the Origin check is on for every bind and flavor. Repeatable. Repeatable.
--allowed-host TEXT none Allowed Host header pattern. Default: loopback names on a loopback bind; on other binds the Host header is not checked unless this is set (Origin still is). Repeatable. Repeatable.
--cors-origin TEXT none Enable CORS for this browser origin (native flavor only). Repeatable. Repeatable.
--json-response flag off Answer HTTP POSTs with JSON, never SSE.
--stateless flag off Session-less Streamable HTTP.
--cache-ttl FLOAT none Cache read-only idempotent tool results for N seconds.
--max-concurrency INT none Max concurrent tool calls.
--page-size INT 100 Items per page for list results (native flavor).
--log-level DEBUG INFO WARNING ERROR WARNING Server log level (logs go to stderr).
-c, --config TEXT none cloudg config.yaml (defaults to the parent command's config).
--policy TEXT none Policy profile name, or a YAML/JSON policy file. Environment: CLOUDG_MCP_POLICY.
--dataset [NAME=]PATH none Preload a dataset (findings.json, inventory-map.json...). Repeatable. Repeatable.
--prefix TEXT `` Prefix for tool and prompt names.
--include-category TEXT none Only expose these categories. Repeatable. Repeatable.
--exclude-category TEXT none Hide these categories. Repeatable. Repeatable.
--include-tool TEXT none Only expose these tools. Repeatable. Repeatable.
--exclude-tool TEXT none Hide these tools. Repeatable. Repeatable.
--read-only flag off Drop tools that write files, call cloud APIs, run scanners or are destructive.
--audit-log FILE none Append a JSONL audit trail (argument values are hashed). Environment: CLOUDG_MCP_AUDIT_LOG.
--timeout FLOAT 300.0 Per-call timeout in seconds.
--registry MODULE:ATTR none Serve a custom Registry (or a zero-argument factory returning one) instead of the built-in catalog.

Generated from the click definition in /opt/hostedtoolcache/Python/3.12.15/x64/lib/python3.12/site-packages/cloudg/mcp/cli.py, so it matches cloudg mcp serve --help.

How it works

The command builds one MCP layer from its options, picks a server implementation, and serves until the transport closes: end of input on stdio, Ctrl+C on HTTP.

flowchart TD
  A["Options and env vars"] --> B["Validate tokens and option combinations"]
  B -->|invalid| X["Error before start"]
  B --> C["Load registry, apply --read-only and filters"]
  C --> D["Apply policy, add middleware"]
  D --> E["Preload --dataset files"]
  E --> F{"--transport"}
  F -->|stdio| G["JSON-RPC on stdin and stdout"]
  F -->|"http, sse"| H["HTTP server on host, port and path"]
  G --> I["Stop: save pseudonym vault"]
  H --> I
From the command line to a running server

Building the layer

These options are shared with tools, resources, prompts, call and read, so you can check what a server will expose before you start it.

The registry is the built-in catalog unless --registry MODULE:ATTR names your own. --read-only then drops every tool that writes files, calls cloud APIs, runs scanners or is marked destructive. --include-category, --exclude-category, --include-tool and --exclude-tool narrow it further, and --prefix is put in front of every tool and prompt name (at most 64 characters from A-Z a-z 0-9 _ . -).

The policy comes from --policy, which accepts a profile name (open, standard, strict, read_only, airgapped, audit, soc-analyst), a YAML or JSON file, or inline JSON. Without the option, cloudg reads CLOUDG_MCP_POLICY, and without that it uses standard. The policy decides which tools each principal sees and which privacy transforms run on results. See Built-in profiles.

Middleware is added from the options. A metrics middleware is always on, which is why cloudg://metrics is always listed. --audit-log (or CLOUDG_MCP_AUDIT_LOG) appends a JSONL record per call with argument values hashed. --max-concurrency caps parallel tool calls, and --cache-ttl caches results of read-only, idempotent tools for that many seconds. --timeout is the default per-call limit, 300 seconds; tools with their own limit, such as the live collection tools, keep theirs.

Each --dataset [NAME=]PATH is loaded before the server accepts a connection. Without a name, an inventory-map.json is named after its folder and anything else after its file stem. A findings.json next to an inventory map is loaded into the same dataset. Dataset paths you give here are trusted and may sit outside the workspace's allowed roots; those roots only limit the paths tools may open.

-c/--config points the layer at a cloudg config.yaml, which the live tools use for providers and credentials. Without it, the config given to the root cloudg -c is used.

Choosing a flavor

--flavor auto uses the official mcp SDK when it is installed and the built-in, dependency-free server otherwise. native, sdk and fastmcp force one. Some options only work with some flavors, and the command refuses the combination instead of ignoring the option:

$ cloudg mcp serve --transport http --flavor sdk --cors-origin http://x
Error: --cors-origin is only supported by the native flavor (use --flavor native)
$ cloudg mcp serve --transport sse --flavor fastmcp --json-response
Error: --json-response / --stateless apply to Streamable HTTP; the fastmcp flavor cannot combine them with --transport sse

--page-size (items per page in list results) only applies to the native flavor. Flavors compares them.

stdio

stdio is the default. The client starts the process and talks newline-delimited JSON-RPC over stdin and stdout. cloudg keeps stdout for protocol messages only: the banner, logs and notices all go to stderr. You can see this by piping requests in by hand. This exchange used the native flavor and a dataset from the test suite's sample estate; output lines are cut:

$ printf '%s\n' \
    '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"shell","version":"0"}}}' \
    '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
    '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"count_assets","arguments":{"group_by":"provider"}}}' \
  | cloudg mcp serve --flavor native --dataset prod=inventory/inventory-map.json 2>/dev/null
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true},...},"serverInfo":{"name":"cloudg","version":"0.6.0","title":"cloudg"},...}}
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\n  \"dataset\": \"prod\",\n  \"group_by\": \"provider\",\n  \"total\": 44, ..."}],"isError":false,"structuredContent":{...}}}

Auth tokens are ignored on stdio. Whoever launched the process is the local user, and the policy treats them that way.

HTTP

--transport http (or its alias streamable-http) serves Streamable HTTP at the address built from --host, --port and --path, which is http://127.0.0.1:8765/mcp by default. --transport sse adds the deprecated /sse and /messages/ endpoints for older clients. When the server is up, it prints one line to stderr:

$ cloudg mcp serve --transport http --port 8799
cloudg MCP server (mcp SDK 2.x, http) listening on http://127.0.0.1:8799/mcp

--json-response answers POSTs with plain JSON instead of an SSE stream, and --stateless drops sessions for clients that cannot keep one.

Three checks protect an HTTP server:

Check Default Change it with
Origin header Loopback origins only, on every bind and every flavor --allowed-origin PATTERN (repeatable, replaces the default)
Host header Loopback names when bound to loopback; not checked on other binds --allowed-host PATTERN (repeatable)
Bearer token None: every HTTP caller is the anonymous principal with the default role --auth-token TOKEN[:ROLES[:ID]] (repeatable) or CLOUDG_MCP_AUTH_TOKENS

Each token spec maps a bearer token to a principal. ROLES is a comma-separated list, and the principal always gets the default role as well. TOKEN can be env:VARNAME so the secret stays out of your shell history and process list. A token that itself contains : needs the full three-part form. CLOUDG_MCP_AUTH_TOKENS holds several specs separated by whitespace.

The tokens are checked before the server starts. An empty token, given directly or through a variable that is not set, stops the command rather than starting a server without the authentication you asked for:

$ cloudg mcp serve --transport http --auth-token env:CLOUDG_MCP_TOKEN
Error: Invalid value for --auth-token: Environment variable 'CLOUDG_MCP_TOKEN' for an auth token is empty

Binding beyond loopback

Keep --host 127.0.0.1 unless you also set --auth-token. On a non-loopback bind the Host header is not checked unless you pass --allowed-host, so set that too.

--cors-origin grants CORS to a browser origin. Only the native flavor supports it.

Stopping

When the transport closes, cloudg saves the pseudonym vault if the policy configures a vault.path. Without one, pseudonyms live in memory and change on the next start unless CLOUDG_MCP_VAULT_KEY is set.

Examples

Serve over stdio with the default standard policy, the way a desktop client starts it:

cloudg mcp serve

Preload yesterday's map and findings and apply the strict policy:

cloudg mcp serve --policy strict --dataset prod=/srv/cloudg/reports/inventory-map.json

Expose only inventory and graph tools, with no way to call the cloud or write files:

cloudg mcp serve --read-only --include-category inventory --include-category graph --dataset ./reports/inventory-map.json

Start without the root banner and config handling, through the standalone cloudg-mcp entry point (python -m cloudg.mcp does the same):

cloudg-mcp serve --dataset ./reports/inventory-map.json

Serve Streamable HTTP on loopback for a local web client:

cloudg mcp serve --transport http

Serve HTTP with two tokens mapped to roles, and keep an audit trail:

export CLOUDG_ANALYST_TOKEN="$(openssl rand -hex 32)"
export CLOUDG_ADMIN_TOKEN="$(openssl rand -hex 32)"
cloudg mcp serve --transport http \
  --auth-token env:CLOUDG_ANALYST_TOKEN:analyst:alice \
  --auth-token env:CLOUDG_ADMIN_TOKEN:admin,analyst:ops \
  --audit-log ./mcp-audit.jsonl

Make the server reachable from other hosts, with tokens and explicit Host and Origin lists:

cloudg mcp serve --transport http --host 0.0.0.0 \
  --auth-token env:CLOUDG_MCP_TOKEN:analyst \
  --allowed-host 'mcp.internal.example:*' \
  --allowed-origin 'https://console.internal.example'

Debug a client that misbehaves, with full logs on stderr:

cloudg mcp serve --log-level DEBUG 2> ./mcp-server.log

Exit codes

Code When
0 The transport closed normally: end of input on stdio, Ctrl+C on HTTP
1 The layer could not be built: a dataset path that does not exist, an invalid --prefix, a policy file that is missing or fails to load (Error: Policy file not found: nope.yaml)
2 An option was rejected before start: an empty or malformed --auth-token, a flavor and option combination that does not work, a --registry that does not resolve to a Registry