Production-grade, read-only MCP server for ServiceNow
Project description
snow-mcp-server
A production-grade, read-only MCP server for ServiceNow: query any table, fetch a
single record, full-text search published knowledge articles, and discover a table's
schema — from Claude Desktop, Claude Code, or any MCP client — with throttling handled
properly (429/503 retried honoring Retry-After) and no live instance required to run
the test suite.
Read-only by construction: the HTTP layer this server is built on
(snow_mcp.client.SnowClient) exposes exactly one verb — get. There is no post,
put, patch, or delete method anywhere in the codebase, so there is nothing in the
tool layer that could ever issue a write, no matter what a caller asks for.
The server can read whatever the account it's configured with can read. ServiceNow ACLs are the actual security boundary — this server enforces nothing beyond what the account itself is permitted to see. Use a dedicated, least-privilege read account, not an admin account, especially against a production instance. Row-level ACLs can also filter individual records out of a table's results and its counts — a query that should match N rows may legitimately show fewer, silently, if the account can't see all of them.
snow-mcp-server is not affiliated with, endorsed by, or sponsored by ServiceNow, Inc.
Quickstart
Requires uv and a ServiceNow instance to point at. The easiest way to get one is a free Personal Developer Instance (PDI) from developer.servicenow.com — no cost, ready in a few minutes.
Once you have an instance, create a dedicated, least-privilege user for this server
rather than reusing an admin account (see the warning above) — or, on a throwaway PDI
where that's overkill, the built-in admin user is fine to start with.
uvx snow-mcp-server
That starts the server over stdio. Until snow-mcp-server is published to PyPI, run it
from a local checkout instead:
uv run --directory <path-to-checkout> snow-mcp-server
# or, straight from GitHub without cloning:
uvx --from git+https://github.com/inogen-ai/snow-mcp-server snow-mcp-server
In practice you'll point an MCP client at it instead of running it directly — for Claude Code:
Note: until snow-mcp-server is published to PyPI, replace uvx snow-mcp-server
in the commands below with the from-source form
uvx --from git+https://github.com/inogen-ai/snow-mcp-server snow-mcp-server (see the
run-from-checkout note above).
claude mcp add servicenow \
-e SNOW_MCP_INSTANCE_URL=https://dev12345.service-now.com \
-e SNOW_MCP_USERNAME=<your-username> \
-e SNOW_MCP_PASSWORD=<your-password> \
-- uvx snow-mcp-server
For Claude Desktop, add to claude_desktop_config.json:
{
"mcpServers": {
"servicenow": {
"command": "uvx",
"args": ["snow-mcp-server"],
"env": {
"SNOW_MCP_INSTANCE_URL": "https://dev12345.service-now.com",
"SNOW_MCP_USERNAME": "<your-username>",
"SNOW_MCP_PASSWORD": "<your-password>"
}
}
}
}
Tools
| Tool | Parameters | Returns |
|---|---|---|
query_records |
table: str, query: str = "", limit: int = 20, fields: str = "" |
Records from any table (e.g. incident, problem, change_request, sys_user) matching the encoded query, restricted to fields (comma-separated) when given. sys_id is always present; number and short_description/name are prioritized when the table has them. Says plainly when more records are available than limit returned. |
get_record |
table: str, sys_id: str |
One full record (all fields, display values) — typically the sys_id from a prior query_records or search_knowledge call. |
search_knowledge |
query: str, limit: int = 10 |
Full-text search across published knowledge base articles (kb_knowledge) — number, short_description, an HTML-stripped snippet of the article body, and sys_id per hit. query is plain search text, not an encoded query. The published-only filter is best-effort relevance filtering, not a security boundary — ServiceNow ACLs are the boundary, same as every other tool here. |
list_table_fields |
table: str |
The fields (element name, column label, internal type) defined on a table — schema discovery so you know what's queryable before writing an encoded query. Capped at 200 fields per table. Reads sys_dictionary, which a hardened instance may ACL — grant the account read access to it, or expect empty results. |
Encoded-query cheat sheet
query_records's query parameter is a ServiceNow encoded query, the same syntax
the platform's own list views use:
| Syntax | Meaning |
|---|---|
^ |
AND — active=true^priority=1 |
^OR |
OR — priority=1^ORpriority=2 |
field=value |
Exact match |
fieldLIKEvalue |
Partial/substring match |
123TEXTQUERY321=text |
Full-text search over the table (what search_knowledge uses internally against kb_knowledge) |
123TEXTQUERY321 relies on ServiceNow's Zing full-text indexing, which is on by
default for kb_knowledge but may not be enabled (or may lag behind recent writes)
for other tables — if a full-text query on a non-kb_knowledge table comes back empty
unexpectedly, that's the first thing to check.
fields (a separate parameter, comma-separated) restricts which columns come back —
sys_id is always included even if you don't ask for it, so results always compose
into a follow-up get_record call.
Environment variables
All settings are prefixed SNOW_MCP_ and can be set in the environment or a .env
file (see .env.example).
| Variable | Default | Purpose |
|---|---|---|
SNOW_MCP_INSTANCE_URL |
(unset, required) | Your instance's bare origin, e.g. https://dev12345.service-now.com — must be https, no path/query/fragment. |
SNOW_MCP_USERNAME |
(unset) | Basic-auth username. Required together with SNOW_MCP_PASSWORD unless SNOW_MCP_TOKEN is set. |
SNOW_MCP_PASSWORD |
(unset) | Basic-auth password. |
SNOW_MCP_TOKEN |
(unset) | OAuth bearer token, sent as Authorization: Bearer. Mutually exclusive with SNOW_MCP_USERNAME/SNOW_MCP_PASSWORD — set exactly one auth mode. |
SNOW_MCP_ITEM_LIMIT |
20 |
Default max records returned per query_records/search_knowledge call before "more available" is reported. |
SNOW_MCP_TIMEOUT_SECONDS |
30.0 |
HTTP timeout (seconds) per ServiceNow request. |
Exactly one auth mode must be configured — basic (SNOW_MCP_USERNAME +
SNOW_MCP_PASSWORD) or bearer (SNOW_MCP_TOKEN). Setting both, or neither, fails
startup immediately with a message naming the env vars to fix, rather than failing
obscurely on the first tool call.
Security notes
- Read-only by construction, not by policy: the HTTP client this server is built on has no write method to call in the first place. See the warning at the top of this README — the account's ServiceNow ACLs, not this server, decide what's actually readable.
- Throttling handled. 429/503 responses are retried honoring the instance's
Retry-Afterheader (clamped to at most 60s so a hostile or buggy value can't hang the process), falling back to exponential backoff (1→2→4s) when no header is sent, capped at 3 retries. - Credentials never appear in error messages, logs, or exceptions raised to an MCP client — failures are reduced to plain, credential-free sentences before they leave the client layer.
- Not affiliated with, endorsed by, or sponsored by ServiceNow, Inc.
Development
Requires Python 3.11+ and uv.
uv sync
uv run pytest -q
uv run ruff check .
No live ServiceNow instance is needed for the test suite — the Table API is faked at
the httpx.MockTransport boundary. See
docs/manual-verification.md for the live-PDI check a
maintainer runs before releases.
Contributing
Contributions welcome — see CONTRIBUTING.md for dev setup and PR expectations, and SECURITY.md for reporting vulnerabilities privately.
Not affiliated with, endorsed by, or sponsored by ServiceNow, Inc.
Part of InoGen's open-source portfolio: kilnworks (self-hostable RAG assistant) and the read-only MCP connectors m365, servicenow, salesforce, and hubspot.
Built and maintained by InoGen.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file snow_mcp_server-0.1.0.tar.gz.
File metadata
- Download URL: snow_mcp_server-0.1.0.tar.gz
- Upload date:
- Size: 2.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0b5764d01a0658fd58ce88598e9589c87630b4278ac8bd68aa877d8830989a6f
|
|
| MD5 |
0ebb5cb1ae6c4a34b3369531efbd66d2
|
|
| BLAKE2b-256 |
473077017a53686ae2fcfcf0bb45a7f8c34aedb4120a7ea0a6c6a052e9a8efc7
|
File details
Details for the file snow_mcp_server-0.1.0-py3-none-any.whl.
File metadata
- Download URL: snow_mcp_server-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65a28498aa602884131739d0234c567d56b3497ab24be000aa21cf2237726166
|
|
| MD5 |
603d8a93344c6406531eb34e94d6948f
|
|
| BLAKE2b-256 |
4077e6a63b8220ca21a403beb3ad329b2ba6fe009b4766f59a814928c41c3b88
|