Skip to main content
Glama
SxMShaDoW

sn-mcp-example

by SxMShaDoW

sn-mcp-example — ServiceNow MCP Server on Cloudflare Workers

A deploy-in-minutes Model Context Protocol (MCP) server for ServiceNow, running on Cloudflare Workers. Give any MCP-capable AI agent (Claude Code, Claude Desktop, Cursor, …) safe read/write access to your instance through the Table API — no agents installed on the instance, no MID server, no plugin.

Part of the "AI Engineer 3 Ways" talk — this repo is The MCP Way:

Way

What the AI gets

Repo

The MCP Way

Curated, typed, documented tools

this repo

The Table API Way

Raw REST + a great AGENTS.md

(separate repo)

The CLI Way

A lifecycle-driven CLI (sn)

sndev.io

Quick start (local)

bun install        # or npm install
cp .dev.vars.example .dev.vars   # fill in your instance + credentials
bun run dev        # http://localhost:8787

Test it with the MCP Inspector:

npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP  →  URL: http://localhost:8787/mcp

Gotcha: quoting basic-auth values in .dev.vars

wrangler dev parses .dev.vars dotenv-style, and unquoted values are silently truncated at the first # ($ also gets variable-expanded). Generated ServiceNow passwords usually contain #, $, = — so an unquoted password arrives mangled and every call fails with 401 User is not authenticated. Always single-quote the values (single quotes are taken literally):

SN_INSTANCE='https://dev000000.service-now.com'
SN_USERNAME='mcp_service_user'
SN_PASSWORD='p@ss w1th #$pecial chars'

If your .dev.vars credentials are correct but you still get 401s, this is the first thing to check.

Related MCP server: NowAIKit

Hook it into Claude Code

claude mcp add --transport http servicenow-dev http://localhost:8787/mcp

Or add to ~/.claude.json:

{
  "mcpServers": {
    "servicenow-dev": {
      "type": "http",
      "url": "http://localhost:8787/mcp"
    }
  }
}

Deploy to Cloudflare

bunx wrangler login
bun run deploy

Then set your secrets (they become live immediately, no redeploy needed):

bunx wrangler secret put SN_INSTANCE   # https://dev000000.service-now.com
bunx wrangler secret put SN_USERNAME   # a service account, NOT your admin user
bunx wrangler secret put SN_PASSWORD

wrangler deploy prints your worker URL; your MCP endpoint is <url>/mcp.

CI/CD: the included GitHub Actions workflow (.github/workflows/deploy.yml) deploys on every push to main — add CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as repo secrets and it just works.

Security notes (please read)

  • Create a dedicated ServiceNow user for the MCP server. For development, you probably want admin. If you wanted to be granular, you could do specific "admin-like" roles - but that is out of scope of this.

  • Credentials live in Workers secrets / .dev.vars — never in git, never sent to the AI. The agent talks to this server; only this server talks to ServiceNow.

  • sn_run_script is powerful by design — it executes server-side JS. Keep the credential least-privileged, and remove tools-script.ts/ tools-cicd.ts registrations in src/tools.ts if you want a read-only or table-API-only server.

  • If you expose this publicly, put your own auth in front of /mcp (the MCP spec's HTTP authorization flow, a Cloudflare Access policy in front of the worker, or a simple shared-token check) — an open MCP endpoint is an open door to your instance.

Tools

Tool

What it does

sn_get_table_schema

Live schema of any table (fields, types, mandatory, references) via sys_dictionary

sn_query_records

Encoded-query search over any table

sn_get_record

Fetch one record by sys_id

sn_create_record

Insert a record

sn_update_record

Patch a record by sys_id

sn_run_script

Run server-side JavaScript (GlideRecord) via a one-shot sys_trigger — no Scripted REST endpoint needed

sn_get_current_update_set

Show the update set the credential is currently capturing into

sn_switch_update_set

Point the credential at a named update set (GlideRecord script) so agent work is captured and reviewable

sn_run_atf_suite

Trigger an ATF test suite through /api/sn_cicd/testsuite/run, poll to completion, return pass/fail rollups

sn_run_instance_scan

Scan update sets with an Instance Scan suite through /api/sn_cicd/instance_scan/suite_scan, return violations + findings

The bundled memo://about resource explains the recommended agent flow (discover schema → query → act) to any client that reads resources.

Context efficiency by design

Modern models tool-call well, MCP is stateless, and clients can defer tools. So the anti-bloat job for this server is narrow: keep the tool surface small, make every tool do more per call, and stay compatible with client-side deferral. (Anthropic's advanced tool use, MCP client best practices.)

  • Few tools, rich params. Ten tools, each composable at call time — sn_query_records takes an encoded query, fields, limit, offset, and displayValue, so the model filters server-side in one call instead of paginating raw tables through its context. A param is cheaper than a tool.

  • Stateless sessionless transport. Every POST /mcp is independent — no Durable Objects, no session state, GET answers 405. Nothing to keep warm, nothing to evict.

  • One call, one result. The CI/CD tools trigger and poll /api/sn_cicd inside the server; the agent never sees the ~20 progress round-trips. sn_get_table_schema folds two Table API reads into one compact schema.

  • sn_run_script is the escape hatch. For work no param can express, the agent sends one GlideRecord script; intermediates stay on the instance and only setScriptOutput returns (hard-capped at 4 KB with a truncation hint).

  • Compact responses. Record summaries strip the Table API's {value, display_value, link} envelope; limits cap row counts.

  • Annotations on every tool (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) so hosts can reason about parallelism, retry safety, and confirmation policies without guessing.

  • Free discovery, cheap listing. Unconfigured servers list zero tools; configured servers list deterministic definitions with 2026-07-28 ttlMs/cacheScope cache hints so host prompt-caching holds.

Deferral and progressive discovery are client features — nothing to implement server-side. Descriptive sn_* names and keyword-rich descriptions are what Tool Search matches against; keep that discipline when adding tools.

If this server grows toward 100 tools

In order of leverage — take each step only when the previous one stops being enough:

  1. Param before tool. Before registering a new tool, try adding a parameter to an existing one (a kind, mode, or query value). Ten composable tools beat thirty overlapping ones — in context cost and in tool-selection accuracy.

  2. Split by audience into separate workers. Reads vs writes vs CI/CD, each with its own credential scope. Hosts keep the read server always-on and connect the write server only for tasks that need it — server-level deferral beats tool-level deferral.

  3. Add the three-layer discovery pairsn_search_tools (name + one-line description) and sn_get_tool_details (one full schema) — once definitions genuinely cost context. Thresholds from the guidance: Anthropic flags >10K tokens of definitions or selection-accuracy problems; the MCP guidance says switch when definitions cross 1-5% of the window. In practice: dozens of tools, not tens.

Project layout

src/
  index.ts          Workers entrypoint — Hono app, sessionless /mcp endpoint
  server.ts         McpServer factory: name/version + about resource
  tools.ts          Shared tool helpers + registration entrypoint
  tools-table.ts    Table API tools (query/get/create/update/schema)
  tools-script.ts   sn_run_script + the update-set switcher
  tools-cicd.ts     ATF suite runs + instance scans via /api/sn_cicd
  servicenow.ts     Table API + sys_trigger script runner + CI/CD helpers
.github/workflows/deploy.yml   Deploy-on-push to Cloudflare
wrangler.jsonc                  Cloudflare config

The agent-safe CI/CD loop

The tools compose into a closed loop where the agent proves its own work:

  1. sn_switch_update_set into a dedicated AGENT-WORK update set

  2. Agent builds changes (create/update records, run scripts)

  3. sn_run_instance_scan over that update set — code-quality gate

  4. sn_run_atf_suite — functional gate (optional)

  5. Human reviews the update set and ships it (or sn validate in the CLI way)

Every change is captured, every change is scanned, every change is tested — by the same AI that made them.

Adding your own tool

src/tools.ts is the only file you need to touch:

server.registerTool("sn_count_open_incidents", {
  description: "Count open P1 incidents",
  inputSchema: z.object({}),
}, async () => {
  const rows = await client.queryTable({
    table: "incident",
    query: "active=true^priority=1",
    fields: ["sys_id"],
    limit: 1,
  });
  return json({ open_p1s: rows.length });
});

License

MIT — see LICENSE. Steal this. Build something awesome.

Related MCP Connectors

Related MCP Servers