MCP Admon Server
Extensible MCP server that gives AI agents structured, rule-driven access to any domain of the Admon platform — starting with ticket management.
Overview
The mcp-admon-server is a Model Context Protocol (MCP) server that exposes platform operations as structured AI-callable tools. It lives in the mcp/ root folder as an independent TypeScript/Node.js package and is intentionally designed to grow: adding a new domain (e.g. shipments, billing, companies) only requires registering a new module — no changes to the transport, auth, or logging layers.
The first domain implemented is ticket management. Before this server existed, AI agents had to infer required fields, validations, and backend endpoints per ticket type — leading to inconsistencies. This server centralizes all that logic: business rules are loaded dynamically from the database, validations run before any backend call, and every tool invocation is logged to mcp_activity_log.
The server supports two transports: HTTP Streamable (for Cursor) and Stdio (for Claude Desktop), selected via the MCP_TRANSPORT environment variable.
Key Concepts
| Concept | Description |
|---|---|
| MCP Tool | A function the AI agent can invoke, with a typed input schema and a description. Analogous to a REST endpoint but for agents. |
| MCP Resource | Read-only catalog data the agent can fetch (e.g. ticket statuses, shipment statuses). |
| Module | A self-contained domain registered into the server (e.g. tickets). Each module lives under src/modules/<domain>/ and exposes its own tools, resources, caches, and business rules. Adding a new module only requires a registerXxxModule(server, client, ...) call in server.ts. |
Shared layer (src/shared/) | Cross-cutting infrastructure available to all modules: BackendClient, ActivityLogger, ShipmentStatusesCache, shared constants, and base TypeScript types. |
| TicketRulesCache | In-memory cache loaded from GET /catalog/ticket-types. Holds the full rules JSON per ticket type including mcp_context, conditions, inputs, and files. TTL: 12 hours. |
| McpContext | JSON sub-field inside catalog_ticket_types.rules. Single source of truth for MCP-specific ticket configuration: use_case, is_blocked, requires_guide, amount_limit_mxn, requires_credit_id, agent_notes. Defined once in the DB, consumed by both the cache and the tools. |
| BackendClient | Internal HTTP client that injects Authorization: Bearer + X-Bot-AI-Signature (HMAC-SHA256) on every request to the Hapi.js backend. |
| ActivityLogger | Fire-and-forget logger that POSTs each tool call result to POST /mcp/activity-log. Never blocks a tool response. Applies automatically to every tool via the withLogging wrapper in server.ts. |
Architecture
Source tree
mcp/src/
├── index.ts # Entry point — selects transport (HTTP / Stdio)
├── server.ts # Wires modules, shared layer, and logging wrapper
├── shared/
│ ├── constants.ts # CACHE_TTL_MS and other cross-module constants
│ ├── http/
│ │ ├── backend.client.ts # Authenticated HTTP client (Bearer + HMAC-SHA256)
│ │ └── shipment-statuses.cache.ts
│ ├── logging/
│ │ └── activity-logger.ts # Fire-and-forget POST /mcp/activity-log
│ ├── resources/
│ │ └── index.ts # registerSharedResources (shipments://statuses)
│ └── types/
│ └── backend.types.ts # Shared TypeScript response interfaces
└── modules/
└── tickets/ # First domain module
├── index.ts # registerTicketsModule — wires all ticket tools
├── business-rules/
│ └── ticket-statuses.constants.ts
├── http/
│ └── ticket-rules.cache.ts # TicketRulesCache + McpContext interface
├── resources/
│ └── ticket-statuses.ts
├── schemas/
│ └── ticket.schemas.ts
└── tools/
├── get-ticket-types.ts
├── create-ticket.ts
├── update-ticket.ts
├── get-ticket.ts
└── list-tickets.tsAdding a new module
To expose a new platform domain to AI agents:
- Create
src/modules/<domain>/index.tsthat exportsregisterXxxModule(server, client, registerTool). - Implement tools under
src/modules/<domain>/tools/, schemas underschemas/, and any domain-specific caches underhttp/. - Call
registerXxxModule(...)inserver.ts— that's the only file that needs to change outside the new module folder.
The logging wrapper, authentication, and transport are inherited automatically.
Data Flow
Generic tool call lifecycle
Tickets module — create_ticket workflow
Tickets module — amount validation (update_ticket to CLAIM_IN_REVIEW)
Tickets module — file upload (embedded in create/update)
Database
Current tables (tickets module)
| Table | Purpose |
|---|---|
company_tickets | Main ticket records |
company_ticket_variables | Key-value pairs per ticket (e.g. days, amount) |
company_files | File metadata (S3 URL, type, ticket reference) |
catalog_ticket_types | Ticket type definitions including rules JSON with mcp_context |
catalog_ticket_statuses | Available ticket statuses |
catalog_shipment_statuses | Shipment statuses used to validate avaliable_status |
mcp_activity_log | Audit log of every MCP tool call across all modules (tool name, status, execution time) |
Key Relationships
company_tickets.ticket_type_id→catalog_ticket_types.idcompany_tickets.ticket_status_id→catalog_ticket_statuses.idcompany_files.ticket_id→company_tickets.idmcp_activity_log.mcp_nameidentifies which MCP server wrote the entry
Key Decisions
| Decision | Reasoning | Alternatives Considered |
|---|---|---|
mcp_context in catalog_ticket_types.rules as single source of truth | Use-case descriptions, blocked types, amount limits, and agent notes all live in the DB — no code change or redeploy needed to update them | Hardcoded TypeScript constants (TICKET_TYPE_USE_CASES, BLOCKED_TICKET_TYPE_IDS) — required code change + redeploy for every rule update |
get_ticket_types tool (dual-mode) instead of passive tickets://types resource | An active tool is reliably called by agents at the right step; a passive resource was often ignored, leading to wrong field collection | Keep tickets://types as a resource — agents didn't consistently consult it before creating tickets |
Amount validation at CLAIM_IN_REVIEW transition (not at creation) | Amount may be unknown at creation; validation only makes sense when the ticket is about to be reviewed for a claim | Validate at creation — too early; amount may change or be absent during initial ticket creation |
Block any ticket type without mcp_context by default | Missing mcp_context means the DB has no guidance for agents — safer to reject than to guess | Allow creation without mcp_context — would let agents proceed without knowing required fields |
Dynamic rules via TicketRulesCache with 12-hour TTL | Rules in catalog_ticket_types.rules can change without redeploying the MCP; TTL avoids a backend call on every tool invocation | No cache (query on every call) — adds latency; or no TTL — stale rules if DB is updated |
Single entry point (index.ts) for both HTTP and Stdio transports | One binary to maintain; transport selected by MCP_TRANSPORT env var | Separate stdio.ts entry — duplicates startup logic |
| Fire-and-forget activity logging | A logging failure must never impact the tool response latency or result | Awaited logging — would add ~10–50ms per call and could propagate DB errors to agents |
| File uploads as base64 in tool input | Agent makes a single create_ticket call even when attaching files | Separate upload_ticket_file tool — required agents to orchestrate 2 calls, caused confusion and duplicate uploads |
check_ticket_by_tracking removed in favor of list_tickets | list_tickets with tracking_number filter returns the same data plus richer context | Keep as standalone tool — redundant given list_tickets capabilities |
withLogging wrapper in server.ts instead of per-tool logging | Centralized; any new tool registered via registerTool gets logging automatically | Per-tool try/catch with logActivity — boilerplate that could be forgotten in new tools |
Dependencies
- Internal:
backend/routes/companies.routes.js(ticket CRUD),backend/routes/catalogs.routes.js(ticket types + shipment statuses),backend/routes/mcp.routes.js(activity log). New modules will add their own backend route dependencies. - External:
@modelcontextprotocol/sdk— MCP server and transport primitivesexpress@^5— HTTP server for Streamable transportzod@^4— Input schema validation and type inference- Node.js ≥ 24.0.0 — Required for native
fetch(no polyfills)
Related Documentation
- API Reference: ./api — Tools, resources, backend endpoints, and environment variables
- User Guide: No UI — this module is used exclusively by AI agents
