Skip to main content
Glama
Xingyuj

servicenow-knowledge-mcp

by Xingyuj

snow-mcp

Python 3.15+ FastMCP 4.x Tools Access

Read-only enterprise knowledge retrieval over MCP

ServiceNow Knowledge · Streamable HTTP · Python · Docker


Overview

A Python 3.15+ / FastMCP 4.x server exposing four read-only ServiceNow Knowledge retrieval tools over stateless Streamable HTTP. It returns source records, not generated answers; it does not provide generic table access, mutations, semantic reranking, OCR, or document parsing. ServiceNow controls downstream access and available content; publication and validity metadata are returned, not an additional MCP publication policy.

Related MCP server: ServiceNow MCP Server

Quick start

From the repository root, with Python 3.15+ and uv 0.13.1 or later installed:

uv python install
uv sync --frozen
cp .env.example .env

Set SERVICENOW_BASE_URL and either a static SERVICENOW_ACCESS_TOKEN or both SERVICENOW_CLIENT_ID and SERVICENOW_CLIENT_SECRET. For local development keep MCP_SCOPE_VERIFY_ENABLED=false and SERVICENOW_OBO_ENABLED=false, then run:

uv run snow-mcp

Connect to http://127.0.0.1:8080/mcp. The default listener is 0.0.0.0:8080; set HOST=127.0.0.1 to restrict local access. Never commit .env or real secrets.

Health endpoint

Meaning

GET /mcp/live

Process can serve HTTP; returns {"status":"healthy","service":"snow-mcp"}

GET /mcp/health, GET /mcp/ready

Lifespan is ready; returns {"status":"ready","service":"snow-mcp"}, otherwise HTTP 503

Readiness is a lifecycle check, not a ServiceNow connectivity or credential check.

The repository pins Python 3.15.0 in .python-version; the Docker image uses the same release.

Docker

docker build -t snow-mcp .
docker run --rm --env-file .env -e HOST=0.0.0.0 -p 127.0.0.1:8080:8080 snow-mcp

The image builds native dependencies in a separate stage with C build tools; the runtime image contains no build toolchain. Initial builds can take longer while Python 3.15 wheels become available.

Use the same ServiceNow configuration as the local setup. The host binding limits access to localhost. These local modes do not authenticate incoming MCP clients; keep them isolated.

Deployment options

  • Local Python or Docker: no Azure account is required for integration-token or ServiceNow OAuth client-credential access.

  • Kubernetes: use the generic Helm chart. Azure identity and Istio are disabled by default.

  • Azure: optional deployment examples cover Azure DevOps, AKS, Terraform, and APIM. Replace all example infrastructure values before use.

For network deployments, provide a trusted authentication gateway and prevent direct backend access. The server's claim decoder does not validate JWT signatures. Scope checks require gateway-validated tokens. Entra ID is specifically required for the optional OBO path; Application Insights is optional telemetry. A subscription key is specific to the APIM example, not the MCP protocol.

Tools and client access

Tool

Purpose

Required scope when MCP_SCOPE_VERIFY_ENABLED=true

search_knowledge

Ordered candidates and snippets; optional limit, knowledge base, language and category

knowledge.search

list_kb_categories

Accessible categories with parent IDs and hierarchy paths

knowledge.category.read

get_kb_article

Canonical content, metadata and attachment references; accepts a 32-hex sys_id or KB + 7 digits

knowledge.article.read

get_kb_article_attachment

Size-limited base64 binary content using known article and attachment sys_ids; no parsing

knowledge.attachment.read

Search first unless an article ID is already known, then retrieve the article and relevant attachments. Category listing is for discovery, not article retrieval. Search preserves ServiceNow ordering; it does not promise UI-equivalent or semantic ranking.

MCP scopes

The service defines four read-only scopes:

  • knowledge.search: search articles and read result snippets.

  • knowledge.category.read: list knowledge categories and their hierarchy.

  • knowledge.article.read: retrieve an article's full content, metadata and attachment references.

  • knowledge.attachment.read: retrieve an article attachment's binary content.

