Cw
БесплатноНе проверенMCP server for ConnectWise Manage ticket workflows, with Docker, auth, health checks, and CI/security scanning.
Описание
MCP server for ConnectWise Manage ticket workflows, with Docker, auth, health checks, and CI/security scanning.
README
A small FastMCP server that gives n8n or any MCP-capable client a cleaner way to work with ConnectWise Manage.
This scaffold is intentionally narrow and practical:
- ticket-focused v1
- HTTP-friendly for Azure deployment
- stdio-friendly for local testing
- central auth/retry/query logic in one place
- lightweight health checks for container and platform probes
The repository is aimed at two main use cases:
- an MCP server that AI agents can call over HTTP or stdio
- a small, readable codebase you can safely adapt for your own ConnectWise tenant
Production deployment at a glance
For production, treat this as a small private API service that happens to speak MCP:
- run the container image from GHCR
- keep
AUTH_ENABLED=true - use a long random
AUTH_BEARER_TOKEN - store ConnectWise keys and bearer tokens as platform secrets
- prefer private/internal ingress; if exposed externally, put it behind HTTPS and source restrictions
- pin deployments to a release tag or SHA tag instead of relying on
latest
Useful docs:
Published image:
ghcr.io/renierm26/connectwise-manage-mcp:latest
For repeatable deployments, prefer a versioned image once releases are cut:
ghcr.io/renierm26/connectwise-manage-mcp:v0.7.0
Security model
The HTTP transport exposes two important routes:
/mcp— protected by bearer-token middleware whenAUTH_ENABLED=true/live— unauthenticated liveness check for platform/container probes; does not call ConnectWise/health— unauthenticated readiness/upstream check; verifies configuration and ConnectWise reachability, but returns only minimal operational status
Additional controls:
AUTH_ALLOWED_IPScan restrict/mcpto specific IPs or CIDR rangesAUTH_TRUST_X_FORWARDED_FOR=truecan be used behind trusted proxies when enforcing forwarded client IPsMCP_STATELESS_HTTP=trueis recommended for n8n and hosted Streamable HTTP clients- the Docker image runs as a non-root user
- the Compose example uses a read-only filesystem, drops capabilities, and disables privilege escalation
- CI runs unit tests, type checks, linting, container startup/auth smoke tests, CodeQL, and Trivy scanning
Do not commit .env files or real ConnectWise credentials.
Release and deploy checklist
Before promoting a deployment:
- Confirm the target commit has green CI and security checks.
- Prefer a release tag such as
v0.7.0, or use the Git SHA image tag. - Update the runtime platform to the selected image tag.
- Verify
/liveafter deployment, then/healthfor upstream readiness. - Verify your MCP client can connect to
/mcpwith the bearer token. - Run a safe read-only tool first, for example
list_boardsorsearch_members.
Architecture at a glance
The request flow is intentionally simple:
- an MCP client calls a tool exposed by FastMCP
- the tool function in
src/connectwise_manage_mcp/tools/validates and shapes arguments ConnectWiseClientbuilds the request, auth headers, and query conditions- the ConnectWise Manage REST API returns raw data
- the tool returns both a compact summary and the raw payload when useful
That split keeps the codebase easy to reason about:
server.pyhandles transport and health checkstools/handles MCP-facing workflowsconnectwise/client.pyhandles API communicationmodels.pyholds lightweight shared shapesconfig.pycentralizes environment-backed settings
Common call sequences for smaller models
If you are choosing tools programmatically, these flows are the safest starting points.
Create a ticket
search_companiesto find the numericcompany_id- optional
search_contactsto find the numericcontact_id - optional
list_boardsif the correct board name is uncertain create_ticket
Update ticket status safely
get_ticketto inspect the current boardlist_boardsif you need to confirm the board idget_board_statusesorget_board_lookupto fetch valid board-specific status namesupdate_ticket_status
Reclassify a ticket safely
get_ticketto inspect current valueslist_boardsto find the numeric board idget_board_lookupto fetch valid status, type, subtype, item, and team names- if changing hierarchy fields, choose them in order:
type_name->sub_type_name->item_name - optional
get_board_subtypesorget_board_itemsfor narrower hierarchy checks update_ticket_classifications
Small-model hint: item_name is not a board-wide independent choice. It only becomes valid after a matching type_name and sub_type_name are chosen.
Add a time entry safely
search_membersto find themember_identifier- optional
list_locationswhen ConnectWise location restrictions apply and the default location may need to be overridden list_work_typesto validatework_typelist_work_rolesto validatework_roleadd_ticket_time_entry
Small-model hint: if a time-entry create fails with a location-related error, the recovery path should be list_locations and then retry add_ticket_time_entry with an allowed numeric location_id.
Names vs ids
ConnectWise write calls mix numeric ids and human-readable names. This is the easiest place for smaller models to make mistakes.
company_idandcontact_idare numeric idsboard_id,type_id, andsubtype_idare numeric ids used by lookup toolsboardincreate_ticketis a board name, not a board idstatusinupdate_ticket_statusis a board-specific status name, not a status id- prefer ids in
update_ticket_classificationswhen known:board_id,status_id,priority_id,type_id,subtype_id,item_id, andteam_id - name fields are still accepted for compatibility:
board,status,type_name,sub_type_name,item_name,team,severity,impact, andsource - lookup tools use
subtype_id; update tools usesubtype_id type_id,subtype_id, anditem_idare a hierarchy, not three independent fields- choose
type_idfirst, thensubtype_id, thenitem_id member_identifierinadd_ticket_time_entryis a string identifier, not the numeric member idlocation_idinadd_ticket_time_entryis a numeric location idwork_typeandwork_roleinadd_ticket_time_entryare names, not ids
Tool response patterns
Most tools follow one of these response shapes.
Single-record reads
{
"ok": true,
"data": {"...": "raw record"},
"summary": {"...": "compact normalized view"}
}
Search and list tools
{
"ok": true,
"count": 2,
"data": [{"...": "compact normalized view"}],
"raw": [{"...": "raw records"}]
}
Bundle tools
{
"ok": true,
"ticket": {"summary": {}, "description": "...", "raw": {}},
"notes": {"count": 0, "data": [], "raw": []},
"timeEntries": {"count": 0, "data": [], "raw": []}
}
Which read tool to choose
When several read tools look similar, use the narrowest tool that answers the question.
- use
search_ticketswhen you do not know the ticket id yet - use
get_ticketwhen you know the ticket id and only need the current ticket record - use
get_ticket_bundlewhen you know the ticket id and need ticket details plus notes and time entries together - use
get_ticket_configuration_lookupwhen you need configuration items already attached to the ticket or assigned to the ticket contact - use
suggest_company_configuration_for_usernamewhen you need the closest company configuration matches for a username before attaching a configuration item; returns the top 5 by default - use
attach_ticket_configurationafter choosing theconfiguration_idto attach it to the service ticket - use
get_ticket_noteswhen you only need notes - use
get_ticket_time_entrieswhen you only need time entries - use
get_companywhen you already knowcompany_id - use
search_companieswhen you only know a company name or identifier fragment - use
search_contactswhen you need a numericcontact_id
Common write recovery paths
If a write fails validation or a required value is unknown, use the matching lookup tool and retry.
- unknown
company_idforcreate_ticket->search_companies - unknown
contact_idforcreate_ticket->search_contacts - invalid status for
update_ticket_status->get_ticket, thenget_board_statusesorget_board_lookup - invalid board, type, subtype, item, or team for
update_ticket_classifications->get_ticket, optionallist_boards, thenget_board_lookup - unknown
member_identifierforadd_ticket_time_entry->search_members - unknown
work_typeforadd_ticket_time_entry->list_work_types - unknown
work_roleforadd_ticket_time_entry->list_work_roles - location-restriction error or unknown
location_idforadd_ticket_time_entry->list_locations
Included tools
get_ticketget_ticket_bundleget_ticket_configuration_lookupsuggest_company_configuration_for_usernameattach_ticket_configurationsearch_ticketslist_sla_risk_ticketscreate_ticketupdate_ticket_statusupdate_ticket_classificationspatch_ticket_classifications_unvalidatedpatch_ticket_type_hierarchy_unvalidatedadd_ticket_noteupdate_ticket_notedelete_ticket_notesave_managed_internal_summary_noteget_ticket_notesget_ticket_time_entriesadd_ticket_time_entrylist_boardsget_board_lookupget_board_statusesget_board_typesget_board_subtypesget_board_itemsget_ticket_type_hierarchysearch_memberslist_work_typeslist_work_roleslist_locationslist_work_typeslist_work_rolesget_companysearch_companiessearch_contacts
Project layout
connectwise-manage-mcp/
├── .devcontainer/
├── .github/workflows/
├── docs/
├── src/connectwise_manage_mcp/
│ ├── connectwise/
│ ├── tools/
│ ├── app.py
│ ├── config.py
│ ├── models.py
│ └── server.py
├── scripts/
├── tests/
├── .dockerignore
├── .env.example
├── .env.azure.example
├── compose.example.yml
├── Dockerfile
├── pyproject.toml
└── README.md
Operations quick links
- Local HTTP endpoint:
http://localhost:8000/mcp - Liveness endpoint:
http://localhost:8000/live - Readiness/upstream health endpoint:
http://localhost:8000/health - Compose example:
compose.example.yml - Runtime smoke test:
scripts/runtime-smoke.sh - CI workflow:
.github/workflows/ci.yml - Security workflow:
.github/workflows/security.yml - Release workflow:
.github/workflows/release.yml - Security policy:
SECURITY.md - Changelog:
CHANGELOG.md
Quick start
1. Copy the folder wherever you want
2. Create your env file
cp .env.example .env
Fill in:
CW_BASE_URLCW_COMPANY_IDCW_PUBLIC_KEYCW_PRIVATE_KEYCW_CLIENT_IDAUTH_BEARER_TOKEN- optionally
AUTH_ALLOWED_IPS
Auth defaults to enabled. For normal Docker, VM, or Azure deployments, leave it that way and use a long random bearer token.
The devcontainer disables auth automatically for local VS Code work.
If you expose the service publicly, adding AUTH_ALLOWED_IPS gives you a second safety layer on top of the bearer token.
Example base URL:
https://your-company.connectwise.com/v4_6_release/apis/3.0
3. Install dependencies and run locally
With uv
uv sync
uv run cwmcp-http
With pip
python -m venv .venv
source .venv/bin/activate
pip install -e .
cwmcp-http
Server endpoint:
http://localhost:8000/mcp
Liveness endpoint:
http://localhost:8000/live
Readiness/upstream health endpoint:
http://localhost:8000/health
Notes:
GET /liveis browser-friendly, returns JSON, and does not call ConnectWise.GET /healthis browser-friendly, returns JSON, and checks configuration plus ConnectWise reachability./healthis intentionally minimal and does not echo raw ConnectWise tenant or licensing details.GET /mcpis not a normal human web page. A plain browser request can return a protocol-level error like406 Not Acceptable, which is expected for FastMCP HTTP transport.- When auth is enabled, MCP clients must send
Authorization: Bearer <AUTH_BEARER_TOKEN>for/mcprequests. - If
AUTH_ALLOWED_IPSis set, only those source IPs or CIDR ranges can reach/mcp.
Preflight configuration check
Validate environment-backed configuration without calling ConnectWise:
cwmcp-preflight
The command prints JSON, redacts secret values by reporting only whether each required setting is configured, and exits non-zero when required configuration is missing or invalid.
Run in stdio mode
Useful for local MCP testing or editor integrations that prefer stdio transport:
cwmcp-stdio
FastMCP CLI test examples
These examples are useful when you want to test the server without wiring a full client first.
Inspect the local server from source
./.venv/bin/fastmcp inspect src/connectwise_manage_mcp/server.py
List available tools from source
./.venv/bin/fastmcp list src/connectwise_manage_mcp/server.py
Call a tool directly from source
./.venv/bin/fastmcp call src/connectwise_manage_mcp/server.py get_ticket --input-json '{"ticket_id": 12345}' --json
Run the server with FastMCP itself over HTTP
./.venv/bin/fastmcp run src/connectwise_manage_mcp/server.py --transport http --host 127.0.0.1 --port 8000
List tools from a running HTTP endpoint
./.venv/bin/fastmcp list http://127.0.0.1:8000/mcp --auth 'super-secret-token'
Call a tool on a running HTTP endpoint
./.venv/bin/fastmcp call http://127.0.0.1:8000/mcp get_board_types --input-json '{"board_id": 12}' --auth 'super-secret-token' --json
Open the FastMCP inspector for local development
./.venv/bin/fastmcp dev inspector src/connectwise_manage_mcp/server.py
Practical notes:
- source-based commands are handy before env vars or containers are fully wired
- HTTP-based commands are handy for validating the real deployed transport path
- if auth is enabled, pass the bearer token with
--auth '<token>' - if the server is not configured, tool calls will fail cleanly and
/healthwill explain why
Run in Docker
docker build -t connectwise-manage-mcp .
docker run --rm -p 8000:8000 --env-file .env connectwise-manage-mcp
Container/platform probes should generally target:
/livefor liveness checks that should not depend on ConnectWise availability/healthfor readiness checks that should include configuration and ConnectWise reachability/mcponly for actual MCP clients with a bearer token, and from allowed IPs if an allowlist is configured
Run in VS Code devcontainer
The included devcontainer is set up to:
- install the project in editable mode
- forward container port
8000 - auto-start
cwmcp-httpwhen the container starts - disable bearer auth by default for local VS Code development
After reopening in the devcontainer, useful checks are:
cat /tmp/cwmcp-http.log
curl http://127.0.0.1:8000/live
curl http://127.0.0.1:8000/health
Suggested Azure deploy target
Use Azure Container Apps if you want the easiest path.
Files added for that:
.env.azure.exampledocs/AZURE_CONTAINER_APPS.md
Recommended split in Azure:
- normal config as env vars
CW_PUBLIC_KEYandCW_PRIVATE_KEYas Container App secrets- internal ingress if only n8n needs access
Main MCP endpoint after deploy:
https://<your-app-fqdn>/mcp
n8n and MCP client usage
Option 1, MCP-aware client path
Use an MCP-capable client or gateway that can connect to:
https://your-service-url/mcp
When auth is enabled, send:
Authorization: Bearer <AUTH_BEARER_TOKEN>
Option 2, plain HTTP helper routes
If you later add your own custom helper endpoints, n8n can call those with standard HTTP Request nodes. In the current scaffold, the custom helper routes are:
GET /live
GET /health
That means the main /mcp endpoint should be treated as MCP transport, not as a generic REST endpoint.
Notes on ConnectWise Manage
This scaffold normalizes some ugly parts of the API, but it is still a thin wrapper. You will probably want to tune:
- status names per board
- required fields for your tenant
- custom field handling
- pagination limits
- agreement / board / company filtering logic
Ticket workflow coverage in this version
This version is shaped around a common triage and update flow:
- read ticket summary and description
- read notes
- read time entries
- read ticket-attached and contact-linked configuration items
- suggest company configuration items by closest username match
- update classification fields like status, priority, board, type, subtype, item, team, severity, impact, and source
- add ticket notes
- update ticket notes
- delete ticket notes
- add time entries
- look up valid boards, statuses, types, subtypes, items, teams, members, work types, and work roles before updating
The quickest tool for AI-driven review is get_ticket_bundle, which returns the ticket plus notes and time entries in one response.
The safest classification flow is usually:
list_boardsget_board_statusesorget_board_lookupget_ticket_type_hierarchyor the step tools:get_board_types, thenget_board_subtypes, thenget_board_itemsupdate_ticket_classificationswith ids when available
Classification hierarchy lookup results are intentionally compact: types, subtypes, and items return only id and name unless include_raw=true is requested.
The safest time-entry flow is usually:
search_memberslist_work_typeslist_work_rolesadd_ticket_time_entry
A typical triage flow is:
get_ticket_bundle- optional
get_ticket_configuration_lookupto see current ticket/contact configuration items - optional
suggest_company_configuration_for_usernameto pick the closest company configuration by username-like fields (lastLoginName,deviceIdentifier, or name) - optional
attach_ticket_configurationonce you have confirmed the correctconfiguration_id - decide on board/status/type updates
update_ticket_classificationsadd_ticket_noteif you want to record the action taken
For paragraph-style notes, prefer text_blocks, content_blocks, notes_blocks, or
internal_notes_blocks. The server joins blocks with blank lines, which avoids fragile
empty-string line items in LLM workflows. Use *_lines only when you need exact
line-by-line control; the server joins lines with newline characters.
Note formatting inputs
Several note-writing tools accept three text shapes. Use exactly one shape per field:
- Direct string:
text,content,initial_description,notes, orinternal_notes - Line array:
*_lines, joined with single newline characters - Paragraph block array:
*_blocks, joined with blank lines
Prefer *_blocks for LLM-generated notes that need paragraph spacing. This avoids
depending on empty string array entries, which some clients or smaller models may drop.
Blank-line preservation was live-tested against ConnectWise by posting and reading back
an internal ticket note.
Example add_ticket_note arguments:
{
"ticket_id": 12345,
"internal": true,
"text_blocks": [
"LLM triage summary:",
"User cannot access VPN after password reset.\nClassification appears to be remote-access VPN.",
"Next actions:\n- Validate MFA state\n- Ask user to retest"
]
}
Example save_managed_internal_summary_note arguments:
{
"ticket_id": 12345,
"content_blocks": [
"LLM classification summary:",
"Classification applied: type_id=3, subtype_id=9, item_id=14.\nStatus changed: status_id=2.",
"Reasoning: Notes and initial description indicate a remote-access VPN incident."
]
}
Example tool calls and results
These are intentionally small, human-readable examples of the shapes this server returns. Exact fields from ConnectWise can vary by tenant.
Example: get_ticket_bundle
Tool call arguments:
{
"ticket_id": 12345,
"notes_page_size": 10,
"time_entries_page_size": 10
}
Example result excerpt:
{
"ok": true,
"ticket": {
"summary": {
"id": 12345,
"summary": "User cannot access VPN",
"board": "Service Desk",
"status": "New",
"type": "Incident",
"subType": "Remote Access",
"item": "VPN",
"priority": "Priority 2",
"company": "Example Co",
"contact": "Jane Smith",
"owner": "helpdesk1",
"updatedAt": "2026-04-20T16:00:00Z"
},
"description": "User reports VPN login failures after password reset.",
"raw": { "...": "full ticket payload" }
},
"notes": {
"count": 2,
"data": [
{
"id": 555,
"text": "Reset VPN profile and requested retest.",
"createdBy": "helpdesk1",
"createdAt": "2026-04-20T15:40:00Z",
"internal": true,
"detail": true,
"resolution": false
}
],
"raw": [
{ "...": "full note payload" }
]
},
"timeEntries": {
"count": 1,
"data": [
{
"id": 777,
"member": "helpdesk1",
"timeStart": "2026-04-20T15:30:00Z",
"timeEnd": "2026-04-20T15:45:00Z",
"actualHours": 0.25,
"hoursDeduct": 0.25,
"billableOption": "Billable",
"workType": "Remote Support",
"workRole": "Engineer",
"notes": "Investigated VPN reset issue.",
"internalNotes": null
}
],
"raw": [
{ "...": "full time entry payload" }
]
}
}
Example: get_ticket_configuration_lookup
Tool call arguments:
{
"ticket_id": 12345
}
Example result excerpt:
{
"ok": true,
"ticketId": 12345,
"attached": {
"count": 1,
"references": [{"id": 77, "deviceIdentifier": "LAPTOP-77"}],
"data": [{"id": 77, "name": "Jane Laptop", "lastLoginName": "jane.smith"}]
},
"contactConfigurations": {
"contactId": 2,
"count": 1,
"data": [{"id": 77, "name": "Jane Laptop", "deviceIdentifier": "LAPTOP-77"}]
}
}
Example: suggest_company_configuration_for_username
Tool call arguments:
{
"ticket_id": 12345,
"username": "jane.smith"
}
Example result excerpt:
{
"ok": true,
"ticketId": 12345,
"companyId": 1,
"usernameCandidates": ["jane.smith"],
"count": 5,
"totalMatched": 100,
"limit": 5,
"suggestion": {
"id": 77,
"name": "Jane Laptop",
"deviceIdentifier": "LAPTOP-77",
"lastLoginName": "jane.smith",
"match": {
"score": 1.0,
"matchedUsername": "jane.smith",
"matchedField": "lastLoginName",
"matchedValue": "jane.smith"
}
}
}
Example: attach_ticket_configuration
Tool call arguments:
{
"ticket_id": 12345,
"configuration_id": 77,
"device_identifier": "LAPTOP-77"
}
Example result excerpt:
{
"ok": true,
"ticketId": 12345,
"configurationId": 77,
"deviceIdentifier": "LAPTOP-77",
"attached": true,
"data": {"id": 77, "deviceIdentifier": "LAPTOP-77"},
"attachedReferences": [{"id": 77, "deviceIdentifier": "LAPTOP-77"}]
}
Example: update_ticket_classifications
Tool call arguments:
{
"ticket_id": 12345,
"board_id": 12,
"status_id": 2,
"type_id": 3,
"subtype_id": 9,
"item_id": 14,
"priority_id": 7
}
Example result excerpt:
{
"ok": true,
"ticketId": 12345,
"updated": {
"status": null,
"statusId": 2,
"priority": null,
"priorityId": 7,
"board": null,
"boardId": 12,
"type": null,
"typeId": 3,
"subType": null,
"subTypeId": 9,
"item": null,
"itemId": 14,
"team": null,
"teamId": null,
"severity": null,
"impact": null,
"source": null
},
"data": {
"...": "raw patch response"
}
}
Example: get_board_lookup
Tool call arguments:
{
"board_id": 12,
"type_id": 3,
"subtype_id": 9
}
Example result excerpt:
{
"ok": true,
"boardId": 12,
"statuses": [
{
"id": 1,
"name": "New",
"board": "Service Desk",
"sort": 0,
"closed": false,
"inactive": false
}
],
"types": [
{
"id": 3,
"name": "Incident"
}
],
"teams": [
{
"id": 4,
"name": "Helpdesk",
"location": "HQ",
"department": "Support"
}
],
"subtypes": [
{
"id": 9,
"name": "Remote Access"
}
],
"items": [
{
"id": 14,
"name": "VPN"
}
],
"raw": {
"...": "full lookup payloads"
}
}
Example: add_ticket_time_entry
Tool call arguments:
{
"ticket_id": 12345,
"member_identifier": "helpdesk1",
"time_start": "2026-04-20T15:30:00Z",
"time_end": "2026-04-20T15:45:00Z",
"actual_hours": 0.25,
"hours_deduct": 0.25,
"location_id": 7,
"work_type": "Remote Support",
"work_role": "Engineer",
"notes_blocks": [
"Investigated VPN reset issue.",
"- reset MFA\n- confirmed VPN access"
]
}
Example result excerpt:
{
"ok": true,
"ticketId": 12345,
"data": {
"...": "raw time-entry payload"
},
"summary": {
"id": 777,
"member": "helpdesk1",
"timeStart": "2026-04-20T15:30:00Z",
"timeEnd": "2026-04-20T15:45:00Z",
"actualHours": 0.25,
"hoursDeduct": 0.25,
"locationId": 7,
"location": null,
"billableOption": null,
"workType": "Remote Support",
"workRole": "Engineer",
"notes": "Investigated VPN reset issue.",
"internalNotes": null
}
}
Troubleshooting
/health returns 503 Service Unavailable
This usually means the server started, but the required ConnectWise environment variables are missing. Check:
CW_BASE_URLCW_COMPANY_IDCW_PUBLIC_KEYCW_PRIVATE_KEYCW_CLIENT_ID
/mcp returns 405 Method Not Allowed or 406 Not Acceptable in a browser
That is usually expected.
The /mcp route is an MCP transport endpoint, not a normal browser page.
Use an MCP client, FastMCP CLI, or check /health in a browser instead.
VS Code forwards a port, but nothing answers on it
In the devcontainer flow, the container may be up before the MCP server has started. Check:
cat /tmp/cwmcp-http.log
curl http://127.0.0.1:8000/health
If needed, restart the server inside the container:
pkill -f cwmcp-http || true
nohup cwmcp-http >/tmp/cwmcp-http.log 2>&1 &
FastMCP CLI tool calls fail immediately
That usually means one of three things:
- the server is not configured yet
- the tool arguments do not match the expected JSON shape
- auth is enabled and the bearer token was not supplied
Try these first:
./.venv/bin/fastmcp inspect src/connectwise_manage_mcp/server.py
./.venv/bin/fastmcp list src/connectwise_manage_mcp/server.py
curl http://127.0.0.1:8000/health
MCP endpoint returns 401 Unauthorized
That usually means bearer auth is enabled and the client did not send the expected token.
For HTTP clients, send:
Authorization: Bearer <AUTH_BEARER_TOKEN>
For FastMCP CLI, use:
./.venv/bin/fastmcp list http://127.0.0.1:8000/mcp --auth '<AUTH_BEARER_TOKEN>'
First improvements I would make
- add board-specific status validation
- add
get_company_by_identifier - add
assign_ticket - add structured models for richer response validation
- add integration tests with mocked ConnectWise responses
Security
Do not commit real .env files. Use Azure Container App secrets or Key Vault in production.
At minimum, treat these as secrets:
CW_PUBLIC_KEYCW_PRIVATE_KEYCW_CLIENT_IDAUTH_BEARER_TOKEN
It is also worth limiting exposure of the HTTP transport. If only automation clients need access, prefer private networking or internal ingress over public internet exposure.
Even with bearer auth enabled, private ingress is still the better default when available.
For public exposure, a good pattern is bearer auth plus AUTH_ALLOWED_IPS for the known client or gateway egress ranges.
Установить Cw в Claude Desktop, Claude Code, Cursor
unyly install cwСтавит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.
Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh
Или настроить вручную
Выполни в терминале:
claude mcp add cw -- uvx --from git+https://github.com/RenierM26/cw-mcp connectwise-manage-mcpПошаговые гайды: как установить Cw
FAQ
Cw MCP бесплатный?
Да, Cw MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Cw?
Нет, Cw работает без API-ключей и переменных окружения.
Cw — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Cw в Claude Desktop, Claude Code или Cursor?
Открой Cw на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
автор: duxiaohuiSupabase
Database, auth and storage
автор: SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare Cw with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
