sn-community-mcp
by wiz0floyd
README.md
# sn-community-mcp
CLI and MCP server for searching the ServiceNow Community forums (community.servicenow.com).
## How it works
The Community runs on the Khoros/Lithium forum platform. This tenant does not expose the
public LiQL REST API (`/community/api/2.1/search` returns 404 for anonymous access). Khoros'
RSS search feed (`/community/s/<id>/rss/search`) is reachable, but **do not use it for
ranked search** — it turned out to sort by recency, not relevance, and returned off-topic,
same-day results for a query like "API Management" while the real search page returned
on-topic results. (That gap was only caught by comparing live output against the actual
search page side by side — the RSS feed still "worked" in every other sense: valid XML,
plausible-looking posts, no errors.)
Instead, this tool fetches and parses the same server-rendered HTML search-results page a
user's browser loads (`/community/forums/searchpage/tab/message?...q=<query>`) — genuinely
relevance-ranked ("Best Match" by default), and readable via a plain HTTP GET with no JS
execution or browser automation needed, since the results are already present in the raw
HTML. `full: true` does a second plain-HTTP fetch of the post's permalink page to pull the
complete body, converted to Markdown.
Because this reads undocumented page structure rather than a stable API, it's more fragile
than a real API would be — see "Design notes" below for how to notice and fix breakage.
## Install
```bash
npm install
npm run build
```
## MCP Server (Claude integration)
The MCP server exposes one tool:
| Tool | Description |
|---|---|
| `search_community` | Search the Community. Returns title, link, board, author, date, and body (snippet or full Markdown). |
### Register with Claude Code
Add to `~/.claude/settings.json`:
```json
{
"mcpServers": {
"sn-community": {
"command": "node",
"args": ["/path/to/sn-community-mcp/dist/mcp-server.js"]
}
}
}
```
### Register with Claude Desktop
Add the same block to your Claude Desktop config, replacing the path with the absolute path
to `dist/mcp-server.js` on your machine.
### `search_community` parameters
- `query` (string, required) — search terms.
- `count` (number, optional, default 10, max 30) — number of results. Fetched 10 at a time
(the observed page size), so counts above 10 make additional page requests.
- `full` (boolean, optional, default `false`) — return the full post as Markdown (fetched from
the post's permalink page) instead of a ~400-character snippet. Combine with a small `count`
to avoid large responses and extra requests. Falls back to the snippet if the permalink
fetch fails or doesn't have the expected content (see Design notes).
There is no `sort` parameter — no working relevance/date sort parameter was found for the
HTML search endpoint (an attempt returned HTTP 500), so results are always in the site's
default "Best Match" order.
## CLI
```bash
npx tsx src/cli.ts search "API Management"
npx tsx src/cli.ts search "API Management" --full -n 3
npx tsx src/cli.ts search "API Management" --json
```
Or after `npm run build`, use the `sn-community` bin directly.
## Development
```bash
npm run dev:mcp # Run MCP server via tsx (no build needed)
npm test # Unit tests (mocked fetch, no network)
npm run test:integration # Live smoke test against the real search page
npm run lint # Type-check without emitting
```
Unit tests replay real captured HTML pages (`tests/fixtures/*.html`) rather than hand-written
fixtures, so selector-based parsing is verified against real markup — including a fixture
that reproduces the one observed permalink-fetch failure case (see below).
## Design notes / known limitations
- **Scraping, not a documented API.** `src/community-client.ts` selects on CSS classes like
`.lia-message-view-message-search-item`, `h1.message-subject`, `.lia-truncated-body-container`,
and `.lia-message-body-content` — standard Khoros/Lithium theme class names, but undocumented
and subject to change without notice. If `search_community` starts returning empty results
or garbled fields, the site's markup likely changed; re-run the CLI with `--json` against a
known-good query, compare to a fresh page fetch, and update the selectors.
- **No confirmed sort/date param for the HTML endpoint.** RSS supported `sort_by`/`sort_order`,
but replaying the same params against the HTML search page returned HTTP 500. Results are
always in "Best Match" (relevance) order — the same default the community site's own search
box uses.
- **`full: true` falls back to the snippet if a permalink doesn't render as expected.** Both
forum-message (`m-p/`) and KB-article (`ta-p/`) permalinks render their full body correctly
via a plain fetch — this was verified directly for both link styles, so it's not a
systemic gap for either. `fetchFullBody()` still treats a missing
`.lia-message-body-content` container as "no full body available" and returns the original
snippet rather than an error, as defense against the case where a permalink 404s, redirects
somewhere unexpected, or the page structure changes. `tests/fixtures/post-page-missing-body.html`
reproduces that fallback path using a page that doesn't contain the body container (a
malformed-URL redirect target captured during testing), not a class of URL that fails in
normal use.
- **No `get_community_post(url)` tool.** The one exception is that `full: true` already covers
the "get a specific post's full content" need for posts found via search; a pasted link's
title/keywords can be searched instead of adding a second tool.
- **No Cloudflare Worker variant.** This ships as a local stdio MCP server only. The sibling
`sn-docs` project (`../Docs CLI`) proves the Worker path
(`WebStandardStreamableHTTPServerTransport` + `wrangler.toml`) if hosted/Claude.ai access is
ever wanted; `cheerio` (used here) runs fine in Workers, so only the Turndown-based
`toMarkdown()` full-body conversion would need a DOM-free port, same as `sn-docs`'s
`toMarkdownWorker`.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues