@alfe.ai/connectwise-psa-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # ConnectWise PSA MCP
2
+
3
+ Runtime-neutral stdio MCP server for ConnectWise PSA (formerly Manage). It runs
4
+ under OpenClaw, Hermes, or Claude Code through the Alfe integration manifest.
5
+
6
+ Launch the released package with `npx -y @alfe.ai/connectwise-psa-mcp@0.1.0`.
7
+ Node 22+ and an existing `alfe login` configuration are required. There are no
8
+ provider-credential environment variables or separate login files.
9
+
10
+ Create a **ConnectWise PSA** connection in Alfe Connections with the final PSA
11
+ API server origin, company ID, and an API member's public/private keys. The
12
+ Alfe service supplies its registered PSA ClientID. For cloud tenants, use your
13
+ region's API hostname (for example `https://api-na.myconnectwise.net`), rather
14
+ than a browser deep link. On-premises deployments require a publicly resolvable
15
+ HTTPS hostname and a trusted TLS certificate. The runtime appends
16
+ `/v4_6_release/apis/3.0` and refuses redirects and non-public destinations.
17
+
18
+ Call `connectwise_psa_list_connections` first. Every provider operation requires
19
+ the selected `connectionId`; it never silently chooses a default account.
20
+ Credentials are resolved again on every operation, so changing keys or removing
21
+ access applies to the next call without restarting the server.
22
+
23
+ Available tools:
24
+
25
+ - Connection discovery
26
+ - List companies, contacts, service boards, priorities, members, and tickets
27
+ - Get a company, contact, or ticket
28
+ - List a board's statuses and a ticket's direct notes
29
+ - Create a service ticket, update selected fields, and append a note
30
+
31
+ List tools return a single page (25 rows by default, maximum 100), with `page`,
32
+ `pageSize`, `conditions`, and `orderBy`. Notes default to internal-only.
33
+ Discussion/resolution notes may be customer-visible; select visibility
34
+ deliberately. Writes are never automatically retried: after a timeout, read the
35
+ ticket or notes to check whether the write succeeded before repeating it.
36
+ The API member's PSA security role controls which operations are allowed.
37
+
38
+ ## API references and verification status
39
+
40
+ - [ConnectWise Getting Started](https://developer.connectwise.com/Best_Practices/Getting_Started)
41
+ documents product-specific developer access and sandbox requests.
42
+ - [PSA REST reference](https://developer.connectwise.com/Products/Manage/REST)
43
+ and [Developer Guide](https://developer.connectwise.com/Products/Manage/Developer_Guide)
44
+ are the canonical endpoint and authentication references.
45
+
46
+ On 2026-09-08 the REST reference and Developer Guide required an approved
47
+ Developer Network login. The implementation uses the PSA REST 3.0 resource
48
+ contract, with local HTTP-contract/security/MCP-exchange tests; those tests do
49
+ not constitute verification against a customer PSA server. An approved PSA
50
+ sandbox should verify company/board/status reads, create a disposable ticket,
51
+ update it, and add an internal note before release. Version-specific codebase
52
+ paths other than `/v4_6_release` are not supported by this release.
53
+
54
+ ## Development
55
+
56
+ ```sh
57
+ pnpm --filter @alfe.ai/connectwise-psa-mcp typecheck
58
+ pnpm --filter @alfe.ai/connectwise-psa-mcp lint
59
+ pnpm --filter @alfe.ai/connectwise-psa-mcp test
60
+ pnpm --filter @alfe.ai/connectwise-psa-mcp build
61
+ ```
package/dist/bin.cjs ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ const require_server = require("./server2.cjs");
3
+ //#region src/bin.ts
4
+ require_server.main().then((server) => {
5
+ let closing;
6
+ const shutdown = () => {
7
+ closing ??= server.close().catch(() => {
8
+ process.stderr.write("ConnectWise PSA shutdown failed.\n");
9
+ });
10
+ };
11
+ process.once("SIGTERM", shutdown);
12
+ process.once("SIGINT", shutdown);
13
+ }).catch((error) => {
14
+ process.stderr.write(`${require_server.safeErrorMessage(error)}\n`);
15
+ process.exitCode = 1;
16
+ });
17
+ //#endregion
package/dist/bin.d.cts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/bin.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { i as main, o as safeErrorMessage } from "./server2.js";
3
+ //#region src/bin.ts
4
+ main().then((server) => {
5
+ let closing;
6
+ const shutdown = () => {
7
+ closing ??= server.close().catch(() => {
8
+ process.stderr.write("ConnectWise PSA shutdown failed.\n");
9
+ });
10
+ };
11
+ process.once("SIGTERM", shutdown);
12
+ process.once("SIGINT", shutdown);
13
+ }).catch((error) => {
14
+ process.stderr.write(`${safeErrorMessage(error)}\n`);
15
+ process.exitCode = 1;
16
+ });
17
+ //#endregion
18
+ export {};
@@ -0,0 +1,7 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_server = require("./server2.cjs");
3
+ exports.PsaClient = require_server.PsaClient;
4
+ exports.SERVER_NAME = require_server.SERVER_NAME;
5
+ exports.SERVER_VERSION = require_server.SERVER_VERSION;
6
+ exports.createServer = require_server.createServer;
7
+ exports.main = require_server.main;
@@ -0,0 +1,102 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
3
+ import { z } from "zod";
4
+
5
+ //#region src/transport.d.ts
6
+ interface ResolvedAddress {
7
+ address: string;
8
+ family: number;
9
+ }
10
+ type ResolveHost = (hostname: string) => Promise<ResolvedAddress[]>;
11
+ interface RequestInput {
12
+ url: URL;
13
+ address: ResolvedAddress;
14
+ method: 'GET' | 'POST' | 'PATCH';
15
+ headers: Record<string, string>;
16
+ body?: string;
17
+ }
18
+ type SendRequest = (input: RequestInput) => Promise<unknown>;
19
+ //#endregion
20
+ //#region src/client.d.ts
21
+ declare const listSchema: z.ZodObject<{
22
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
23
+ pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
24
+ conditions: z.ZodOptional<z.ZodString>;
25
+ orderBy: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strict>;
27
+ declare const createTicketSchema: z.ZodObject<{
28
+ summary: z.ZodString;
29
+ companyId: z.ZodCoercedNumber<unknown>;
30
+ boardId: z.ZodCoercedNumber<unknown>;
31
+ contactId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
32
+ statusId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
33
+ priorityId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
34
+ initialDescription: z.ZodOptional<z.ZodString>;
35
+ }, z.core.$strict>;
36
+ declare const updateTicketSchema: z.ZodObject<{
37
+ summary: z.ZodOptional<z.ZodString>;
38
+ statusId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
39
+ priorityId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
40
+ ownerId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
41
+ contactId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
42
+ }, z.core.$strict>;
43
+ declare const noteSchema: z.ZodObject<{
44
+ text: z.ZodString;
45
+ visibility: z.ZodDefault<z.ZodEnum<{
46
+ internal: "internal";
47
+ discussion: "discussion";
48
+ resolution: "resolution";
49
+ }>>;
50
+ }, z.core.$strict>;
51
+ declare const LIST_PATHS: {
52
+ readonly companies: "/company/companies";
53
+ readonly contacts: "/company/contacts";
54
+ readonly boards: "/service/boards";
55
+ readonly priorities: "/service/priorities";
56
+ readonly members: "/system/members";
57
+ readonly tickets: "/service/tickets";
58
+ };
59
+ type ListResource = keyof typeof LIST_PATHS;
60
+ type ListInput = z.input<typeof listSchema>;
61
+ interface PsaCredentials {
62
+ provider: 'connectwise-psa';
63
+ connectionId: string;
64
+ siteUrl: string;
65
+ companyId: string;
66
+ publicKey: string;
67
+ privateKey: string;
68
+ clientId: string;
69
+ }
70
+ interface PsaClientOptions {
71
+ resolve?: ResolveHost;
72
+ send?: SendRequest;
73
+ }
74
+ /** Direct, finite REST operations; no arbitrary URL tool and no mutation retries. */
75
+ declare class PsaClient {
76
+ private readonly credentials;
77
+ private readonly options;
78
+ constructor(credentials: unknown, options?: PsaClientOptions);
79
+ list(resource: ListResource, input?: ListInput): Promise<unknown[]>;
80
+ get(resource: 'companies' | 'contacts' | 'tickets', id: number): Promise<Record<string, unknown>>;
81
+ listBoardStatuses(boardId: number, input?: ListInput): Promise<unknown[]>;
82
+ listTicketNotes(ticketId: number, input?: ListInput): Promise<unknown[]>;
83
+ createTicket(input: z.input<typeof createTicketSchema>): Promise<Record<string, unknown>>;
84
+ updateTicket(ticketId: number, input: z.input<typeof updateTicketSchema>): Promise<Record<string, unknown>>;
85
+ addTicketNote(ticketId: number, input: z.input<typeof noteSchema>): Promise<Record<string, unknown>>;
86
+ private listPath;
87
+ private call;
88
+ }
89
+ //#endregion
90
+ //#region src/tools.d.ts
91
+ interface ConnectClient {
92
+ getConnectProviderAccounts(provider: string): Promise<unknown>;
93
+ getConnectionCredentials(connectionId: string): Promise<unknown>;
94
+ }
95
+ //#endregion
96
+ //#region src/server.d.ts
97
+ declare const SERVER_VERSION: string;
98
+ declare const SERVER_NAME = "connectwise-psa";
99
+ declare function createServer(connect: ConnectClient, options?: PsaClientOptions): McpServer;
100
+ declare function main(connect?: ConnectClient, transport?: Transport): Promise<McpServer>;
101
+ //#endregion
102
+ export { type ConnectClient, type ListInput, type ListResource, PsaClient, type PsaClientOptions, type PsaCredentials, SERVER_NAME, SERVER_VERSION, createServer, main };
@@ -0,0 +1,102 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
+
5
+ //#region src/transport.d.ts
6
+ interface ResolvedAddress {
7
+ address: string;
8
+ family: number;
9
+ }
10
+ type ResolveHost = (hostname: string) => Promise<ResolvedAddress[]>;
11
+ interface RequestInput {
12
+ url: URL;
13
+ address: ResolvedAddress;
14
+ method: 'GET' | 'POST' | 'PATCH';
15
+ headers: Record<string, string>;
16
+ body?: string;
17
+ }
18
+ type SendRequest = (input: RequestInput) => Promise<unknown>;
19
+ //#endregion
20
+ //#region src/client.d.ts
21
+ declare const listSchema: z.ZodObject<{
22
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
23
+ pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
24
+ conditions: z.ZodOptional<z.ZodString>;
25
+ orderBy: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strict>;
27
+ declare const createTicketSchema: z.ZodObject<{
28
+ summary: z.ZodString;
29
+ companyId: z.ZodCoercedNumber<unknown>;
30
+ boardId: z.ZodCoercedNumber<unknown>;
31
+ contactId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
32
+ statusId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
33
+ priorityId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
34
+ initialDescription: z.ZodOptional<z.ZodString>;
35
+ }, z.core.$strict>;
36
+ declare const updateTicketSchema: z.ZodObject<{
37
+ summary: z.ZodOptional<z.ZodString>;
38
+ statusId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
39
+ priorityId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
40
+ ownerId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
41
+ contactId: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
42
+ }, z.core.$strict>;
43
+ declare const noteSchema: z.ZodObject<{
44
+ text: z.ZodString;
45
+ visibility: z.ZodDefault<z.ZodEnum<{
46
+ internal: "internal";
47
+ discussion: "discussion";
48
+ resolution: "resolution";
49
+ }>>;
50
+ }, z.core.$strict>;
51
+ declare const LIST_PATHS: {
52
+ readonly companies: "/company/companies";
53
+ readonly contacts: "/company/contacts";
54
+ readonly boards: "/service/boards";
55
+ readonly priorities: "/service/priorities";
56
+ readonly members: "/system/members";
57
+ readonly tickets: "/service/tickets";
58
+ };
59
+ type ListResource = keyof typeof LIST_PATHS;
60
+ type ListInput = z.input<typeof listSchema>;
61
+ interface PsaCredentials {
62
+ provider: 'connectwise-psa';
63
+ connectionId: string;
64
+ siteUrl: string;
65
+ companyId: string;
66
+ publicKey: string;
67
+ privateKey: string;
68
+ clientId: string;
69
+ }
70
+ interface PsaClientOptions {
71
+ resolve?: ResolveHost;
72
+ send?: SendRequest;
73
+ }
74
+ /** Direct, finite REST operations; no arbitrary URL tool and no mutation retries. */
75
+ declare class PsaClient {
76
+ private readonly credentials;
77
+ private readonly options;
78
+ constructor(credentials: unknown, options?: PsaClientOptions);
79
+ list(resource: ListResource, input?: ListInput): Promise<unknown[]>;
80
+ get(resource: 'companies' | 'contacts' | 'tickets', id: number): Promise<Record<string, unknown>>;
81
+ listBoardStatuses(boardId: number, input?: ListInput): Promise<unknown[]>;
82
+ listTicketNotes(ticketId: number, input?: ListInput): Promise<unknown[]>;
83
+ createTicket(input: z.input<typeof createTicketSchema>): Promise<Record<string, unknown>>;
84
+ updateTicket(ticketId: number, input: z.input<typeof updateTicketSchema>): Promise<Record<string, unknown>>;
85
+ addTicketNote(ticketId: number, input: z.input<typeof noteSchema>): Promise<Record<string, unknown>>;
86
+ private listPath;
87
+ private call;
88
+ }
89
+ //#endregion
90
+ //#region src/tools.d.ts
91
+ interface ConnectClient {
92
+ getConnectProviderAccounts(provider: string): Promise<unknown>;
93
+ getConnectionCredentials(connectionId: string): Promise<unknown>;
94
+ }
95
+ //#endregion
96
+ //#region src/server.d.ts
97
+ declare const SERVER_VERSION: string;
98
+ declare const SERVER_NAME = "connectwise-psa";
99
+ declare function createServer(connect: ConnectClient, options?: PsaClientOptions): McpServer;
100
+ declare function main(connect?: ConnectClient, transport?: Transport): Promise<McpServer>;
101
+ //#endregion
102
+ export { type ConnectClient, type ListInput, type ListResource, PsaClient, type PsaClientOptions, type PsaCredentials, SERVER_NAME, SERVER_VERSION, createServer, main };
package/dist/server.js ADDED
@@ -0,0 +1,2 @@
1
+ import { a as PsaClient, i as main, n as SERVER_VERSION, r as createServer, t as SERVER_NAME } from "./server2.js";
2
+ export { PsaClient, SERVER_NAME, SERVER_VERSION, createServer, main };