With MCP_SCOPE_VERIFY_ENABLED=true, each tool requires its corresponding scope in the caller's token. Scopes do not imply one another: search access does not grant article or attachment access. With the switch off, tool scope checks are skipped, independently of OBO. These scopes authorize MCP tool calls, not access to individual ServiceNow records; ServiceNow still applies its own permissions to the downstream identity.

For isolated local use, a typical client configuration is:

{
  "mcpServers": {
    "snow-mcp": {
      "type": "http",
      "url": "http://127.0.0.1:8080/mcp"
    }
  }
}

Client syntax varies. For remote access, configure the credentials required by your gateway using the client's supported mechanism; do not commit keys or tokens. The Azure example explains APIM subscription keys and Entra tokens.

Architecture: two implemented auth paths

SERVICENOW_OBO_ENABLED selects the downstream authenticator at runtime

graph TD
    Client["MCP Client"] -->|User token| Gateway["Trusted Gateway: JWT Validation"]
    Gateway -->|Validated token| MCP["FastMCP Server"]
    MCP -->|Select downstream auth| Switch{"OBO Enabled?"}
    Switch -->|false| Integration["Integration Authentication"]
    Switch -->|true| Entra["Entra OBO Exchange"]
    Integration -->|Integration identity| SN["ServiceNow Knowledge APIs"]
    Entra -->|Delegated user identity| SN

The diagram shows a gateway-backed deployment; isolated local development may connect directly to FastMCP. The gateway performs JWT validation; Azure APIM is one optional implementation. FastMCP extracts claims when OBO or MCP scope verification is enabled; per-tool scope enforcement is controlled independently by MCP_SCOPE_VERIFY_ENABLED. The decision node corresponds to SERVICENOW_OBO_ENABLED; arrow labels distinguish the identity used for downstream access. Credential selection and prerequisites are detailed below.

Runtime path

Credentials and identity

Behavior

OBO disabled (false, default)

Static ServiceNow token, otherwise ServiceNow OAuth client credentials

ServiceNow authorizes the integration identity, not the caller. A configured static token takes precedence over client credentials. Inbound APIM scope checks remain independently configurable.

OBO enabled (true)

Incoming user assertion + Entra tenant/client/secret + downstream scope

Exchanges the assertion at the tenant-specific Entra v2 token endpoint, then calls ServiceNow with the delegated token. Independent of MCP_SCOPE_VERIFY_ENABLED; integration-token/client-credential settings are not used and there is no fallback to them.

OBO caches tokens per incoming assertion, bounded by both token lifetimes (maximum 256 cache entries). ServiceNow must already accept the downstream Entra token and map the user to the intended ACLs. App registration, delegated permissions/consent and ServiceNow trust/user mapping are deployment prerequisites, not provisioned by this server.

Security boundary

The authentication gateway must validate the incoming JWT and forward its original Authorization header. With OBO enabled, ClientApp may instead forward that validated token as x-mcp-servicenow-auth: Bearer <token>. This header is used only when Authorization is absent, before claim extraction and OBO context creation. An empty or invalid Authorization header does not trigger fallback. The gateway must validate the token in whichever header is used and strip untrusted caller-supplied forwarding headers. The MCP verifier decodes trusted claims and applies tool scopes; it does not validate JWT signatures, issuer or audience. APIM_EXPECTED_* values only produce diagnostic comparisons, never enforce token validation. Block direct backend access and ensure every permitted route has the same validation guarantee before enabling APIM auth. With both scope verification and OBO disabled, MCP does not enforce inbound authentication or tool scopes; use this only for isolated local development or an explicitly secured deployment boundary.

TLS uses the operating-system trust store. The Docker image uses the system CA bundle; no organization certificate is bundled. Logs exclude raw credentials, authorization headers and article bodies; DEBUG diagnostics can include token fingerprints and claim comparisons, so restrict log access and retention. Application Insights is optional and initialization failures do not prevent startup.

Environment variable configuration

The application reads settings from .env in the working directory and from the process environment; process values take precedence. Names are case-insensitive. Unknown .env keys are ignored. Settings are cached: restart after changes. Defaults below are the code defaults, not example-file placeholders or Helm overrides. unset means no configured value; use omission for optional settings unless blank behavior is explicitly stated. Secrets belong in an approved secret manager/Kubernetes Secret, not a ConfigMap.

The tables cover every application setting and deployment-related variables. Recommendations are examples, not existing prod deployment configuration. dev means isolated local development; deployed dev should use the same APIM boundary as test/prod.

Required / failure impact distinguishes values you must supply from settings whose defaults already work. Conditional requirements apply only to the selected feature or authentication path; optional does not mean an invalid supplied value is accepted. Invalid types, ranges or constrained values can fail settings validation at startup. OBO/APIM configuration checks run during server construction. ServiceNow URL, integration credentials and the default/max search-limit relationship are checked when the retrieval service is first initialized (normally the first tool call), not by the readiness endpoint. Missing retrieval credentials can therefore leave HTTP/health running while tools fail. Valid configuration alone does not guarantee valid credentials, upstream connectivity, permissions or compatible API routes.

Connection and integration authentication

Variable

Required / failure impact

Code default

Purpose / affected functionality

Dev / test / prod recommendation or example

SERVICENOW_BASE_URL

Required for all retrieval; no usable default

"" (required)

ServiceNow origin for all retrieval and integration OAuth calls; trailing / removed

Separate instances: https://dev-instance.service-now.com / https://test-instance.service-now.com / https://prod-instance.service-now.com

SERVICENOW_KNOWLEDGE_API_PATH

Default sufficient; correct route required for search, articles and attachments

api/sn_km_api/knowledge/articles

Search, article and article attachment API route

Keep default in all environments unless instance routing differs

SERVICENOW_CATEGORIES_API_PATH

Default sufficient; correct route required for category listing

api/now/table/kb_category

Category hierarchy API route

Keep default in all environments

SERVICENOW_ACCESS_TOKEN

Conditional: OBO off; supply this or both integration client credentials

unset

Static outbound bearer token when OBO is off; takes precedence over integration client credentials

Dev: optional short-lived token; test/prod: omit when using managed client credentials or OBO

SERVICENOW_CLIENT_ID

Conditional: OBO off and no static token; required with client secret

unset

Integration OAuth client ID when OBO is off and no static token is configured

Dedicated least-privilege client per instance; unused with OBO

SERVICENOW_CLIENT_SECRET

Conditional: OBO off and no static token; required with client ID

unset

Secret paired with integration client ID

Dev: local secret; test/prod: secret-manager injection and rotation; unused with OBO

SERVICENOW_OAUTH_TOKEN_PATH

Default sufficient; correct route required for integration client credentials

oauth_token.do

Integration client-credentials token endpoint

Default in all environments unless instance requires another route

SERVICENOW_OAUTH_SCOPE

Optional; required only if the integration OAuth provider requires a scope

unset

Optional scope sent in integration token request

Omit unless required by the instance; use that instance's approved scope in each environment

SERVICENOW_API_VERSION

Optional; required only if the ServiceNow instance requires a version header

unset

Optional Accept-Version header for ServiceNow requests

Omit unless the instance requires a specific version; test it before prod

APIM and delegated OBO authentication

MCP_SCOPE_VERIFY_ENABLED controls tool scope checks independently of OBO. Trusted JWT claim extraction runs when either switch is enabled; OBO always requires a user token, even when tool scope checks are disabled. The gateway remains responsible for cryptographic JWT validation.

Variable

Required / failure impact

Code default

Purpose / affected functionality

Dev / test / prod recommendation or example

MCP_SCOPE_VERIFY_ENABLED

Optional; independent of OBO

false

Enables fixed per-tool scope checks; OBO still extracts user claims when this is off

Enable only with validated-token forwarding and blocked bypass routes

SERVICENOW_OBO_ENABLED

Optional; must be true to use delegated user access (default uses integration identity)

false

Selects delegated OBO instead of integration authentication

Dev: false for integration setup; test/prod: true if user-level ACLs and downstream trust are configured, otherwise false with intentional integration ACLs

ENTRA_TENANT_ID

Conditional: required when OBO is enabled

unset

Tenant-specific OBO endpoint; required with OBO; common, organizations, consumers rejected

Actual tenant GUID per environment; omit without OBO

ENTRA_CLIENT_ID

Conditional: required when OBO is enabled

unset

Middle-tier Entra app performing OBO; required with OBO

Environment-specific app registration ID; omit without OBO

ENTRA_CLIENT_SECRET

Conditional: required when OBO is enabled

unset

Middle-tier app secret; required with OBO

Dev: local secret; test/prod: secret-manager injection and rotation; omit without OBO

SERVICENOW_OBO_SCOPE

Conditional: required when OBO is enabled

unset

Downstream delegated scope requested from Entra; required with OBO

Example api://<downstream-app-id>/.default, only if that resource has approved delegated permissions/consent; use environment-specific resource

APIM_SCOPE_CLAIM_NAMES

Default sufficient; non-empty matching claims required when APIM auth is enabled

scp,scope,roles

Comma-separated claims merged for tool authorization

Keep default in all environments, or limit to the claims actually validated/issued by your identity setup; must be non-empty with APIM auth

APIM_SUBJECT_CLAIM_NAMES

Default sufficient; non-empty matching claims required when APIM auth is enabled

oid,sub

Ordered claims used to identify the caller

Keep default in all environments; must be non-empty with APIM auth

APIM_EXPECTED_AUDIENCE

Optional; no explicit value required

""

Diagnostic audience comparison only

Local: blank; test/prod: expected middle-tier API audience, e.g. api://<mcp-app-id>; enforce separately in APIM

APIM_EXPECTED_ISSUER

Optional; no explicit value required

""

Diagnostic issuer comparison only

Local: blank; test/prod: exact issuer configured in APIM, e.g. https://login.microsoftonline.com/<tenant-id>/v2.0

APIM_EXPECTED_TENANT_ID

Optional; no explicit value required

""

Diagnostic tenant comparison only

Local: blank; test/prod: expected tenant GUID; enforce separately in APIM

TOKEN_FINGERPRINT_KEY

Optional; if supplied, must be exactly 64 hex characters or settings validation fails

unset

DEBUG token correlation: unset/blank uses SHA-256; a key uses truncated HMAC-SHA-256

Dev: omit unless troubleshooting; test/prod: separate secret of exactly 64 hexadecimal characters if diagnostics are needed; never reuse an app secret

Tool scopes are fixed in src/snow_mcp/scopes.py, not configurable by environment variables. OBO requires all four ENTRA_*/scope values above and a gateway-validated user token, but does not require tool scope verification to be enabled. Missing or invalid OBO settings fail configuration validation.

Retrieval filters and fields

Variable

Required / failure impact

Code default

Purpose / affected functionality

Dev / test / prod recommendation or example

SERVICENOW_KNOWLEDGE_BASE

Optional; no explicit value required

unset

Default knowledge-base identifier for search; a tool argument can override it

Dev: omit or sandbox sys_id; test/prod: intended base sys_id if a default is needed; not an authorization boundary

SERVICENOW_LANGUAGE

Optional; no explicit value required

unset

Default search language; a tool argument can override it

Omit for unrestricted language; use e.g. en in all environments if required

SERVICENOW_SEARCH_CATEGORY_ALLOWLIST

Optional

""

Search-only category display-name restriction; unset or blank disables it

Set approved comma-separated category names when restrictions are required

SERVICENOW_SEARCH_KNOWLEDGE_BASE_ALLOWLIST

Optional; blank leaves knowledge bases unrestricted by this filter

""

Search-only knowledge-base display-name restriction; blank disables it

Dev: blank or sandbox base; test/prod: explicitly approved names, e.g. HR,IT

SERVICENOW_SEARCH_FIELDS

Default sufficient; retain category/base fields when their search filters are active

sys_id,number,short_description,description,kb_knowledge_base,kb_category,score

Fields requested for search result mapping and allowlist checks

Keep default in all environments, especially kb_category and kb_knowledge_base

SERVICENOW_ARTICLE_FIELDS

