Open iT / ServiceNow MCP Pilot
by markberin
README.md
# Open iT / ServiceNow MCP Pilot
This repository contains a deliberately small remote MCP server scaffold for an Open iT / ServiceNow SAM Pro pilot. It exposes read-only tools over Streamable HTTP. Product inventory is deterministic mock data; Knowledge Base searches use the public documentation at `docs.openit.com`. The server does **not** call Open iT product or ServiceNow APIs.
## What it exposes
- MCP endpoint: `/api/mcp`
- Public health endpoint: `/api/health`
- Tool: `find_products_without_entitlements`
- Tool: `summarize_entitlement_coverage`
- Tool: `search_openit_knowledge_base`
The MCP endpoint follows the Microsoft Learn MCP pattern: clients use Streamable HTTP, while a normal browser `GET` receives HTTP `405 Method Not Allowed` and a short explanation. The endpoint is stateless and returns JSON MCP responses, which keeps it suitable for horizontally scaled or serverless hosting later. `api/index.py` exposes the same ASGI application as a future Vercel entry point; no deployment is included.
## Run locally
Python 3.11 or newer is required.
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"
$env:OPENIT_MCP_API_KEY = "local-dev-key"
$env:MCP_BIND_HOST = "127.0.0.1"
$env:PORT = "8000"
$env:MCP_BASE_URL = "http://127.0.0.1:8000"
openit-mcp
```
For bash/zsh, activate with `source .venv/bin/activate` and configure the key with:
```bash
export OPENIT_MCP_API_KEY="local-dev-key"
export MCP_BIND_HOST="127.0.0.1"
export PORT="8000"
export MCP_BASE_URL="http://127.0.0.1:8000"
```
`MCP_BIND_HOST` and `PORT` are used only by the local `openit-mcp` Uvicorn launcher. `MCP_BASE_URL` is used only by external clients such as `examples/invoke.py`; the server itself routes internally with relative paths and never calls itself through an absolute URL.
The server intentionally returns `503` from MCP `POST` and `DELETE` requests when `OPENIT_MCP_API_KEY` is unset. Clients should send the key as a bearer token:
```text
Authorization: Bearer local-dev-key
```
For ServiceNow compatibility, the server also accepts the raw key in `Authorization` as well as `X-API-Key: local-dev-key`. The health endpoint does not require authentication.
Check health:
```powershell
Invoke-RestMethod "$env:MCP_BASE_URL/api/health"
```
In a second terminal with the same virtual environment and API key, run the included MCP client example:
```powershell
python examples/invoke.py
```
You can also connect an MCP Inspector or other Streamable HTTP client to `$MCP_BASE_URL/api/mcp` and configure the `Authorization` header shown above.
Opening the MCP URL directly in a browser returns:
```text
HTTP 405 Method Not Allowed
This is an MCP server endpoint and cannot be accessed directly via a browser. Please use a streamable HTTP MCP client.
```
## Tool contract
`find_products_without_entitlements` accepts:
| Input | Type | Required | Behavior |
| --- | --- | --- | --- |
| `publisher` | string | No | Exact, case-insensitive publisher filter |
| `limit` | integer | No | Defaults to `20`; allowed range is `1` to `100` |
It returns structured JSON in this shape:
```json
{
"count": 1,
"items": [
{
"product_name": "Open iT Analyzer Pro",
"publisher": "Open iT",
"reason": "No entitlement record found"
}
],
"explanation": "These products currently do not have matching entitlement records in the mock dataset.",
"generated_at": "2026-08-05T12:00:00Z"
}
```
Results are sorted by publisher and product name before `limit` is applied.
### `summarize_entitlement_coverage`
This tool summarizes entitlement coverage across the mock product inventory. It accepts:
| Input | Type | Required | Behavior |
| --- | --- | --- | --- |
| `publisher` | string | No | Exact, case-insensitive publisher filter |
It returns structured JSON in this shape:
```json
{
"publisher": null,
"total_products": 3,
"covered_products": 1,
"uncovered_products": 2,
"total_entitlement_records": 25,
"coverage_percentage": 33.33,
"explanation": "Coverage is the percentage of discovered products in scope that have at least one matching entitlement record in the mock dataset.",
"generated_at": "2026-08-18T12:00:00Z"
}
```
Coverage is calculated at the product level: a product is covered when its entitlement count is greater than zero. An empty publisher result reports zero products and `0.0` percent coverage.
### `search_openit_knowledge_base`
This tool searches the live [Open iT Online Docs](https://docs.openit.com/) index and returns official page or section references. It accepts:
| Input | Type | Required | Behavior |
| --- | --- | --- | --- |
| `query` | string | Yes | Question or keywords; 3 to 200 characters |
| `limit` | integer | No | Defaults to `5`; allowed range is `1` to `10` |
| `include_summaries` | boolean | No | Defaults to `true`; includes a relevant description or indexed excerpt |
Each result contains the page or section title, documentation breadcrumb, optional summary, and direct `reference_url`. An agent should cite those URLs when it uses the returned guidance. Searches require outbound HTTPS access to `docs.openit.com`. The published index is cached in memory for 15 minutes to reduce repeated downloads while still refreshing as the online documentation changes.
## Tests
```powershell
pytest
```
The tests verify the published tool list, structured output, entitlement coverage summaries, documentation-index result parsing and references, publisher filtering, deterministic limiting, browser `405` behavior, real HTTP MCP initialization/list/call requests, the public health endpoint, and fail-closed API key behavior.
## Future hosted configuration
Loopback addresses are local-only: inside Vercel they refer to the function instance, not the public deployment or another service. The network-base address ending in `.0` at the start of the IPv4 loopback block is not an application host and must not be used. Hosted URLs must come from deployment environment variables, while same-app routes should remain relative.
Vercel imports the ASGI `app` from `api/index.py`; it does not execute the local Uvicorn launcher, so `MCP_BIND_HOST` and `PORT` are not deployment assumptions. Configure these server environment variables before hosting:
```text
OPENIT_MCP_API_KEY=<production-secret>
# Recommended when using a stable custom domain:
OPENIT_MCP_ALLOWED_HOSTS=openit-mcp.example.com,openit-mcp.example.com:*
```
The server automatically adds `VERCEL_URL`, `VERCEL_BRANCH_URL`, and `VERCEL_PROJECT_PRODUCTION_URL` to its MCP host allowlist when Vercel exposes those system variables. An explicit `OPENIT_MCP_ALLOWED_HOSTS` remains recommended for a stable custom domain.
Set `MCP_BASE_URL=https://openit-mcp.example.com` only if a hosted client or job runs `examples/invoke.py`; the MCP server routes `/api/mcp` and `/api/health` without it. Keep the API key in Vercel's secret environment configuration and use HTTPS. Environment changes apply only to new deployments, so redeploy after changing them.
## Connect from ServiceNow AI Agent Studio
Use the following values after deploying to Vercel:
| Field | Value |
| --- | --- |
| Name | `Open iT SAM Pilot` |
| Authentication type | `API Key` |
| MCP server URL | `https://<your-stable-vercel-host>/api/mcp` |
| API key | The raw value of `OPENIT_MCP_API_KEY` |
Do not add a `Bearer` prefix in the ServiceNow API Key field. The server accepts ServiceNow's raw `Authorization` value. A browser request to the URL still returns the intentional `405`; tool discovery uses authenticated MCP `POST` requests.
## Before ServiceNow integration
This pilot still needs a real Open iT data adapter, agreement on the product/entitlement matching rules and response fields, ServiceNow remote MCP connection configuration, production authentication (preferably OAuth 2.1 rather than a shared key), HTTPS hosting, logging/monitoring, secret rotation, and end-to-end validation from AI Agent Studio.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues