servicenow-mcp
A small MCP server that lets AI agents create, search, and update ServiceNow incidents through the Table API.
README
servicenow-mcp
An MCP server that bridges AI coding agents (OpenHands,
Claude Code, Claude Desktop, …) to ServiceNow ITSM. It exposes three tools — create, query and
update incidents — over the ServiceNow Table API, so an agent that just shipped a fix can
also leave the change trail where ops teams expect it: an incident with work notes and a
state. Single file (server.py), official Python mcp package, httpx,
python-dotenv. Built and verified against a live ServiceNow instance (Australia release)
over both stdio and streamable HTTP transports.
Verified end-to-end
An OpenHands agent picked up todo-api issue #2 in the demo repo (matheuscalma/todo-api), fixed the code on a feature branch, ran the full test suite (24/24 passing) and opened todo-api PR #4. It then used this server to create incident INC0010002 documenting the change, attached the PR link as a work note, and moved the incident to In Progress. A human reviewed and merged the PR and resolved the incident — code change and ITSM record closed together, no manual ticketing. (All issue/PR references in this section are in the todo-api demo repo, not this one.)
| Step | Actor | Tool / artifact |
|---|---|---|
| Pick up todo-api issue #2, fix on a feature branch | OpenHands agent | git |
| Run test suite → 24/24 | OpenHands agent | pytest |
| Open todo-api PR #4 | OpenHands agent | GitHub |
| Create incident INC0010002 describing the change | OpenHands agent | create_incident |
| Attach PR link as a work note | OpenHands agent | update_incident(work_notes=…) |
| Move incident to In Progress | OpenHands agent | update_incident(state="In Progress") |
| Review + merge PR, resolve incident | Human | GitHub / ServiceNow UI |
Artifact trail: todo-api issue #2 → branch → todo-api PR #4 → INC0010002 (work note with PR URL, state New → In Progress → Resolved).
Architecture
┌──────────────────────┐ MCP (stdio or ┌───────────────────┐ HTTPS / basic auth ┌────────────────────┐
│ Agent │ streamable HTTP) │ server.py │ Table API (JSON) │ ServiceNow │
│ OpenHands / Claude │ ─────────────────▶ │ create_incident │ ─────────────────────▶ │ /api/now/table/ │
│ │ ◀───────────────── │ query_incidents │ ◀───────────────────── │ incident │
│ │ tool results │ update_incident │ records / errors │ │
└──────────────────────┘ └───────────────────┘ └────────────────────┘
│
└── ServiceNowClient (httpx.AsyncClient, timeouts,
readable errors; swappable for tests)
Setup
Requires Python 3.12+ and uv.
uv sync
Create a .env next to server.py (it is git-ignored) with basic-auth credentials for
your instance — a Personal Developer Instance works fine:
SNOW_INSTANCE_URL=https://devXXXXXX.service-now.com
SNOW_USERNAME=admin
SNOW_PASSWORD=your-password
# optional, default 15
SNOW_TIMEOUT_SECONDS=15
Running
Default is stdio (the MCP client launches the server as a subprocess; nothing to see):
uv run server.py
Run as HTTP (streamable HTTP on http://0.0.0.0:8765/mcp instead of stdio): uv run server.py --http, or set SNOW_MCP_TRANSPORT=http.
Run in Docker
The Dockerfile builds an HTTP-mode image (python 3.12-slim + uv, non-root,
SNOW_MCP_TRANSPORT=http preset, port 8765). Credentials are passed at runtime only —
.env is excluded via .dockerignore and never copied into the image.
docker build -t servicenow-mcp .
docker run --rm --env-file .env -p 8765:8765 servicenow-mcp
The MCP endpoint is then http://localhost:8765/mcp. Verify:
curl -s -X POST http://localhost:8765/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
Deploy to Kubernetes (Helm)
deploy/chart is a minimal chart: Deployment (1 replica), ClusterIP Service
on 8765, and a Secret with SNOW_* credentials built from values. For real deployments create
the Secret yourself and set servicenow.existingSecret instead of putting credentials in values.
kind create cluster --name servicenow-mcp && kind load docker-image servicenow-mcp:latest --name servicenow-mcp
helm install servicenow-mcp deploy/chart \
--set servicenow.instanceUrl=https://devXXXXXX.service-now.com \
--set servicenow.username=admin --set servicenow.password='...'
kubectl port-forward svc/servicenow-mcp 8765:8765 # then use http://localhost:8765/mcp
Registering with an MCP client
Point the client at uv with the project directory so the .venv and .env are picked up:
{
"mcpServers": {
"servicenow": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/servicenow-mcp", "run", "server.py"]
}
}
}
For Claude Code: claude mcp add servicenow -- uv --directory /absolute/path/to/servicenow-mcp run server.py.
For an HTTP client, start with --http and point it at http://<host>:8765/mcp.
Environment variables already set in the client's environment take precedence over .env.
Tools
| Tool | Parameters | What it does | Returns |
|---|---|---|---|
create_incident |
short_description: str (required) · description: str = "" · urgency: str = "3" ("1" High, "2" Medium, "3" Low) |
Creates a new incident. | {number, sys_id, short_description, state, urgency} |
query_incidents |
query_text: str = "" · state: str = "" · limit: int = 5 (1–100) |
Lists incidents newest first. query_text that looks like INC0010001 matches the number exactly; other text runs ServiceNow keyword search (123TEXTQUERY321), falling back to a LIKE match on short_description/description so just-created records are found. state accepts a code ("1"…"8") or a name (New, In Progress, On Hold, Resolved, Closed, Canceled). |
[{number, sys_id, short_description, state, state_code, urgency, created_on}, …] |
update_incident |
number: str (required) · work_notes: str = "" · state: str = "" |
Looks the incident up by number, appends a work note and/or sets the state. At least one of work_notes/state is required. |
{number, sys_id, changed: {…}, state} |
Errors
All tools raise a readable tool error (surfaced to the agent as isError) instead of a
stack trace, e.g.:
Authentication failed (401): check SNOW_USERNAME and SNOW_PASSWORD. ServiceNow said: User is not authenticated …Could not connect to https://…: [Errno 8] nodename nor servname provided … Check SNOW_INSTANCE_URL and your network connection.Timed out after 15s talking to https://… The instance may be hibernating (PDI) or unreachable.Incident 'INC9999999' was not found on https://….Forbidden (403): the user lacks permission … or the update was rejected by a business rule. ServiceNow said: …(e.g. resolving an incident without resolution fields).
Design notes
- Query routing.
query_incidentspicks one of three encoded queries: input matchingINC\d+→ exactnumber=match; anything else → ServiceNow full-text keyword search (123TEXTQUERY321=…, relevance-ranked, stemmed); if that returns nothing →LIKEon short_description/description. Two facts measured against the live instance forced this:123TEXTQUERY321cannot be OR'd with other clauses (the whole query silently returns nothing), and its index lags writes by ~3–5 s, so an agent that just created a ticket would not find it. The LIKE fallback sees fresh rows immediately; keyword search still wins for "what's open about printers?"-style lookups. - mcp 1.x / 2.x compat.
mcp2.x renamedFastMCP→MCPServer(mcp.server.mcpserver);server.pytries the 2.x import first and falls back tomcp.server.fastmcp.FastMCP, so it runs on either major version. - Swappable client seam. All network access goes through
ServiceNowClient(onehttpx.AsyncClient: basic auth, JSON headers, timeout; every failure becomes aServiceNowErrorwith a human-readable message). Tools obtain it viaget_client(); tests callset_client(fake)to inject a mock without touching the network. Records are fetched withsysparm_display_value=all, so state/urgency labels come from the instance rather than a hard-coded map (friendly-name aliases are input-only). - Transport security. HTTP mode binds
0.0.0.0:8765. Themcplibrary only auto-enables DNS-rebinding protection for loopback binds, so on0.0.0.0anyHost/Originis accepted. Fine on a lab machine; pass atransport_security=(TransportSecuritySettingswithallowed_hosts/allowed_origins) tomcp.run(...)before exposing it beyond localhost.
Smoke test
Run against the live instance (writes one incident):
uv run python -c "
import asyncio, server
async def main():
c = await server.create_incident('MCP server smoke test', 'created by smoke test')
print(c)
print(await server.query_incidents(query_text='MCP server smoke test'))
print(await server.update_incident(c['number'], work_notes='hello from MCP'))
asyncio.run(main())
"
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。