Architecture
swsd-mcp is a thin, stateless wrapper around SolarWinds Service Desk’s REST API, exposed via the Model Context Protocol. The whole system is three boxes — a client, the MCP server, and the upstream API — connected by two possible transports.
Request flow
Section titled “Request flow”The three boxes
Section titled “The three boxes”MCP clients
Section titled “MCP clients”Two categories, distinguished by where the swsd-mcp process actually runs:
-
Local clients — Claude Desktop, Claude Code, Cursor, Continue, Cline. Each spawns swsd-mcp as a child process via
npx swsd-mcp. Communication is over stdio (stdin/stdout JSON-RPC). The user’s SWSD token is provided via theSWSD_TOKENenvironment variable in the MCP config. -
Hosted clients — Microsoft Copilot Studio, custom HTTP clients. These can’t spawn local processes, so swsd-mcp runs as a long-lived HTTP server somewhere accessible (the Deployment guide covers Azure Container Apps). Each request includes the user’s token in the
Authorization: Bearer <token>orX-SWSD-Token: <token>header.
swsd-mcp
Section titled “swsd-mcp”The middle box. Single Node.js process, ~3000 lines of TypeScript, four direct production dependencies. Exposes 23 tools organized into profiles.
The server itself has no SWSD identity — every API call is made using the caller’s token. The token exists in process memory only for the lifetime of a single request. There’s no token cache, no session store, no persistence layer. See Security → Zero credentials at rest.
SWSD REST API
Section titled “SWSD REST API”The upstream. api.samanage.com for US tenants, apieu.samanage.com for EU. Shared endpoint — tenant identity rides in the JWT, not in the URL. Custom domains like support.yourcompany.com are vanity CNAMEs for the SWSD web UI only; the API endpoint is the regional one.
The SWSD_BASE_URL env var is validated at startup against *.samanage.com. SSRF defense — if an attacker ever influences the env var, they can’t redirect tokens to a server they control.
Transport choice
Section titled “Transport choice”The transport you pick depends entirely on the client’s capability:
| If your client… | Use transport | Why |
|---|---|---|
| Spawns local subprocesses (Claude Desktop, Code, Cursor, Continue, Cline) | stdio | Simplest, no hosting, each user’s token stays local |
| Can only call HTTP endpoints (Copilot Studio, custom integrations) | Streamable HTTP | Required because the client can’t spawn the server itself |
| You want one shared instance for a team | Streamable HTTP | Many users, one deploy, each providing their own token per request |
Both transports run the same tool handlers and produce identical results — the wire protocol is the only difference.
Request lifecycle
Section titled “Request lifecycle”What happens when an MCP client calls a tool like swsd_list_incidents:
- Client packages the call as MCP JSON-RPC:
{ "method": "tools/call", "params": { "name": "swsd_list_incidents", "arguments": {...} } } - Transport delivers it — stdin (stdio) or POST
/mcpbody (HTTP) - swsd-mcp validates input via Zod schemas (tight types, descriptive errors)
- Token resolution —
SWSD_TOKENenv var (stdio) or request header (HTTP) - Outbound request to SWSD with the resolved token, 30-second timeout, automatic retry on 5xx for read-only calls
- Response mapping — SWSD’s full payload is projected into a compact, agent-friendly shape (every field that lands in the agent’s context is tokens spent)
- Structured MCP response returned to the client, with both human-readable text and structured data
For HTTP transport specifically, additional steps run between (2) and (3): rate-limit check (per-(token, IP) hashed key), Origin header validation against allowlist, and Express trust-proxy resolution for accurate req.ip.
What’s intentionally not in this picture
Section titled “What’s intentionally not in this picture”- No backing store. swsd-mcp has no database, no Redis, no file cache. Restarting the process loses nothing because there’s nothing to lose.
- No queueing. Every tool call is synchronous request → SWSD → response. Long-running operations are SWSD’s problem, not ours.
- No multi-tenant routing. The server is single-tenant — your
SWSD_BASE_URLis the one tenant it talks to. Multi-tenancy happens by running multiple instances, not by routing within one. - No webhook ingress. swsd-mcp is purely a request-response server. SWSD doesn’t push events to it.
These are deliberate constraints — they keep the attack surface small, the operational story trivial, and the deployment cost near-zero.
See also
Section titled “See also”- Configuration — the env vars that tune transport, profiles, retry, and rate-limit behavior
- Security — the threat model, supply-chain controls, and standards alignment
- Deployment — Docker, Azure Container Apps, and Copilot Studio setup