AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Snow Meta Tool

mcp-ozgurkarahan-snow-meta-tool · by ozgurkarahan

ServiceNow Meta Tool (MCP Server) exposing ServiceNow as 3 generic tools (discover, query, write) with per-user identity propagation via Azure APIM JWT Bearer OBO flow

No reviews yet
0 installs
21 views
0.0% view→install

Install

$ agentstack add mcp-ozgurkarahan-snow-meta-tool

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-ozgurkarahan-snow-meta-tool)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Snow Meta Tool? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

ServiceNow MCP Meta-Tool

[](https://modelcontextprotocol.io/) [](https://developer.servicenow.com/) [](https://learn.microsoft.com/azure/api-management/) [](https://www.python.org/) [](LICENSE)

MCP server exposing ServiceNow as 3 generic tools (discover, query, write) with per-user identity propagation. An AI agent discovers tables and fields at runtime, then queries and writes records as the authenticated user — no service account, no shared credentials.

Architecture

> [draw.io source](docs/diagrams/azure-architecture.drawio)

> Part of the Identity-Propagated MCP Meta-Tool series — building enterprise-grade MCP tools where every action is traceable to the real user, not a service account. Each tool follows the same pattern: generic tools + runtime schema discovery + On-Behalf-Of identity flow through Azure APIM. > > | Tool | System | Status | > |---|---|---| > | Salesforce Meta-Tool | Salesforce CRM | ✅ Production | > | ServiceNow Meta-Tool (this repo) | ServiceNow ITSM | ✅ Production | > > When both tools are connected to the same agent, it can work across CRM and ITSM in a single conversation — see [joint use cases](TESTPROMPTS.md#-scenario-10-salesforce-crm--servicenow-itsm-joint-use-cases). > > 📖 Read the story: From Theory to Production: Salesforce Meta-Tool & Identity Propagation — no service accounts in the data path, every action traceable to a real user. Companion repo: propagate-id-entra — the end-to-end Entra ID identity-propagation pattern this series builds on.


💡 Why a Meta-Tool?

Most MCP integrations build one tool per API endpointlist_incidents, create_change_request, get_user, etc. This creates a combinatorial explosion: ServiceNow has 4,000+ tables, each with dozens of fields. You'd need thousands of tools, and the agent would struggle to pick the right one.

The meta-tool pattern flips this: 3 generic tools cover the entire ServiceNow data model. The agent discovers the schema at runtime and constructs queries dynamically:

| ❌ Traditional Approach | ✅ Meta-Tool Approach | |---|---| | list_incidents(priority, state) | query(table="incident", query="priority=1^state=2") | | create_change_request(title, ...) | write(table="change_request", operation="create", ...) | | get_incident_fields() | discover(table="incident") | | 100+ hardcoded tools | 3 tools for any table |

> 🧠 This works because LLMs are good at composing queries from natural language — they don't need pre-built tool signatures for every table.


🔐 Why Identity Propagation?

Most AI integrations use a shared service account — every user's actions appear as "API Bot" in the audit log. This creates three problems:

| | Problem | Impact | |---|---|---| | 👤 | No accountability | Can't tell who did what — audit trail shows the service account, not the user | | 🔓 | Over-privileged access | Service account needs permissions for everything any user might do | | ⚖️ | Compliance risk | SOX, HIPAA, ITIL require actions traceable to individual users |

This project solves all three with On-Behalf-Of (OBO) identity propagation:

User "alice@company.com" asks agent: "Create a P2 incident for the network outage"

❌ Traditional:  ServiceNow sees → sys_created_by: "api_service_account"
✅ This project: ServiceNow sees → sys_created_by: "alice@company.com"

> 🛡️ The APIM policy exchanges the user's Azure AD token for a ServiceNow token scoped to that specific user. Every API call runs with that user's permissions and appears in the audit log under their name.


🏗️ Architecture

                          Azure AD
                            │
                            │ Bearer token (user identity)
                            ▼
                    ┌───────────────┐
                    │  Azure APIM   │
                    │  OBO Policy   │
                    └───────┬───────┘
                            │
           1. Validate Azure AD token
           2. Extract user email (preferred_username / upn)
           3. Build JWT Bearer assertion (RS256, per-user sub)
           4. Exchange at ServiceNow oauth_token.do
           5. Cache SN token per user (25 min)
           6. Forward request with SN Bearer token
                            │
                            ▼
                ┌────────────────────────┐
                │  servicenow-mcp        │
                │  (Container App)       │
                │                        │
                │  FastMCP + 3 tools:    │
                │  🔍 discover           │
                │  📊 query              │
                │  ✏️ write              │
                └────────┬───────────────┘
                         │
                         │ ServiceNow Table API
                         │ (Bearer token per user)
                         ▼
                ┌──────────────────┐
                │  ServiceNow      │
                │  Instance        │
                └──────────────────┘

🔄 Identity flow: Azure AD user → APIM extracts email from JWT → builds ServiceNow JWT Bearer assertion with sub=email → exchanges for ServiceNow access token → all API calls run as that ServiceNow user.

Two auth modes:

  • 🌐 Passthrough (APIM): Bearer token injected by Azure API Management. The APIM policy handles the Azure AD → ServiceNow token exchange. No secrets on the MCP server.
  • 💻 Self-managed (local dev): JWT Bearer exchange at oauth_token.do using a local RSA private key. Useful for development and testing without APIM.

🛠️ Tools

🔍 discover — Table & Field Discovery

Two modes:

  • Table search: discover(filter="incident") — search sys_db_object by name/label
  • Field metadata: discover(table="incident") — query sys_dictionary for field definitions
  • + Picklists: discover(table="incident", include_choices=True) — adds choice values from sys_choice

📊 query — Read Records, Search, Aggregate

Three modes:

  • Record query: query(table="incident", query="priority=1^state!=6", fields="number,short_description") — encoded query with auto-pagination
  • Text search: query(table="incident", text_search="password reset") — full-text search via TEXTQUERY operator
  • Aggregate: query(table="incident", aggregate=True, group_by="priority") — counts/sums/averages via Stats API

✏️ write — Create, Update, Delete

Three operations:

  • Create: write(table="incident", operation="create", field_values={...})
  • Update: write(table="incident", operation="update", sys_id="...", field_values={...})
  • Delete: write(table="incident", operation="delete", sys_id="...")

> 📋 Approvals are just table writes: update sysapproval_approver with state=approved/rejected.


🚀 Quick Start

📋 Prerequisites

  • 🐍 Python 3.12+
  • 🔑 RSA key pair for JWT signing
  • ☁️ ServiceNow instance with OAuth JWT Bearer app configured ([setup guide](#-servicenow-instance-setup))

💻 Local Development

cd src/servicenow-mcp
pip install -r requirements.txt

export SN_INSTANCE_URL=https://.service-now.com
export SN_CLIENT_ID=
export SN_JWT_KID=
export SN_JWT_KEY_PATH=../../certs/sn-jwt-bearer.key
export SN_JWT_SUB=

python app.py
# 🟢 Server starts on http://localhost:8000
# Health check: GET /health
# MCP endpoint: POST /mcp

🐳 Docker

cd src/servicenow-mcp
docker build -t servicenow-mcp .
docker run -p 8000:8000 \
  -e SN_INSTANCE_URL=https://.service-now.com \
  -e SN_CLIENT_ID= \
  -e SN_JWT_KID= \
  -e SN_JWT_KEY_PATH=/app/certs/key.pem \
  -e SN_JWT_SUB= \
  -v ./certs:/app/certs:ro \
  servicenow-mcp

☁️ Deploy to Azure

# Generate PFX from existing key + cert
openssl pkcs12 -export -out certs/sn-jwt-bearer.pfx \
  -inkey certs/sn-jwt-bearer.key -in certs/sn-jwt-bearer.crt -passout pass:

# Initialize and configure
azd init
azd env set SN_INSTANCE_URL "https://.service-now.com"
azd env set SN_OAUTH_CLIENT_ID ""
azd env set SN_JWT_BEARER_KID ""
azd env set AZURE_RESOURCE_GROUP ""
# Set shared resource names (APIM, ACR, KV, CAE, etc.)

# 🚀 Provision infrastructure + deploy
azd up

> The azd provision step creates: Container App, APIM API + backend + OBO policy, Named Values, Key Vault cert, and AI Foundry connection. The post-provision hook uploads the PFX certificate to Key Vault and configures the APIM certificate binding.


⚙️ Environment Variables

| Variable | Required | Default | Purpose | |---|---|---|---| | SN_INSTANCE_URL | ✅ Yes | — | ServiceNow instance URL | | SN_CLIENT_ID | 💻 Self-managed | — | OAuth JWT Bearer client_id | | SN_JWT_KID | 💻 Self-managed | — | kid from jwt_verifier_map | | SN_JWT_KEY_PATH | 💻 Self-managed | — | Path to RSA private key (.pem) | | SN_JWT_SUB | 💻 Self-managed | — | JWT sub claim (user email) | | PORT | ❌ No | 8000 | HTTP listen port | | APPLICATIONINSIGHTS_CONNECTION_STRING | ❌ No | — | Azure Monitor telemetry |

> In passthrough mode (behind APIM), only SN_INSTANCE_URL is required. The bearer token comes from the APIM policy via the Authorization header.


📁 Key Paths

| Path | Description | |---|---| | 📄 src/servicenow-mcp/app.py | FastMCP server + 3 tools + middleware | | 📄 src/servicenow-mcp/servicenow_client.py | Async ServiceNow REST client (JWT auth, caching) | | 🐳 src/servicenow-mcp/Dockerfile | Multi-stage Docker build | | 🏗️ infra/main.bicep | Root IaC module (resource-group scoped) | | 🏗️ infra/modules/ | Bicep modules: Container App, APIM API, cert, Foundry connection | | 📜 infra/policies/ | APIM XML policies: OBO token exchange, PRM metadata | | 🔧 hooks/postprovision.py | Post-deploy: cert upload, APIM binding, Named Values, Foundry | | 🧪 scripts/test_jwt_bearer.py | Automated SN instance setup + feasibility test | | 🔑 certs/ | RSA keys for JWT signing (gitignored) |


🏢 Infrastructure

The project deploys into an existing resource group alongside the Salesforce MCP tool, sharing:

| Resource | Shared With | |---|---| | 🌐 Azure API Management (APIM) | SF MCP | | 📦 Container Apps Environment | SF MCP | | 🐳 Azure Container Registry (ACR) | SF MCP | | 🔑 Key Vault | SF MCP | | 📊 Application Insights | SF MCP | | 🤖 AI Foundry project | SF MCP |

SN-specific resources created by azd provision:

  • 📦 Container App (ca-sn-mcp) — runs the FastMCP server
  • 🌐 APIM API (servicenow-mcp-obo) — native MCP type with OBO policy
  • 🏷️ APIM Named Values — SN OAuth client ID, instance URL, JWT kid, cert thumbprint
  • 🔐 APIM Certificate — JWT Bearer signing cert (from Key Vault)
  • 🤖 Foundry Connection (servicenow-obo) — RemoteTool with UserEntraToken auth

🔄 APIM OBO Policy

The APIM policy (infra/policies/sn-mcp-obo-policy.xml) handles the full token exchange:

| Step | Action | |---|---| | 1️⃣ | Validate Azure AD token (v1 + v2 issuers, audience: https://ai.azure.com) | | 2️⃣ | Extract user email from preferred_username / upn / unique_name claims | | 3️⃣ | Build RS256 JWT Bearer assertion with sub=email, kid, unique jti | | 4️⃣ | Exchange assertion at ServiceNow oauth_token.do for a user-scoped access token | | 5️⃣ | Cache the ServiceNow token per user for 25 min (5-min safety margin on 30-min lifetime) | | 6️⃣ | Forward the request with the ServiceNow Bearer token + X-User-Email header |

> 📡 A separate PRM endpoint (/.well-known/oauth-protected-resource) advertises Azure AD as the authorization server per RFC 9728.


☁️ ServiceNow Instance Setup

Required configuration on the ServiceNow instance:

| Step | What | Details | |---|---|---| | 1️⃣ | 🔐 X.509 Certificate | Upload to sys_certificate (type: trust_store_cert) | | 2️⃣ | 🔑 OAuth JWT App | Create in oauth_jwt with inbound_grant_type="jwt", public_client=true, user_field="email" | | 3️⃣ | 🗺️ JWT Verifier Map | Create in jwt_verifier_map linking kid to certificate | | 4️⃣ | 👤 User | Non-admin user with itil + personalize_dictionary roles |

> 🤖 The scripts/test_jwt_bearer.py script automates all of this: > ``bash > python scripts/test_jwt_bearer.py \ > --instance https://.service-now.com \ > --admin-password "" > ``

👥 ServiceNow Roles

| Role | Purpose | Required For | |---|---|---| | 🟢 itil | Base ITSM role — read/write incidents, changes, problems | All basic operations | | 🟡 personalize_dictionary | Read sys_dictionary table (field metadata) | discover(table=...), field validation in write | | 🔴 admin | Full access | BLOCKED — ServiceNow rejects JWT Bearer for admin users |

> 💡 Recommended: Assign itil + personalize_dictionary to MCP users. The server gracefully degrades if personalize_dictionary is missing.


📖 ServiceNow Encoded Query Syntax

ServiceNow uses encoded query strings (not SQL). Key operators:

| Operator | Syntax | Example | |---|---|---| | Equals | field=value | priority=1 | | Not equals | field!=value | state!=6 | | Contains | fieldLIKEvalue | short_descriptionLIKEpassword | | Starts with | fieldSTARTSWITHvalue | numberSTARTSWITHINC | | Greater than | field>value | priority>2 | | In list | fieldINvalue1,value2 | priorityIN1,2,3 | | Empty | fieldISEMPTY | assigned_toISEMPTY | | AND | ^ | priority=1^state=2 | | OR | ^OR | priority=1^ORpriority=2 | | Order by | ^ORDERBYfield | ^ORDERBYDESCsys_created_on |


🧪 Testing & Demo

| Document | Description | |---|---| | 📋 [TESTPROMPTS.md](TESTPROMPTS.md) | 10 business scenarios with 40+ agent prompts — service desk triage, change management, major incidents, executive dashboards, and Salesforce CRM + ServiceNow ITSM joint workflows | | 🎯 [DEMODATASETUP.md](DEMODATASETUP.md) | Step-by-step guide to populate both Salesforce and ServiceNow with correlated demo data for cross-platform scenarios |


🔗 Related Projects

  • salesforce-meta-tool-identity-propagation — The Salesforce counterpart: same meta-tool pattern and OBO identity propagation, applied to Salesforce CRM. Deploy both side-by-side to power a Customer 360 AI Foundry agent that correlates Salesforce CRM data with ServiceNow incidents, changes, and requests under the user's own identity in each system.

📄 License

MIT

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.