Autotask MCP Server
# Autotask MCP Server
[](https://github.com/WYRE-AI/autotask-mcp/actions/workflows/release.yml)
[](https://codecov.io/gh/WYRE-AI/autotask-mcp)
[](https://opensource.org/licenses/Apache-2.0)
[](https://nodejs.org/)
**Give your AI assistant direct access to Autotask.** Search tickets, create time entries, look up companies, manage projects โ all through natural language. No more copy-pasting between browser tabs and chat windows.
This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that connects Claude (or any MCP-compatible AI) to your Autotask PSA environment. Your AI assistant gets 101 tools covering the operations MSP teams use daily: ticket triage, time logging, company lookups, project management, billing review, and more.
If you run an MSP on Autotask and you're tired of the context-switching tax, this is for you.
> **Part of the [MSP Claude Plugins](https://github.com/WYRE-AI/msp-claude-plugins) ecosystem** โ a growing suite of AI integrations for the MSP stack including [Datto RMM](https://github.com/WYRE-AI/datto-rmm-mcp), [IT Glue](https://github.com/WYRE-AI/itglue-mcp), [HaloPSA](https://github.com/WYRE-AI/halopsa-mcp), [ConnectWise Automate](https://github.com/WYRE-AI/connectwise-automate-mcp), [NinjaOne](https://github.com/WYRE-AI/ninjaone-mcp), [Huntress](https://github.com/WYRE-AI/huntress-mcp), and more. Built by MSPs, for MSPs.
<a href="https://glama.ai/mcp/servers/@wyre-ai/autotask-mcp">
<img width="380" height="200" src="https://glama.ai/mcp/servers/@wyre-ai/autotask-mcp/badge" alt="Autotask MCP server" />
</a>
## One-Click Deployment
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/WYRE-AI/autotask-mcp/tree/main)
[](https://deploy.workers.cloudflare.com/?url=https://github.com/WYRE-AI/autotask-mcp)
> **Note โ no GitHub Packages token required.** Unlike most WYRE MCP servers,
> `autotask-mcp` does **not** depend on a private `@wyre-ai/*` package on
> GitHub Packages. Its only WYRE dependency is the `autotask-node` SDK, declared
> as a git dependency on the **public** `WYRE-AI/autotask-node` repo, which
> `npm install` resolves anonymously. The DigitalOcean one-click deploy therefore
> works without any `NODE_AUTH_TOKEN`/`GITHUB_TOKEN` build variable.
## Quick Start
**Claude Desktop** โ download, open, done:
1. Download `autotask-mcp.mcpb` from the [latest release](https://github.com/WYRE-AI/autotask-mcp/releases/latest)
2. Open the file (double-click or drag into Claude Desktop)
3. Enter your Autotask credentials when prompted (Username, Secret, Integration Code)
No terminal, no JSON editing, no Node.js install required.
**Claude Code (CLI):**
```bash
claude mcp add autotask-mcp \
-e AUTOTASK_USERNAME=your-user@company.com \
-e AUTOTASK_SECRET=your-secret \
-e AUTOTASK_INTEGRATION_CODE=your-code \
-- npx -y github:WYRE-AI/autotask-mcp
```
See [Installation](#installation) for Docker and from-source methods.
## Features
- **๐ MCP Protocol Compliance**: Full support for MCP resources and tools
- **๐ด Interactive Ticket Card (MCP Apps)**: `autotask_get_ticket_details` renders as an interactive card in MCP Apps hosts (Claude Desktop/web) with an in-card "Add note" round-trip; neutral theme by default, brandable via `MCP_BRAND_*` env vars. In every host, the text the assistant reads carries the full ticket: description, contact, linked configuration item, custom fields (UDFs), recent notes and the complete record as JSON
- **๐ ๏ธ Comprehensive API Coverage**: 101 tools spanning companies, contacts, tickets, projects, billing items, time entries, notes, attachments, and more
- **๐ Advanced Search**: Powerful search capabilities with filters across all entities
- **๐ CRUD Operations**: Create, read, update operations for core Autotask entities
- **๐ ID-to-Name Mapping**: Automatic resolution of company and resource IDs to human-readable names
- **โก Intelligent Caching**: Smart caching system for improved performance and reduced API calls
- **๐ Secure Authentication**: Enterprise-grade API security with Autotask credentials
- **๐ Dual Transport**: Supports both stdio (local) and HTTP Streamable (remote/Docker) transports
- **๐ฆ MCPB Packaging**: One-click installation via MCP Bundle for desktop clients
- **๐ณ Docker Ready**: Containerized deployment with HTTP transport and health checks
- **๐ Structured Logging**: Comprehensive logging with configurable levels and formats
- **๐งช Test Coverage**: Comprehensive test suite with 80%+ coverage
## Table of Contents
- [Installation](#installation)
- [Configuration](#configuration)
- [Gateway Mode](#gateway-mode)
- [Usage](#usage)
- [API Reference](#api-reference)
- [ID-to-Name Mapping](#id-to-name-mapping)
- [HTTP Transport](#http-transport)
- [Docker Deployment](#docker-deployment)
- [Migration Guide](docs/MIGRATION_GUIDE.md)
- [Development](#development)
- [Testing](#testing)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [Contributors](#contributors)
- [License](#license)
## Installation
### Option 1: MCPB Bundle (Claude Desktop)
The simplest method โ no terminal, no JSON editing, no Node.js install required.
1. Download `autotask-mcp.mcpb` from the [latest release](https://github.com/WYRE-AI/autotask-mcp/releases/latest)
2. Open the file (double-click or drag into Claude Desktop)
3. Enter your Autotask credentials when prompted (Username, Secret, Integration Code)
For **Claude Code (CLI)**, one command:
```bash
claude mcp add autotask-mcp \
-e AUTOTASK_USERNAME=your-user@company.com \
-e AUTOTASK_SECRET=your-secret \
-e AUTOTASK_INTEGRATION_CODE=your-code \
-- npx -y github:WYRE-AI/autotask-mcp
```
### Option 2: Docker
**Local (stdio โ for Claude Desktop or Claude Code):**
```json
{
"mcpServers": {
"autotask": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-e", "MCP_TRANSPORT=stdio",
"-e", "AUTOTASK_USERNAME=your-user@company.com",
"-e", "AUTOTASK_SECRET=your-secret",
"-e", "AUTOTASK_INTEGRATION_CODE=your-code",
"--entrypoint", "node",
"ghcr.io/wyre-ai/autotask-mcp:latest",
"dist/entry.js"
]
}
}
}
```
**Remote (HTTP Streamable โ for server deployments):**
```bash
docker run -d \
--name autotask-mcp \
-p 127.0.0.1:8080:8080 \
-e CONDUIT_S2S_SECRET="your-real-s2s-secret" \
-e AUTOTASK_USERNAME="your-user@company.com" \
-e AUTOTASK_SECRET="your-secret" \
-e AUTOTASK_INTEGRATION_CODE="your-code" \
--restart unless-stopped \
ghcr.io/wyre-ai/autotask-mcp:latest
# Verify (health does not require X-Gateway-S2S)
curl http://127.0.0.1:8080/health
```
The HTTP server refuses to start when `CONDUIT_S2S_SECRET` is empty or a known placeholder. Clients connect to `http://127.0.0.1:8080/mcp` using MCP Streamable HTTP transport and must send `X-Gateway-S2S`. See [X-Gateway-S2S signing](#x-gateway-s2s-signing).
**Gateway Mode (for MCP Gateway deployments):**
When deploying behind an MCP Gateway that injects credentials via HTTP headers. This container has no static Autotask credentials:
```bash
docker run -d \
--name autotask-mcp \
-p 127.0.0.1:8080:8080 \
-e CONDUIT_S2S_SECRET="your-real-s2s-secret" \
-e AUTH_MODE=gateway \
--restart unless-stopped \
ghcr.io/wyre-ai/autotask-mcp:latest
```
The gateway injects credentials via headers:
- `X-Gateway-S2S`: service-to-service proof (required on `/mcp`)
- `X-API-Key`: Autotask username
- `X-API-Secret`: Autotask secret
- `X-Integration-Code`: Autotask integration code
- `X-Impersonation-Resource-Id`: (optional) Autotask resource ID to act on behalf of
A `/mcp` request missing any of the three Autotask headers returns 401 and does not fall back to `AUTOTASK_*` in the environment. See [Gateway Mode](#gateway-mode) for details.
### Option 3: From Source (Development)
```bash
git clone https://github.com/WYRE-AI/autotask-mcp.git
cd autotask-mcp
npm ci && npm run build
```
Then point your MCP client at `dist/entry.js`:
```json
{
"mcpServers": {
"autotask": {
"command": "node",
"args": ["/path/to/autotask-mcp/dist/entry.js"],
"env": {
"AUTOTASK_USERNAME": "your-user@company.com",
"AUTOTASK_SECRET": "your-secret",
"AUTOTASK_INTEGRATION_CODE": "your-code"
}
}
}
}
```
### Prerequisites
- Valid Autotask API credentials (API user email, secret, integration code)
- MCP-compatible client (Claude Desktop, Claude Code, etc.)
- Docker (for Option 2) or Node.js 18+ (for Option 3)
## Configuration
### Environment Variables
Create a `.env` file with your configuration:
```bash
# Required Autotask API credentials (Local Mode)
AUTOTASK_USERNAME=your-api-user@example.com
AUTOTASK_SECRET=your-secret-key
AUTOTASK_INTEGRATION_CODE=your-integration-code
# Optional configuration
# AUTOTASK_API_URL is auto-detected from AUTOTASK_USERNAME via Autotask's
# unauthenticated zoneInformation endpoint on first connect. Only set this
# explicitly to override auto-detection (e.g. for an on-prem proxy).
# AUTOTASK_API_URL=https://webservices2.autotask.net/atservicesrest/
MCP_SERVER_NAME=autotask-mcp
# Authentication mode
AUTH_MODE=env # env (local), gateway (hosted)
# Required to start HTTP. Empty and known placeholders are rejected.
# Never commit a real value. stdio does not use this.
CONDUIT_S2S_SECRET=
# Local HTTP only. Exactly "1" skips X-Gateway-S2S and forces 127.0.0.1.
# MCP_ALLOW_INSECURE_DEV=1
# Transport (stdio for local/desktop, http for remote/Docker)
MCP_TRANSPORT=stdio # stdio, http
MCP_HTTP_PORT=8080 # HTTP transport port (only used when MCP_TRANSPORT=http)
# Honored when CONDUIT_S2S_SECRET is set. Without a usable secret the bind is 127.0.0.1.
MCP_HTTP_HOST=0.0.0.0 # HTTP transport bind address
# Logging
LOG_LEVEL=info # error, warn, info, debug
LOG_FORMAT=simple # simple, json
# Search-result name enrichment
# Max concurrent Autotask API calls used to resolve company/resource names on
# search results. Kept low to stay under Autotask's per-integration
# concurrent-thread limit (raising it risks HTTP 429 "thread threshold").
AUTOTASK_ENHANCE_CONCURRENCY=3
# Max Autotask REST calls in flight per tenant. Further calls queue instead of
# tripping the concurrent-thread limit (HTTP 429 "thread threshold").
AUTOTASK_MAX_INFLIGHT=3
# Environment
NODE_ENV=production
```
### Gateway Mode
When deployed behind an MCP Gateway (e.g., `mcp.wyre.ai`), the server operates in gateway mode where credentials are injected via HTTP headers on each request.
**Enable Gateway Mode:**
```bash
AUTH_MODE=gateway
MCP_TRANSPORT=http
CONDUIT_S2S_SECRET=<gateway-provisioned secret>
```
Every `/mcp` request must include a valid `X-Gateway-S2S` header and the three Autotask credential headers. A request missing any credential header returns 401. The server does not fall back to `AUTOTASK_USERNAME`, `AUTOTASK_SECRET`, or `AUTOTASK_INTEGRATION_CODE` in the process environment.
**Expected Headers:**
| Header | Description |
|--------|-------------|
| `X-Gateway-S2S` | Service-to-service proof. `t=<unix seconds>,v1=<hex hmac-sha256>`. Required on `/mcp` whenever `CONDUIT_S2S_SECRET` is set. See [X-Gateway-S2S signing](#x-gateway-s2s-signing). |
| `X-API-Key` | Autotask API username (email) |
| `X-API-Secret` | Autotask API secret key |
| `X-Integration-Code` | Autotask integration code |
| `X-API-URL` | (Optional) Custom Autotask API URL |
| `X-Impersonation-Resource-Id` | (Optional) Autotask resource ID to act on behalf of. Forwarded to Autotask as its `ImpersonationResourceId` header, so actions are attributed to that resource instead of the API user, and recorded in the entity's read-only `impersonatorCreatorResourceID` field. Must be a positive integer; anything else is ignored with a warning. The impersonated resource must itself have permission for the action, and the API user's security level must permit impersonation. |
### X-Gateway-S2S signing
`MCP_TRANSPORT=http` refuses to start unless `CONDUIT_S2S_SECRET` is a real secret. Empty values and known placeholders are rejected, including `changeme`, `placeholder`, `your-s2s-secret`, `secret`, `password`, `test`, and any value that is only an unresolved `${...}` template. The process logs an error and exits non-zero. It never prints the secret.
`MCP_ALLOW_INSECURE_DEV=1` is the local-only escape hatch. The process logs a warning, does not check `X-Gateway-S2S`, and listens on `127.0.0.1` only. A non-loopback `MCP_HTTP_HOST` (including `0.0.0.0`, `localhost`, and `::1`) is ignored in that mode and the override is logged.
Without a usable secret the bind address defaults to `127.0.0.1`. A configured secret honors `MCP_HTTP_HOST` (default `0.0.0.0` when that variable is unset, which is what the container image sets). Publish the host port on loopback unless a proxy sits in front.
`/health` stays unauthenticated and does not read credentials. The stdio transport does not use `CONDUIT_S2S_SECRET`.
With a usable secret, every `/mcp` request must include:
```
X-Gateway-S2S: t=<unix seconds>,v1=<lowercase hex>
```
`v1` is HMAC-SHA256, hex-encoded, of the ASCII string `t=<unix seconds>` (the same `t=` prefix that starts the header), keyed with `CONDUIT_S2S_SECRET`. The timestamp must be within 300 seconds of the server clock. A missing or invalid header returns HTTP 401. The secret is never written to logs or the response body.
```bash
# t is the current unix time. v1 is HMAC-SHA256 of the literal "t=$t".
t=$(date +%s)
v1=$(printf 't=%s' "$t" | openssl dgst -sha256 -hmac "$CONDUIT_S2S_SECRET" | awk '{print $NF}')
curl -sS http://127.0.0.1:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "X-Gateway-S2S: t=${t},v1=${v1}" \
-H "X-API-Key: api-user@example.com" \
-H "X-API-Secret: the-api-secret" \
-H "X-Integration-Code: the-integration-code" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
In `AUTH_MODE=env`, `/mcp` still requires a valid `X-Gateway-S2S` header. Autotask credentials then come from `AUTOTASK_*`. In `AUTH_MODE=gateway`, the three `X-API-*` headers above are required as well and are the only credential source for that request.
**Health Check Response (Gateway Mode):**
```json
{
"status": "ok",
"transport": "http",
"authMode": "gateway",
"timestamp": "2026-02-05T10:00:00.000Z"
}
```
For detailed migration instructions, see the [Migration Guide](docs/MIGRATION_GUIDE.md).
๐ก **Pro Tip**: Copy the above content to a `.env` file in your project root.
### Autotask API Setup
1. **Create API User**: In Autotask, create a dedicated API user with appropriate permissions
2. **Generate Secret**: Generate an API secret for the user
3. **Integration Code**: Obtain your integration code from Autotask
4. **Permissions**: Ensure the API user has read/write access to required entities
For detailed setup instructions, see the [Autotask API documentation](https://ww3.autotask.net/help/DeveloperHelp/Content/AdminSetup/2ExtensionsIntegrations/APIs/REST/REST_API_Home.htm).
## Usage
### Command Line
```bash
# Start the MCP server (stdio transport, for piping to an MCP client)
node dist/entry.js
# HTTP refuses to start until CONDUIT_S2S_SECRET is a real secret
CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" MCP_TRANSPORT=http node dist/index.js
# Local HTTP only, without a service-to-service secret (always binds 127.0.0.1)
MCP_ALLOW_INSECURE_DEV=1 MCP_TRANSPORT=http node dist/index.js
```
### MCP Client Configuration
See [Installation](#installation) for all setup methods.
## API Reference
### Resources
Resources provide read-only access to Autotask data:
- `autotask://companies` - List all companies
- `autotask://companies/{id}` - Get specific company
- `autotask://contacts` - List all contacts
- `autotask://contacts/{id}` - Get specific contact
- `autotask://tickets` - List all tickets
- `autotask://tickets/{id}` - Get specific ticket
- `autotask://time-entries` - List time entries
### Tools
The server provides 101 tools for interacting with Autotask:
#### Company Operations
- `autotask_search_companies` - Search companies with filters
- `autotask_create_company` - Create new company
- `autotask_update_company` - Update existing company
#### Contact Operations
- `autotask_search_contacts` - Search contacts with filters
- `autotask_create_contact` - Create new contact
#### Ticket Operations
- `autotask_search_tickets` - Search tickets with filters
- `autotask_get_ticket_details` - Get a ticket by ID: description, status/priority/queue, company, contact, assignee, dates, linked configuration item, resolution, custom fields (UDFs), recent notes and the full record (`fullDetails: true` for untruncated text and all UDFs)
- `autotask_create_ticket` - Create new ticket
#### Time Entry Operations
- `autotask_create_time_entry` - Log time entry
- `autotask_search_time_entries` - Search time entries with filters (resource, ticket, task, date range); each entry includes `summaryNotes` and `internalNotes`
#### Billing Items (Approve and Post Workflow)
- `autotask_search_billing_items` - Search approved and posted billing items
- `autotask_get_billing_item` - Get specific billing item by ID
- `autotask_search_billing_item_approval_levels` - Search multi-level approval records for time entries
#### Project Operations
- `autotask_search_projects` - Search projects with filters
- `autotask_create_project` - Create new project
#### Resource Operations
- `autotask_search_resources` - Search resources (technicians/users)
#### Note Operations
- `autotask_get_ticket_note` / `autotask_search_ticket_notes` / `autotask_create_ticket_note`
- `autotask_get_project_note` / `autotask_search_project_notes` / `autotask_create_project_note`
- `autotask_get_company_note` / `autotask_search_company_notes` / `autotask_create_company_note`
#### Attachment Operations
- `autotask_get_ticket_attachment` - Get ticket attachment
- `autotask_search_ticket_attachments` - Search ticket attachments
- `autotask_get_ticket_note_attachment` - Get an attachment on a ticket note (e.g. a pasted screenshot)
- `autotask_search_ticket_note_attachments` - Search attachments on a ticket note
#### Financial Operations
- `autotask_get_expense_report` / `autotask_search_expense_reports` / `autotask_create_expense_report`
- `autotask_get_quote` / `autotask_search_quotes` / `autotask_create_quote`
- `autotask_get_opportunity` / `autotask_search_opportunities` / `autotask_create_opportunity`
- `autotask_update_opportunity` - Update an opportunity (stage, status, revenue/cost lines, UDFs)
- `autotask_search_invoices` - Search invoices
#### Contract Operations
- `autotask_search_contracts` - Search contracts (name, company, status, type, end-date range)
- `autotask_get_contract` - Get a single contract by ID
- `autotask_list_expiring_contracts` - Expiring/expired contracts report (next N days, per company or org-wide)
- `autotask_create_contract` / `autotask_create_contracts_bulk` - Create contract shells, one or many
- `autotask_update_contract` - Update a contract (e.g. extend/renew end date)
- `autotask_create_contract_service` / `autotask_update_contract_service` - Manage contract service lines
#### Configuration Items
- `autotask_search_configuration_items` - Search configuration items (assets)
#### Task Operations
- `autotask_search_tasks` - Search project tasks
- `autotask_create_task` - Create project task
#### Utility Operations
- `autotask_test_connection` - Test API connectivity
### Example Tool Usage
```javascript
// Search for companies
{
"name": "autotask_search_companies",
"arguments": {
"searchTerm": "Acme Corp",
"isActive": true,
"pageSize": 10
}
}
// Create a new ticket
{
"name": "autotask_create_ticket",
"arguments": {
"companyID": 12345,
"title": "Server maintenance request",
"description": "Need to perform monthly server maintenance",
"priority": 2,
"status": 1
}
}
```
## ID-to-Name Mapping
The Autotask MCP server includes intelligent ID-to-name mapping that automatically resolves company and resource IDs to human-readable names, making API responses much more useful for AI assistants and human users.
### Automatic Enhancement
All search and detail tools automatically include an `_enhanced` field with resolved names:
```json
{
"id": 12345,
"title": "Sample Ticket",
"companyID": 678,
"assignedResourceID": 90,
"_enhanced": {
"companyName": "Acme Corporation",
"assignedResourceName": "John Smith"
}
}
```
### How It Works
ID-to-name mapping is applied automatically to all search and detail tool results. No additional tools are needed โ the `_enhanced` field is added transparently to every response that contains company or resource IDs.
### Performance Features
- **Smart Caching**: Names are cached for 30 minutes to reduce API calls
- **Bulk Operations**: Efficient batch lookups for multiple IDs
- **Graceful Fallback**: Returns "Unknown Company (123)" if lookup fails
- **Parallel Processing**: Multiple mappings resolved simultaneously
### Testing Mapping
Test the mapping functionality:
```bash
npm run test:mapping
```
For detailed mapping documentation, see [docs/mapping.md](docs/mapping.md).
## HTTP Transport
The server supports the MCP Streamable HTTP transport for remote deployments (e.g., Docker, cloud hosting). Set `MCP_TRANSPORT=http` to enable it.
```bash
# Start with HTTP transport (exits unless CONDUIT_S2S_SECRET is a real secret)
CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" MCP_TRANSPORT=http MCP_HTTP_PORT=8080 node dist/index.js
```
The HTTP transport exposes:
- `POST /mcp` โ MCP Streamable HTTP endpoint. Requires `X-Gateway-S2S` when `CONDUIT_S2S_SECRET` is set. See [X-Gateway-S2S signing](#x-gateway-s2s-signing).
- `GET /health` โ Unauthenticated health check (returns `{"status":"ok"}`). Does not read credentials.
Clients must send requests to `/mcp` with `Accept: application/json, text/event-stream` headers per the MCP Streamable HTTP specification.
## Docker Deployment
The Docker image uses HTTP transport by default (port 8080) with a built-in health check.
### Using Pre-built Image from GitHub Container Registry
The Docker image defaults to **HTTP transport** on port 8080 โ suitable for remote/server deployments where clients connect over the network.
```bash
# Pull the latest image
docker pull ghcr.io/wyre-ai/autotask-mcp:latest
# Run container with HTTP transport (default). The image binds 0.0.0.0 inside
# the container; publish on loopback and pass the S2S secret.
docker run -d \
--name autotask-mcp \
-p 127.0.0.1:8080:8080 \
-e CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" \
-e AUTOTASK_USERNAME="your-api-user@example.com" \
-e AUTOTASK_SECRET="your-secret-key" \
-e AUTOTASK_INTEGRATION_CODE="your-integration-code" \
--restart unless-stopped \
ghcr.io/wyre-ai/autotask-mcp:latest
# Verify it's running (health does not require X-Gateway-S2S)
curl http://127.0.0.1:8080/health
```
Compose will not start when `CONDUIT_S2S_SECRET` is unset. The checked-in gateway service sets `AUTH_MODE=gateway`, publishes `127.0.0.1:8080:8080`, and does not inject `AUTOTASK_*`:
```bash
CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" docker compose up
```
For **stdio** usage with Claude Desktop, see [Installation Option 2](#option-2-docker).
### Quick Start (From Source)
```bash
# Clone repository
git clone https://github.com/WYRE-AI/autotask-mcp.git
cd autotask-mcp
# Create environment file
cp .env.example .env
# Edit .env with your credentials
# Start with docker-compose
docker compose up -d
```
### Production Deployment
```bash
# Build production image locally
docker build -t autotask-mcp:latest .
# Run container
docker run -d \
--name autotask-mcp \
--env-file .env \
--restart unless-stopped \
autotask-mcp:latest
```
### Development Mode
```bash
# Start development environment with hot reload
docker compose --profile dev up autotask-mcp-dev
```
## Development
### Setup
```bash
git clone https://github.com/WYRE-AI/autotask-mcp.git
cd autotask-mcp
npm install
```
### Available Scripts
```bash
npm run dev # Start development server with hot reload
npm run build # Build for production
npm run test # Run test suite
npm run test:watch # Run tests in watch mode
npm run test:coverage # Run tests with coverage
npm run lint # Run ESLint
npm run lint:fix # Fix ESLint issues
```
### Project Structure
```
autotask-mcp/
โโโ src/
โ โโโ handlers/ # MCP request handlers
โ โโโ mcp/ # MCP server implementation
โ โโโ services/ # Autotask service layer
โ โโโ types/ # TypeScript type definitions
โ โโโ utils/ # Utility functions (config, logger, cache)
โ โโโ entry.ts # Entry point (stdout guard + .env loader)
โ โโโ index.ts # Server bootstrap (config, logger, server init)
โโโ tests/ # Test files
โโโ scripts/ # Build and packaging scripts
โ โโโ pack-mcpb.js # MCPB bundle creation
โโโ manifest.json # MCPB manifest for desktop distribution
โโโ Dockerfile # Container definition (HTTP transport)
โโโ docker-compose.yml # Multi-service orchestration
โโโ package.json # Project configuration
```
## Testing
### Running Tests
```bash
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Run in watch mode
npm run test:watch
# Run specific test file
npm test -- tests/autotask-service.test.ts
```
### Test Categories
- **Unit Tests**: Service layer and utility functions
- **Integration Tests**: MCP protocol compliance
- **API Tests**: Autotask API integration (requires credentials)
### Coverage Requirements
- Minimum 80% coverage for all metrics
- 100% coverage for critical paths (authentication, data handling)
## Configuration Reference
### Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `AUTOTASK_USERNAME` | โ
| - | Autotask API username (email) |
| `AUTOTASK_SECRET` | โ
| - | Autotask API secret key |
| `AUTOTASK_INTEGRATION_CODE` | โ
| - | Autotask integration code |
| `AUTOTASK_API_URL` | โ | Auto-detected | Autotask API endpoint URL |
| `MCP_SERVER_NAME` | โ | `autotask-mcp` | MCP server name |
| `MCP_TRANSPORT` | โ | `stdio` | Transport type (`stdio` or `http`) |
| `MCP_HTTP_PORT` | โ | `8080` | HTTP transport port |
| `MCP_HTTP_HOST` | โ | `0.0.0.0` when a secret is set; `127.0.0.1` without one | HTTP bind address. A configured `CONDUIT_S2S_SECRET` honors this variable. `MCP_ALLOW_INSECURE_DEV=1` forces `127.0.0.1`. |
| `CONDUIT_S2S_SECRET` | Yes for HTTP | โ | Service-to-service secret for `X-Gateway-S2S`. HTTP exits if this is empty or a known placeholder. Never commit a real value. |
| `MCP_ALLOW_INSECURE_DEV` | โ | unset | Set to exactly `1` for local HTTP without a usable secret. Logs a warning, skips `X-Gateway-S2S`, and listens on `127.0.0.1` only. |
| `LOG_LEVEL` | โ | `info` | Logging level |
| `LOG_FORMAT` | โ | `simple` | Log output format |
| `AUTOTASK_ENHANCE_CONCURRENCY` | โ | `3` | Max concurrent Autotask API calls used to resolve company/resource names on search results. Kept low to stay under Autotask's concurrent-thread limit. |
| `AUTOTASK_MAX_INFLIGHT` | โ | `3` | Max Autotask REST calls in flight per tenant. Further calls queue in the HTTP client instead of tripping the concurrent-thread limit. |
| `NODE_ENV` | โ | `development` | Node.js environment |
### Logging Levels
- `error`: Only error messages
- `warn`: Warnings and errors
- `info`: General information, warnings, and errors
- `debug`: Detailed debugging information
### Log Formats
- `simple`: Human-readable console output
- `json`: Structured JSON output (recommended for production)
## Rate Limits
Autotask enforces per-integration-code API thresholds on a rolling 1-hour window:
- **~10,000 req/hr (soft)** โ warning email, sporadic `HTTP 429` responses
- **~20,000 req/hr (hard)** โ sustained `HTTP 429` until the window rolls
LLM-driven workflows fan out easily โ "status report on all open projects with notes" can issue hundreds of requests across a few minutes. The server tries to make this safer:
- **429 responses are surfaced as structured errors.** Tool results carry `error_type: "rate_limited"` and a `retry_after_seconds` field parsed from Autotask's `Retry-After` header. The error message explicitly tells the LLM **not to retry** and to ask the user to scope the query โ this prevents repeated retries from extending the cooldown.
- **Concurrent-thread 429s are queued, not cooled down for a minute.** Autotask also returns HTTP 429 when an integration exceeds its thread threshold (the body mentions "thread threshold"). That is a burst problem, separate from the hourly quota. The HTTP client allows `AUTOTASK_MAX_INFLIGHT` calls in flight per tenant (default 3) and queues the rest. A thread-threshold 429 on an idempotent call (`GET`, `DELETE`, or `POST /{Entity}/query`) is retried with a short backoff and does not arm the 60s cooldown. Hourly-threshold 429s still arm it. Create/update calls are not retried automatically.
- **Fan-out tool descriptions include rate-limit tips.** Tools that are commonly looped over (`autotask_search_ticket_notes`, `autotask_search_project_notes`, `autotask_search_company_notes`, `autotask_search_time_entries`, `autotask_search_ticket_attachments`, `autotask_search_ticket_note_attachments`) include a hint reminding the LLM to scope the parent record list before iterating.
### Raising the limit
Per-integration thresholds can be increased in Autotask:
1. Autotask Admin โ **Resources/Users (HR) โ Resources**
2. Edit the dedicated API user โ **Workflow Rules โ API Tracking Identifier**
3. Adjust the threshold for the integration code your MCP server uses
This is the right answer when a single integration code is shared between Claude/Copilot/etc. and other tooling. For LLM-heavy workloads, dedicate a separate API user (and integration code) so a fan-out from one client doesn't starve others.
### Patterns that help
- **Always scope by date range** when searching notes, time entries, attachments. Even a 30-day window can drop call count by an order of magnitude.
- **Cache parent lookups.** If you're iterating over 100 tickets, fetch the ticket list once and reuse it across follow-up queries; don't re-search per child.
- **Use `autotask_get_field_info`** to discover picklist values once per session rather than refetching them per call.
If you're seeing threshold warnings from Autotask but the server seems fine, the LLM driver is probably issuing fan-out patterns. Tighten the prompt to scope before iterating.
## Troubleshooting
### Common Issues
#### Authentication Errors
```
Error: Missing required Autotask credentials
```
**Solution**: Ensure all required environment variables are set correctly.
#### Connection Timeouts
```
Error: Connection to Autotask API failed
```
**Solutions**:
- Check network connectivity
- Verify API endpoint URL
- Confirm API user has proper permissions
#### Permission Denied
```
Error: User does not have permission to access this resource
```
**Solution**: Review Autotask API user permissions and security level settings.
### Debug Mode
Enable debug logging for detailed troubleshooting:
```bash
LOG_LEVEL=debug npm start
```
### Health Checks
Test server connectivity:
```bash
# Run test suite
npm run test
# For HTTP transport, check the health endpoint
curl http://localhost:8080/health
# Returns: {"status":"ok"}
# Test API connection with debug logging
LOG_LEVEL=debug npm start
```
### Autotask API Rate Limits
**Problem**: `429 Too Many Requests` or "thread limit exceeded" errors when Claude queries aggressively
Autotask enforces **3 concurrent threads per endpoint per API tracking identifier**. When an LLM issues multiple tool calls simultaneously (e.g., searching tickets, companies, and contacts at once), requests can pile up and hit this limit.
**Built-in mitigation**: The HTTP client caps in-flight requests per tenant (`AUTOTASK_MAX_INFLIGHT`, default 3) and queues the rest, so a parallel burst waits for a free slot instead of tripping the thread threshold. You may notice slower responses under heavy load. A thread-threshold 429 on an idempotent call is retried briefly; the hourly request threshold still surfaces as a structured rate-limit error and a short tenant cooldown.
**Critical for team/multi-user deployments**: If multiple users or the MCP Gateway share the **same API credentials**, they compete for the same 3-thread budget. This can cause noticeable slowdowns and, in severe cases, queued requests that time out.
**Solution โ one API key per team**: Create a dedicated Autotask API user per team or integration. Each user has an independent `integrationCode` with its own thread budget:
1. **Admin > Resources (Users) > Resources/Users** โ Add Resource
2. Set Security Level to **API User**
3. Note the username, secret, and integration code
4. Set `AUTOTASK_USERNAME`, `AUTOTASK_SECRET`, and `AUTOTASK_INTEGRATION_CODE` per team
```
Support Team โ AUTOTASK_INTEGRATION_CODE=SUPPORT_TEAM_CODE (3 threads)
Projects Team โ AUTOTASK_INTEGRATION_CODE=PROJECTS_TEAM_CODE (3 threads, independent)
```
Additionally, Autotask limits **10,000 total requests per hour** across all integrations hitting your tenant. If you hit this limit, all integrations will start receiving 429s โ another reason to use targeted queries with appropriate filters.
### MCP Client Issues
**Problem**: MCP server not appearing in Claude Desktop
**Solutions**:
1. Check configuration file syntax (valid JSON)
2. Verify file path in the configuration
3. Ensure environment variables are set correctly
4. Restart Claude Desktop completely
**Problem**: "Invalid JSON-RPC message: [dotenv@...] injecting env" / Server disconnected
**Cause**: The `autotask-node` library calls `dotenv.config()` at module load time. dotenv v17+ writes status messages via `console.log` to stdout, which corrupts the MCP stdio JSON-RPC channel.
**Solution**: Ensure you're using `dist/entry.js` (not `dist/index.js`) as the entry point. The entry wrapper redirects `console.log` to stderr before any libraries load.
**Problem**: Slow responses
**Solutions**:
1. Check network connectivity to Autotask API
2. Enable debug logging (`LOG_LEVEL=debug`) to identify bottlenecks
3. The server caches company/resource names for 30 minutes automatically
### Security Best Practices
- Store credentials in environment variables, not directly in config files
- Limit Autotask API user permissions to the minimum required
- Rotate API credentials regularly
- For Docker deployments, use secrets management rather than plain environment variables
## Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
### Development Guidelines
- Follow TypeScript best practices
- Maintain test coverage above 80%
- Use conventional commit messages
- Update documentation for API changes
- Add tests for new features
## License
This project is licensed under the **Apache License 2.0**. See the [LICENSE](LICENSE) file for details.
### Contributor License Agreement
By submitting a pull request, you agree to the terms of our [Contributor License Agreement](CLA.md). This ensures that contributions can be properly licensed and that you have the right to submit the code.
## Contributors
| Avatar | Name | Contributions |
| --- | --- | --- |
| <a href="https://github.com/asachs01"><img src="https://github.com/asachs01.png" width="60" /></a> | [@asachs01](https://github.com/asachs01) | Maintainer |
| <a href="https://github.com/Baphomet480"><img src="https://github.com/Baphomet480.png" width="60" /></a> | [@Baphomet480](https://github.com/Baphomet480) | CLI bin fix |
## Support
- ๐ [Documentation](https://github.com/WYRE-AI/autotask-mcp/wiki)
- ๐ [Issue Tracker](https://github.com/WYRE-AI/autotask-mcp/issues)
- ๐ฌ [Discussions](https://github.com/WYRE-AI/autotask-mcp/discussions)
## Acknowledgments
- [Model Context Protocol](https://modelcontextprotocol.io/) by Anthropic
- [Autotask REST API](https://ww3.autotask.net/help/DeveloperHelp/Content/APIs/REST/REST_API_Home.htm) by Kaseya
- [autotask-node](https://www.npmjs.com/package/autotask-node) library
---
Built by [WYRE Technology](https://github.com/WYRE-AI) โ part of the [MSP Claude Plugins](https://github.com/WYRE-AI/msp-claude-plugins) ecosystem TDQS
Scored across 98 tools
Tools are mostly entity-scoped (get_/search_/create_/update_/delete_), but the sheer volume and some near-duplicates (e.g., get_ticket_details vs search_ticket_history, service_call_ticket association vs service_call CRUD) can confuse. The meta tool list_categories is also odd in a flat toolset.
Naming is predominantly consistent with an autotask_<verb>_<entity> pattern. Minor inconsistencies: list_* vs search_* (list_phases, list_categories) and get_ticket_details vs get_ticket_history where 'details' is vague.
98 tools is far beyond the practical limit for MCP usability. Even for a broad PSA platform, the count will overwhelm context windows and decision-making. Several tools are narrowly scoped (e.g., search_service_call_tickets) and could be merged or split more judiciously.
The surface covers many core entities (tickets, companies, projects, service calls, quotes, billing), but significant gaps exist: no update/delete for notes or attachments, no update/delete for tasks or projects, no search/get for service calls (only create/delete/link), and opportunities/products/contracts are read-only.