GLPI MCP服务器

为AI助手提供GLPI IT服务管理平台接入能力的MCP服务器,通过REST API提供工单、问题、变更、资产、用户、知识库等46个工具,适用于Claude Desktop、Cursor、Open WebUI等MCP客户端。

  • by GMS64260
  • TypeScript
  • 混合部署
  • 33 星标
  • 更新于
在 GitHub 查看 GMS64260/mcp-glpi

使用说明

mcp-glpi

A Model Context Protocol server for GLPI, the open-source IT service management suite. It gives an AI assistant (Claude Desktop, Claude Code, Cursor, Open WebUI, any MCP client) 46 tools over the GLPI REST API: tickets and their timeline, problems, changes, validations, assets and every other common itemtype, users and groups, the knowledge base, multi-criteria search and reporting. Outputs are compact JSON built for a model's context window, write tools are annotated and can be switched off with one variable, and the 50 tool names of v3 keep working as hidden aliases.

Version 4.0.0. See CHANGELOG.md for the release notes and Upgrading from 3.x below.

Install and configure

Requirements: Node.js 20 or later, a GLPI 10 or 11 instance with the REST API enabled (Setup > General > API), an API client (app token, optional) and a user API token (My settings > Remote access keys) or a login and password.

stdio: Claude Desktop, Claude Code, Cursor

stdio is the default transport: the client starts the server and talks to it over stdin/stdout. Claude Desktop (claude_desktop_config.json) and Cursor (~/.cursor/mcp.json or .cursor/mcp.json in a project) use the same shape:

{
  "mcpServers": {
    "glpi": {
      "command": "npx",
      "args": ["-y", "mcp-glpi"],
      "env": {
        "GLPI_URL": "https://glpi.example.com",
        "GLPI_APP_TOKEN": "your-app-token",
        "GLPI_USER_TOKEN": "your-user-token",
        "GLPI_READ_ONLY": "1"
      }
    }
  }
}

Drop GLPI_READ_ONLY to allow writes. Claude Code:

claude mcp add glpi \
  -e GLPI_URL=https://glpi.example.com \
  -e GLPI_APP_TOKEN=your-app-token \
  -e GLPI_USER_TOKEN=your-user-token \
  -- npx -y mcp-glpi

The server opens a GLPI session at startup (or on the first call if GLPI is unreachable then) and closes it when the client disconnects.

Streamable HTTP: Open WebUI and other web clients

MCP_TRANSPORT=http GLPI_URL=https://glpi.example.com GLPI_USER_TOKEN=... npx -y mcp-glpi
# same with flags
npx -y mcp-glpi --http --host 127.0.0.1 --port 3000 --path /mcp
MCP GLPI Server v4.0.0 listening on http://127.0.0.1:3000/mcp
  transport: streamable HTTP (stateless, JSON responses)
  auth: none — bind to loopback or set MCP_HTTP_AUTH_TOKEN before exposing this port
Flag Description
--http Shorthand for --transport http
--transport <mode> stdio (default) or http
--host <host> Bind address (default 127.0.0.1)
--port <port> Port; 0 picks a free port (default 3000)
--path <path> Endpoint path (default /mcp)
-h, --help Print usage

CLI flags win over the MCP_* variables. Endpoints: POST /mcp (JSON-RPC, one JSON response per request) and GET /health (liveness, no auth, no GLPI call). GET and DELETE /mcp answer 405: the server is stateless and never pushes messages. A POST must accept both application/json and text/event-stream (or */*), as MCP clients do:

curl -s http://127.0.0.1:3000/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Open WebUI (v0.6.31 or later): Admin Settings > External Tools > +, type MCP (Streamable HTTP) (not OpenAPI), URL http://<host>:3000/mcp (http://host.docker.internal:3000/mcp when Open WebUI runs in Docker and mcp-glpi on the host with Docker Desktop; on Linux, bind mcp-glpi to an address the container can reach). Auth: None, or Bearer with the value of MCP_HTTP_AUTH_TOKEN (Open WebUI rejects Bearer with an empty key). Then enable the tool per chat under + > Integrations > Tools.

Docker. No image is published; the npm package runs in any Node image. Inside a container the server must bind to 0.0.0.0, so set a token:

docker run -d --name mcp-glpi -p 127.0.0.1:3000:3000 \
  -e GLPI_URL=https://glpi.example.com -e GLPI_USER_TOKEN=... \
  -e MCP_HTTP_AUTH_TOKEN=change-me -e GLPI_READ_ONLY=1 \
  node:20-alpine npx -y mcp-glpi --http --host 0.0.0.0

See Security notes before exposing the port.

Environment variables

Variable Default Description
GLPI_URL required Base URL of GLPI, without /apirest.php
GLPI_APP_TOKEN none API client token (Setup > General > API), if the API client has one
GLPI_USER_TOKEN none User API token. Either this or GLPI_USERNAME + GLPI_PASSWORD is required
GLPI_USERNAME, GLPI_PASSWORD none Login and password (basic auth on initSession) when no user token is set
GLPI_TIMEOUT_MS 15000 Timeout of one GLPI call, body read included. Positive integer (0 is refused at startup)
GLPI_MAX_RETRIES 2 Retries per call (see Retries); 0 disables them
GLPI_MAX_CONCURRENCY 8 Maximum GLPI calls in flight for the whole process. Positive integer (0 is refused at startup)
GLPI_DEBUG off Any value: log retries and re-authentication to stderr, keep raw search options in memory
GLPI_TOOLSETS all Comma-separated toolsets to register on top of core, which is always registered (see Tool catalogue); unknown names are ignored with a warning
GLPI_READ_ONLY off 1/true/yes/on: register only the 21 read-only tools and the 2 session switches, which change the session context, not GLPI data (23)
GLPI_LEGACY_TOOLS off 1/true/yes/on: also list the 50 v3 aliases in tools/list (they are callable either way)
GLPI_UPLOAD_DIR none Directory glpi_upload_document may read file_path from. Unset: uploads by path are refused, content_base64 still works
MCP_TRANSPORT stdio stdio or http
MCP_HTTP_HOST 127.0.0.1 HTTP bind address
MCP_HTTP_PORT 3000 HTTP port; falls back to PORT
MCP_HTTP_PATH /mcp HTTP endpoint path
MCP_HTTP_AUTH_TOKEN none When set, every MCP request needs Authorization: Bearer <token> (401 otherwise)
MCP_HTTP_ALLOWED_HOSTS loopback names Comma-separated Host header allowlist (DNS-rebinding protection). Default when bound to loopback: localhost, 127.0.0.1, [::1], host.docker.internal; off when bound elsewhere

Retries

GET calls are retried after a network error, a timeout or a 5xx. POST, PUT and DELETE are retried only when the connection failed before the request was sent (ECONNREFUSED, ENOTFOUND, EAI_AGAIN), never after a timeout or a 5xx, because the write may have been applied. A 429 is retried for every method and honours Retry-After. Delays grow exponentially from 300 ms, are capped at 30 s and get ±20 % jitter. An expired session (401) is re-opened once and the call replayed.

Tool catalogue

46 tools in 9 toolsets: core, tickets, itil, assets, users, admin, session, kb, stats. core (glpi_list_items, glpi_get_item, glpi_search_v2, glpi_count, glpi_list_search_options, glpi_get_session_info) is always registered, whatever GLPI_TOOLSETS says: GLPI_TOOLSETS=tickets lists the 18 ticket tools and these 6. W marks write tools (removed by GLPI_READ_ONLY=1); the others carry readOnlyHint: true. The two session switches are kept in read-only mode and carry destructiveHint: false. Updates, status changes, assignments and deletes carry destructiveHint: true. Every tool rejects unknown arguments.

tickets (18)

Tool Main parameters What it does
glpi_search_tickets status, open_only, assigned_user_id, assigned_group_id, requester_user_id, observer_user_id, category_id, entity_id, priority, urgency, date_from, date_to, text_search, paging Find tickets; newest update first. The three *_user_id filters take a User id or "me"
glpi_get_ticket id, with_logs, verbose One ticket with labels, plain-text content and followup/task/solution counts
glpi_get_ticket_timeline id, itemtype, kinds Followups, tasks, solutions, validations, documents of a Ticket, Change or Problem, oldest first
glpi_list_overdue_tickets entity_id, start, limit Open tickets past time_to_resolve, oldest deadline first
glpi_get_ticket_satisfaction ticket_id Satisfaction survey (score, comment, dates)
glpi_create_ticket W name, content, type, urgency, impact, priority, category_id, entity_id, requester_user_id, requester_group_id, user_id_assign, group_id_assign, time_to_resolve Create a ticket (defaults: type 1 Incident, urgency 3)
glpi_update_ticket W id, name, content, status, urgency, impact, priority, itilcategories_id, type, time_to_resolve Change the given fields
glpi_set_ticket_status W id, status (1-6) Change only the status
glpi_delete_ticket W id, purge, confirm_id Move to the trash; purge: true + confirm_id = id deletes for good
glpi_add_followup W ticket_id, itemtype, content, is_private Comment on a Ticket, Change or Problem
glpi_add_task W ticket_id, itemtype, content, actiontime (s), state, is_private, users_id_tech, groups_id_tech, begin, end Work log / planned task
glpi_add_solution W ticket_id, itemtype, content, solutiontypes_id Add a solution; GLPI marks the item solved
glpi_assign_ticket W ticket_id, user_id, group_id, type (actor role, default 2) Add a user and/or group as requester, assignee or observer
glpi_link_tickets W ticket_id, other_ticket_id, relation Link two tickets (see Link relations)
glpi_add_ticket_validation W ticket_id, users_id_validate, comment_submission Ask a user to approve a ticket
glpi_set_validation_status W validation_id, status (2/3), comment_validation Approve or refuse a validation
glpi_upload_document W content_base64 + filename, or file_path; name, ticket_id Upload a Document (max 20 MB), optionally attached to a ticket
glpi_attach_document_to_ticket W ticket_id, document_id Link an existing Document to a ticket

"me" is the connected account: its id is read once from the GLPI session (glpiID) and cached for the life of the process. The actor filters use the standard search-option ids 4 (requester), 5 (technician) and 66 (observer): the three options share one uid and column, so unlike the other filters they cannot be resolved through listSearchOptions. On a GLPI where they were renumbered, filter with glpi_search_v2.

itemtype on the timeline and on followup/task/solution is Ticket (default), Change or Problem; ticket_id / id is then the id of that item. Problems have no validations.

itil (8)

Tool Main parameters What it does
glpi_list_problems paging, sort, order, is_deleted, verbose Problems, newest first, with labels
glpi_get_problem id, verbose One problem with counts
glpi_create_problem W name, content, urgency, impact, priority, category_id, entity_id Create a problem
glpi_update_problem W id, name, content, status, urgency, impact, priority, itilcategories_id Change the given fields
glpi_list_changes paging, sort, order, is_deleted, verbose Changes, newest first, with labels
glpi_get_change id, verbose One change with counts (validations included)
glpi_create_change W same as glpi_create_problem Create a change
glpi_update_change W same as glpi_update_problem Change the given fields

To filter problems or changes, use glpi_search_v2. Their followups, tasks and solutions go through the ticket tools with itemtype.

core (2) and assets (3): generic item tools

These work on 24 itemtypes: Computer, Software, NetworkEquipment, Printer, Monitor, Phone, Peripheral, Rack, Enclosure, PDU, KnowbaseItem, Contract, Supplier, Location, Entity, Project, Document, Group, ITILCategory, Ticket, Problem, Change, User, Profile.

Tool Main parameters What it does
glpi_list_items core itemtype, paging, sort, order, search_text, is_deleted, expand_dropdowns, fields, verbose Compact rows of one itemtype; search_text = {column: "substring"}, "^x$" for exact
glpi_get_item core itemtype, id, with, verbose One item; with embeds softwares, networkports, connections, disks, documents, infocoms, contracts, tickets, problems, changes, logs
glpi_create_item W itemtype, input Create from GLPI column values, e.g. {"name":"PC-42","locations_id":3}
glpi_update_item W itemtype, id, input Change the given columns; Computer and Project accept only their usual fields, Ticket / Problem / Change exactly those of glpi_update_ticket / glpi_update_problem / glpi_update_change
glpi_delete_item W itemtype, id, purge, confirm_id Trash, or purge with confirm_id = id

input never accepts password / password2 for User.

users (3), admin (2), session (2)

Tool Main parameters What it does
glpi_list_users paging, sort, order, active_only (default true), verbose Users
glpi_get_user id, verbose One user
glpi_find_user query, exact, limit (default 10, max 50) Match login, last name or first name; exact login first. exact: true returns that login or null
glpi_create_user W (admin) name (login), realname, firstname, email, phone, profiles_id, entities_id Create an account, without password; email becomes a GLPI user e-mail (_useremails)
glpi_add_user_to_group W (admin) user_id, group_id, is_manager Group membership
glpi_get_session_info core verbose me (connected user id and names), active profile and entity, available profiles and entities
glpi_set_active_entity session entity_id (id or "all"), is_recursive Switch the active entity of the shared session
glpi_set_active_profile session profile_id Switch the active profile of the shared session

core search (3), kb (2), stats (2)

Tool Main parameters What it does
glpi_search_v2 core itemtype, criteria, forcedisplay, paging, sort, order, fetch_all, max_rows, expand_dropdowns, is_deleted Search any itemtype; rows with readable keys
glpi_count core itemtype, criteria {itemtype, totalcount} without rows
glpi_list_search_options core itemtype, query Searchable fields (id, uid, column, datatype, searchtypes)
glpi_search_knowbase query, start, limit KB articles whose title contains query
glpi_create_knowbase_item W name, answer, is_faq, knowbaseitemcategories_id Create a KB article
glpi_tickets_stats_by dimension (status, category, technician, entity, month), date_from, date_to, entity_id Ticket counts per dimension and total; month covers the last 6 months by default, 36 at most
glpi_get_asset_stats none Counts of computers, monitors, printers, network equipment, phones, software

criteria is [{field, searchtype, value, link}]. field is a search-option id, uid, column or label (an unknown name is rejected with the 5 closest options); searchtype is contains, notcontains, equals, notequals, lessthan, morethan, under, notunder, empty or notempty; link (AND, OR, AND NOT, OR NOT) joins it to the previous criterion. For a status, use equals with a code, notold (open), old (solved or closed) or all: GLPI's status option is not ordered, so lessthan 5 also matches Solved.

{"itemtype": "Computer", "criteria": [{"field": "name", "searchtype": "contains", "value": "PC-"},
 {"link": "AND", "field": "Computer.Location.completename", "searchtype": "contains", "value": "Paris"}]}

Output conventions

  • Compact JSON: every result is one text block of unindented JSON. GLPI HATEOAS links are removed.
  • List envelope: list and search tools return {totalcount, start, count, next_start, items}. Default page 25 rows, limit up to 500. Pass start: next_start for the next page; next_start: null means last page. Search tools also take fetch_all: true (follows pages up to max_rows, default 1000). The legacy range: "0-49" is still accepted on list tools.
  • Readable search keys: search/ rows (glpi_search_tickets, glpi_search_v2, glpi_list_overdue_tickets, glpi_search_knowbase) use names instead of option ids. Ticket rows have name, status, priority, urgency, category, entity, requester_name, requester_group, technician_name, technician_group, observer_name, observer_group, date, date_mod, solvedate, closedate, time_to_resolve, content; Change and Problem rows category, entity, status, date, date_mod. Other options: own column -> column name, joined column -> last two uid segments (itilcategory_completename), clashes suffixed with the option id. expand_dropdowns: false returns ids instead of labels.
  • Labels: tickets, problems and changes carry status_label, urgency_label and priority_label next to the codes; validations carry status_label.
  • Text fields (content, answer, comment, comment_submission, comment_validation) are converted from GLPI's HTML (often entity-encoded twice) to plain text and cut at max_content_chars (default 2000, 0 = no cap) with a …[truncated, N chars] marker. raw_html: true returns the stored HTML uncut.
  • Compact projections: detail and list tools keep the useful fields of each itemtype (asset: name, serial, inventory number, state, location, users, groups, manufacturer, type/model...). fields: [...] on glpi_list_items picks the keys; verbose: true returns every GLPI field.
  • Writes return {"success": true, ...} with the new or changed id (id, followup_id, task_id, solution_id, validation_id, document_id, link_id...).
  • Dates are YYYY-MM-DD HH:MM:SS or YYYY-MM-DD in GLPI server time.

Errors

Two kinds of failure, so a model can tell what to fix:

  • Protocol errors (JSON-RPC), only when the call does not match the listed tool: arguments that fail the advertised schema (unknown key, wrong type, missing required argument) give InvalidParams, e.g. Invalid arguments for glpi_get_ticket: id: Expected number, received string; an unknown tool gives MethodNotFound; a disabled one InvalidRequest with the reason (Tool glpi_create_ticket is disabled: the server runs read-only (GLPI_READ_ONLY=1)).
  • isError results for everything else, with a text the model can act on. Argument problems found while running the tool start with Invalid arguments for <tool>: and say how to fix them: purge without confirm_id, file_path outside GLPI_UPLOAD_DIR, "me" that cannot be resolved, a bad link relation, a search field or sort that is not a search option of the itemtype, a field glpi_update_item does not allow, the removed include_deleted, nothing to update. GLPI and network failures give the GLPI call and a hint:
GLPI POST ITILFollowup -> 403 ERROR_RIGHT_MISSING: You don't have permission to perform this action.
Hint: the active GLPI profile lacks the right for this action (or the item is outside the active entities). Check glpi_get_session_info; glpi_set_active_profile / glpi_set_active_entity (session toolset, kept by GLPI_READ_ONLY) can switch.

Hints cover expired or missing sessions, missing rights, ERROR_GLPI_ADD / UPDATE / DELETE / PURGE, unknown items or itemtypes, bad request bodies, wrong app token, IP not allowed, timeouts (raise GLPI_TIMEOUT_MS) and an unreachable GLPI (check GLPI_URL).

Prompts and resources

Prompts spell out the tool sequence for common jobs. They only read and then propose write calls, which the model must confirm with the user. A prompt is registered only when every tool it names is listed: with GLPI_READ_ONLY=1 only daily_report and my_open_tickets remain, and GLPI_TOOLSETS can drop more. The server names the skipped prompts in one stderr line at startup.

Prompt Arguments Workflow
triage_ticket id Read the ticket and its timeline, classify it (type, category, urgency, impact), propose an assignee, a first response and the calls to apply them
daily_report entity_id, date (optional, default today) New, solved and closed counts, backlog, overdue tickets, top categories, 10-line summary
draft_solution id Read the ticket, search the knowledge base and similar solved tickets, draft a solution, propose glpi_add_solution
kb_from_ticket id Turn a solved ticket into a generic KB article, propose glpi_create_knowbase_item (or an update of an existing article)
my_open_tickets none Open tickets assigned to the connected account (assigned_user_id: "me"), overdue first, with the three to handle next

Resources (read-only, application/json, same conventions as the tools). glpi://tickets/* need the tickets toolset and glpi://problems/open / glpi://changes/pending the itil toolset; the others are always there:

URI Content
glpi://tickets/open Open tickets (status notold), 100 most recently updated
glpi://tickets/recent 20 most recently opened tickets
glpi://problems/open Problems with status 1, 2, 3, 4, 7, 8; 50 most recently updated
glpi://changes/pending Changes with status 1, 2, 3, 4, 7, 9, 10, 11, 12; 50 most recently updated
glpi://computers First 100 computers not in the trash
glpi://groups First 100 groups
glpi://categories First 100 ITIL categories
glpi://stats/tickets Ticket counts: total, new, processing, pending, solved, closed
glpi://stats/assets Asset counts per type
glpi://ticket/{id} Template: one ticket with labels and its last 10 timeline entries (800 chars each)
glpi://kb/{id} Template: one KB article, answer as plain text (10,000 chars max)
glpi://user/{id} Template: one user (login, names, phones, entity, profile, location, last login)

Reference

The server sends these tables once per session in its instructions, so parameter descriptions stay short.

Ticket, problem and change status

Ids are GLPI's CommonITILObject constants; they are not in workflow order.

Id Ticket Problem Change
1 New New New
2 Processing (assigned) Processing (assigned) Processing (assigned)
3 Processing (planned) Processing (planned) Processing (planned)
4 Pending Pending Pending
5 Solved Solved Applied (solved)
6 Closed Closed Closed
7 Accepted Accepted
8 Under observation Review (under observation)
9 Evaluation
10 Approval
11 Testing
12 Qualification
13 Refused
14 Cancelled

Change workflow order: 1, 9, 10, 7, 4, 11, 12, 5, 8, 6 (13 and 14 end it). Search values: notold = open (1-4 for tickets), old = 5-6, all.

Urgency, impact, priority

Code Urgency / impact Priority
1 Very low Very low
2 Low Low
3 Medium Medium
4 High High
5 Very high Very high
6 Major (set by hand)

GLPI computes the priority from urgency and impact when it is not given.

Other codes

Field Values
Ticket type 1 Incident, 2 Request
Actor role (glpi_assign_ticket type) 1 Requester, 2 Assigned, 3 Observer
Task state 0 Information, 1 To do, 2 Done
Validation status 1 Waiting, 2 Granted, 3 Refused
Timeline kinds followup, task, solution, validation, document

Link relations

glpi_link_tickets reads as "ticket_id relation other_ticket_id":

relation Meaning GLPI link
link (default) Related 1
duplicate ticket_id duplicates other_ticket_id 2
child_of ticket_id is a child of other_ticket_id 3 (SON_OF)
parent_of ticket_id is the parent of other_ticket_id 4 (PARENT_OF)

The v3 form parent_id + child_id (+ numeric link_type) is still accepted; there, 3 and 4 both mean "parent_id is the parent of child_id".

Upgrading from 3.x

The configuration is unchanged: same variables, same npx mcp-glpi. What changes is the tool list and the shape of the results. Node.js 20 or later is now required (v3: 18).

Renamed tools

v4 lists 46 tools instead of 87. The 50 removed names still work: they are hidden aliases that translate their arguments and call the v4 tool, so they return v4 output. GLPI_LEGACY_TOOLS=1 lists them again (without schemas). They disappear with their target when a toolset or read-only mode removes it. Each alias accepts every argument its v3 tool listed (a test calls each one with its full v3 argument set); include_deleted gets an isError result that explains its removal.

v3 tool v4 call
glpi_list_tickets glpi_search_tickets (range -> start/limit, expand_dropdowns dropped)
glpi_get_ticket_followups, _tasks, _solutions, _validations, _documents {ticket_id} glpi_get_ticket_timeline {id, kinds: ["followup" / "task" / "solution" / "validation" / "document"]}
glpi_get_change_tasks {change_id} glpi_get_ticket_timeline {itemtype: "Change", id, kinds: ["task"]}
glpi_get_problem_tasks {problem_id} glpi_get_ticket_timeline {itemtype: "Problem", id, kinds: ["task"]}
glpi_list_computers, _softwares, _network_equipments, _printers, _monitors, _phones glpi_list_items {itemtype: Computer / Software / NetworkEquipment / Printer / Monitor / Phone} (range -> start/limit)
glpi_get_computer, _software, _network_equipment, _printer, _monitor, _phone glpi_get_item {itemtype, id, with} (with_softwares, with_networkports, with_connections, with_documents -> with)
glpi_create_computer, glpi_create_software glpi_create_item {itemtype, input: <all arguments>} (booleans -> 0/1)
glpi_update_computer glpi_update_item {itemtype: "Computer", id, input: <other arguments>}
glpi_delete_computer {id, force} glpi_delete_item {itemtype: "Computer", id, purge: force, confirm_id: id} (v3 behaviour kept)
glpi_list_knowbase, glpi_get_knowbase_item glpi_list_items / glpi_get_item {itemtype: "KnowbaseItem"}
glpi_list_contracts, glpi_get_contract, glpi_create_contract glpi_list_items / glpi_get_item / glpi_create_item {itemtype: "Contract"}
glpi_list_suppliers, glpi_get_supplier, glpi_create_supplier same with itemtype: "Supplier"
glpi_list_locations, glpi_get_location, glpi_create_location same with itemtype: "Location"
glpi_list_projects, glpi_get_project, glpi_create_project, glpi_update_project glpi_list_items / glpi_get_item / glpi_create_item / glpi_update_item {itemtype: "Project"}
glpi_list_groups, glpi_get_group, glpi_create_group glpi_list_items / glpi_get_item / glpi_create_item {itemtype: "Group"}
glpi_list_categories glpi_list_items {itemtype: "ITILCategory"}
glpi_list_entities, glpi_get_entity glpi_list_items / glpi_get_item {itemtype: "Entity"}
glpi_list_documents, glpi_get_document glpi_list_items / glpi_get_item {itemtype: "Document"}
glpi_search_user {name} glpi_find_user {query: name, exact: true}
glpi_get_ticket_stats glpi_tickets_stats_by {dimension: "status", ...}
glpi_search {itemtype, field, searchtype, value} glpi_search_v2 {itemtype, criteria: [{field, searchtype, value}]} (range -> start/limit; limit, expand_dropdowns kept)

The 37 other v3 names are unchanged v4 tools.

Behaviour changes

  • Output shapes. Compact JSON; lists are the {totalcount, start, count, next_start, items} envelope; detail tools return a compact projection (verbose: true for every field); text is plain and capped at 2000 characters (max_content_chars, raw_html); ITIL items carry status_label, urgency_label, priority_label. Timeline aliases return {itemtype, id, count, timeline: [{kind, date, ...}]}, not a bare array.
  • Default limit 25 rows per page (max 500) on every list and search tool.
  • Strict arguments. Unknown keys are a protocol error instead of being ignored. include_deleted is gone (an isError result says why): is_deleted: true lists only trashed rows. Ids must be positive integers (numeric strings are accepted).
  • Status filters run in GLPI. glpi_list_tickets {status} used to filter one page client-side and miss most matches; it now searches server-side.
  • Open tickets use notold. open_only, glpi_list_overdue_tickets and glpi://tickets/open select status notold; the v3 lessthan 5 matched Solved tickets.
  • Purge needs confirm_id. glpi_delete_ticket and glpi_delete_item move to the trash; purge: true (or the legacy force: true on glpi_delete_ticket) also needs confirm_id equal to id. Only the glpi_delete_computer alias keeps the v3 behaviour.
  • Uploads by path need GLPI_UPLOAD_DIR. Without it, send content_base64 + filename.
  • glpi_create_user has no password. Set it in GLPI, by invitation, or rely on LDAP/SSO.
  • Search rows have readable keys instead of numeric option ids, so code that read row["12"] must read row.status.
  • glpi_search_user matches the login exactly and fails when several accounts share it.
  • glpi_link_tickets: v3 inverted link_type 3; see Link relations.
  • glpi_create_user email is sent as _useremails, so GLPI actually stores it (v3 sent a plain email field, which GLPI ignores).
  • Package entry. main, types and exports now point to the library (dist/lib.js); importing mcp-glpi no longer starts a server. The mcp-glpi command (dist/index.js) is unchanged.

Security notes

  • Writes change production data. GLPI_READ_ONLY=1 registers only the 21 read-only tools and the 2 session switches (glpi_set_active_entity, glpi_set_active_profile: they change the session context, not GLPI data); write calls then fail at protocol level. Without it, every write tool is annotated so clients can ask for confirmation, and the server instructions tell the model to confirm with the user first.
  • The GLPI account sets the ceiling. The server can do what the configured user and profile can do. Use a dedicated account with the narrowest profile and entities that fit, and GLPI_TOOLSETS to drop what you do not need.
  • One GLPI session per process. In HTTP mode every client shares the configured account and session: glpi_set_active_entity and glpi_set_active_profile switch it for all of them. Run one process per team or permission level if that matters.
  • HTTP exposure. The endpoint binds to 127.0.0.1 and has no auth by default. Before binding to another interface, set MCP_HTTP_AUTH_TOKEN, keep the port on a private network or behind TLS, and set MCP_HTTP_ALLOWED_HOSTS; host validation is off when not bound to loopback. The server warns at startup when HTTP runs without GLPI_READ_ONLY.
  • Uploads. file_path is read only inside GLPI_UPLOAD_DIR (real path, symlinks resolved, regular files only, 20 MB max). Point it at a dedicated directory, never at a home directory.
  • Purge needs confirm_id; writes are never replayed after a timeout or a 5xx.
  • Secrets stay in the client configuration or the environment; the server never returns them, and the passwords of User items cannot be written.

Development

git clone https://github.com/GMS64260/mcp-glpi.git
cd mcp-glpi
npm install
npm run build        # tsc -> dist/
npm run typecheck    # tsc --noEmit
npm run lint         # ESLint 9 + typescript-eslint (npm run lint:fix)
npm test             # node:test through tsx, fetch mocked, no GLPI needed
npm run measure      # size of tools/list (full and read-only) and instructions
npm run dev          # run src/index.ts with tsx
npm start            # node dist/index.js (stdio); npm start -- --http for HTTP

npm run measure builds the catalogue in-process and prints its size, the largest tools and where the characters go. Today: 46 tools, 25,287 characters (about 7.0k tokens); 23 tools and 12,844 characters with GLPI_READ_ONLY=1; instructions 2,458 characters. test/server.test.ts pins the ceilings.

CI (.github/workflows/ci.yml) builds, lints and tests on Node 20 and 22, and checks the catalogue ceilings. Publishing (publish.yml) runs when a GitHub release is published: it builds, tests and runs npm publish --provenance with the NPM_TOKEN repository secret; the release tag should match the package.json version. prepublishOnly runs build, lint and tests again. The tarball holds dist/ (no source maps), README.md, CHANGELOG.md and LICENSE.

npm run smoke runs read-only checks against a real GLPI, reading GLPI_URL and the credentials from the environment or a .env file at the repository root (never commit it). npm run smoke -- --write adds a write cycle: it creates a test ticket and moves it to the trash.

Library use

The package entry (dist/lib.js, typed) has no side effects: it reads no environment variable and opens no session. It exports GlpiClient, GlpiHttp, GlpiSearch, SearchOptionsCache, createMcpServer and the tool catalogue (ALL_TOOLS, ALL_ALIASES, INSTRUCTIONS), the ITIL code tables (TICKET_STATUS, CHANGE_STATUS, TICKET_PRIORITY...), the error helpers and the output helpers (compactItem, htmlToText, listEnvelope...).

import { GlpiClient, createMcpServer } from 'mcp-glpi';

const client = new GlpiClient({ url: 'https://glpi.example.com', userToken: process.env.GLPI_USER_TOKEN });
const server = createMcpServer({ client, options: { readOnly: true } });
// connect `server` to any MCP transport

Layout: src/index.ts (CLI, configuration, transports), src/lib.ts (library entry), src/server.ts (server factory and instructions), src/tools/ (one module per domain, the registry and the v3 aliases), src/output.ts (output conventions), src/errors.ts (error text and hints), src/prompts.ts, src/resources.ts, src/glpi-client.ts and src/http.ts (GLPI API).

License and links

MIT, see LICENSE.