Default sufficient; retain content/metadata fields for complete article retrieval

sys_id,number,short_description,text,kb_knowledge_base,kb_category,workflow_state,published,valid_to,sys_updated_on

Fields requested for canonical article content and metadata

Keep default in all environments; removing text or metadata reduces returned content

SERVICENOW_CATEGORY_FIELDS

Default sufficient; retain ID/name/parent fields for category hierarchy

sys_id,label,value,parent_id,full_category,active

Fields requested for category IDs, names and hierarchy

Keep default in all environments

Allowlists use comma-separated display names, trimmed and matched case-insensitively. Names in each list are ORed; the two lists are ANDed. Search sends an encoded-query filter and also checks returned candidates locally. Missing/nonmatching fields are excluded when their list is non-empty; ranks are preserved, total counts retained results, and no extra request fills filtered-out slots. A caller's category narrows, never overrides, the category allowlist. Names containing ^, line breaks or a leading javascript: are rejected. These filters do not restrict category listing, direct article retrieval or attachment retrieval; downstream ACLs remain the access boundary.

Bounds, reliability and observability

Variable

Required / failure impact

Code default

Purpose / affected functionality

Dev / test / prod recommendation or example

REQUEST_TIMEOUT_SECONDS

Default sufficient; no explicit value required

10

HTTP timeout for ServiceNow and token exchanges; range (0,120]

Start with 10 in all environments; raise only after measuring upstream latency

DEFAULT_SEARCH_LIMIT

Default sufficient; no explicit value required

5

Result limit when caller omits it; at least 1 and no greater than max

5 in all environments

MAX_SEARCH_LIMIT

Default sufficient; no explicit value required

20

Upper bound for search results; at least 1

20 in all environments; reduce if response budgets require it

CATEGORY_PAGE_SIZE

Default sufficient; no explicit value required

100

Category pagination batch size; range 1–500

100 in all environments

MAX_ARTICLE_CONTENT_CHARS

Default sufficient; no explicit value required

100000

Article text truncation threshold; minimum 100

100000 in all environments; lower if client context budgets require it

MAX_ATTACHMENT_BYTES

Default sufficient; no explicit value required

5000000

Maximum attachment download size; minimum 1; oversized downloads rejected

5000000 in all environments; consider base64 response overhead before increasing

TRANSIENT_RETRY_ATTEMPTS

Default sufficient; no explicit value required

2

Extra retries for transient ServiceNow retrieval failures; range 0–5, not an OBO exchange retry setting

2 in all environments; 0 for deterministic failure testing

RETRY_BACKOFF_SECONDS

Default sufficient; no explicit value required

0.2

Base delay for retrieval retry backoff; range 0–10

0.2 in all environments; tune against upstream rate limits

APPLICATIONINSIGHTS_CONNECTION_STRING

Conditional: required only for Application Insights telemetry; server works without it

unset

Enables optional Azure Monitor telemetry; missing/placeholder disables it

Dev: omit or dedicated dev resource; test/prod: inject respective resource connection string

ENVIRONMENT

Default sufficient; no explicit value required

development

Environment label in lifecycle/telemetry setup logs; does not switch auth or retrieval behavior

dev / test / prod

LOG_LEVEL

Default sufficient; no explicit value required

INFO

Application logging verbosity, including DEBUG auth diagnostics

Dev: DEBUG temporarily; test/prod: INFO, DEBUG only during controlled troubleshooting

HOST

Default sufficient; no explicit value required

0.0.0.0

CLI server bind address

Local dev: 127.0.0.1; container test/prod: 0.0.0.0 behind a protected ingress

PORT

Default sufficient; no explicit value required

8080

CLI server port; range 1–65535

8080 in all environments unless platform requires another port

Deployment variables

These are not application settings in ServiceNowKnowledgeConfig:

Variable

Required / failure impact

Default

Consumer / effect

Dev / test / prod recommendation or example

PATH_BASE

Not required by Python server; deployment routing is configured separately

No application default

Not injected by the generic chart; Python server does not read it or change /mcp with it

Set routing through Helm/Istio/APIM, not this variable alone; e.g. environment suffix dev / test / prod

Helm runs Uvicorn with mcp.args.host and mcp.args.port as explicit command arguments; those, not HOST/PORT, control the Helm listener. ConfigMap values come from config, and secretEnv.existingSecret supplies secret environment values. .env.example is for local setup, not a production manifest.

The Dockerfile also sets these runtime/library variables (not Pydantic settings). Keep the image defaults across dev/test/prod unless your platform explicitly requires an override:

Variable

Required / failure impact

Docker default

Purpose / affected functionality

PYTHONDONTWRITEBYTECODE

Default sufficient; no explicit value required

1

Prevents Python from writing .pyc files

PYTHONUNBUFFERED

Default sufficient; no explicit value required

1

Flushes Python output promptly for container log collection

FASTMCP_CHECK_FOR_UPDATES

Default sufficient; no explicit value required

off

Disables FastMCP update checks

PATH

Default sufficient; no explicit value required

/app/.venv/bin:$PATH

Resolves commands from the image's Python virtual environment

SSL_CERT_FILE

Default sufficient; no explicit value required

/etc/ssl/certs/ca-certificates.crt

CA bundle for compatible TLS libraries; application HTTP clients explicitly use the OS trust store

REQUESTS_CA_BUNDLE

Default sufficient; no explicit value required

/etc/ssl/certs/ca-certificates.crt

CA bundle for Requests-based dependencies; not a switch to disable TLS verification

Docker builds install locked dependencies from the public package index and use the system CA bundle. Configure any private package feed or organization CA outside this repository.

Example environment profiles

Shared safe starting values (apply the correct instance and explicit search allowlists from the tables for each environment):

REQUEST_TIMEOUT_SECONDS=10
DEFAULT_SEARCH_LIMIT=5
MAX_SEARCH_LIMIT=20
MAX_ATTACHMENT_BYTES=5000000
LOG_LEVEL=INFO
PORT=8080

Isolated local dev, integration identity:

ENVIRONMENT=dev
HOST=127.0.0.1
MCP_SCOPE_VERIFY_ENABLED=false
SERVICENOW_OBO_ENABLED=false
SERVICENOW_BASE_URL=https://dev-instance.service-now.com
SERVICENOW_CLIENT_ID=<dev-integration-client-id>
SERVICENOW_CLIENT_SECRET=<inject-dev-secret>

APIM-backed test, exercising delegated OBO:

ENVIRONMENT=test
HOST=0.0.0.0
MCP_SCOPE_VERIFY_ENABLED=true
SERVICENOW_OBO_ENABLED=true
SERVICENOW_BASE_URL=https://test-instance.service-now.com
ENTRA_TENANT_ID=<test-tenant-guid>
ENTRA_CLIENT_ID=<test-middle-tier-app-id>
ENTRA_CLIENT_SECRET=<inject-test-secret>
SERVICENOW_OBO_SCOPE=api://<test-downstream-app-id>/.default

APIM-backed prod uses the tested identity path. For delegated access use the test profile with production instance, tenant, app, scope. Set ENVIRONMENT=prod and inject credentials separately, and the production Application Insights connection string. For intentionally shared integration access use this profile instead:

ENVIRONMENT=prod
HOST=0.0.0.0
MCP_SCOPE_VERIFY_ENABLED=true
SERVICENOW_OBO_ENABLED=false
SERVICENOW_BASE_URL=https://prod-instance.service-now.com
SERVICENOW_CLIENT_ID=<prod-integration-client-id>
SERVICENOW_CLIENT_SECRET=<inject-prod-secret>
APPLICATIONINSIGHTS_CONNECTION_STRING=<prod-resource-connection-string>

Placeholders are illustrative, not usable credentials. Omit SERVICENOW_ACCESS_TOKEN when using client credentials. Neither enabling OBO nor APIM auth configures the external identity provider, gateway policy or ServiceNow ACLs automatically.

Development

uv sync --frozen --extra dev
uv run pytest
uv run ruff format --check src tests
uv run ruff check src tests
uv run mypy src

Related MCP Connectors

Related MCP Servers