Skip to main content
Glama

mcp-freshservice

A Model Context Protocol server for the Freshservice REST API v2. It lets an AI assistant work with tickets, changes, problems, releases, assets, requesters, agents, groups, the service catalog and the knowledge base.

Forked from effytech/freshservice_mcp and reworked for v2 (see CHANGELOG.md for breaking changes).

Quick start

You need a Freshservice API key (Profile Settings → API key) and your domain.

uvx mcp-freshservice --version

Claude Desktop / any MCP client (claude_desktop_config.json):

{
  "mcpServers": {
    "freshservice": {
      "command": "uvx",
      "args": ["mcp-freshservice"],
      "env": {
        "FRESHSERVICE_DOMAIN": "yourcompany.freshservice.com",
        "FRESHSERVICE_APIKEY": "<your-api-key>"
      }
    }
  }
}

Claude Code: claude mcp add freshservice -e FRESHSERVICE_DOMAIN=yourcompany.freshservice.com -e FRESHSERVICE_APIKEY=... -- uvx mcp-freshservice

Related MCP server: Freshservice MCP Server

Configuration

Variable

Required

Description

FRESHSERVICE_DOMAIN

yes

yourcompany.freshservice.com (a bare yourcompany also works)

FRESHSERVICE_APIKEY

yes

API key of the agent the server acts as. Permissions follow that agent

FRESHSERVICE_WORKSPACE_ID

no

Default workspace_id for calls on multi-workspace accounts. A tool's own workspace_id argument wins

FRESHSERVICE_READ_ONLY

no

1 exposes only read-only tools

FRESHSERVICE_TOOLSETS

no

Which toolsets to expose: names, default (the core set, used when unset) and/or all. See Choosing tools

FRESHSERVICE_TIMEOUT

no

HTTP timeout in seconds (default 30)

FRESHSERVICE_ATTACHMENT_DIRS

no

Folder(s) the server may read attachments from (; between several on Windows, : elsewhere). Attachments are off until set

FRESHSERVICE_MAX_RETRIES

no

Retries after a rate limit (429) or a transient 502/503/504 on reads (default 3, 0 disables)

MCP_TRANSPORT, MCP_HOST, MCP_PORT

no

stdio (default), streamable-http or sse

The same switches exist as flags: --read-only, --toolsets tickets,solutions, --transport.

Choosing tools

The server can expose 204 tools (full list in TOOLS.md), but some clients cap the tools per agent (for example 128) and models choose better from a shorter list. So by default only a 76-tool core set is enabled; everything else is opt-in by toolset.

uvx mcp-freshservice --list-toolsets                         # see what's available
uvx mcp-freshservice --toolsets default,changes,assets       # core set + changes + assets
uvx mcp-freshservice --toolsets all                          # everything
uvx mcp-freshservice --toolsets tickets,solutions --read-only

The same works with FRESHSERVICE_TOOLSETS=default,changes in the client's env. A list of plain names is exact (only those toolsets); add default to keep the core set.

Toolset

Default

Tools

Covers

tickets

yes

17

tickets, replies and notes (with attachments), conversations, child tickets, requested items

ticket_time_entries

yes

5

time logged on tickets

ticket_tasks

5

ticket tasks

ticket_approvals

9

service-request approval groups and approvals

requesters

yes

9

requesters, fields, reactivate, convert to agent

agents

yes

9

agents, fields, roles

groups

yes

13

agent groups, requester groups and membership

solutions

yes

18

knowledge-base categories, folders, articles, search

service_catalog

yes

5

browse/search items, place service requests

changes

9

change requests and planning fields

change_notes, change_tasks, change_time_entries

5 each

notes, tasks, time entries on changes

change_approvals

9

change approval groups, chains, approvals

problems, releases

8, 7

problem and release records

problem_*, release_* (notes, tasks, time_entries)

5 each

notes, tasks, time entries

assets

16

assets, asset types, canned responses

organisation

20

workspaces, departments, locations, vendors, products

Every tool is annotated read-only / destructive so clients can auto-approve safely.

