Deployment¶
Deployment Options¶
servicenow-api supports local stdio, a loopback-only development listener, a
least-privilege stdio container, and a remote authenticated HTTPS boundary.
Provider endpoint, credential, selector, identity, and trust material are supplied
at runtime through AgentConfig; none is stored in this repository.
Installed stdio process¶
{
"mcpServers": {
"servicenow": {
"command": "servicenow-mcp",
"args": [],
"env": {"MCP_TOOL_MODE": "intent"}
}
}
}
Loopback development listener¶
Do not expose this listener beyond loopback. Network deployments require direct TLS
or an explicitly trusted TLS-terminating ingress, configured authentication, exact
MCP_ALLOWED_HOSTS, and an exact trusted-proxy CIDR policy.
Least-privilege local container¶
docker run -i --rm \
--read-only \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--pids-limit=256 \
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m \
-e TRANSPORT=stdio \
registry.example.invalid/servicenow-api@sha256:<digest> servicenow-mcp
The operator projects the selected AgentConfig profile into the process at runtime; the image remains immutable and contains no environment connection profile.
Remote authenticated HTTPS endpoint¶
Store the real remote URL, outbound identity reference, and TLS-profile reference in
AgentConfig, not in MCP client JSON or documentation.
This page covers running servicenow-api as a long-lived server: the transports, a
Docker Compose stack, the companion A2A agent server, putting it behind a Caddy
reverse proxy, and giving it a DNS name with Technitium.
servicenow-apiships an MCP server (console scriptservicenow-mcp) and a companion A2A agent server (console scriptservicenow-agent). The MCP server is a typed, deterministic tool surface a policy router / agent calls; the agent server is a Pydantic-AI graph agent that consumes those tools.
Run the MCP server¶
The transport is selected with --transport (or the TRANSPORT env var):
Health check (HTTP transports):
Configuration (environment)¶
servicenow-api is configured entirely from the environment. The required set to
connect to ServiceNow:
| Var | Default | Meaning |
|---|---|---|
SERVICENOW_INSTANCE |
(unset) | ServiceNow instance URL (alias SERVICENOW_URL) |
SERVICENOW_USERNAME |
(unset) | User id (basic auth) |
SERVICENOW_PASSWORD |
(unset) | Password (basic auth) |
SERVICENOW_CLIENT_ID |
(unset) | OAuth client id (optional) |
SERVICENOW_CLIENT_SECRET |
(unset) | OAuth client secret (optional) |
SERVICENOW_TLS_PROFILE |
system |
Named outbound TLS policy from AgentConfig |
HOST / PORT / TRANSPORT |
0.0.0.0 / 8000 / stdio |
HTTP transport binding |
Each ServiceNow tool domain (incidents, change management, CMDB, DevOps, …) is gated
by its own *TOOL toggle (for example INCIDENTSTOOL, CMDBTOOL,
CHANGE_MANAGEMENTTOOL), all defaulting to True. The full set, including telemetry
(ENABLE_OTEL) and access-governance (EUNOMIA_*) variables, is documented in
.env.example.
Copy it to .env and populate only what you use.
Backing Service (ServiceNow)¶
ServiceNow is a managed SaaS platform — there is no local backing system to
provision. Point SERVICENOW_INSTANCE at your tenant (for example a personal
developer instance from the ServiceNow Developer Program) and supply credentials via
the variables above. Because the backing system is hosted, only connection
configuration is required; no platform.md recipe applies.
Docker Compose¶
The repo ships docker/mcp.compose.yml.
It reads a sibling .env and publishes the HTTP server on :8000:
services:
servicenow-api-mcp:
image: example/servicenow-api@sha256:<digest>
container_name: servicenow-api-mcp
hostname: servicenow-api-mcp
restart: always
env_file:
- ../.env
environment:
- PYTHONUNBUFFERED=1
- HOST=0.0.0.0
- PORT=8000
- TRANSPORT=streamable-http
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 30s
timeout: 10s
retries: 3
cp .env.example .env # then edit SERVICENOW_* values
docker compose -f docker/mcp.compose.yml up -d
docker compose -f docker/mcp.compose.yml logs -f
ServiceNow SDK CLI (now-sdk)¶
Both the mcp and agent image targets also ship the official ServiceNow SDK CLI —
Node.js LTS plus a global @servicenow/sdk install, built in its own discarded
builder-node stage (see docker/Dockerfile)
so no separate Node install is required to author, build, and deploy a Studio scoped
app. It is reachable two ways:
- Inside the container —
now-sdk --version,now-sdk init,now-sdk build, etc. are onPATHnext to the Python console scripts: - Through MCP tools — the
servicenow_sdktool family (servicenow_api/sdk_client.py) wraps the same CLI asinit/auth/build/deploy/transform/dependenciesactions, so an operator can scaffold → build → deploy a scoped app entirely through MCP without a shell into the container. Seeservicenow_api/mcp_server.py(register_sdk_tools) and theSDK_WORKDIR/SDKTOOLenv vars.
A2A agent server¶
servicenow-api also ships a Pydantic-AI agent server (console script
servicenow-agent). It consumes the MCP tool surface over MCP_URL, exposes an
A2A / web interface on port 9004, and is published in the same image.
docker/agent.compose.yml
runs the MCP server and the agent together:
services:
servicenow-api-mcp:
image: example/servicenow-api@sha256:<digest>
container_name: servicenow-api-mcp
hostname: servicenow-api-mcp
restart: always
env_file:
- ../.env
environment:
- PYTHONUNBUFFERED=1
- HOST=0.0.0.0
- PORT=8000
- TRANSPORT=streamable-http
ports:
- "8000:8000"
servicenow-api-agent:
image: example/servicenow-api@sha256:<digest>
container_name: servicenow-api-agent
hostname: servicenow-api-agent
restart: always
depends_on:
- servicenow-api-mcp
env_file:
- ../.env
command: [ "servicenow-agent" ]
environment:
- PYTHONUNBUFFERED=1
- HOST=0.0.0.0
- PORT=9004
- MCP_URL=http://servicenow-api-mcp:8000/mcp
- PROVIDER=${PROVIDER:-openai}
- MODEL_ID=${MODEL_ID:-gpt-4o}
- ENABLE_WEB_UI=True
ports:
- "9004:9004"
docker compose -f docker/agent.compose.yml up -d
curl -s http://localhost:9004/health # {"status":"OK"}
The agent reaches the MCP server by container name through MCP_URL; set PROVIDER
and MODEL_ID to select the backing LLM.
Behind a Caddy reverse proxy¶
Expose the HTTP server on a hostname with automatic TLS. Add to your Caddyfile:
# Internal (self-signed) — homelab .example.invalid zone
servicenow-api.example.invalid {
tls internal
reverse_proxy servicenow-api-mcp:8000
}
# Public — automatic Let's Encrypt
servicenow-api.example.com {
reverse_proxy servicenow-api-mcp:8000
}
Reload Caddy:
DNS with Technitium¶
Point the hostname at the host running Caddy. Via the Technitium API:
curl -s "http://technitium.example.invalid:5380/api/zones/records/add" \
--data-urlencode "token=$TECHNITIUM_DNS_TOKEN" \
--data-urlencode "domain=servicenow-api.example.invalid" \
--data-urlencode "zone=arpa" \
--data-urlencode "type=A" \
--data-urlencode "ipAddress=192.0.2.10" \
--data-urlencode "ttl=3600"
…or add an A record servicenow-api.example.invalid → <caddy-host-ip> in the Technitium web
console (http://technitium.example.invalid:5380). The ecosystem
technitium-dns-mcp automates
this as a tool.
Register with an MCP client¶
Add to your client's mcp_config.json:
{
"mcpServers": {
"servicenow-api": {
"command": "uv",
"args": ["run", "--with", "servicenow-api", "servicenow-mcp"],
"env": {
"SERVICENOW_INSTANCE": "https://your-instance.service-now.com",
"SERVICENOW_USERNAME": "admin",
"SERVICENOW_PASSWORD": "your_password"
}
}
}
}
For a remote HTTP server, point the client at http://servicenow-api.example.invalid/mcp instead.