FS_MCP_thin
# FS_MCP_thin π
[](https://modelcontextprotocol.io/)
[](https://api.freshservice.com/v2/)
A powerful, **read-only** [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that bridges the gap between your AI assistant (like Claude) and your **Freshservice** Service Desk. Empower your AI with deep context to triage incidents, analyze asset distribution, and summarize support history.
---
## β¨ Features
- π« **Ticket Intelligence** β Deep dive into tickets, conversations, private notes, and service request items.
- π **Knowledge Integration** β Search and retrieve the full content of your solution articles.
- π§ **Problem Analysis** β Inspect root-cause investigation records.
- π¦ **Inventory Visibility** β Browse and drill down into Assets (Hardware) and Software inventory.
- π€ **Identity Context** β Lookup Requesters and Agents, and view their specific ticket/asset history.
- β±οΈ **Governance Context** β Access SLA policies, business hours, and org structure as standing context.
- π¬ **Canned Success** β Use your curated response templates to draft high-quality replies.
- πͺΆ **Token-Efficient** β Responses are shaped (field-projected, HTML-stripped, compacted) before they reach the model, cutting context bloat 5-10x on list/detail calls. See [Response Shaping](#-response-shaping-token-economy).
---
## π Prerequisites
- **Node.js**: Version 18.x or higher.
- **Freshservice**: An active account with API access enabled.
- **API Key**: Found in your Profile Settings β API Key.
---
## π Quick Start
### 1. Installation
```bash
# Clone or download the repository
cd FS_MCP_thin
# Install dependencies
npm install
# Setup environment variables
cp .env.example .env
```
### 2. Configuration
The server needs exactly two environment variables. **Both are required** β if either is missing the server exits immediately on startup with an error.
| Variable | What it is | Where to get it |
|----------|-----------|-----------------|
| `FRESHSERVICE_DOMAIN` | Your Freshservice URL | The host in your browser, e.g. `acme.freshservice.com`. |
| `FRESHSERVICE_API_KEY` | Your personal API key | Freshservice β avatar β **Profile Settings** β **API Key** (right sidebar). |
**Domain format:** supply just the host (`acme.freshservice.com`). A full URL (`https://acme.freshservice.com`) also works β the client adds `https://` only if no protocol is present. Do **not** include a trailing slash or path.
**How auth works:** Freshservice uses HTTP Basic auth with the **API key as the username** and a dummy password. The server handles this for you β you only ever supply the raw key.
Edit the `.env` file with your details:
```env
FRESHSERVICE_DOMAIN=acme.freshservice.com
FRESHSERVICE_API_KEY=your_api_key_here
```
> `.env` is git-ignored. Your credentials never leave your machine β see [Security & Privacy](#-security--privacy).
### 3. Integration with Claude Desktop
Add this snippet to your `claude_desktop_config.json`. The `env` block here is an **alternative** to a `.env` file β when Claude Desktop launches the server it injects these, so you don't need both. (If both exist, the values in `env` take precedence.)
```json
{
"mcpServers": {
"freshservice": {
"command": "node",
"args": ["C:\\path\\to\\FS_MCP_thin\\src\\index.js"],
"env": {
"FRESHSERVICE_DOMAIN": "acme.freshservice.com",
"FRESHSERVICE_API_KEY": "your_api_key_here"
}
}
}
}
```
> Use the **absolute path** to `src/index.js`. On Windows, escape backslashes (`\\`) as shown.
### Troubleshooting credentials
| Symptom | Likely cause |
|---------|--------------|
| Server exits with `FRESHSERVICE_DOMAIN and FRESHSERVICE_API_KEY ... are required` | One or both env vars are unset (typo, wrong key name, or `.env` not loaded). |
| `401 Unauthorized` on every call | API key is wrong, revoked, or copied with surrounding whitespace. |
| `403 Forbidden` on specific tools (assets, problems, catalog) | Your account's **role or plan tier** doesn't grant access to that resource β not a config bug. |
| `404 Not Found` | Wrong domain, or the record ID doesn't exist. |
| `429 Too Many Requests` | Rate limit hit. Limits are per-plan; the server surfaces the error but does **not** auto-retry. |
---
## π Project Structure
```
FS_MCP_thin/
βββ src/
β βββ index.js # MCP server entry point β registers all tools,
β β # resources, and prompts; wires responses through shape.js
β βββ freshservice.js # FreshserviceClient β thin axios wrapper over the
β β # Freshservice v2 REST API (auth, requests, error mapping)
β βββ shape.js # Token-economy engine β projects/strips/compacts responses
β βββ shape.config.js # Declarative per-domain field/shaping rules (edit this to tune output)
β βββ index.test.js # Tests for tool registration & handler behavior
β βββ freshservice.test.js # Tests for the API client
β βββ shape.test.js # Tests for the shaping engine + config
βββ eslint.config.js # ESLint flat config (Node + Jest, recommended rules)
βββ tsconfig.json # TypeScript config for `checkJs` type-checking
βββ .env.example # Template for required environment variables
βββ .gitignore # Ignores node_modules/ and .env (credentials)
βββ package.json # Dependencies, scripts, bin entry
βββ README.md
```
**Data flow:** `index.js` (MCP request) β `freshservice.js` (API call) β `shape.js` + `shape.config.js` (trim response) β back to the AI assistant.
**Where to make changes:**
- Add/modify a **tool, resource, or prompt** β `src/index.js`
- Add/modify a **Freshservice API call** β `src/freshservice.js`
- Change **what fields the model sees** β `src/shape.config.js` (no code changes needed)
---
## π§° Available Tools
### π« Tickets & Conversations
| Tool | Purpose |
|------|---------|
| `search_tickets` | Flexible search using `status`, `priority`, or `subject`. |
| `list_open_tickets` | List open tickets (status:2) with pagination. |
| `get_ticket` | Curated detail view of a ticket (key fields + HTML-stripped body). |
| `get_ticket_conversations` | Complete thread of public and private messages. |
| `get_ticket_notes` | Isolates private internal notes for audit/context. |
| `get_service_request_items`| Views quantity and custom fields for SR items. |
### π Knowledge Base & Problems
| Tool | Purpose |
|------|---------|
| `search_kb` | Find articles by keywords. |
| `get_kb_article` | Retrieve the full article body (Markdown/HTML). |
| `list_problems` | Get a list of recent Problem records. |
| `get_problem` | Detailed view of a root cause investigation. |
### π¦ Assets & Software
| Tool | Purpose |
|------|---------|
| `list_assets` | Browse the CI (Configuration Item) inventory. |
| `get_asset` | Deep dive into a specific piece of hardware. |
| `list_software` | See software installations and licenses. |
### ?? Service Catalog & Responses
| Tool | Purpose |
|------|---------|
| `list_service_catalog_items` | List items available in the service catalog. |
| `get_service_catalog_item` | Retrieve details of a specific service catalog item. |
| `list_canned_responses` | List canned response templates. |
### π€ Identity & History
| Tool | Purpose |
|------|---------|
| `search_requesters` | Find users by name or email. |
| `get_requester` | Detailed profile of a user. |
| `get_requester_tickets` | Historical view of all tickets for a specific user. |
| `get_requester_assets` | List of hardware currently assigned to a user. |
| `search_agents` | Find support staff members. |
| `get_agent` / `get_agent_group`| Inspect specific agents or support teams. |
---
## π Available Resources (Standing Context)
Resources can be attached to a conversation to provide the AI with constant reference data without needing a tool call.
| URI | Content |
|-----|---------|
| `freshservice://departments` | Your organizational hierarchy. |
| `freshservice://ticket-fields` | The schema for custom ticket fields and their values. |
| `freshservice://agent-groups` | Valid routing destinations (support teams). |
| `freshservice://sla-policies` | Your response and resolution time targets. |
| `freshservice://business-hours` | Operation schedules for various teams. |
---
## πͺ Available Prompts (Workflows)
| Prompt | Scenario |
|--------|----------|
| `triage_ticket` | **Input:** Ticket ID. **Output:** A comprehensive assessment including history, SR items, and suggested KB articles. |
| `summarize_ticket` | **Input:** Ticket ID. **Output:** A concise executive summary perfect for handoffs or manager reviews. |
| `draft_reply` | **Input:** Ticket ID. **Output:** A professional draft reply incorporating KB solutions and empathy. |
| `sla_check` | **Input:** Ticket ID. **Output:** An analysis of breach risk based on business hours and policies. |
---
## π Search Syntax Reference
The `search_tickets` tool supports the [Freshservice Filter API](https://api.freshservice.com/v2/#filter_tickets) syntax:
- **Status Codes**: `2`: Open, `3`: Pending, `4`: Resolved, `5`: Closed.
- **Priority Codes**: `1`: Low, `2`: Medium, `3`: High, `4`: Urgent.
**Examples for AI:**
- `"status:2 AND priority:4"` β Show me urgent open tickets.
- `"created_at:>'2024-01-01'"` β Show me tickets from this year.
- `"type:'Incident'"` β Filter for incidents only.
---
## πͺΆ Response Shaping (Token Economy)
Freshservice API responses are verbose β a single ticket carries 40-60 fields (many `null`), HTML bodies, and internal metadata the model never needs. To avoid bloating the AI's context window, **tool and resource responses pass through a shaping layer** before they're returned. Most are field-projected and HTML-stripped; a few (e.g. service-request items, whose `custom_fields` hold the request's actual answers) are compacted but deliberately kept whole.
**What it does:**
- **Compact JSON** β no pretty-print whitespace (~15-30% saving on every call).
- **Field projection** β list/search tools return lean summary rows (e.g. a ticket β `id, subject, status, priority, requester_id, group_id, timestamps`); detail tools (`get_ticket`, `get_asset`, β¦) return a curated, richer set.
- **HTML stripping** β descriptions, conversation bodies, and articles are converted from HTML to plain text (`description` β `description_text`), then truncated if very long.
- **Noise removal** β `null`/empty fields and empty custom-field entries are dropped; attachments are reduced to filenames.
- **Defensive fallback** β if a response doesn't match the expected shape, it passes through compacted but otherwise untouched (never mangled).
**Summary-then-drill-down:** list tools intentionally return *summaries*. When the model needs everything about one record, it calls the matching `get_*` tool. This keeps broad queries cheap.
### Tuning what the model sees
All shaping rules are **declarative** and live in one file β [`src/shape.config.js`](src/shape.config.js). The engine ([`src/shape.js`](src/shape.js)) contains no per-domain logic, so adjusting output requires **no code changes**.
Each domain defines a `list` (summary) and/or `detail` profile:
```js
tickets: {
container: 'tickets', // array key for list responses
item: 'ticket', // object key for single-record responses
list: { fields: ['id', 'subject', 'status', 'priority', /* β¦ */] },
detail: {
fields: ['id', 'subject', 'status', /* β¦moreβ¦ */],
text: [{ from: ['description_text', 'description'], to: 'description_text', max: 2000 }],
objects: ['custom_fields'], // kept, but null/empty entries dropped
},
}
```
| Profile key | Effect |
|-------------|--------|
| `fields` | Whitelist of keys to keep (present + non-empty only). |
| `text` | Take the first available `from` field, strip HTML, truncate to `max`, store under `to`. |
| `objects` | Custom-field-style objects to keep but compact (drop `null`/empty). |
| `names` | Reduce an array of `{ name, β¦ }` objects to an array of names (e.g. attachments). |
To add a field to a tool's output, append it to that domain's `fields` array. To expose more/less body text, change `max`. That's the whole workflow.
> **Note:** the field names in `shape.config.js` are mapped to the Freshservice v2 API. If your instance relies on a field that isn't surfaced, add it to the relevant `fields` array β it's a one-line change.
---
## β‘ Performance & Reliability
The Freshservice client ([`src/freshservice.js`](src/freshservice.js)) includes several measures to stay fast and resilient under real-world conditions:
- **Request timeout** β every request times out after **10s** rather than hanging indefinitely on a stalled connection.
- **Automatic retry with backoff** β transient failures (`429` rate limits, `5xx`, network/timeout errors) are retried up to **3 times** with exponential backoff. A `429`'s `Retry-After` header is honored when present. Non-transient errors (`401`, `403`, `404`) are **not** retried.
- **Caching of static data** β near-static reference endpoints (departments, ticket/requester fields, SLA policies, business hours, agent groups) are cached in memory for **10 minutes**, so the AI's repeated context-gathering doesn't re-hit the API. Failed lookups are never cached.
- **Concurrent pagination** β tools that aggregate every page (`list_canned_responses`, `list_service_catalog_items`) fetch page 1, then pull the remaining pages in **bounded-concurrency batches** (`per_page=100`, 5 pages at a time) instead of one slow page at a time β turning ~10 sequential round-trips into 2-3 parallel batches.
> These are tuned by constants at the top of `src/freshservice.js` (`STATIC_TTL_MS`, `REQUEST_TIMEOUT_MS`, `MAX_RETRIES`).
---
## π§ͺ Development
| Command | What it does |
|---------|--------------|
| `npm start` | Run the MCP server on stdio. |
| `npm test` | Run the Jest test suite. |
| `npm run lint` | Lint with ESLint (flat config, `eslint:recommended` + Node/Jest globals). |
| `npm run lint:fix` | Lint and auto-fix where possible. |
| `npm run typecheck` | Type-check the JS via TypeScript in `checkJs` mode (no emit). |
| `npm run check` | Run lint + typecheck + tests together (use before committing). |
**Type-checking** uses TypeScript's `checkJs` against the application code (`tsconfig.json`). It catches real errors β e.g. wrong API shapes or SDK misuse β without requiring the code to be migrated to TypeScript. Test files are excluded from `tsc` (their Jest mocks are validated by the test runner, not the type-checker). Implicit-`any` is allowed, so the checker surfaces genuine type errors rather than annotation noise.
---
## π Security & Privacy
1. **Read-Only**: This server has NO capability to create, edit, or delete data. It is inherently safe.
2. **Local Only**: Credentials NEVER leave your machine.
3. **Auditability**: All API requests are standard HTTPS and appear in your Freshservice API logs.
## π License
ISC License. Build with freedom.
TDQS
Scored across 23 tools
Most tools have clear resource-action pairs. The only overlap is search_tickets vs list_open_tickets, but descriptions clarify that the latter is a shortcut for a specific filter. No other ambiguous pairs.
All tools follow a consistent snake_case verb_noun pattern using search/list/get, making it predictable and easy to understand.
23 tools is on the heavy side (16-25 range). The broad Freshservice domain partly justifies it, but some tools are redundant (e.g., list_open_tickets as a special case of search_tickets), so it feels slightly bloated.
The server is entirely read-only for core entities like tickets, problems, and assets, with no create/update/delete operations. This is a significant gap for an ITSM tool, limiting agents to lookups only.