forked from spencerm/freshservice-claude
Add multi-user auth: _config reads X-Freshservice-Domain/X-Freshservice-Key from the per-request headers (via the SDK request_ctx, set inside the tool's task) and prefers them over the container env. One shared server can now act as each caller's own Freshservice identity — actions attributed to them, their permissions, their rate limit — while the env vars remain a single-user fallback. Verified end-to-end that concurrent clients don't cross-wire and that a client with no creds errors instead of falling back silently. Docs/.env.example updated with the multi-user client config and the guidance to omit env FS creds on a shared deployment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
112 lines
4.2 KiB
Markdown
112 lines
4.2 KiB
Markdown
# Freshservice MCP server (for Cowork / Claude Desktop)
|
|
|
|
Cowork runs the agent inside an isolated VM, so a locally-installed *skill*
|
|
can't see your credentials or reach your Freshservice host. This MCP server
|
|
runs as a container on the LAN box (`192.168.101.12`) — outside the VM — and
|
|
Claude Desktop connects to it through `mcp-remote`, exactly like the existing
|
|
`sharepoint-lists` server. Credentials and network both live on the LAN box.
|
|
|
|
(The skill in the parent directory remains the right tool for the Claude Code
|
|
CLI, which is not sandboxed.)
|
|
|
|
## Deploy on 192.168.101.12
|
|
|
|
```bash
|
|
# on the LAN box, in this directory
|
|
cp .env.example .env
|
|
# edit .env: FRESHSERVICE_DOMAIN, FRESHSERVICE_API_KEY, MCP_AUTH_TOKEN
|
|
# token: openssl rand -hex 32
|
|
docker compose up -d --build
|
|
docker compose logs -f # confirm it started on :3838 inside the container
|
|
```
|
|
|
|
The container listens on container port 3838, published to host port **3839**
|
|
(3838 is used by sharepoint-lists). Adjust in `docker-compose.yml` if needed.
|
|
|
|
### Verify
|
|
|
|
```bash
|
|
# from the LAN box or any host that can reach it:
|
|
curl -s -o /dev/null -w "%{http_code}\n" -X POST \
|
|
http://192.168.101.12:3839/mcp \
|
|
-H "Authorization: Bearer <MCP_AUTH_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Accept: application/json, text/event-stream" \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}'
|
|
# expect 200 (401 means bad/missing token)
|
|
```
|
|
|
|
## Connect Claude Desktop
|
|
|
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` under
|
|
`mcpServers` (alongside `sharepoint-lists`):
|
|
|
|
```json
|
|
"freshservice": {
|
|
"command": "/opt/homebrew/bin/npx",
|
|
"args": [
|
|
"-y", "mcp-remote",
|
|
"http://192.168.101.12:3839/mcp",
|
|
"--allow-http",
|
|
"--header", "Authorization:${MCP_AUTH_HEADER}"
|
|
],
|
|
"env": { "MCP_AUTH_HEADER": "Bearer <MCP_AUTH_TOKEN>" }
|
|
}
|
|
```
|
|
|
|
Restart Claude Desktop. The Freshservice tools then appear in chat and Cowork.
|
|
|
|
## Multiple users
|
|
|
|
The server can act as **each caller's own Freshservice identity** instead of a
|
|
single shared one. Each client sends its own credentials as headers on every
|
|
request; the server uses them per-call (they take precedence over the env
|
|
vars). Actions are then attributed to each person, with their own permissions
|
|
and rate limits.
|
|
|
|
To run the server multi-user:
|
|
|
|
1. **Omit `FRESHSERVICE_DOMAIN` / `FRESHSERVICE_API_KEY` from `.env`** (keep only
|
|
`MCP_AUTH_TOKEN`). With no env fallback, a client that forgets its headers
|
|
gets a clean "Missing credentials" error rather than silently acting as
|
|
whoever owns the env key.
|
|
2. Each user adds the extra headers to **their own** `claude_desktop_config.json`,
|
|
with **their own** Freshservice API key (Profile settings → API key):
|
|
|
|
```json
|
|
"freshservice": {
|
|
"command": "/opt/homebrew/bin/npx",
|
|
"args": [
|
|
"-y", "mcp-remote",
|
|
"http://192.168.101.12:3839/mcp",
|
|
"--allow-http",
|
|
"--header", "Authorization:${MCP_AUTH_HEADER}",
|
|
"--header", "X-Freshservice-Domain:${FS_DOMAIN}",
|
|
"--header", "X-Freshservice-Key:${FS_KEY}"
|
|
],
|
|
"env": {
|
|
"MCP_AUTH_HEADER": "Bearer <shared MCP_AUTH_TOKEN>",
|
|
"FS_DOMAIN": "pesco.freshservice.com",
|
|
"FS_KEY": "<that user's own Freshservice API key>"
|
|
}
|
|
}
|
|
```
|
|
|
|
`MCP_AUTH_TOKEN` is the same shared value for everyone (it only gates *reaching*
|
|
the server); `FS_KEY` is each person's own identity. The key stays on the
|
|
user's machine and travels per-request — the server never stores it.
|
|
|
|
> Note: the LAN transport is plain HTTP, so these headers travel unencrypted.
|
|
> Fine on a trusted network; put a TLS-terminating reverse proxy in front if
|
|
> this ever leaves it.
|
|
|
|
## Tools
|
|
|
|
Tickets (`list_tickets`, `get_ticket`, `create_ticket`, `update_ticket`,
|
|
`reply_ticket`, `add_note`, `list_ticket_conversations`), assets
|
|
(`search_assets`, `get_asset`, `update_asset`), people (`find_requester`,
|
|
`list_agents`, `list_groups`, `list_departments`), change/problem/release
|
|
(`list_changes`, `get_change`, `create_change`, `list_problems`, `get_problem`,
|
|
`list_releases`, `get_release`), and `freshservice_request` for any other v2
|
|
endpoint.
|