Skip to main content
Glama
smitraonline

snow-mcp

by smitraonline
README.md
# snow-mcp

An MCP (Model Context Protocol) server for interacting with a ServiceNow instance via its Table API. Exposes tools to list/search, get, create, and update records in the `incident`, `sc_request` (Request), and `sc_req_item` (Requested Item) tables, plus generic tools that work against any ServiceNow table by name.

## Setup

Requires Python >=3.14 and [uv](https://docs.astral.sh/uv/).

1. Create a `.env` file in the project root with your ServiceNow instance credentials:

   ```
   SERVICENOW_INSTANCE_URL="https://your-instance.service-now.com"
   SERVICENOW_USER="your-username"
   SERVICENOW_PASS="your-password"

   # Optional, only used when running over HTTP (see below):
   MCP_TRANSPORT="stdio"    # "stdio" (default) or "http"
   MCP_HOST="127.0.0.1"     # HTTP bind host, default 127.0.0.1
   MCP_PORT="8000"          # HTTP bind port, default 8000
   ```

2. Install dependencies:

   ```
   uv sync
   ```

## Testing

```
uv run pytest
```

The suite mocks ServiceNow's HTTP API (via [respx](https://lundberg.github.io/respx/)) and never talks to a real instance, so it runs without a `.env` file. It combines an in-process MCP client (`mcp.shared.memory.create_connected_server_and_client_session`, connected to the server over in-memory streams) with direct unit tests of `ServiceNowClient`. See `tests/` and the Testing section of [CLAUDE.md](CLAUDE.md) for details.

## Running

Start the server over stdio (for use by an MCP client):

```
uv run main.py
```

To test interactively with the MCP Inspector:

```
uv run mcp dev main.py
```

### Running over HTTP

Set `MCP_TRANSPORT=http` (in `.env` or the environment) and run the server the same way:

```
MCP_TRANSPORT=http uv run main.py
```

This serves both MCP HTTP transports from one process, bound to `MCP_HOST`/`MCP_PORT` (default `127.0.0.1:8000`):

- Streamable HTTP (current spec): `http://127.0.0.1:8000/mcp`
- SSE (legacy transport): `http://127.0.0.1:8000/sse`

Stdio remains the default transport; the client-registration example below is unaffected by this.

### Registering with an MCP client

```json
{
  "mcpServers": {
    "snow-mcp": {
      "command": "uv",
      "args": ["run", "--directory", "C:\\Projects\\AI\\MCP\\snow-mcp", "main.py"]
    }
  }
}
```

## Running in a container (Podman, Windows)

Requires [Podman](https://podman.io/) on Windows with a running machine (`podman machine init` once, then `podman machine start`). The image runs as a non-root user and serves the HTTP transport (`MCP_TRANSPORT=http`) so it's reachable from the host over the published port.

1. Make sure your `.env` file exists in the project root (see Setup above) — `compose.yaml` loads it via `env_file`.
2. Build and start:

   ```
   podman compose up -d --build
   ```

   (If `podman compose` isn't available, use `podman-compose up -d --build` instead — Podman shells out to whichever compose provider is installed.)

3. The server is now reachable at `http://127.0.0.1:8000/mcp` (streamable HTTP) — point an HTTP-based MCP client at it, same as the `MCP_TRANSPORT=http` case above.
4. Stop it:

   ```
   podman compose down
   ```

To build/run without compose:

```
podman build -f Containerfile -t snow-mcp .
podman run -d --name snow-mcp -p 8000:8000 --env-file .env -e MCP_TRANSPORT=http snow-mcp
```

## Tools

Per-table tools (same shape for `incident`, `sc_request` → `*_requests`, `sc_req_item` → `*_requested_items`):

- `list_incidents` / `list_requests` / `list_requested_items` — search with an encoded query (`query`), sort (`order_by`, `order_desc`), pagination (`limit`, `offset`), field selection (`fields`), and display values (`display_value`)
- `get_incident` / `get_request` / `get_requested_item` — fetch a single record by `sys_id`
- `create_incident` / `create_request` / `create_requested_item` — create a record from a `fields` dict
- `update_incident` / `update_request` / `update_requested_item` — partially update a record by `sys_id`
- `get_incident_work_notes` / `get_request_work_notes` / `get_requested_item_work_notes` — read a record's work notes (journal entries) newest first, with `limit`/`offset` pagination and optional author filtering (`created_by`, exact username match)
- `add_incident_work_note` / `add_request_work_note` / `add_requested_item_work_note` — append a work note (`note`) to a record by `sys_id`

Generic tools, for any table by technical name:

- `list_table_records`, `get_table_record`, `create_table_record`, `update_table_record`

There is no delete tool.

TDQS

A3.6/5.0

Scored across 22 tools

Disambiguation5/5

Each tool targets a specific entity (incident, request, requested item, or generic table) and action (create, get, list, update, add work note, get work notes). There is no ambiguity between tools, even with similar naming patterns.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern with underscores, e.g., add_incident_work_note, create_incident, list_requests. No mixing of casing or verb styles.

Tool Count4/5

22 tools cover specific ServiceNow entities (incident, request, requested item) plus generic table operations. While slightly above the typical 3-15 range, the count is justified by the need for distinct operations on each entity and generic access.

Completeness3/5

CRUD operations are present for incidents, requests, requested items, and generic tables, along with work notes read/write. However, deletion operations (e.g., delete_incident) are missing, and entity-specific operations for other common tables like change_request are absent, relying on generic table tools which may not cover all needs.

Maintenance

ActivityInactive
ResponsivenessNo issues