Conventions

  • Single records come back as Freshservice returns them: {"ticket": {...}}.

  • Lists return one page plus pagination: {page, per_page, next_page, has_more}. per_page is at most 100.

  • Errors are real MCP tool errors (isError: true) carrying Freshservice's message and field errors, with hints for 401/403/404/429.

  • Filtering: see Query syntax.

  • Numeric codes: ticket status 2 Open, 3 Pending, 4 Resolved, 5 Closed; priority 1 Low … 4 Urgent; source 1 Email, 2 Portal, 3 Phone, 4 Chat … (see list_ticket_sources for the full list).

  • Attachments: send_ticket_reply, create_ticket_note, create_ticket and update_ticket_conversation accept local file paths (max 15 files / 40 MB), but only from folders listed in FRESHSERVICE_ATTACHMENT_DIRS. Until you set it, attachments are refused, so a prompt-injected path can't upload an arbitrary local file.

Example prompts: "Show ticket 12031 with its conversations and resolution notes", "How much time was logged on ticket 8?", "List open P1 incidents unassigned for a day", "Search the KB for printer setup".

Query syntax

filter_tickets, filter_changes, filter_requesters, filter_agents and the query / filter / search arguments of the list tools use Freshservice's query language. Freshservice requires the whole query to be enclosed in a pair of double quotes (without them the API answers 500), so the server adds them for you; you can pass status:3 or "status:3" and get the same request.

Rule

Example

Fields are snake_case and case-sensitive (custom fields by API name)

status:3

Numbers bare, strings and dates in single quotes

tag:'vip', planned_start_date:>'2025-07-14'

AND, OR and parentheses

(group_id:12 OR group_id:15) AND status:3

:> / :< are inclusive (>= and <=), confirmed on a live account

status:<5 means status 5 or lower

null finds empty values

agent_id:null

Starts-with (requesters and agents only)

~[first_name|last_name]:'ad'

Max 512 characters (checked locally); filter results are 30 per page

Changes: "status:3" awaiting approval, "approval_status:1" approved, "approval_status:1 AND status:<5" approved and not closed (status 6 is Closed, and :< includes its boundary, so <6 would include closed changes), "status:3 AND priority:1". approval_status is 0 Requested, 1 Approved, 2 Rejected, 3 Not requested. A change view and query cannot be combined.

Not every endpoint supports every field: departments and locations filter on name only; asset search takes one field (name, asset_tag, serial_number, ...); releases only have predefined filter_name values. The per-tool descriptions list the supported fields. Filtered requester/agent pages are capped at 40; changes to users can take a few minutes to be indexed.

Rate limits

Freshservice limits requests per minute. When it answers HTTP 429 the server waits for the Retry-After time (up to 20 seconds) and tries again, up to FRESHSERVICE_MAX_RETRIES times; transient 502/503/504 errors on reads are retried the same way. If the wait would be longer than 20 seconds, or retries run out, the tool reports the rate-limit error instead. Writes are only ever retried after a 429, because a request that was rate-limited was not processed.

Running remotely

MCP_TRANSPORT=streamable-http MCP_PORT=8000 uvx mcp-freshservice

The server is stateless over streamable HTTP and serves /mcp. It has no authentication of its own: run it behind a gateway/reverse proxy that authenticates callers, because everyone who can reach it acts with the configured API key.

Development

uv sync
uv run pytest        # offline, mocks the Freshservice API
uv run ruff check .

To check the server against a real Freshservice account (read-only by default; never prints your key):

uv run python scripts/live_check.py            # the core tools, read-only
uv run python scripts/live_check.py --sweep    # every read-only tool of every toolset

The sweep calls each read-only tool once, using real IDs it finds along the way. A wrong endpoint shows up as a failure; an empty list or a 403 (module not on your plan, or no permission) is a skip.

When you point an MCP client at a local checkout with uvx --from <folder> mcp-freshservice, uvx reuses a cached build and will not notice your code changes (--refresh does not help). Add --no-cache to the uvx arguments while developing, e.g. "args": ["--no-cache", "--from", "G:/path/to/mcp-freshservice", "mcp-freshservice"].

Tests call tools through FastMCP's real call_tool path against a mocked API, so output-schema problems (like the old get_ticket_by_id failure) are caught without credentials. Each tool module lives in src/mcp_freshservice/tools/; add tools with @ts.tool(title, kind) and they are registered, filtered and annotated automatically.

Releasing

  1. Bump version in pyproject.toml and add a ## <version> section to CHANGELOG.md.

  2. Merge to main, then create a GitHub release tagged v<version>.

  3. The publish workflow first checks that the tag, the package version and the changelog agree, then runs lint and the tests, and only then uploads to PyPI (after your approval on the pypi environment).

python scripts/check_release.py v<version> runs the first check locally. PyPI never accepts a version twice, so the check exists to fail before anything is uploaded.

License

MIT, see LICENSE.

Available Tools

76 tools
add_requester_to_groupAdd requester to groupB
Idempotent

Add a requester to a manual requester group.

Inputs: requester_id (required), requester_group_id (required). Returns: object

convert_requester_to_agentConvert requester to agentC
Idempotent

Convert a requester into an occasional agent.

Inputs: requester_id (required). Returns: object

create_agentCreate agentB

Create an agent. Assign at least one role so the agent can sign in.

Inputs: email (required), first_name (required), roles, language, +11 more. Returns: object

create_child_ticketCreate child ticketC

Create a child ticket under an existing parent ticket.

Inputs: subject (required), ticket_id (required), description (required), email, +7 more. Returns: object

create_groupCreate agent groupC

Create an agent group.

Inputs: name (required), members, agent_ids, group_type, +6 more. Returns: object

create_requesterCreate requesterB

Create a requester. One of primary_email, work_phone_number or mobile_phone_number is required.

Inputs: first_name (required), address, language, job_title, +13 more. Returns: object

create_requester_groupCreate requester groupC

Create a requester group.

Inputs: name (required), type, conditions, description. Returns: object

create_service_requestCreate service requestC

Place a service request for a catalog item.

Inputs: email (required), display_id (required), quantity, child_items, +2 more. Returns: object

create_solution_articleCreate solution articleB

Create a knowledge-base article (a draft unless status is 2).

Inputs: title (required), folder_id (required), description (required), tags, +4 more. Returns: object

create_solution_categoryCreate solution categoryC

Create a knowledge-base category.

Inputs: name (required), description, workspace_id. Returns: object

create_solution_folderCreate solution folderC

Create a knowledge-base folder.

Inputs: name (required), category_id (required), group_ids, visibility, +4 more. Returns: object

create_ticketCreate ticketA

Create an incident or service-request ticket. Some ticket forms make fields such as group_id, responder_id, category or custom fields mandatory; check get_ticket_fields when the API reports missing_field.

Inputs: subject (required), description (required), name, tags, +22 more. Returns: object

create_ticket_noteAdd ticket noteB

Add a note to a ticket (private by default).

Inputs: body (required), ticket_id (required), private, user_id, +3 more. Returns: object

create_ticket_time_entryLog ticket timeC

Log time against a ticket.

Inputs: agent_id (required), ticket_id (required), note, task_id, +5 more. Returns: object

deactivate_agentDeactivate agentA
DestructiveIdempotent

Deactivate an agent (they become a requester; reversible with reactivate_agent).

Inputs: agent_id (required). Returns: object

deactivate_requesterDeactivate requesterA
DestructiveIdempotent

Deactivate a requester (reversible with reactivate_requester).

Inputs: requester_id (required). Returns: object

delete_groupDelete agent groupC
DestructiveIdempotent

Delete an agent group.

Inputs: group_id (required). Returns: object

delete_requester_groupDelete requester groupC
DestructiveIdempotent

Delete a requester group.

Inputs: requester_group_id (required). Returns: object

delete_solution_articleDelete solution articleA
DestructiveIdempotent

Move an article to trash.

Inputs: article_id (required). Returns: object

delete_solution_categoryDelete solution categoryA
DestructiveIdempotent

Move a category (and its folders and articles) to trash.

Inputs: category_id (required). Returns: object

delete_solution_folderDelete solution folderA
DestructiveIdempotent

Move a folder (and its articles) to trash.

Inputs: folder_id (required). Returns: object

delete_ticketDelete ticketA
DestructiveIdempotent

Move a ticket to trash. It can be brought back with restore_ticket.

Inputs: ticket_id (required). Returns: object

delete_ticket_conversationDelete ticket conversationB
DestructiveIdempotent

Delete a note or reply from a ticket.

Inputs: conversation_id (required). Returns: object

