snow-mcp
by dataoracle
README.md
# snow-mcp: read-only ServiceNow RITM server
An MCP server that lets Claude (or any MCP client) read a ServiceNow requested
item (RITM): its details, catalog variables, approvals, SLAs, comments and work
notes, and attachments. It can also start from a catalog task (SCTASK) and
resolve its parent RITM.
It has no write path: the ServiceNow client only issues GET requests, so it
cannot approve, update or create anything.
It runs as a hardened systemd service. The ServiceNow password and the MCP
access token are sealed with the machine's TPM, and systemd decrypts them into
memory only when the service starts. Secrets never sit in plain-text files or
environment variables.
## Tools
| Tool | Returns |
|---|---|
| `get_ritm_full(number)` | Everything below except attachment contents, in one call |
| `get_ritm(number)` | Main attributes, parent REQ, link to the record |
| `get_ritm_variables(number)` | Catalog form answers |
| `get_ritm_approvals(number)` | Approvers and states on the RITM and parent REQ, group approvals |
| `get_ritm_slas(number)` | SLA definitions, stage, breach flag, time left |
| `get_ritm_activity(number)` | Comments and work notes |
| `list_ritm_attachments(number)` | Attachments on the RITM and REQ, with IDs |
| `get_attachment(attachment_id)` | Text of PDF/DOCX/XLSX/text files, or the image itself |
| `get_sctask(number, include_ritm=True)` | Catalog task attributes, its parent RITM number, and optionally the RITM's details |
## Requirements
- **Server:** Linux with systemd 250 or newer and a TPM 2.0 chip (for example Ubuntu 24.04 or Debian 12), plus Python 3.10+ with `venv` support (`sudo apt install python3-venv` on Ubuntu/Debian).
- **ServiceNow:** an integration account with basic authentication and read access to these tables: `sc_req_item`, `sc_request`, `sc_task`, `sc_item_option_mtom`, `sysapproval_approver`, `sysapproval_group`, `task_sla` and `sys_attachment`. Tables the account can't read return a 403 note in the matching section. The rest still works.
## Installation
Run these on the server from a clone of this repository.
```bash
# 1. Install code, virtual environment, config file and systemd unit
sudo ./deploy/install.sh
# 2. Set your instance URL and integration user
sudo nano /etc/snow-mcp/snow-mcp.env
# 3. Seal the secrets (input is read without echo and never written to disk)
sudo ./deploy/seal-credential.sh snow_pass # the ServiceNow password
sudo ./deploy/seal-credential.sh mcp_token --generate # a new random MCP access token
# 4. Start the service and enable it at boot
sudo systemctl enable --now snow-mcp
# 5. Check it
systemctl status snow-mcp
journalctl -u snow-mcp -n 30
curl -s localhost:8765/health # {"status":"ok"}
```
Show the generated token once so you can put it in your MCP client config:
```bash
sudo systemd-creds decrypt --name=mcp_token /etc/credstore.encrypted/snow-mcp.mcp_token.cred -; echo
```
## Connecting a client
The service listens on `127.0.0.1:8765` only. The MCP endpoint is `/mcp`, and
every request except `/health` must send `Authorization: Bearer <token>`.
To reach it from another machine, put an HTTPS front end in front of the port:
a reverse proxy (nginx, Caddy), a Tailscale service, or an SSH tunnel.
Example Claude Desktop config using [`mcp-remote`](https://www.npmjs.com/package/mcp-remote):
```json
{
"mcpServers": {
"snow_mcp": {
"command": "npx",
"args": ["mcp-remote", "https://your-host.example.com/mcp",
"--header", "Authorization:${AUTH}"],
"env": { "AUTH": "Bearer <your-token>" }
}
}
}
```
Then ask Claude about a real RITM or SCTASK to confirm it works end to end.
## Layout on the server
| Path | What |
|---|---|
| `/opt/snow-mcp/app` | Application code (read-only to the service) |
| `/opt/snow-mcp/venv` | Python virtual environment |
| `/etc/snow-mcp/snow-mcp.env` | Non-secret settings: instance URL, user name, limits |
| `/etc/credstore.encrypted/snow-mcp.*.cred` | TPM-sealed password and MCP token |
| `/etc/systemd/system/snow-mcp.service` | The unit |
## Configuration
Settings in `/etc/snow-mcp/snow-mcp.env` (see [`deploy/snow-mcp.env.example`](deploy/snow-mcp.env.example)):
| Variable | Default | Meaning |
|---|---|---|
| `SNOW_INSTANCE_URL` | required | e.g. `https://yourinstance.service-now.com` |
| `SNOW_USER` | required | Integration account user name |
| `MAX_ATTACHMENT_MB` | `15` | Largest attachment `get_attachment` will download |
| `MAX_TEXT_CHARS` | `150000` | Extracted text is cut off after this many characters |
| `SAVE_DIR` | off | Keep copies of downloaded attachments; must be under `/var/lib/snow-mcp/` |
Restart the service after changing settings.
## Day-to-day
```bash
sudo systemctl restart snow-mcp # after changing settings or credentials
journalctl -u snow-mcp -f # follow logs
systemd-analyze security snow-mcp # hardening score (lower is better)
# Update code: pull the new version, then
sudo ./deploy/install.sh # keeps config and credentials, restarts if running
# Change the ServiceNow password
sudo ./deploy/seal-credential.sh snow_pass
sudo systemctl restart snow-mcp
# Rotate the MCP token
sudo ./deploy/seal-credential.sh mcp_token --generate
sudo systemd-creds decrypt --name=mcp_token /etc/credstore.encrypted/snow-mcp.mcp_token.cred -; echo
sudo systemctl restart snow-mcp # then update your MCP client with the new token
```
To reuse an existing token instead of generating one, run
`sudo ./deploy/seal-credential.sh mcp_token` and paste it at the prompt.
## Notes
- **After a BIOS/firmware update or a Secure Boot change,** the TPM may refuse
to decrypt the credentials. The service then fails to start with a credential
error in the journal. Fix it by re-running both `seal-credential.sh` commands.
- **What the TPM sealing protects against:** copies of the disk, backups, sync
tools or a git push picking up the files, and other users or services on the
server. It does not stop someone with root on the running machine, so keep
SSH key-only and root access tight.
- **If the service won't start** and the journal mentions a memory-protection
error, a dependency needs writable executable memory. Comment out
`MemoryDenyWriteExecute=yes` in the unit, then run
`sudo systemctl daemon-reload && sudo systemctl restart snow-mcp`.
- If a section of `get_ritm_full` returns `{"error": "... 403 ..."}`, the
integration account lacks read access to that table. The rest still works.
## Local development
Without systemd, secrets fall back to the `SNOW_PASS` / `MCP_TOKEN` environment
variables (or to files named by `SNOW_PASS_FILE` / `MCP_TOKEN_FILE`).
```bash
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
export SNOW_INSTANCE_URL=https://yourinstance.service-now.com SNOW_USER=integration.user
export SNOW_PASS=... MCP_TOKEN=...
python server.py # serves http://127.0.0.1:8765/mcp
```
## License
MIT, see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues