Skip to main content
Glama
pavithrakumaran10

ServiceNow MCP Server

README.md
# ServiceNow Developer Actions — MCP Server

An [MCP](https://modelcontextprotocol.io) 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.

---
<img width="1249" height="778" alt="20260627-0956-14 5005578" src="https://github.com/user-attachments/assets/955521d6-39b2-4872-8033-c1f5c879fec3" />




## 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):

```javascript
// 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

```bash
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:

```json
{
  "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._