@alfe.ai/connectwise-automate-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 +74 -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 +8 -0
- package/dist/server.d.cts +95 -0
- package/dist/server.d.ts +95 -0
- package/dist/server.js +2 -0
- package/dist/server2.cjs +622 -0
- package/dist/server2.js +557 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# ConnectWise Automate MCP
|
|
2
|
+
|
|
3
|
+
Runtime-agnostic stdio MCP server for Automate client, location and endpoint
|
|
4
|
+
inventory, available scripts, computer monitors, and script/monitor history.
|
|
5
|
+
Every provider operation requires an explicit Alfe `connectionId`.
|
|
6
|
+
|
|
7
|
+
Create a **ConnectWise Automate** connection in Alfe using the public HTTPS
|
|
8
|
+
server origin and an integrator username/password. Alfe supplies the vendor
|
|
9
|
+
ClientID. This server reads Alfe configuration through `@alfe.ai/config` and
|
|
10
|
+
resolves fresh connection credentials through `AgentApiClient` on every call.
|
|
11
|
+
It requires no ConnectWise environment variables or copied secrets.
|
|
12
|
+
|
|
13
|
+
The integration manifest starts the first release with:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
mcp_servers:
|
|
17
|
+
- id: connectwise-automate
|
|
18
|
+
command: npx
|
|
19
|
+
args: ["-y", "@alfe.ai/connectwise-automate-mcp@0.1.0"]
|
|
20
|
+
env: {}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Call `automate_list_connections`, then pass the chosen `connectionId` to:
|
|
24
|
+
|
|
25
|
+
- `automate_check_connection`
|
|
26
|
+
- `automate_list_clients`
|
|
27
|
+
- `automate_list_locations`
|
|
28
|
+
- `automate_list_computers`
|
|
29
|
+
- `automate_list_scripts`
|
|
30
|
+
- `automate_list_monitor_history`
|
|
31
|
+
- `automate_list_internal_monitor_results`
|
|
32
|
+
- `automate_list_computer_monitors` (also requires `computerId`)
|
|
33
|
+
- `automate_list_computer_script_history` (also requires `computerId`)
|
|
34
|
+
|
|
35
|
+
List tools accept a 1-based `page`, `pageSize` (1–200, default 50), and an
|
|
36
|
+
Automate `condition` expression. They return `{ items, page, pageSize }` for
|
|
37
|
+
one page. Scripts are discovered and their execution history can be inspected;
|
|
38
|
+
these tools do not execute scripts, reboot computers, or change monitors.
|
|
39
|
+
|
|
40
|
+
The server calls `/cwa/api/v1/` on the selected origin. It caches bearer tokens
|
|
41
|
+
per connection, refreshes while they are still valid, and reauthenticates after
|
|
42
|
+
expiration or one failed GET. Credential rotation and revoked Alfe scope take
|
|
43
|
+
effect on the next tool call. Interactive/2FA login challenges return an
|
|
44
|
+
actionable connection error.
|
|
45
|
+
|
|
46
|
+
Private-network and loopback servers are unsupported. Each request resolves
|
|
47
|
+
the hostname afresh, rejects any non-public DNS answer, and pins the HTTPS
|
|
48
|
+
socket to a checked IP while retaining TLS hostname validation. Redirects are
|
|
49
|
+
never followed. Requests, DNS, responses and tool results have explicit bounds;
|
|
50
|
+
raw provider errors and credential-bearing fields are never forwarded.
|
|
51
|
+
|
|
52
|
+
## API contract and verification
|
|
53
|
+
|
|
54
|
+
The primary API reference is the [ConnectWise Automate developer
|
|
55
|
+
portal](https://developer.connectwise.com/Products/Automate/Integrating_with_Automate/API),
|
|
56
|
+
which requires a ConnectWise developer login. The implementation was checked
|
|
57
|
+
against its exported **Automate API v1** OpenAPI contracts, preserved in the
|
|
58
|
+
[System](https://github.com/covenanttechnologysolutions/connectwise-rest/blob/d04740719c5a62008af4dc31287a621b2f07f860/generator/automate-json/System.json),
|
|
59
|
+
[Company](https://github.com/covenanttechnologysolutions/connectwise-rest/blob/d04740719c5a62008af4dc31287a621b2f07f860/generator/automate-json/Company.json),
|
|
60
|
+
[Computers](https://github.com/covenanttechnologysolutions/connectwise-rest/blob/d04740719c5a62008af4dc31287a621b2f07f860/generator/automate-json/Computers.json),
|
|
61
|
+
[Scripts](https://github.com/covenanttechnologysolutions/connectwise-rest/blob/d04740719c5a62008af4dc31287a621b2f07f860/generator/automate-json/Scripts.json)
|
|
62
|
+
and [Monitors](https://github.com/covenanttechnologysolutions/connectwise-rest/blob/d04740719c5a62008af4dc31287a621b2f07f860/generator/automate-json/Monitors.json)
|
|
63
|
+
files at that immutable mirror revision. The mirror is independently
|
|
64
|
+
maintained; it is not a live contract obtained from the customer's server.
|
|
65
|
+
|
|
66
|
+
Tests exercise the token wire format/lifecycle, endpoint and pagination
|
|
67
|
+
contracts, connection selection/rotation/revocation, DNS and response bounds,
|
|
68
|
+
error redaction, and real in-memory MCP protocol requests. No live customer
|
|
69
|
+
Automate server was available during implementation. Before declaring a
|
|
70
|
+
deployment operational, configure a real vendor ClientID and integrator
|
|
71
|
+
connection, run `automate_check_connection`, page each relevant inventory tool,
|
|
72
|
+
and verify refresh against the installed Automate version. An account must
|
|
73
|
+
have the corresponding Automate read permissions; permission failures are
|
|
74
|
+
reported without widening access.
|
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.startServer().then(({ close }) => {
|
|
5
|
+
let stopping = false;
|
|
6
|
+
const shutdown = () => {
|
|
7
|
+
if (stopping) return;
|
|
8
|
+
stopping = true;
|
|
9
|
+
close().then(() => process.exit(0), () => process.exit(1));
|
|
10
|
+
};
|
|
11
|
+
process.once("SIGINT", shutdown);
|
|
12
|
+
process.once("SIGTERM", shutdown);
|
|
13
|
+
}).catch((error) => {
|
|
14
|
+
process.stderr.write(`[ERROR] connectwise-automate-mcp startup: ${require_server.safeError(error).code}\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 { a as startServer, s as safeError } from "./server2.js";
|
|
3
|
+
//#region src/bin.ts
|
|
4
|
+
startServer().then(({ close }) => {
|
|
5
|
+
let stopping = false;
|
|
6
|
+
const shutdown = () => {
|
|
7
|
+
if (stopping) return;
|
|
8
|
+
stopping = true;
|
|
9
|
+
close().then(() => process.exit(0), () => process.exit(1));
|
|
10
|
+
};
|
|
11
|
+
process.once("SIGINT", shutdown);
|
|
12
|
+
process.once("SIGTERM", shutdown);
|
|
13
|
+
}).catch((error) => {
|
|
14
|
+
process.stderr.write(`[ERROR] connectwise-automate-mcp startup: ${safeError(error).code}\n`);
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
});
|
|
17
|
+
//#endregion
|
|
18
|
+
export {};
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_server = require("./server2.cjs");
|
|
3
|
+
exports.AutomateClient = require_server.AutomateClient;
|
|
4
|
+
exports.AutomateRuntime = require_server.AutomateRuntime;
|
|
5
|
+
exports.SERVER_NAME = require_server.SERVER_NAME;
|
|
6
|
+
exports.SERVER_VERSION = require_server.SERVER_VERSION;
|
|
7
|
+
exports.createServer = require_server.createServer;
|
|
8
|
+
exports.startServer = require_server.startServer;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
3
|
+
|
|
4
|
+
//#region src/boundary.d.ts
|
|
5
|
+
|
|
6
|
+
interface AutomateCredentials {
|
|
7
|
+
serverUrl: string;
|
|
8
|
+
username: string;
|
|
9
|
+
password: string;
|
|
10
|
+
clientId: string;
|
|
11
|
+
}
|
|
12
|
+
interface Address {
|
|
13
|
+
address: string;
|
|
14
|
+
family: number;
|
|
15
|
+
}
|
|
16
|
+
type Resolver = (hostname: string) => Promise<Address[]>;
|
|
17
|
+
interface HttpRequest {
|
|
18
|
+
url: URL;
|
|
19
|
+
address: Address;
|
|
20
|
+
method: "GET" | "POST";
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
body?: string;
|
|
23
|
+
signal: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
interface HttpResponse {
|
|
26
|
+
status: number;
|
|
27
|
+
body: string;
|
|
28
|
+
}
|
|
29
|
+
type Transport$1 = (request: HttpRequest) => Promise<HttpResponse>;
|
|
30
|
+
/** TLS validates the original hostname while lookup pins the checked public IP. */
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/client.d.ts
|
|
33
|
+
declare const LIST_RESOURCES: readonly ["clients", "locations", "computers", "scripts", "monitor_history", "internal_monitor_results"];
|
|
34
|
+
type ListResource = (typeof LIST_RESOURCES)[number];
|
|
35
|
+
interface ListOptions {
|
|
36
|
+
page?: number;
|
|
37
|
+
pageSize?: number;
|
|
38
|
+
condition?: string;
|
|
39
|
+
}
|
|
40
|
+
interface ClientOptions {
|
|
41
|
+
resolve?: Resolver;
|
|
42
|
+
transport?: Transport$1;
|
|
43
|
+
now?: () => number;
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
}
|
|
46
|
+
declare class AutomateClient {
|
|
47
|
+
private readonly credentials;
|
|
48
|
+
private readonly resolve;
|
|
49
|
+
private readonly transport;
|
|
50
|
+
private readonly now;
|
|
51
|
+
private readonly timeoutMs;
|
|
52
|
+
private token;
|
|
53
|
+
private tokenRequest;
|
|
54
|
+
constructor(credentials: AutomateCredentials, options?: ClientOptions);
|
|
55
|
+
list(resource: ListResource, options?: ListOptions): Promise<unknown>;
|
|
56
|
+
listComputer(resource: "monitors" | "script_history", computerId: number, options?: ListOptions): Promise<unknown>;
|
|
57
|
+
checkConnection(): Promise<{
|
|
58
|
+
connected: true;
|
|
59
|
+
}>;
|
|
60
|
+
private readList;
|
|
61
|
+
private getToken;
|
|
62
|
+
private acquireToken;
|
|
63
|
+
private mintToken;
|
|
64
|
+
private request;
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/server.d.ts
|
|
68
|
+
declare const SERVER_NAME = "connectwise-automate-mcp";
|
|
69
|
+
declare const SERVER_VERSION: string;
|
|
70
|
+
/** Raw responses remain unknown until the runtime validates their authority. */
|
|
71
|
+
interface ConnectionSource {
|
|
72
|
+
getConnectProviderAccounts(provider: string): Promise<unknown>;
|
|
73
|
+
getConnectionCredentials(connectionId: string): Promise<unknown>;
|
|
74
|
+
}
|
|
75
|
+
declare class AutomateRuntime {
|
|
76
|
+
private readonly source;
|
|
77
|
+
private readonly options;
|
|
78
|
+
private readonly clients;
|
|
79
|
+
constructor(source: ConnectionSource, options?: ClientOptions);
|
|
80
|
+
listConnections(): Promise<unknown>;
|
|
81
|
+
getClient(selector: string): Promise<AutomateClient>;
|
|
82
|
+
clear(): void;
|
|
83
|
+
}
|
|
84
|
+
declare function createServer(source: ConnectionSource, options?: ClientOptions & {
|
|
85
|
+
emit?: (line: string) => void;
|
|
86
|
+
}): {
|
|
87
|
+
server: McpServer;
|
|
88
|
+
runtime: AutomateRuntime;
|
|
89
|
+
};
|
|
90
|
+
declare function startServer(source?: ConnectionSource, transport?: Transport): Promise<{
|
|
91
|
+
server: McpServer;
|
|
92
|
+
close: () => Promise<void>;
|
|
93
|
+
}>;
|
|
94
|
+
//#endregion
|
|
95
|
+
export { AutomateClient, type AutomateCredentials, AutomateRuntime, type ClientOptions, ConnectionSource, type ListOptions, type ListResource, SERVER_NAME, SERVER_VERSION, createServer, startServer };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
3
|
+
|
|
4
|
+
//#region src/boundary.d.ts
|
|
5
|
+
|
|
6
|
+
interface AutomateCredentials {
|
|
7
|
+
serverUrl: string;
|
|
8
|
+
username: string;
|
|
9
|
+
password: string;
|
|
10
|
+
clientId: string;
|
|
11
|
+
}
|
|
12
|
+
interface Address {
|
|
13
|
+
address: string;
|
|
14
|
+
family: number;
|
|
15
|
+
}
|
|
16
|
+
type Resolver = (hostname: string) => Promise<Address[]>;
|
|
17
|
+
interface HttpRequest {
|
|
18
|
+
url: URL;
|
|
19
|
+
address: Address;
|
|
20
|
+
method: "GET" | "POST";
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
body?: string;
|
|
23
|
+
signal: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
interface HttpResponse {
|
|
26
|
+
status: number;
|
|
27
|
+
body: string;
|
|
28
|
+
}
|
|
29
|
+
type Transport$1 = (request: HttpRequest) => Promise<HttpResponse>;
|
|
30
|
+
/** TLS validates the original hostname while lookup pins the checked public IP. */
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/client.d.ts
|
|
33
|
+
declare const LIST_RESOURCES: readonly ["clients", "locations", "computers", "scripts", "monitor_history", "internal_monitor_results"];
|
|
34
|
+
type ListResource = (typeof LIST_RESOURCES)[number];
|
|
35
|
+
interface ListOptions {
|
|
36
|
+
page?: number;
|
|
37
|
+
pageSize?: number;
|
|
38
|
+
condition?: string;
|
|
39
|
+
}
|
|
40
|
+
interface ClientOptions {
|
|
41
|
+
resolve?: Resolver;
|
|
42
|
+
transport?: Transport$1;
|
|
43
|
+
now?: () => number;
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
}
|
|
46
|
+
declare class AutomateClient {
|
|
47
|
+
private readonly credentials;
|
|
48
|
+
private readonly resolve;
|
|
49
|
+
private readonly transport;
|
|
50
|
+
private readonly now;
|
|
51
|
+
private readonly timeoutMs;
|
|
52
|
+
private token;
|
|
53
|
+
private tokenRequest;
|
|
54
|
+
constructor(credentials: AutomateCredentials, options?: ClientOptions);
|
|
55
|
+
list(resource: ListResource, options?: ListOptions): Promise<unknown>;
|
|
56
|
+
listComputer(resource: "monitors" | "script_history", computerId: number, options?: ListOptions): Promise<unknown>;
|
|
57
|
+
checkConnection(): Promise<{
|
|
58
|
+
connected: true;
|
|
59
|
+
}>;
|
|
60
|
+
private readList;
|
|
61
|
+
private getToken;
|
|
62
|
+
private acquireToken;
|
|
63
|
+
private mintToken;
|
|
64
|
+
private request;
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/server.d.ts
|
|
68
|
+
declare const SERVER_NAME = "connectwise-automate-mcp";
|
|
69
|
+
declare const SERVER_VERSION: string;
|
|
70
|
+
/** Raw responses remain unknown until the runtime validates their authority. */
|
|
71
|
+
interface ConnectionSource {
|
|
72
|
+
getConnectProviderAccounts(provider: string): Promise<unknown>;
|
|
73
|
+
getConnectionCredentials(connectionId: string): Promise<unknown>;
|
|
74
|
+
}
|
|
75
|
+
declare class AutomateRuntime {
|
|
76
|
+
private readonly source;
|
|
77
|
+
private readonly options;
|
|
78
|
+
private readonly clients;
|
|
79
|
+
constructor(source: ConnectionSource, options?: ClientOptions);
|
|
80
|
+
listConnections(): Promise<unknown>;
|
|
81
|
+
getClient(selector: string): Promise<AutomateClient>;
|
|
82
|
+
clear(): void;
|
|
83
|
+
}
|
|
84
|
+
declare function createServer(source: ConnectionSource, options?: ClientOptions & {
|
|
85
|
+
emit?: (line: string) => void;
|
|
86
|
+
}): {
|
|
87
|
+
server: McpServer;
|
|
88
|
+
runtime: AutomateRuntime;
|
|
89
|
+
};
|
|
90
|
+
declare function startServer(source?: ConnectionSource, transport?: Transport): Promise<{
|
|
91
|
+
server: McpServer;
|
|
92
|
+
close: () => Promise<void>;
|
|
93
|
+
}>;
|
|
94
|
+
//#endregion
|
|
95
|
+
export { AutomateClient, type AutomateCredentials, AutomateRuntime, type ClientOptions, ConnectionSource, type ListOptions, type ListResource, SERVER_NAME, SERVER_VERSION, createServer, startServer };
|
package/dist/server.js
ADDED