Customer support
Zendesk
Tickets, replies, macros and views in your Zendesk, worked by your agent.
12 endpoints
Your agent reads service tickets, writes notes and logs time in ConnectWise PSA. You sign in once, with no API member, no clientId and no server to host. It works inside the ChatGPT and Claude apps.
10 tools Read and write
ConnectWise PSA has an official REST API but no ConnectWise-hosted MCP server (checked 6 Nov 2026). The API needs a Developer Network account, a clientId and an API member with a public and a private key. Cotlet adds 10 ConnectWise tools hosted, over MCP for agents and REST for scripts, with none of that setup.
| What you need | Official REST API | Community MCP server | Cotlet |
|---|---|---|---|
| Find tickets by board, status or priority | Yes, you code it | Yes | Yes |
| Read the notes of a ticket | Yes, you code it | Yes | Yes |
| Add a note, update or create a ticket | Yes, you code it | Yes | Yes, after your OK |
| Log and read time entries | Yes, you code it | Yes | Yes, after your OK |
| No API member, keys or clientId | No | No | Yes, your own sign-in |
| Runs in the ChatGPT and Claude apps, 24/7 | No | You host it | Yes, hosted |
Checked 6 Nov 2026. Sources: ConnectWise Developer Network: sign-in and ClientId · Zapier Help: Get started with ConnectWise Manage on Zapier · GitHub: mspstack/mcp-connectwise-psa · ConnectWise Docs: PSA integration, API members and keys
Use your own PSA login.
Paste one Cotlet address.
Your agent works the tickets.
We never keep your ConnectWise password, and no customer note or ticket change happens until you approve it. Who can see your session
Use MCP with your AI agent, or the same tools over REST in n8n, Make and scripts.
Enable ConnectWise for agents. Sign up, log in to ConnectWise PSA in its Cloud Browser, then press Enable for agents.
Add the Cotlet address. Add https://cotlet.dev/mcp/v1 as an MCP server or connector in your agent.
Approve the connection. Chat apps ask you to sign in to Cotlet. Editors, n8n and scripts use an API key from the console.
One shared address, https://cotlet.dev/mcp/v1, also carries ConnectWise next to your other apps. If you enable ConnectWise after connecting, refresh the connector so the new tools show up.
Settings > Connectors > Add custom connector
URL: https://cotlet.dev/mcp/v1/connect
Claude Code:
claude mcp add --transport http cotlet-connectwise \
https://cotlet.dev/mcp/v1 \
--header "Authorization: Bearer $COTLET_API_KEY" Settings > Apps & Connectors > Create
MCP server URL: https://cotlet.dev/mcp/v1/connect
Authentication: OAuth // ~/.cursor/mcp.json
{
"mcpServers": {
"cotlet-connectwise": {
"url": "https://cotlet.dev/mcp/v1",
"headers": { "Authorization": "Bearer ${env:COTLET_API_KEY}" }
}
}
} # ~/.codex/config.toml
[mcp_servers.cotlet-connectwise]
url = "https://cotlet.dev/mcp/v1"
bearer_token_env_var = "COTLET_API_KEY" MCP Client Tool (inside an AI Agent)
Transport: HTTP Streamable
URL: https://cotlet.dev/mcp/v1
Auth: Bearer, <your key>
Tool: connectwise_read Make: MCP Client > Call a tool
Zapier: MCP Client by Zapier > Run Tool
Server URL: https://cotlet.dev/mcp/v1
Token: <your key>
Tool: connectwise_write Read boards first, so statuses and types are real names. A ticket can only use values that exist on its board.
Returns each board with the values you can set on a ticket: statuses, types, subtypes and items. These are the values of the board setup in PSA, so a ticket can only use the ones that exist on its board. Run it once before update_ticket or create_ticket, so the agent uses real names. What you see follows your security role.
MCP callconnectwise_read → list_service_boards
REST endpointGET https://cotlet.dev/api/v1/connectwise/service-boards
| Name | Type | Description |
|---|---|---|
| cursor string | string | Next page cursor from the previous call. |
{
"boards": [
{
"id": 1,
"name": "Service Desk",
"statuses": [
"New",
"In Progress",
"Waiting on Client",
"Closed"
],
"types": [
"Incident",
"Request"
],
"subtypes": [
"Email",
"Printing"
],
"items": [
"Outlook",
"Printer"
]
},
{
"id": 2,
"name": "Projects",
"statuses": [
"Scheduled",
"In Progress",
"Closed"
],
"types": [
"Project task"
],
"subtypes": [],
"items": []
}
],
"next_cursor": null
} Returns one row per ticket with number, summary, company, board, status, priority, owner and the date of the last update. The most recently updated tickets come first. Without a filter it lists the tickets your role can see, one page at a time. Follow next_cursor until it comes back empty.
MCP callconnectwise_read → list_tickets
REST endpointGET https://cotlet.dev/api/v1/connectwise/tickets
| Name | Type | Description |
|---|---|---|
| board string | string | Board name from list_service_boards. |
| status string | string | Status name on that board, for example "New". |
| priority string | string | Priority name as shown on the ticket. |
| owner string | string | Member identifier of the ticket owner. |
| company string | string | Company identifier from search_companies. |
| query string | string | Text to find in the ticket summary. |
| updated_before string | string | ISO date. Only tickets last updated before this day. |
| cursor string | string | Next page cursor from the previous call. |
{
"tickets": [
{
"ticket_id": 30412,
"summary": "Printer queue stuck on floor 2",
"company": "Northwind Dental",
"board": "Service Desk",
"status": "New",
"priority": "Priority 2 - High",
"owner": null,
"updated_at": "2026-11-05T08:41:00Z"
},
{
"ticket_id": 30398,
"summary": "New starter needs laptop and mailbox",
"company": "Acme Logistics",
"board": "Service Desk",
"status": "In Progress",
"priority": "Priority 3 - Normal",
"owner": "mreyes",
"updated_at": "2026-11-04T15:12:00Z"
}
],
"next_cursor": null
} Returns the ticket header: summary, company, contact, board, status, type, subtype, item, priority and owner. It also returns the last 5 notes from the Discussion, Internal and Resolution tabs, so the agent can read before it acts. For the full thread use list_ticket_notes.
MCP callconnectwise_read → get_ticket
REST endpointGET https://cotlet.dev/api/v1/connectwise/tickets/{ticket_id}
| Name | Type | Description |
|---|---|---|
| ticket_id integer required | integer | Ticket number from list_tickets or get_ticket. |
{
"ticket_id": 1
} {
"ticket_id": 30412,
"summary": "Printer queue stuck on floor 2",
"company": "Northwind Dental",
"contact": "Dana Whitfield",
"board": "Service Desk",
"status": "New",
"type": "Incident",
"subtype": "Printing",
"item": "Printer",
"priority": "Priority 2 - High",
"owner": null,
"latest_notes": [
{
"note_id": 884102,
"note_type": "discussion",
"author": "Dana Whitfield",
"created_at": "2026-11-05T08:41:00Z",
"text": "Nothing prints since 8 am. Three people are waiting."
}
]
} Creates the ticket and returns its number. It lands on the board like a ticket created by hand, so anything your board does for new tickets runs. A board that does not exist, or a status or type that is not on that board, fails without creating anything. By default you confirm each call; you can switch that off for this tool.
MCP callconnectwise_write → create_ticket
REST endpointPOST https://cotlet.dev/api/v1/connectwise/tickets
| Name | Type | Description |
|---|---|---|
| summary string required | string | Ticket summary, one line. |
| company string required | string | Company identifier from search_companies. |
| board string required | string | Board name from list_service_boards. |
| status string | string | Status on that board. The board default when left out. |
| priority string | string | Priority name as shown on the ticket. |
| type string | string | Type from list_service_boards. |
| owner string | string | Member identifier of the owner. |
| description string | string | First note of the ticket, added as a Discussion note. |
{
"summary": "summary",
"company": "company",
"board": "board"
} {
"ticket_id": 30431,
"summary": "Quarterly review",
"company": "Northwind Dental",
"board": "Projects",
"status": "Scheduled",
"created_at": "2026-11-05T09:30:00Z"
} Changes only the fields you pass and returns the ticket as it is afterwards. When the board changes, PSA keeps status, type, subtype and item if they exist on the new board. A status change can run the rules of that board, such as closing, so each call waits for your OK. By default you confirm each call; you can switch that off for this tool.
MCP callconnectwise_write → update_ticket
REST endpointPATCH https://cotlet.dev/api/v1/connectwise/tickets/{ticket_id}
| Name | Type | Description |
|---|---|---|
| ticket_id integer required | integer | Ticket number from list_tickets or get_ticket. |
| status string | string | Status name on the ticket board. |
| priority string | string | Priority name as shown on the ticket. |
| type string | string | Type from list_service_boards. |
| subtype string | string | Subtype from list_service_boards. |
| item string | string | Item from list_service_boards. |
| owner string | string | Member identifier of the new owner. |
| board string | string | Move the ticket to this board. |
{
"ticket_id": 1
} {
"ticket_id": 30412,
"board": "Service Desk",
"status": "In Progress",
"priority": "Priority 2 - High",
"type": "Incident",
"owner": "mreyes",
"updated_at": "2026-11-05T09:12:00Z"
} Discussion, Internal and Resolution notes sit on separate tabs. Notes cannot be removed by these tools.
Returns notes oldest first, so the thread reads in order, with author, date and which tab each note sits on. PSA keeps Discussion, Internal and Resolution notes on separate tabs, and a ticket holds up to 1000 notes in total. Discussion notes are the ones customers can receive.
MCP callconnectwise_read → list_ticket_notes
REST endpointGET https://cotlet.dev/api/v1/connectwise/ticket-notes
| Name | Type | Description |
|---|---|---|
| ticket_id integer required | integer | Ticket number from list_tickets or get_ticket. |
| note_type string | string | all, discussion, internal or resolution. Default all. |
| cursor string | string | Next page cursor from the previous call. |
{
"ticket_id": 1
} {
"ticket_id": 30412,
"notes": [
{
"note_id": 884102,
"note_type": "discussion",
"author": "Dana Whitfield",
"created_at": "2026-11-05T08:41:00Z",
"text": "Nothing prints since 8 am. Three people are waiting."
},
{
"note_id": 884119,
"note_type": "internal",
"author": "Maria Reyes",
"created_at": "2026-11-05T09:05:00Z",
"text": "Spooler service stopped on the print server. Restarting after lunch."
}
],
"next_cursor": null
} Adds the note to the tab you name and returns its id. In PSA, Discussion notes go to external and internal contacts, while Internal and Resolution notes go only to internal contacts. A ticket holds up to 1000 notes in total, and a note cannot be removed by this tool. By default you confirm each call; you can switch that off for this tool.
MCP callconnectwise_write → add_ticket_note
REST endpointPOST https://cotlet.dev/api/v1/connectwise/tickets/{ticket_id}/note
| Name | Type | Description |
|---|---|---|
| ticket_id integer required | integer | Ticket number from list_tickets or get_ticket. |
| note_type string required | string | discussion, internal or resolution. |
| text string required | string | The note text. |
{
"ticket_id": 30412,
"note_type": "discussion",
"text": "Hi Dana, the print service stopped on the server. We restart it at 1 pm and will confirm here when printing works."
} {
"ticket_id": 30412,
"note_id": 884140,
"note_type": "discussion",
"created_at": "2026-11-05T09:20:00Z"
} Time feeds invoices. Work type and work role change the billed rate, so the agent leaves them out unless you name them.
Returns one row per time entry with ticket, member, date, hours, work type, work role and the billable option. Use it before add_time_entry to see whether a ticket already has time. Give at least one of ticket_id, member or a date range, so the result stays small.
MCP callconnectwise_read → list_time_entries
REST endpointGET https://cotlet.dev/api/v1/connectwise/time-entries
| Name | Type | Description |
|---|---|---|
| ticket_id integer | integer | Only entries on this ticket. |
| member string | string | Member identifier of who logged the time. |
| date_from string | string | ISO date, first day included. |
| date_to string | string | ISO date, last day included. |
| cursor string | string | Next page cursor from the previous call. |
{
"entries": [
{
"time_entry_id": 55120,
"ticket_id": 30398,
"member": "mreyes",
"date": "2026-11-04",
"hours": 1.5,
"work_type": "Remote",
"work_role": "Service Desk",
"billable_option": "Billable"
},
{
"time_entry_id": 55131,
"ticket_id": 30398,
"member": "mreyes",
"date": "2026-11-04",
"hours": 0.5,
"work_type": "Remote",
"work_role": "Service Desk",
"billable_option": "Billable"
}
],
"next_cursor": null
} Creates the time entry for the signed-in member and returns it. Work type and work role change the rate that is billed, so the agent leaves them out unless you name them. Time feeds invoices, so each call waits for your OK. By default you confirm each call; you can switch that off for this tool.
MCP callconnectwise_write → add_time_entry
REST endpointPOST https://cotlet.dev/api/v1/connectwise/tickets/{ticket_id}/time-entries
| Name | Type | Description |
|---|---|---|
| ticket_id integer required | integer | Ticket number from list_tickets or get_ticket. |
| hours number required | number | Hours worked, for example 1.5. |
| date string | string | ISO date of the work. Today when left out. |
| work_type string | string | Work type as named in PSA. |
| work_role string | string | Work role as named in PSA. |
| billable_option string | string | Billable, Do Not Bill or No Charge, as on the time entry screen. |
| notes string | string | Note of the time entry. |
{
"ticket_id": 1,
"hours": 1
} {
"time_entry_id": 55162,
"ticket_id": 30412,
"member": "mreyes",
"date": "2026-11-05",
"hours": 1.5,
"work_type": "Remote",
"billable_option": "Billable"
} Look up the company identifier before you create or filter tickets.
Returns the company identifier, name and status of each match. Use the identifier in create_ticket and list_tickets. A name that matches several companies returns all of them, and the agent asks you which one you mean.
MCP callconnectwise_read → search_companies
REST endpointGET https://cotlet.dev/api/v1/connectwise/companies/search
| Name | Type | Description |
|---|---|---|
| query string required | string | Part of the company name or its identifier. |
| cursor string | string | Next page cursor from the previous call. |
{
"query": "query"
} {
"companies": [
{
"company_id": "NorthwindDental",
"name": "Northwind Dental",
"status": "Active"
},
{
"company_id": "NorthwindVet",
"name": "Northwind Veterinary",
"status": "Active"
}
],
"next_cursor": null
} Your logged-in ConnectWise profile, encrypted with AES-256-GCM, in Finland. Not the password.
The 10 ConnectWise tools. Never the cookie or your login.
What your security role sees: tickets, notes, time, companies.
Invoices, agreements, members, security roles and deleting.
You sign in again in your own browser, with your MFA. We never type it.
Disable for agents, reset the browser, or end the session in ConnectWise.
Our site scores A+ on Qualys SSL Labs, securityheaders.com and Mozilla Observatory (live results).
You sign in with your own PSA login, so the agent sees and changes only what that login can. A technician role stays a technician role. Boards, tickets and companies you cannot open do not show up in a result.
Discussion notes are the ones customers can receive, so they wait for your OK. Internal and Resolution notes stay with your team.
When a ConnectWise screen moves or a field is renamed, the fix happens on our side and your prompts keep working. status.cotlet.dev watches the site, console and API every 3 minutes.
| Action | Undo |
|---|---|
| Create a ticket | Close or edit it in PSA |
| Update status, priority or owner | Set the old value again in PSA |
| Add a Discussion note | No, customers may already have it |
| Add an Internal or Resolution note | Edit or delete it in PSA, if your role allows |
| Add a time entry | Edit or delete it in PSA before it is invoiced |
All 4 write tools ask first: create_ticket, update_ticket, add_ticket_note, add_time_entry. Reads reach nobody and run at once. Switch the wait off per tool for a scheduled run.
A status change can run the rules of that board, such as closing, so check the board before you switch the wait off.
{
"name": "add_ticket_note",
"arguments": {
"ticket_id": 30412,
"note_type": "discussion",
"text": "Hi Dana, the print service stopped on the server. We restart it at 1 pm and will confirm here when printing works."
}
} curl -X POST https://cotlet.dev/api/v1/connectwise/tickets/30412/note \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"note_type":"discussion","text":"Hi Dana, the print service stopped on the server. We restart it at 1 pm and will confirm here when printing works."}' {
"ticket_id": 30412,
"note_id": 884140,
"note_type": "discussion",
"created_at": "2026-11-05T09:20:00Z"
} Scripts and cron jobs reach the same tools over REST at /api/v1/connectwise, with the same key and price. A write over REST waits for your OK too and answers 202 until you approve it. A failed call costs nothing. The tools table lists the REST path of each tool.
About $65 a monthfor one dispatcher at a small MSP, 11,180 credits. Your ConnectWise plan is billed by ConnectWise as before.
| Job | Calls | Credits |
|---|---|---|
| Triage new tickets, daily | 21 × 22 | 4,620 |
| Customer updates on open tickets, daily | 11 × 22 | 2,420 |
| Time entries for closed tickets, daily | 8 × 22 | 1,760 |
| Stale ticket report, weekly | 17 × 4 | 680 |
| Quarterly review tickets from a list | 10 × 2 | 200 |
| Cloud Browser, 30 days | 1,500 | |
| Total | 11,180 |
The month is our example: a small MSP with 10 to 25 technicians, about 10 new tickets a day, 22 working days. Dollars at the $29 pack rate. ConnectWise publishes no PSA prices; third-party buyer data (Vendr, February 2026) puts 10 to 25 users at $95 to $145 per user per month.
| Credits | Price, one time | Calls at 10 credits | Per 1,000 credits |
|---|---|---|---|
| 5,000 | $29 | 500 | $5.80 |
| 15,000 | $79 | 1,500 | $5.27 |
| 32,000 | $149 | 3,200 | $4.66 |
| 60,000 | $249 | 6,000 | $4.15 |
Only successful calls use credits; failed calls and timeouts cost nothing. At a balance of 0 calls pause, never a surprise charge. Authenticate with the Authorization: Bearer header.
| Option | What it does | Where it stops |
|---|---|---|
| mspstack/mcp-connectwise-psa | MIT, TypeScript. Tickets, notes, status, scheduling, time entries and timesheets. 6 stars, last push 7 Oct 2026. | You host it and give it a member's API keys and a clientId. |
| taddiemason/Connectwise-MCP-Server | MIT, Docker bridge for OpenWebUI, read only. 5 stars, last push 30 Oct 2026. | Reads only, and needs API keys. |
| Xssentials/connectwise-mcp | 21 tools for ConnectWise Manage. 1 star, last push 2 Sep 2026. | Local, API keys in a config file. |
| Allied Business Solutions | 120 tools across service desk, projects, time, companies and finance, listed 25 Apr 2026. | Built on the REST API, so it needs API keys too. |
| Pipedream | Hosted MCP for ConnectWise PSA on Pipedream's connected accounts. | Needs a Pipedream account and the API credentials. |
| Zapier | Official ConnectWise Manage app: create tickets, notes, companies, contacts. | No time entry action, and notes arrive by polling. |
ConnectWise has no MCP server of its own (checked 6 Nov 2026). The community servers are good if you are happy to run one and keep the API keys safe. Cotlet is for the dispatcher who wants the same jobs inside ChatGPT or Claude without an API member, a clientId or a server.
Take Group sp. z o.o. runs Cotlet. It is a software company registered in Poland, EU (VAT PL6342984273), working since 2019, founded by Bart Krawczyk (Co-founder, CTO) and Jake Slugocki (Co-founder, SEO, sales and marketing). Write to contact@cotlet.dev with questions about the ConnectWise tools.
Sign up at cotlet.dev, log in to ConnectWise once in its Cloud Browser and add Cotlet to ChatGPT or Claude, following the steps in your console. No API key is needed. ChatGPT and Claude then work in your ConnectWise account through Cotlet, in their phone apps too.
With Cotlet, ChatGPT, Claude and other AI agents handle jobs like these in ConnectWise: triage new service tickets; update customers on open tickets; log time on finished tickets. You ask in plain words, and anything that reaches other people waits for your OK first.
Not that we could find (checked 6 Nov 2026). ConnectWise PSA has an official REST API and an official Zapier app. MCP servers for PSA come from the community and other vendors.
Create a free account at https://cotlet.dev, open ConnectWise under Integrations in the console and log in to ConnectWise PSA in its Cloud Browser. The trial starts with your first app, no card asked.
No. Cotlet works through your own ConnectWise sign-in, so you create no API member and no keys. The official REST API needs a Developer Network login, a ClientId and an API member with a public and a private key (ConnectWise Developer Network; ConnectWise Docs, PSA integration).
Discussion notes are sent to external and internal contacts. Internal and Resolution notes go only to internal contacts (ConnectWise Docs, Ticket Tab - Service Ticket). A Discussion note from your agent waits for your OK by default.
Yes. Cotlet is hosted, so the chat apps reach it by the shared address. Nothing runs on your computer.
Yes. Call Cotlet's REST API from a script or an HTTP step in n8n or Make with your Cotlet key. The same tools, inputs and price as MCP.
No. Cotlet is independent and not affiliated with ConnectWise, LLC.
Free 7-day trial with 2,000 credits, no card needed.
May we use Google Analytics to see which pages help people? It sets cookies only if you accept. No ads, and we never sell data. Cookie policy