Skip to main content
Glama
cbreland-DLR

FS_MCP_thin

by cbreland-DLR
README.md
# FS_MCP_thin πŸš€

[![MCP](https://img.shields.io/badge/MCP-1.0.0-blue)](https://modelcontextprotocol.io/)
[![Freshservice](https://img.shields.io/badge/API-v2-green)](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

B3.2/5.0

Scored across 23 tools

Disambiguation4/5

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.

Naming Consistency5/5

All tools follow a consistent snake_case verb_noun pattern using search/list/get, making it predictable and easy to understand.

Tool Count3/5

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.

Completeness2/5

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.

Maintenance

ActivityStale
ResponsivenessNo issues