mfa-servicenow-mcp
An MFA-first ServiceNow MCP server that enables AI agents to interact with live ServiceNow instances through browser-based authentication (Okta, Entra ID, SAML, MFA) or API Key, providing read and write access to incidents, portal components, workflows, source code, and more.
Discovery & Schema
sn_discover— Find tables by name or label keywordsn_schema— Fetch field definitions, types, and constraintssn_resolve_url— Parse a ServiceNow URL to identify table, sys_id, scope, and suggested next toollist_tool_packages— See available and loaded tool packages
Data Querying
sn_query— Generic table query with filtering, ordering, field selection, and reference resolutionsn_aggregate— COUNT/SUM/AVG/MIN/MAX aggregations with optional group-bysn_health— Check API connectivity, auth status, and server version
Logging
get_logs— Query system, journal, transaction, and background script logs
Source Code Search & Analysis
search_server_code— Keyword search across 22 server-side code types (Script Includes, Business Rules, ACLs, etc.)search_portal_regex_matches— Regex search over portal widget/provider/Script Include codeget_metadata_source— Retrieve a full source record by name or sys_idtrace_portal_route_targets— Map widget → provider → route relationshipsextract_table_dependencies— Build a GlideRecord table dependency graph from server scriptsanalyze_widget_performance— Analyze widget code patterns, transaction logs, and provider usage
Portal & Widget Management
get_page— Get/list Service Portal pages with layout trees and widget placementsget_widget_instance— Get widget placement details on a pageget_widget_bundle— Fetch a full widget bundle (HTML, scripts, providers, CSS/JS) in one callget_portal_component_code— Fetch widget/provider/Script Include fieldsmanage_widget_dependency— CRUD and link/unlink Angular providers and CSS/JS dependencies (write requires confirm='approve')manage_script_include— List/get Script Includes (write requires confirm='approve')
Source Download & Sync
download_app_sources— Full app scope download to disk with incremental sync supportdownload_portal_sources— Targeted portal widget/provider download with incremental syncdownload_attachment— Download record attachments (xlsx, PDF, Word, etc.) to local disk
Local Offline Analysis
audit_local_sources— Generate cross-reference graphs, dead code detection, and HTML audit reports (no API calls)diff_local_component— Diff local edits vs. remote or a second snapshotquery_local_graph— Answer dependency/impact questions from audit graph files offline
Workflow & Flow Designer
manage_flow_designer— Read/edit Flow Designer flows, executions, and action sourcesmanage_workflow— Read/manage legacy Workflow engine items (write requires confirm='approve')
Developer Productivity
get_developer_changes— List a developer's recent changes across portal tables
Safety Features
All write/mutating operations require explicit
confirm='approve'Dry-run preview support on write tools
Concurrent-edit and duplicate-create guards
Read-only defaults — write tools only available in elevated packages
Enables authentication to ServiceNow through Okta, supporting MFA/SSO login via real browser automation (Playwright) for seamless integration with Okta-secured environments.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mfa-servicenow-mcpshow me the last 5 incidents created today"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MFA ServiceNow MCP
🌐 English | 🇰🇷 한국어 | 🇯🇵 日本語 | 🇮🇳 हिन्दी | 🇨🇳 简体中文 | 🇪🇸 Español | 🚀 GitHub Pages
MFA-first ServiceNow MCP server. Authenticates via real browser (Playwright) so Okta, Entra ID, SAML, and any MFA/SSO login just works. Also supports API Key for headless/Docker environments.
Built for personal use — use at your own risk. This project was created primarily for the author's own workflows. Risk is actively minimized (read-only defaults, write guards, dry-run previews, and confirm='approve' gates on every write), but it operates against live ServiceNow instances. You are solely responsible for what it does on your instances. Provided "as is", without warranty of any kind (Apache-2.0, see LICENSE). Review what a tool will do before approving it.
Table of Contents
Related MCP server: servicenow-mcp
Setup
Two steps: install, then add the server to your MCP client config. No installer command, no per-client flags.
1. Install
The default is uvx — no separate install step, it just runs. For most people this is the whole story.
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows PowerShell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
uvx --refresh --with playwright --from mfa-servicenow-mcp servicenow-mcp --version # fetch + verify the server
uvx --with playwright playwright install chromium # Chromium for MFA/SSO loginTo update — uvx caches the last version it downloaded and keeps reusing it, so a new release has to be pulled in explicitly with --refresh:
uvx --refresh --with playwright --from mfa-servicenow-mcp servicenow-mcp --version
uvx --with playwright playwright install chromium # a newer Playwright needs a newer Chromium buildIf uvx is blocked — pip
Windows Smart App Control stops uvx from running at all: uvx unpacks an unsigned temporary executable on every run, and SAC blocks it. If uvx suddenly stopped working right after a Windows update, this is almost certainly why. Use pip instead:
pip install mfa-servicenow-mcp playwright
python -m playwright install chromiumTo update:
pip install --upgrade mfa-servicenow-mcp playwright
python -m playwright install chromiumA Python from the python.org installer (signed, 3.10+) passes SAC as-is. Launch it with python -m servicenow_mcp rather than the servicenow-mcp console script — that script is an unsigned .exe shim pip generates, and SAC blocks it too.
On mac/Linux the one pip caveat is that Homebrew and distro Pythons refuse global installs under PEP 668 (
externally-managed-environment). Use the python.org installer, or just stay on uvx.
Installing Chromium up front matters either way. Deferring it to the first tool call means a ~150 MB download racing the MCP host's handshake deadline, which surfaces as connection closed.
Guided setup. Running
servicenow-mcp setupwith no flags (pip:python -m servicenow_mcp setup) walks you through numbered menus (pick clients and auth type by number or name — no free-text guessing), in English or Korean (auto-detected from your locale; force withSERVICENOW_MCP_LANG=ko|en).
2. Configure your MCP client
Add the server to your client's config file. The env block is identical no matter how you installed — only command/args follow the path you picked above:
Install |
|
|
uvx (default) |
|
|
pip |
|
|
Only two env vars are required; MCP_TOOL_PACKAGE defaults to standard, so leave it out unless you need a different package.
Single instance
If you only use one instance, this is all you need.
Claude Code — .mcp.json (project root) / ~/.claude.json (global):
{
"mcpServers": {
"servicenow": {
"command": "uvx",
"args": ["--with", "playwright", "--from", "mfa-servicenow-mcp", "servicenow-mcp"],
"env": {
"SERVICENOW_INSTANCE_URL": "https://your-instance.service-now.com",
"SERVICENOW_AUTH_TYPE": "browser"
}
}
}
}If you installed via pip, swap command/args to this — everything else stays the same:
"command": "python",
"args": ["-m", "servicenow_mcp"],Codex — .codex/config.toml (project) / ~/.codex/config.toml (global):
[mcp_servers.servicenow]
command = "uvx"
args = ["--with", "playwright", "--from", "mfa-servicenow-mcp", "servicenow-mcp"]
# pip: command = "python" / args = ["-m", "servicenow_mcp"]
[mcp_servers.servicenow.env]
SERVICENOW_INSTANCE_URL = "https://your-instance.service-now.com"
SERVICENOW_AUTH_TYPE = "browser"OpenCode — opencode.json (project root):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"servicenow": {
"type": "local",
"command": ["uvx", "--with", "playwright", "--from", "mfa-servicenow-mcp", "servicenow-mcp"],
"enabled": true,
"environment": {
"SERVICENOW_INSTANCE_URL": "https://your-instance.service-now.com",
"SERVICENOW_AUTH_TYPE": "browser"
}
}
}
}Other clients (Cursor, VS Code, Antigravity, Zed, …) and full env options (auth types, tool packages) are in MCP Client Configuration.
Then restart the client. The first browser tool call opens a window for Okta/Entra ID/SAML/MFA login. Sessions persist — no re-login every time.
Multiple instances (dev / test / prod)
If you work across dev / test / prod, don't stand up several servers. Changing only env lets a single connection handle all of them:
"env": {
"SERVICENOW_ACTIVE_INSTANCE": "dev",
"SERVICENOW_INSTANCE_CONFIG": "{ \"dev\": { \"url\": \"https://acme-dev.service-now.com\", \"auth_type\": \"browser\", \"allow_writes\": true }, \"prod\": { \"url\": \"https://acme.service-now.com\", \"auth_type\": \"browser\" } }"
}All that changes is an alias list taking the place of SERVICENOW_INSTANCE_URL; command/args stay the same. What you get:
Production is protected by default — an alias without
allow_writesis read-only. In the example above,prodcannot be written to at all.Query another instance without restarting — pass
instanceto a read tool, as insn_query(instance="prod", ...).Compare across instances —
compare_instancesputs the same component from dev and prod side by side.One login — the browser session is shared across aliases.
The full rules (write routing, gates, ${ENV} references) live in Multiple instances — two approaches. Only reach for B. Multi-process there if the connections have to look separate in your client's UI.
Prefer an AI to do it? Paste into Claude Code / Cursor / Codex / etc.:
Install and configure mfa-servicenow-mcp following https://raw.githubusercontent.com/jshsakura/mfa-servicenow-mcp/main/docs/llm-setup.md
If your corporate network blocks the install
TLS-inspecting proxies (Zscaler and friends) and blocked PyPI access have their own path — see Install (offline / corporate).
Features
Browser authentication for MFA/SSO environments (Okta, Entra ID, SAML, MFA)
4 auth modes: Browser, Basic, OAuth, API Key
66 registered tools with 6 active package profiles plus disabled
none— from minimal read-only to broad bundled CRUD4 workflow skills with safety gates, sub-agent delegation, and verified pipelines
Streamable HTTP transport — keep stdio as the default, or expose
/mcpfor HTTP-capable clients and bridgesLocal source audit with HTML report, cross-reference graph, dead code detection, and auto-generated domain knowledge
Authoritative relationship graphs on disk —
_graph.json(widget→Angular Provider, from the live M2M) and_page_graph.json(page→widget, fromsp_instance) let the LLM answer dependency questions offline instead of re-querying the instanceIncremental sync (
incremental=True) — re-download only records changed since last sync (sys_updated_onwatermark), likegit pull;reconcile_deletions=Trueflags records deleted on the instanceCross-scope dep auto-resolve in
download_app_sources— pulls global-scope Script Includes, Widgets, Angular Providers, and UI Macros that the app references, so the local bundle is self-contained for analysisAttachment download (
download_attachment) — fetch a record's attachment file(s) (xlsx, PDF, Word, …) to local disk by attachment sys_id or by parenttable+record; resolves a record's attachments automatically and writes bytes to disk so the LLM reads them fromsaved_pathDry-run preview on every write tool (
dry_run=True) — returns field-level diff, dependency counts, and precision notes before any side effect. Uses read-only APIs, works under all auth modes.Write intent gate: every mutation requires an explicit
confirm='approve'(accidental-write guardrail, not an adversarial boundary — see Safety Policy)Payload safety limits, per-field truncation, and total response budget (200K chars)
Transient network error retry with backoff
Tool packages for core, standard, service desk, portal developers, and platform developers —
fullavailable for advanced users (see warning)Developer productivity tools: activity tracking, uncommitted changes, dependency mapping, daily summary
Full coverage of core ServiceNow artifact tables (see Supported Tables)
CI/CD with auto-tagging, PyPI publishing, and Docker multi-platform builds
Supported ServiceNow Tables
Artifact Type | Table Name | Source Search | Developer Tracking | Safety (Heavy Table) |
Script Include |
| ✅ | ✅ | 🛡️ |
Business Rule |
| ✅ | ✅ | 🛡️ |
Client Script |
| ✅ | ✅ | 🛡️ |
Catalog Client Script |
| ✅ | ⬜ | ⬜ |
UI Action |
| ✅ | ✅ | 🛡️ |
UI Script |
| ✅ | ✅ | 🛡️ |
UI Page |
| ✅ | ✅ | 🛡️ |
UI Macro |
| ✅ | ⬜ | 🛡️ |
Scripted REST API |
| ✅ | ✅ | 🛡️ |
Fix Script |
| ✅ | ✅ | 🛡️ |
Scheduled Job |
| ✅ | ⬜ | ⬜ |
Script Action |
| ✅ | ⬜ | ⬜ |
Email Notification |
| ✅ | ⬜ | ⬜ |
ACL |
| ✅ | ⬜ | ⬜ |
Transform Script |
| ✅ | ⬜ | ⬜ |
Processor |
| ✅ | ⬜ | ⬜ |
Service Portal Widget |
| ✅ | ✅ | 🛡️ |
Angular Provider |
| ✅ | ✅ | ⬜ |
Portal Header/Footer |
| ✅ | ⬜ | ⬜ |
Portal CSS |
| ✅ | ⬜ | ⬜ |
Angular Template |
| ✅ | ⬜ | ⬜ |
Metadata / XML Definitions |
| ✅ | ⬜ | 🛡️ |
Update XML |
| ✅ | ⬜ | ⬜ |
Install (offline / corporate)
For most users the Setup above (uvx) is all you need. Two corporate-network cases are worth calling out.
The common case is PyPI reachable, but HTTPS is TLS-inspected (Zscaler / Netskope / corporate MITM) — that is what the section below covers.
If PyPI itself is blocked outright, neither uvx nor pip can reach the package. Ask your IT team to allowlist pypi.org and files.pythonhosted.org, or to mirror the package on an internal index you can point pip install --index-url at.
Installing behind a TLS-inspecting proxy (Zscaler etc.)
Use this when PyPI is reachable but a TLS-inspecting proxy re-signs HTTPS, so installs and runtime calls fail with SSL: CERTIFICATE_VERIFY_FAILED. Registering the proxy's root CA in the OS trust store is not enough — Python (pip, requests, httpx), curl_cffi, and Playwright each ship their own CA bundle (certifi / libcurl / node) and ignore the OS store unless you point them at the cert via env.
1. Get the proxy root CA as a PEM file (ask IT, or export it from the OS keychain). Assume it lands at /etc/ssl/zscaler-root.pem (Windows: C:\certs\zscaler-root.pem).
2. Install — point the installer at the cert:
pip install --cert /etc/ssl/zscaler-root.pem mfa-servicenow-mcp
python -m playwright install chromium # NODE_EXTRA_CA_CERTS (step 3) covers its downloadPrefer uvx? uv can use the OS trust store directly (where the proxy CA is already registered):
UV_NATIVE_TLS=1 uvx --with playwright --from mfa-servicenow-mcp servicenow-mcp --version3. Runtime — set the CA path in your MCP client env. The non-obvious part: live ServiceNow calls go through curl_cffi (libcurl), which reads CURL_CA_BUNDLE — not REQUESTS_CA_BUNDLE. Set all of them so every layer trusts the proxy:
{
"mcpServers": {
"servicenow": {
"command": "python",
"args": ["-m", "servicenow_mcp"],
"env": {
"SERVICENOW_INSTANCE_URL": "https://your-instance.service-now.com",
"SERVICENOW_AUTH_TYPE": "browser",
"CURL_CA_BUNDLE": "/etc/ssl/zscaler-root.pem",
"REQUESTS_CA_BUNDLE": "/etc/ssl/zscaler-root.pem",
"SSL_CERT_FILE": "/etc/ssl/zscaler-root.pem",
"NODE_EXTRA_CA_CERTS": "/etc/ssl/zscaler-root.pem"
}
}
}
}Env var | Layer it fixes |
| curl_cffi / libcurl — the actual ServiceNow API + browser-login probe calls |
|
|
| Python stdlib |
| Playwright's Chromium download |
|
|
In a fully-inspected network the proxy re-signs every host, so the single proxy-root PEM covers all HTTPS. If some hosts bypass the proxy, concatenate the proxy root with certifi's bundle (python -m certifi prints its path) into one PEM and point the env vars at that.
Last resort if you genuinely can't obtain the PEM:
pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org mfa-servicenow-mcpskips verification for the install only — it does nothing for runtime ServiceNow calls, which still needCURL_CA_BUNDLE. Prefer the cert path;--trusted-hostdisables a security control.
MCP Client Configuration
Recommended: use Setup above. Use the copy-paste configs below when you need to inspect, repair, or hand-manage a client config file.
Each project can connect to a different ServiceNow instance. Set the config in your project directory so each project has its own instance URL and credentials.
Client | Project Config | Global Config | Format |
Claude Code |
|
| JSON |
Cursor |
| Project only | JSON |
VS Code (Copilot) |
| Project only | JSON |
Zed | Global only |
| JSON |
OpenAI Codex |
|
| TOML |
OpenCode |
| Project only | JSON |
Windsurf | Global only |
| JSON |
Claude Desktop | Global only |
| JSON |
AntiGravity | Global only |
| JSON |
Docker | Env vars only | Env vars only | Env vars |
Copy-paste configs for each client: Client Setup Guide
SERVICENOW_USERNAME/SERVICENOW_PASSWORDare optional — they prefill the MFA login form. On Windows, set these as system environment variables.
Profiles vs. multi-process
The examples above are single-instance — that stays the default. With more than one instance there are two ways to go, and it's worth picking one before you configure anything:
A. Profiles (recommended) | B. Multi-process | |
Server processes | 1 | one per instance |
Connections the client sees | 1 | 3 |
Choosing an instance |
| pinned to the process |
Cross-instance comparison | works ( | impossible — processes don't know each other |
Browser login | one shared session | one login per process |
Write control |
| per-process config |
Most people want A. Write safety is already solved there — leave allow_writes off a prod alias and it's read-only, and writes to a non-active instance have to clear the confirm_instance gate. On top of that, only A gives you cross-instance comparison and a single login.
Pick B only when the connections need to be visibly separate in the client UI. Tool names come through as mcp_snow-prd_*, so a human tells them apart at a glance. The cost is three logins, no comparison, and three copies of the config. Details: Telling multiple connections apart.
A. Profiles
To switch between several instances from one client, list them in SERVICENOW_INSTANCE_CONFIG (alias → settings) and pick the active one with SERVICENOW_ACTIVE_INSTANCE. Each alias can carry its own credentials (username / password / auth_type / api_key); ${ENV} references keep secrets out of the JSON. The single-instance SERVICENOW_INSTANCE_URL form still works as a fallback.
{
"mcpServers": {
"servicenow": {
"command": "uvx",
"args": ["--with", "playwright", "--from", "mfa-servicenow-mcp", "servicenow-mcp"],
"env": {
"MCP_TOOL_PACKAGE": "standard",
"SERVICENOW_ACTIVE_INSTANCE": "dev",
"SERVICENOW_INSTANCE_CONFIG": "{ \"dev\": { \"url\": \"https://acme-dev.service-now.com\", \"auth_type\": \"browser\", \"username\": \"dev_user\", \"password\": \"${SERVICENOW_DEV_PASSWORD}\", \"allow_writes\": true }, \"test\": { \"url\": \"https://acme-test.service-now.com\", \"auth_type\": \"browser\", \"username\": \"test_user\", \"password\": \"${SERVICENOW_TEST_PASSWORD}\" } }"
}
}
}
}SERVICENOW_ACTIVE_INSTANCE is where writes default; read tools peek at the others with instance="test", and a single write can be routed to a non-active instance with instance="test" confirm_instance="test" confirm="approve" (guarded, and verified after it lands). Full rules (write routing, gating, comparison, ${ENV}): Multi-Instance Mode.
B. Multi-process
Only worth it when you want the connections split apart in the client UI. Each entry pins one instance and gets its own name via --server-name:
{
"mcpServers": {
"snow-dev": {
"command": "uvx",
"args": ["--with", "playwright", "--from", "mfa-servicenow-mcp", "servicenow-mcp", "--server-name", "snow-dev"],
"env": {
"SERVICENOW_INSTANCE_URL": "https://acme-dev.service-now.com",
"SERVICENOW_AUTH_TYPE": "browser"
}
},
"snow-prd": {
"command": "uvx",
"args": ["--with", "playwright", "--from", "mfa-servicenow-mcp", "servicenow-mcp", "--server-name", "snow-prd"],
"env": {
"SERVICENOW_INSTANCE_URL": "https://acme.service-now.com",
"SERVICENOW_AUTH_TYPE": "browser",
"MCP_TOOL_PACKAGE": "standard"
}
}
}
}Tool names are then pinned to mcp_snow-dev_* / mcp_snow-prd_*. Drop --server-name and both advertise themselves as ServiceNow, so the client numbers them by load order (mcp_servicenow, mcp_servicenow2) — and that numbering can shift between restarts, which means you can never trust which one is production.
To keep the production connection read-only, give it a read-only MCP_TOOL_PACKAGE. Unlike A, there is no allow_writes alias gate here — the tool package is the only thing blocking writes.
Login prompts come up per process, and cross-instance tools like
compare_instancesare unavailable — each process only knows its own instance. If that bites, go with A.
Authentication
Choose the auth mode based on your ServiceNow environment.
Browser Auth (MFA/SSO) — Default
The Setup command uses browser auth by default. Optional flags:
Flag | Env Variable | Default | Description |
|
| — | Prefill login form username |
|
| — | Prefill login form password |
|
|
| Run browser without GUI |
|
|
| Login timeout in seconds |
|
|
| Session TTL in minutes |
|
| — | Override the Chromium profile path. Rarely needed — see the sandbox note below before setting it. |
|
| user-specific | Session validation endpoint (avoids 401 on non-admin sessions) |
|
| — | Custom login page URL |
Login sharing across hosts and instances — how it actually works
The server caches two things under ~/.mfa_servicenow_mcp/: the Playwright profile (Chromium SSO cookies) and a session JSON (parsed cookies reused on the next start). Both are scoped per instance + username — files are named profile_<host>_<user> and session_<host>_<user>.json.
That scoping does two things for you automatically, with no configuration:
Multiple hosts share one login. Claude Code and Codex on the same machine both resolve
~/.mfa_servicenow_mcp/, so whichever logs in first writes the session and the other reuses it — no second MFA prompt.Different instances / different credentials stay isolated. Each instance+user gets its own profile and session file, so dev and test (or two accounts) never collide. For multiple instances, configure them in
SERVICENOW_INSTANCE_CONFIG(JSON) — each alias gets its own scoped cache; you do not manage this with a profile path.
Do not set SERVICENOW_BROWSER_USER_DATA_DIR to "share" logins. It overrides the profile path verbatim — the per-instance scoping is bypassed, so every instance you run is forced into one Chromium profile and their cookies collide. The only legitimate use is a narrow one: a sandboxed host (e.g. Claude Desktop on macOS) that remaps HOME to a container path, so its ~/.mfa_servicenow_mcp/ no longer matches the terminal's. In that single-instance case, point the sandboxed host at the real home path:
# Only when a sandbox remapped HOME, and only for a single-instance host
export SERVICENOW_BROWSER_USER_DATA_DIR="/Users/you/.mfa_servicenow_mcp/profile_acme"If you run more than one instance, leave this unset and let the per-instance scoping do its job.
Basic Auth
Use this for PDIs or instances without MFA.
python -m servicenow_mcp \
--instance-url "https://your-instance.service-now.com" \
--auth-type "basic" \
--username "your_id" \
--password "your_password"OAuth
Current CLI support expects OAuth password grant inputs.
python -m servicenow_mcp \
--instance-url "https://your-instance.service-now.com" \
--auth-type "oauth" \
--client-id "your_client_id" \
--client-secret "your_client_secret" \
--username "your_id" \
--password "your_password"If --token-url is omitted, the server defaults to https://<instance>/oauth_token.do.
API Key
python -m servicenow_mcp \
--instance-url "https://your-instance.service-now.com" \
--auth-type "api_key" \
--api-key "your_api_key"Default header: X-ServiceNow-API-Key (customizable with --api-key-header).
Tool Packages
MCP_TOOL_PACKAGE controls which tools the server exposes. Default: standard — no config needed for most users.
Any package above standard grants write access and is an advanced option. service_desk, portal_developer, platform_developer, and full all let an AI agent create, update, and delete records — full does so across every domain at once. Most users should stay on the read-only default standard and only opt up to the narrowest write package their task actually requires.
Read-only (safe defaults):
Package | Tools | ~Tokens | Description |
| 0 | 0 | Disabled profile for intentionally turning tools off |
| 12 | ~3.0K | Minimal read-only essentials for health, schema, discovery, and key artifact lookups |
| 29 | ~7.3K | (Default) Read-only across incidents, changes, portal, logs, and source analysis |
⚠️ Write-capable (advanced — grants create/update/delete):
Package | Tools | ~Tokens | Description |
| 31 | ~8.2K | ⚠️ standard + incident and change operational writes |
| 41 | ~10.6K | ⚠️ standard + portal, changeset, script include, and local-sync delivery writes |
| 41 | ~10.8K | ⚠️ standard + workflow, Flow Designer, UI policy, incident/change, and script writes |
| 55 | ~13.8K | ⚠️ Most advanced — all write tools across all domains at once |
~Tokens is the approximate footprint each package's tool schemas add to the model's context per request (measured with tiktoken
cl100k_baseover the server's compacted schemas; actual Claude counts vary slightly). Staying on the narrowest package keeps the context budget — and cost — down.
Each server process binds to one active ServiceNow instance for ordinary tools. A write to a different configured instance is possible per call, but only through an explicit, guarded acknowledgement (below) — never a silent switch.
Multi-Instance Mode (comparison + guarded single-call writes)
When you need to compare dev/test/prod or deploy to a chosen one, opt into named instances with SERVICENOW_INSTANCE_CONFIG. SERVICENOW_ACTIVE_INSTANCE is still required.
Two things are global, one is per-instance:
Tool surface is global — set once with
MCP_TOOL_PACKAGE. Only one instance is ever active per server process, so there is no per-instance tool package.Write permission is per-instance — each alias carries
allow_writes. It is enforced at call time against the active instance: a write tool can be loaded but still refused if the active instance hasallow_writes: false. Writes are opt-in: omitallow_writesand the instance is read-only.Credentials are per-instance with global fallback — put
username/password/api_key(andauth_type) on an alias to override; omit them and the alias inherits the globalSERVICENOW_USERNAME/SERVICENOW_PASSWORD/ etc. So if every instance shares one login, set it once globally and leave the alias entries credential-free.
Other rules:
Read tools accept an
instanceargument to run a single read against a non-active instance — e.g.sn_query(instance="test", table="incident", ...)orsn_health(instance="test")whiledevstays active. Every read tool in your package exposes it in its schema (enum of configured aliases). This is how you peek at another instance's data without restarting.A single write can be routed to a non-active instance, but never silently. Pass
instance="test" confirm_instance="test" confirm="approve"(target named twice — as intent and acknowledgement) and the target must haveallow_writes=true. Only that one write goes there; the active instance is restored immediately after. A target/confirm mismatch or a read-only target is refused with an explicit message, so a dev/test/prod mix-up cannot land on the wrong instance. The write is then re-read on the target and reported aslanded(orWRITE_NOT_LANDED), withtarget_instanceechoed — "success" means the content is confirmed present on the intended instance, not just a 200.list_instancesreports configured aliases plus the active one and each one's write flag.compare_instancesperforms read-only table comparisons across aliases.Switching the default active instance requires restarting the MCP client — it is read once at server startup, not refreshed live. (Per-call
instance=routing above does not need a restart.)
Example — shared global login, per-instance write gating:
export MCP_TOOL_PACKAGE=standard
export SERVICENOW_USERNAME=svc_account
export SERVICENOW_PASSWORD='...'
export SERVICENOW_ACTIVE_INSTANCE=dev
export SERVICENOW_INSTANCE_CONFIG='{
"dev": { "url": "https://acme-dev.service-now.com", "allow_writes": true },
"test": { "url": "https://acme-test.service-now.com", "allow_writes": true },
"prod": { "url": "https://acme-prod.service-now.com", "allow_writes": false }
}'To give an instance its own login instead, add the fields to that alias (a ${ENV} reference is resolved, so you can keep secrets out of the JSON):
"prod": { "url": "https://acme.service-now.com", "username": "prod_user", "password": "${SERVICENOW_PROD_PASSWORD}" }Use compare_instances for dev/test drift checks. For promoting MANY records (especially Service Portal / scoped tables), prefer an Update Set (commit on source, retrieve + commit on target in the UI) over per-record cross-instance writes — it bypasses the per-table/SP ACLs that single Table-API writes hit.
If a tool is not available in your current package, the server tells you which package includes it.
For the full reference (all packages, inheritance details, config syntax): Tool Packages Advanced Guide.
CLI Reference
Server Options
Flag | Env Variable | Default | Description |
|
| required | ServiceNow instance URL |
|
|
| Auth mode: |
|
|
| Tool package to load |
|
|
| MCP server name advertised to the client |
|
|
| MCP transport: |
|
|
| Host for |
|
|
| Port for |
|
|
| Streamable HTTP endpoint path |
|
| loopback hosts | Comma-separated Host allowlist for DNS rebinding protection |
|
|
| Disable DNS rebinding protection behind trusted network controls |
|
|
| Return JSON responses instead of SSE streams |
|
|
| HTTP request timeout (seconds) |
|
|
| Enable debug logging |
HTTP transport example:
servicenow-mcp --transport http --http-host 127.0.0.1 --http-port 8000The MCP endpoint is http://127.0.0.1:8000/mcp; /health returns a lightweight health response.
Telling multiple connections apart (--server-name)
If you register several server entries in one client (dev / stg / prd as separate processes), they all default to the name ServiceNow, so the client disambiguates them by load order — mcp_servicenow, mcp_servicenow2, mcp_servicenow3. That numbering can change between restarts, which makes it untrustworthy for telling which one is production. Name each connection instead:
servicenow-mcp --server-name snow-prd # uvx / console script
python -m servicenow_mcp --server-name snow-prd # pipThe tool namespace is then pinned to mcp_snow-prd_*. SERVICENOW_MCP_SERVER_NAME does the same thing as an env var, and the flag wins if both are set. Unset, it stays ServiceNow, so existing configs keep working.
Looking to switch instances inside one server instead? That's Multi-Instance Mode, not this. The two are unrelated —
--server-nameis the name the client sees, while a multi-instance alias names an instance inside a single process.
Basic Auth
Flag | Env Variable |
|
|
|
|
OAuth
Flag | Env Variable |
|
|
|
|
|
|
|
|
|
|
API Key
Flag | Env Variable | Default |
|
| — |
|
|
|
Script Execution
Flag | Env Variable |
|
|
Keeping Up to Date
Pick the one matching how you installed (the same commands are in the Install section):
# uvx — it caches the last version it downloaded, so --refresh is how you pull a new one
uvx --refresh --with playwright --from mfa-servicenow-mcp servicenow-mcp --version
uvx --with playwright playwright install chromium# pip
pip install --upgrade mfa-servicenow-mcp playwright
python -m playwright install chromiumChromium gets refreshed alongside in both cases because a newer Playwright wants a different Chromium build (see below).
After refreshing, restart your MCP client (Claude Code, Cursor, etc.) to load the new version.
Check the current version:
uvx --from mfa-servicenow-mcp servicenow-mcp --version # uvx
python -m servicenow_mcp --version # pipWhy Chromium has to be installed up front
A new Playwright release wants a different Chromium build. Left alone, the first browser tool call has to fetch ~150 MB of browser binaries — which on a slow link blows past the MCP host's handshake timeout and surfaces as:
MCP startup failed: handshaking with MCP server failed: connection closed: initialize responseThat's why the upgrade commands above run playwright install chromium every time.
Why we no longer auto-install Chromium inside the MCP server: that download used to run during the first tool call. On a slow link the subprocess outlived the host's handshake deadline and the client reported "connection closed". v1.13.1 changed this — the MCP server now only warns if Chromium is missing. Install it ahead of time (out-of-band, no handshake timer).
Safety Policy
All mutating tools require an explicit confirm='approve' argument.
Rules:
Mutating tools with prefixes such as
create_,update_,delete_,remove_,add_,move_,activate_,deactivate_,commit_,publish_,submit_,approve_,reject_,resolve_,reorder_, andexecute_require confirmation.You must pass
confirm='approve'.Without that parameter, the server rejects the request before execution.
This policy applies regardless of the selected tool package.
What this gate is — and is not. The server enforces
confirm='approve'before any write, but the argument is supplied by the same LLM that issued the call. So the gate is an intent checkpoint that stops accidental or ambiguous mutations — it forces a deliberate, auditable "yes" and a preview hint. It is not a defense against a determined or prompt-injected agent, which can simply includeconfirm='approve'. Treat it as guardrails, not a security boundary: review what a tool will do before approving, run against a least-privilege ServiceNow account, and do not rely on confirmation alone in adversarial settings. For high-stakes automation, add out-of-band approval.
Write Guards
Beyond the confirm gate, every write runs through deterministic guards that block unsafe writes before they reach ServiceNow. The concurrent-edit and duplicate-create checks run after the confirm gate, so an unconfirmed write never touches the network. Each guard fails open on a denied/failed pre-read — it never blocks a legitimate write just because it couldn't look first. The intent is simple: you should never be able to silently clobber a teammate's change — if someone else touched the record, the write stops and tells you, rather than overwriting and moving on.
Fail-open vs fail-closed. The default (fail-open) favors availability: if the pre-write audit read cannot run (network error, ACL denial, 5xx), the write proceeds. That means the concurrent-edit guard can silently no-op exactly when the instance is unreachable. Security-sensitive deployments can set
SERVICENOW_WRITE_GUARDS_FAIL=closedso a guard that could not verify blocks instead — trading availability for the guarantee that a lost-update check never silently passes. Scoped to genuine read failures; a successful read that finds no conflict still proceeds.
Guard | Protects against | Override / toggle |
Concurrent edit (G3/G8) | Blindly overwriting a record a different user edited within the last 10 min. Covers |
|
Source push drift (live anchor + update-set HOLD) | Pushing edited source back with |
|
Duplicate create (G9) | Silently creating a second record with a name that already exists, on tables ServiceNow does not make unique ( | pass |
Flow Designer raw write (G6) | Raw | — |
Publish-class (G7) | Accidental publish/commit/push — needs a second | — |
Cross-instance push | Pushing local source downloaded from instance A into instance B (origin read from | re-download from the correct instance |
Disable the whole layer with SERVICENOW_WRITE_GUARDS=off. In multi-instance mode, every write response also carries an instance_target field (and reads routed elsewhere an instance_source) so the instance a call hit is always visible.
Portal Investigation Safety
Portal investigation tools are conservative by default:
search_portal_regex_matchesstarts with widget-only scanning, linked expansion off, and small default limits.trace_portal_route_targetsis the preferred follow-up for compact Widget -> Provider -> route target evidence.download_portal_sourcesdoes not pull linked Script Includes or Angular Providers unless explicitly requested.Large portal scans are capped server-side and return warnings when the request exceeds safe defaults.
Pattern matching modes:
Mode | Behavior |
| Plain strings treated literally, regex-looking patterns remain regex |
| Always escape the pattern first; safest for route/token strings |
| Use only when you intentionally need regex operators |
Performance Optimizations
The server includes several layers of performance optimization to minimize latency and token usage.
Serialization
orjson backend: All JSON serialization uses
json_fast(orjson when available, stdlib fallback). 2-4x faster than stdlibjsonfor both loads and dumps.Compact output: Tool responses are serialized without indentation or extra whitespace, saving 20-30% tokens per response.
Double-parse avoidance:
serialize_tool_outputdetects already-compact JSON strings and skips re-serialization.
Caching
OrderedDict LRU cache: Query results are cached with O(1) eviction using
OrderedDict.popitem(). 256 max entries, 30-second TTL (600s for stable metadata: schema/scope/choice tables), thread-safe.Tool schema cache: Pydantic
model_json_schema()output is cached per model type, avoiding repeated schema generation.Lazy tool discovery: Only tool modules required by the active
MCP_TOOL_PACKAGEare imported at startup. Unused modules are skipped entirely.
Network
Browser-grade TLS by default: The HTTP layer routes through
curl_cffiwith a Chrome impersonation profile (chrome120by default), so the TLS handshake is byte-for-byte like a real browser — instances behind Cloudflare/Akamai or JA3 bot-detection that reject stock Pythonrequestswork with no extra config. Opt out withSERVICENOW_TLS_IMPERSONATE=off.Session keep-alive (browser auth): While you're actively working, a background thread pings the instance every 5 minutes with the same lightweight probe the restore path uses, so ServiceNow's sliding idle timeout never kills the session between tool calls — no more surprise MFA windows after a lunch break. It never opens a browser (a dead session just waits for the next real call to re-auth), stops after 6 hours without real activity, and is tunable via
SERVICENOW_SESSION_KEEPALIVE=off,SERVICENOW_SESSION_KEEPALIVE_INTERVAL_S(default 300, min 60), andSERVICENOW_SESSION_KEEPALIVE_MAX_IDLE_S(default 21600).HTTP session pooling: Persistent session with TCP keep-alive and gzip/deflate compression (60-80% payload reduction on large JSON). The stock-
requestsopt-out path mounts a 20-connectionHTTPAdapter.Parallel pagination:
sn_query_allfetches the first page sequentially for total count, then retrieves remaining pages concurrently viaThreadPoolExecutor(up to 4 workers).Dynamic page sizing: When remaining records fit in a single page (<=100), the page size is enlarged to avoid extra round-trips.
Batch API:
sn_batchcombines multiple REST sub-requests into a single/api/now/batchPOST, with automatic chunking at the 150-request limit.Parallel chunked M2M queries: Widget-to-provider M2M lookups split into 100-ID chunks are executed concurrently rather than sequentially.
Schema & Startup
Shallow-copy schema injection: Confirmation schema (
confirm='approve') is injected via lightweight dict copy instead ofcopy.deepcopy, reducinglist_toolsoverhead.No-count optimization: Subsequent pagination pages use
sysparm_no_count=trueto skip server-side total count computation.Payload safety: Heavy tables (
sp_widget,sys_script, etc.) have automatic field clamping and limit restrictions to prevent context window overflow.
Local Source Audit
Download and analyze your entire ServiceNow application locally — no repeated API calls, no context waste.
Step 1: download_app_sources(scope="x_company_app") → All server-side code + cross-scope deps to disk
Step 2: audit_local_sources(source_root="temp/...") → Analysis + HTML reportStep 1 runs auto_resolve_deps=True by default: after the in-scope download it scans every
.js/.html/.xml file and fetches any referenced sys_script_include, sp_widget,
sp_angular_provider, or sys_ui_macro records not already in the bundle — no matter
what scope they live in. Pulled deps are saved into the same tree with
"is_dependency": true in their _metadata.json, so the audit in Step 2 sees the
complete call graph. Set auto_resolve_deps=False if you only want in-scope records.
Tip — pull a whole scope, including
global: passscope="global"to dump every global-scope record, or keep your app scope and letauto_resolve_depsreach intoglobalfor the records you actually reference. Either way the local bundle is self-contained, so analysis runs entirely offline against disk.
Incremental Sync
Re-downloading a large app on every run is slow and risks timeouts. Pass incremental=True
to fetch only what changed since the last download — like git pull instead of a fresh
clone. Works on both download_app_sources and download_portal_sources.
download_app_sources(scope="x_company_app") # 1st run: full download
download_app_sources(scope="x_company_app", incremental=True) # later: changed records onlyHow it works: the first download records each record's
sys_updated_oninto_sync_meta.json. On an incremental run, every source family queriessys_updated_on >= <latest seen>(server-side timestamps, no clock skew), re-downloads just those records, and leaves unchanged local files untouched.Deletions: timestamp deltas can't see deleted records. Add
reconcile_deletions=Trueto list records present locally but gone on the instance — reported as warnings underdeletion_candidates, never deleted automatically.First run / no prior data: falls back to a full download automatically.
Run a full (non-incremental) download periodically to stay fully in sync.
Download Safety & Completeness
The download is the source of truth for offline analysis, so it is built to be deterministic and to never look complete when it isn't:
Scope auto-resolution. Pass the app namespace (
x_company_app), its display name ("My App"), or asys_scopesys_id — all resolve to the canonical namespace, so the local folder (temp/<instance>/<namespace>/) and every query are identical every run. The resolved value is echoed asscope_resolution.No silent caps. If a source family hits
max_records_per_type, it is flagged loudly: a per-familycapped: trueinsource_types, the family inincomplete_types, and a top-levelcomplete: false. A truncated download can never masquerade as a full one.Cross-instance / stale guards. Pushing back (
update_remote_from_local) checks the local tree's recorded origin against the connected instance; a resume re-download that keeps a stale local copy preserves the real sync watermark and warns instead of hiding the drift.Relationship metadata at download time. Widget→Angular-Provider edges (
_graph.json) and widget→CSS/JS-dependency edges (_dependency_graph.json) are captured from the live M2M tables during the portal download — analysis reads the real graph instead of guessing from code.Transitive dependency depth. Cross-scope deps resolve
2passes deep by default (conservative). Raise withSERVICENOW_DEP_MAX_DEPTH(clamped to1–6) to chase longer A→B→C→D chains.One-call graph build. Pass
build_graph=Truetodownload_app_sourcesto run the offline relationship audit right after the download — no extra API cost.Create → local sync nudge. When you create a widget/page on the instance and a local tree exists for that scope, the create response adds a
local_out_of_syncmessage with the exactdownload_portal_sources(...)command to pull the new record into local. It never writes local files for you.
What Gets Generated
File | Purpose |
| Self-contained dark-theme HTML report — open in browser |
| Who calls who — Script Include chains, GlideRecord table refs |
| Authoritative widget→Angular Provider edges from the live M2M (not text-guessed) |
| Authoritative widget→CSS/JS dependency edges from |
| Page→widget placements derived locally from |
| Dead code candidates — unreferenced SIs, unused widgets |
| Per-table BR/CS/ACL execution sequence with order numbers |
| Auto-generated app profile — table maps, hub scripts, warnings |
| Field definitions for every referenced table |
| Per-family |
Individual Download Tools
Use the orchestrator for a full dump, or download_server_sources for a targeted single-family refresh:
Tool | Sources |
| Full app dump (all families + portal + schema + cross-scope deps) |
| Widgets, Angular Providers, linked Script Includes |
| Targeted refresh — |
| sys_dictionary field definitions |
All downloads write full source to disk with zero truncation. Only a summary is returned to the LLM context.
Skills
Tools are raw API calls. Skills are what make your LLM actually useful — verified pipelines with safety gates, rollback, and context-aware sub-agent delegation. MCP server + skills is the complete setup for LLM-driven ServiceNow automation.
4 skills today, more coming with every release.
Tools Only | Tools + Skills | |
Safety | LLM decides | Gates enforced (diff → preview → confirm → apply) |
Tokens | Source dumps in context | Delegate to sub-agent, summary only |
Accuracy | LLM guesses tool order | Verified pipeline |
Rollback | Might forget | Server-side version history (ServiceNow Versions tab / update sets) |
Install Skills
# Claude Code
uvx --from mfa-servicenow-mcp servicenow-mcp-skills claude
# OpenAI Codex
uvx --from mfa-servicenow-mcp servicenow-mcp-skills codex
# OpenCode
uvx --from mfa-servicenow-mcp servicenow-mcp-skills opencode
# Antigravity
uvx --from mfa-servicenow-mcp servicenow-mcp-skills antigravityThe installer downloads the skill files from this repository's skills/ directory and places them in a project-local LLM directory. No authentication or configuration needed.
If
servicenow-mcp-skillsis blocked by security policy on Windows, call it as a module instead — same behavior:python -m servicenow_mcp.setup_skills claude
Client | Install Path | Auto-Discovery |
Claude Code |
|
|
OpenAI Codex |
| Skills loaded on next agent session |
OpenCode |
| Skills loaded on next session |
Antigravity |
| Skills activated on next session |
How it works: Each skill is a standalone Markdown file with YAML frontmatter (metadata) and pipeline instructions. The LLM client reads these files from the install path and exposes them as callable commands or skill triggers.
Update: Re-run the same install command — it replaces all existing skill files (clean install, no merge).
Remove skills only: delete the skill install directory manually (for example rm -rf .claude/commands/servicenow/).
Skill Categories
Category | Skills | Purpose |
| 1 | local source audit — cross-references, dead code, execution order, HTML report |
| 1 | flow trigger tracing — which workflows/flows fire when a table changes |
| 2 | app source download, local sync (diff → push with conflict detection) |
Skill Metadata
Each skill includes metadata that helps LLMs optimize execution:
context_cost: low|medium|high # → high = delegate to sub-agent
safety_level: none|confirm|staged # → staged = mandatory diff/preview/apply
delegatable: true|false # → can run in sub-agent to save context
triggers: ["위젯 분석", "analyze widget"] # → LLM trigger matchingFor the full skill reference, see skills/SKILL.md.
MCP Resources (Built-in Skill Guides)
Skills are also exposed as MCP resources directly from the server — no client-side installation required. Any MCP-compliant client can discover and read them on demand.
# List available skill guides
list_resources → skill://manage/local-sync, skill://manage/app-source-download, ...
# Read a specific guide
read_resource("skill://manage/local-sync") → full pipeline with safety gatesTools that have a matching skill guide show a → skill://... hint in their description. The guide content is pull-based — zero token cost until the client actually reads it.
Feature | Client-side Skills | MCP Resources |
Availability | Requires install command | Built-in, any client |
Token cost | Loaded by client | Pull on demand (0 until read) |
Discovery | Slash commands / triggers |
|
Best for | Power users, slash commands | Universal guidance |
Docker
API Key auth only (MFA browser auth requires GUI, not available in containers).
docker run -it --rm \
-e SERVICENOW_INSTANCE_URL=https://your-instance.service-now.com \
-e SERVICENOW_AUTH_TYPE=api_key \
-e SERVICENOW_API_KEY=your-api-key \
ghcr.io/jshsakura/mfa-servicenow-mcp:latestSee Client Setup Guide for local build options.
Developer Setup
If you want to modify the source locally:
git clone https://github.com/jshsakura/mfa-servicenow-mcp.git
cd mfa-servicenow-mcp
uv venv
uv pip install -e ".[browser,dev]"
uvx --with playwright playwright install chromiumRunning Tests
uv run pytestLinting & Formatting
uv run black src/ tests/
uv run isort src/ tests/
uv run ruff check src/ tests/
uv run mypy src/Building
uv buildWindows: see Windows Installation Guide
Documentation
LLM Setup Guide — AI-guided one-line installation flow
Client Setup Guide — Installer-first setup plus fallback client configs
Tool Inventory — Complete tool list by category and package
Catalog Guide — Service catalog CRUD and optimization
Change Management — Change request lifecycle and approval
Workflow Management — Workflow (wf_workflow engine) and Flow Designer tools
Related Projects and Acknowledgements
This repository includes tools consolidated and refactored from earlier internal / legacy ServiceNow MCP implementations. The current surface is organized around bundled
manage_*tools (see tool_utils.py).This project is focused on safe, diff-first MCP server use cases: every write goes through confirm + write-guards (concurrent-edit, duplicate-create, publish, Flow Designer), and source edits are diffed against the live remote before they are pushed.
License
Apache License 2.0
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceThe most comprehensive ServiceNow MCP server. 17 tools for full CRUD, CMDB graph traversal, background scripts, ATF testing, and more.Last updated7611MIT
- Alicense-qualityDmaintenanceMCP server to interact with ServiceNow instances, enabling ITSM, CMDB, workflow, and knowledge search operations.Last updatedMIT
- Flicense-qualityBmaintenanceMCP server providing CRUD tools for ServiceNow business rules, client scripts, and script includes.Last updated
- Alicense-qualityBmaintenanceMCP server that gives Claude Code tools to read, write, and configure a ServiceNow instance, enabling hands-off flow design and management via basic auth.Last updated89MIT
Related MCP Connectors
MCP server for AI access to SmartBear tools, including BugSnag, Reflect, Swagger, PactFlow, QTM4J.
The official MCP Server from Mia-Platform to interact with Mia-Platform Console
MCP server for interacting with the Supabase platform
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/jshsakura/mfa-servicenow-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server