delete_ticket_time_entryDelete ticket time entryC
DestructiveIdempotent

Delete a time entry on a ticket.

Inputs: ticket_id (required), time_entry_id (required). Returns: object

filter_agentsFilter agentsA
Read-onlyIdempotent

Search agents (30 per page, newest first; results cannot be sorted).

Fields: first_name, last_name, name, job_title, email, work_phone_number, mobile_phone_number, department_id, reporting_manager_id, time_zone, language, location_id, created_at, updated_at, external_id and custom fields. The ~ operator does "starts with" on first_name, last_name, name, email, mobile_phone_number and work_phone_number. Changes can take a few minutes to be indexed.

Inputs: query (required), page. Returns: object

filter_requestersFilter requestersA
Read-onlyIdempotent

Search requesters (30 per page, newest first; results cannot be sorted).

Fields: first_name, last_name, name, job_title, primary_email, work_phone_number, mobile_phone_number, department_id, reporting_manager_id, time_zone, language, location_id, created_at, updated_at, external_id and custom fields. The ~ operator does "starts with" on first_name, last_name, name, primary_email, mobile_phone_number and work_phone_number. Changes can take a few minutes to be indexed.

Inputs: query (required), page, include_agents. Returns: object

filter_ticketsFilter ticketsA
Read-onlyIdempotent

Search tickets with the Freshservice query language (30 results per page).

Filterable fields: requester_id, email, agent_id, group_id, priority, status, impact, urgency, tag, due_by and fr_due_by (YYYY-MM-DD), created_at and updated_at (YYYY-MM-DD or UTC datetime), workspace_id (0 = all workspaces) and custom fields by API name (see get_ticket_fields). Field names are case-sensitive snake_case. Examples: "status:2 AND priority:4", "agent_id:null AND created_at:>'2025-01-01'", "(group_id:12 OR group_id:15) AND status:3", "tag:'vip'". :> and :< are documented as inclusive (>= and <=). Values must be valid ones (status 2-5, priority 1-4, ...) or the API answers 400. The response includes "total" with the overall match count.

Inputs: query (required), page, workspace_id. Returns: object

get_agentGet agentB
Read-onlyIdempotent

Get an agent by ID, including roles and group memberships.

Inputs: agent_id (required). Returns: object

get_agent_fieldsList agent fieldsB
Read-onlyIdempotent

List agent fields, including custom fields.

Inputs: no named parameters. Returns: object

get_agent_groupGet agent groupA
Read-onlyIdempotent

Get an agent group by ID, including its members.

Inputs: group_id (required). Returns: object

get_all_agent_groupsList agent groupsC
Read-onlyIdempotent

List agent groups.

Inputs: page, per_page. Returns: object

get_all_agentsList agentsC
Read-onlyIdempotent

List agents.

Inputs: page, email, state, active, +1 more. Returns: object

get_all_requester_groupsList requester groupsC
Read-onlyIdempotent

List requester groups.

Inputs: page, per_page. Returns: object

get_all_requestersList requestersB
Read-onlyIdempotent

List requesters, optionally looked up by email or phone.

Inputs: page, email, active, per_page, +3 more. Returns: object

get_all_solution_categoryList solution categoriesC
Read-onlyIdempotent

List knowledge-base categories.

Inputs: page, per_page, workspace_id. Returns: object

get_list_of_solution_articleList solution articlesA
Read-onlyIdempotent

List the articles in a folder: metadata plus a short plain-text snippet, not the full body.

Use get_solution_article with an article id to read the whole article.

Inputs: folder_id (required), page, filter, per_page, +1 more. Returns: object

get_list_of_solution_folderList solution foldersC
Read-onlyIdempotent

List the folders in a category.

Inputs: category_id (required), page, filter, per_page. Returns: object

get_requested_itemsGet requested itemsA
Read-onlyIdempotent

List the service catalog items requested on a service-request ticket.

Inputs: ticket_id (required). Returns: object

get_requesterGet requesterC
Read-onlyIdempotent

Get a requester by ID.

Inputs: requester_id (required). Returns: object

get_requester_groupGet requester groupB
Read-onlyIdempotent

Get a requester group by ID.

Inputs: requester_group_id (required). Returns: object

get_service_itemGet service itemA
Read-onlyIdempotent

Get a service item, including its form fields (custom_fields names to use when placing a request).

Inputs: display_id (required). Returns: object

get_solution_articleGet solution articleA
Read-onlyIdempotent

Read a knowledge-base article by ID: the full HTML in description and plain text in description_text.

Use the id from search_solution_articles or get_list_of_solution_article.

Inputs: article_id (required). Returns: object

get_solution_categoryGet solution categoryB
Read-onlyIdempotent

Get a knowledge-base category by ID.

Inputs: category_id (required). Returns: object

get_solution_folderGet solution folderC
Read-onlyIdempotent

Get a knowledge-base folder by ID.

Inputs: folder_id (required). Returns: object

get_ticket_by_idGet ticketA
Read-onlyIdempotent

Get a ticket by ID. Returns {"ticket": {...}} with all standard fields, custom_fields, planned dates, SLA flags (is_escalated, fr_escalated), attachments and, when available, the resolution notes.

Inputs: ticket_id (required), include. Returns: object

get_ticket_fieldsGet ticket form fieldsB
Read-onlyIdempotent

List ticket form fields, including custom fields, their API names and allowed choices.

Inputs: workspace_id. Returns: object

get_ticketsList ticketsA
Read-onlyIdempotent

List tickets (newest first). Without a filter or updated_since, only the last 30 days are returned.

For arbitrary conditions (status, priority, agent, group, dates, custom fields) use filter_tickets.

Inputs: page, type, email, filter, +6 more. Returns: object

list_all_requester_fieldsList requester fieldsA
Read-onlyIdempotent

List requester fields, including custom fields and their API names.

Inputs: no named parameters. Returns: object

list_all_ticket_conversationList ticket conversationsA
Read-onlyIdempotent

List the replies and notes on a ticket, oldest first. Each has body/body_text, private, incoming, source, user_id, to/cc/bcc emails and attachments.

Inputs: ticket_id (required), page, per_page. Returns: object

list_requester_group_membersList requester group membersC
Read-onlyIdempotent

List the requesters in a requester group.

Inputs: requester_group_id (required), page, per_page. Returns: object

list_rolesList agent rolesA
Read-onlyIdempotent

List agent roles (needed for create_agent's roles).

Inputs: page, per_page. Returns: object

list_service_catalog_categoriesList service catalog categoriesC
Read-onlyIdempotent

List service catalog categories.

Inputs: workspace_id. Returns: object

list_service_itemsList service itemsB
Read-onlyIdempotent

List one page of service catalog items.

Inputs: page, per_page, category_id, workspace_id. Returns: object

list_solution_subfoldersList solution sub-foldersC
Read-onlyIdempotent

List the sub-folders of a folder.

Inputs: folder_id (required), page, per_page. Returns: object

list_ticket_activitiesList ticket activitiesA
Read-onlyIdempotent

List the audit trail of a ticket (field changes, assignments, status changes).

Inputs: ticket_id (required). Returns: object

list_ticket_sourcesList ticket sourcesA
Read-onlyIdempotent

List ticket sources (channels), built-in and custom, with the numeric IDs create_ticket expects.

Returns {"sources": [{"id": 1, "value": "Email"}, ...]}. Freshservice has no endpoint that lists sources (/ticket_fields/sources only creates one), so they are read from the ticket form's "source" field.

Inputs: workspace_id. Returns: object

list_ticket_time_entriesList ticket time entriesB
Read-onlyIdempotent

List time entries (time tracked by agents) on a ticket.

Inputs: ticket_id (required), page, per_page. Returns: object

publish_solution_articlePublish solution articleB
Idempotent

Publish a draft article (sets status to 2).

Inputs: article_id (required). Returns: object

reactivate_agentReactivate agentB
Idempotent

Reactivate a deactivated agent.

Inputs: agent_id (required). Returns: object

reactivate_requesterReactivate requesterA
Idempotent

Reactivate a deactivated requester.

Inputs: requester_id (required). Returns: object

remove_requester_from_groupRemove requester from groupB
Idempotent

Remove a requester from a manual requester group.

Inputs: requester_id (required), requester_group_id (required). Returns: object

restore_ticketRestore ticketA
Idempotent

Restore a deleted (trashed) ticket.

Inputs: ticket_id (required). Returns: object

search_service_itemsSearch service itemsC
Read-onlyIdempotent

Search service catalog items by keyword.

Inputs: search_term (required), page, per_page, user_email, +1 more. Returns: object

search_solution_articlesSearch solution articlesA
Read-onlyIdempotent

Full-text search across published knowledge-base articles.

Each result has id, title, folder_id/category_id, status and a short plain-text snippet (the first ~300 characters). Freshservice returns every article's full body here, which makes 30 results very large, so they are trimmed unless include_body is true. To read an article, call get_solution_article with its id (full HTML in description, plain text in description_text).

Inputs: search_term (required), page, per_page, user_email, +1 more. Returns: object

send_ticket_replyReply to ticketB

Send a public reply to the requester on a ticket.

Inputs: body (required), ticket_id (required), user_id, cc_emails, +3 more. Returns: object

update_agentUpdate agentB
Idempotent

Update an agent. Only the supplied fields change.

Inputs: agent_id (required), agent_fields (required). Returns: object

update_groupUpdate agent groupA
Idempotent

Update an agent group. Only the supplied fields change.

Inputs: group_id (required), group_fields (required). Returns: object

update_requesterUpdate requesterB
Idempotent

Update a requester. Only the supplied fields change.

Inputs: requester_id (required), requester_fields (required). Returns: object

update_requester_groupUpdate requester groupC
Idempotent

Update a requester group.

Inputs: requester_group_id (required), name, conditions, description. Returns: object

update_solution_articleUpdate solution articleC
Idempotent

Update a knowledge-base article.

Inputs: article_id (required), tags, title, status, +5 more. Returns: object

update_solution_categoryUpdate solution categoryC
Idempotent

Update a knowledge-base category.

Inputs: category_id (required), name, description, default_category. Returns: object

update_solution_folderUpdate solution folderC
Idempotent

Update a knowledge-base folder.

Inputs: folder_id (required), name, group_ids, visibility, +3 more. Returns: object

update_ticketUpdate ticketB
Idempotent

Update a ticket. Only the supplied fields change.

Inputs: ticket_id (required), ticket_fields (required), attachments. Returns: object

update_ticket_conversationUpdate ticket conversationA
Idempotent

Edit a note or reply. Only notes and replies created through the API/agents can be edited.

Inputs: body (required), conversation_id (required), attachments. Returns: object

update_ticket_time_entryUpdate ticket time entryC
Idempotent

Update a time entry on a ticket. Time entry fields: time_spent ('hh:mm'), note, agent_id, billable, executed_at (ISO-8601), timer_running, task_id, custom_fields.

Inputs: ticket_id (required), time_entry_id (required), time_entry_fields (required). Returns: object

view_ticket_time_entryView ticket time entryB
Read-onlyIdempotent

Get one time entry on a ticket.

Inputs: ticket_id (required), time_entry_id (required). Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    C2.9/5.0

    Scored across 76 tools

    Disambiguation4/5

    Most tools are separated by resource and action (agent vs requester vs ticket vs solution), and descriptions clarify list/search/get variants. A few boundaries are fuzzy—e.g. create_group is an agent group while requester groups are explicitly prefixed, and solution folder/article listing uses several get/list patterns—but tools are not broadly indistinguishable.

    Naming Consistency3/5

    Names are all snake_case and generally verb_noun, but the verb vocabulary is inconsistent: get_all_agents vs list_roles vs filter_agents, get_list_of_solution_folder vs list_solution_subfolders, and singular get_all_solution_category. The pattern is readable but not predictable enough for a top score.

    Tool Count1/5

    76 tools is far beyond the 3–15 well-scoped range and exceeds the 50+ extreme-mismatch threshold. Even for a broad ITSM product, this surface should be split or namespaced into smaller MCP servers.

    Completeness4/5

    Core service-desk resources have strong lifecycle coverage: tickets (create/update/delete/restore, conversations, time entries), agents/requesters (create/update/deactivate/reactivate), groups (CRUD), and knowledge base (CRUD/search/publish). Gaps remain around admin CRUD for service catalog items and broader ITSM modules such as changes, problems, and assets, but the apparent service-desk scope is largely covered.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers