Skip to main content
Glama
pavithrakumaran10

ServiceNow MCP Server

ServiceNow Developer Actions — MCP Server

An MCP server that connects Claude Desktop (or any MCP client) to a ServiceNow instance, exposing guarded developer actions as natural-language tools. Claude can read incidents, inspect CMDB and table schemas, and create records — with every write isolated to a scoped Update Set, restricted to an allowlist of tables, and protected by a dry-run preview.

Built with FastMCP, a typed REST client, and an explicit guardrail layer — the difference between an LLM API wrapper and an agent tool you'd let near a production instance.


Why this project

This demonstrates two skill sets at once:

  • AI engineering / agent architecture — agentic tool design with safety rails: input schemas (Pydantic), an allowlist + blocklist for writable tables, automatic Update Set scoping, and a dry_run mode on risky tools so the model previews intent before mutating anything.

  • ServiceNow integration architecture — a resilient Table API client with OAuth2 (with Basic-auth fallback), token refresh on 401, and exponential backoff on 429/5xx, plus CMDB and sys_dictionary introspection tools.


Architecture

Claude Desktop / Claude Code
   │  (MCP protocol over stdio)
   ▼
FastMCP Server  ── server.py
   │
   ├── tools (read)   get_incidents · query_table · query_records · get_record
   │                  describe_table · get_cmdb_cis · get_update_set
   ├── tools (write)  create_incident · create_script_include · create_record
   │                  update_record · execute_background_script   ← guarded
   │
   ├── guardrails.py  allowlist · blocklist · update-set enforcement · dry-run
   ▼
ServiceNowClient  ── client.py   (OAuth2 / Basic · retry · backoff · typed errors)
   │  REST (Table API + a companion Scripted REST resource for script execution)
   ▼
ServiceNow instance (PDI)

Tools

Tool

Type

Notes

get_incidents

read

Newest-first, active filter

query_table

read

GlideRecord-style query on any table

query_records

read

Like query_table, with explicit field selection

get_record

read

Fetch a single record by sys_id

describe_table

read

Field inspection via sys_dictionary

get_cmdb_cis

read

List CIs from a CMDB class

get_update_set

read

List changes currently inside an Update Set, for review

create_incident

write

Allowlisted, Update-Set enforced, dry_run

create_script_include

write

dry_run=True by default

create_record

write

Any allowlisted table (Business Rules, UI Actions, Flows…)

update_record

write

Patch a field (e.g. script) on any allowlisted table

execute_background_script

write

Preview-only by default; confirm=True required to run

Guardrails

Guardrail

What it does

Enforced by

Writable allowlist

Only dev-artifact tables (sys_script, sys_script_include, sys_script_client, sys_ui_action, sys_ui_page, catalog_script_client, sys_hub_flow, incident, sc_req_item, task) may be written.

assert_writable()

Blocklist wins

Security/identity tables (sys_user, sys_user_group, sys_security_acl, sys_user_role, sys_properties) are refused even if a table were also allowlisted.

assert_writable() (blocklist checked first)

Update Set enforcement

Before any write, the server resolves MCP_DEV_UPDATE_SET_ID, confirms it's a real in progress Update Set, makes it the current set for the authenticated user (via sys_user_preference), and reads that preference back to confirm before proceeding. Unset, not found, not in-progress, or unconfirmed → the write is refused. No write can silently land in Default or in whatever set was last active.

ensure_update_set()

Dry-run by default

create_script_include, create_record, update_record, and execute_background_script default to previewing rather than writing; you opt in per call.

dry_run / confirm params

Background script two-step

execute_background_script always returns the script text plus a best-effort static scan of GlideRecord table references first. It only executes when called again with confirm=True — never automatically.

execute_background_script()

Confirmation summary

Every successful write returns table, sys_id, changed fields, and the Update Set it landed in.

write_result()

Setting up execute_background_script

The Table API has no native endpoint for ad hoc script execution, so this tool POSTs to a small Scripted REST resource you create once on the PDI (Studio → Scripted REST APIs):

// Resource: POST /api/x_snc/mcp_dev/execute  (path must match SN_SCRIPT_EXEC_PATH)
(function process(request, response) {
    var body = request.body.data;
    var result;
    try {
        result = String(eval(body.script));
    } catch (e) {
        result = "ERROR: " + e.message;
    }
    return { result: result };
})(request, response);

Scope this resource's ACL to an admin-only role — it is equivalent to background script access and should never be broadly exposed.

Build a Business Rule by chat

  1. "Show me the field name for the incident summary." → Claude calls describe_table("incident") to confirm before writing code against it.

  2. "Create a Business Rule called 'Auto-assign VPN incidents' on incident, before insert, that sets assignment_group when short_description contains 'VPN'. Dry run first." → Claude calls create_record("sys_script", {...}, dry_run=True) and shows you the payload it would insert.

  3. "Looks right, go ahead." → Claude re-calls with dry_run=False. ensure_update_set() confirms MCP_DEV_UPDATE_SET_ID is current, the record is inserted, and you get back table / sys_id / fields / Update Set in the response.

  4. "What's in the Update Set so far?" → Claude calls get_update_set() and lists every change recorded, so you can review before promoting or exporting it.


Setup

git clone <your-repo-url> && cd servicenow-mcp
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
cp .env.example .env        # fill in your PDI URL, credentials, and MCP_DEV_UPDATE_SET_ID
pytest                      # all green

Connect to Claude Desktop

Add to claude_desktop_config.json (Developer → Open App Configuration File), then restart:

{
  "mcpServers": {
    "servicenow": {
      "command": "python",
      "args": ["-m", "servicenow_mcp.server"],
      "cwd": "/absolute/path/to/servicenow-mcp/src",
      "env": {
        "SN_URL": "https://dev.service-now.com/",
        "SN_USER": "user",
        "SN_PASS": "pass",
        "SN_UPDATE_SET": "MCP Automated Changes",
        "MCP_DEV_UPDATE_SET_ID": "sys_id_of_your_dev_update_set"
      }
    }
  }
}

A 🔧 indicator confirms the tools loaded. Then try:

"Show me the 5 newest active incidents" "Describe the cmdb_ci_server table" "Create an incident: VPN gateway down, urgency high — dry run first"


Tech

Python 3.10+ · FastMCP · Pydantic v2 · requests · pytest + responses

Roadmap

  • OAuth client-credentials grant (service account)

  • commit_to_source_control tool wrapping Studio's Git integration

  • Read-through cache for schema/CMDB lookups

  • Structured audit log of every write the server performs


Author: Pavithra Kumaran S — ServiceNow Senior Consultant exploring AI agent architecture.