@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 +61 -0
- package/dist/bin.cjs +17 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +18 -0
- package/dist/server.cjs +7 -0
- package/dist/server.d.cts +102 -0
- package/dist/server.d.ts +102 -0
- package/dist/server.js +2 -0
- package/dist/server2.cjs +524 -0
- package/dist/server2.js +489 -0
- package/package.json +39 -0
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 {};
|
package/dist/server.cjs
ADDED
|
@@ -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 };
|
package/dist/server.d.ts
ADDED
|
@@ -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