@goxtechnologies/connectwise-psa-mcp 1.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.
Files changed (147) hide show
  1. package/data/connectwise_api.db +0 -0
  2. package/data/manage.json +298179 -0
  3. package/dist/index.d.ts +10 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +116 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/operations/analytics-extended.d.ts +6 -0
  8. package/dist/operations/analytics-extended.d.ts.map +1 -0
  9. package/dist/operations/analytics-extended.js +825 -0
  10. package/dist/operations/analytics-extended.js.map +1 -0
  11. package/dist/operations/analytics-msp-assets.d.ts +3 -0
  12. package/dist/operations/analytics-msp-assets.d.ts.map +1 -0
  13. package/dist/operations/analytics-msp-assets.js +180 -0
  14. package/dist/operations/analytics-msp-assets.js.map +1 -0
  15. package/dist/operations/analytics-msp-clients.d.ts +3 -0
  16. package/dist/operations/analytics-msp-clients.d.ts.map +1 -0
  17. package/dist/operations/analytics-msp-clients.js +198 -0
  18. package/dist/operations/analytics-msp-clients.js.map +1 -0
  19. package/dist/operations/analytics-msp-comms.d.ts +3 -0
  20. package/dist/operations/analytics-msp-comms.d.ts.map +1 -0
  21. package/dist/operations/analytics-msp-comms.js +127 -0
  22. package/dist/operations/analytics-msp-comms.js.map +1 -0
  23. package/dist/operations/analytics-msp-contracts.d.ts +3 -0
  24. package/dist/operations/analytics-msp-contracts.d.ts.map +1 -0
  25. package/dist/operations/analytics-msp-contracts.js +91 -0
  26. package/dist/operations/analytics-msp-contracts.js.map +1 -0
  27. package/dist/operations/analytics-msp-financial.d.ts +3 -0
  28. package/dist/operations/analytics-msp-financial.d.ts.map +1 -0
  29. package/dist/operations/analytics-msp-financial.js +300 -0
  30. package/dist/operations/analytics-msp-financial.js.map +1 -0
  31. package/dist/operations/analytics-msp-procurement.d.ts +3 -0
  32. package/dist/operations/analytics-msp-procurement.d.ts.map +1 -0
  33. package/dist/operations/analytics-msp-procurement.js +78 -0
  34. package/dist/operations/analytics-msp-procurement.js.map +1 -0
  35. package/dist/operations/analytics-msp-projects.d.ts +3 -0
  36. package/dist/operations/analytics-msp-projects.d.ts.map +1 -0
  37. package/dist/operations/analytics-msp-projects.js +190 -0
  38. package/dist/operations/analytics-msp-projects.js.map +1 -0
  39. package/dist/operations/analytics-msp-sales.d.ts +3 -0
  40. package/dist/operations/analytics-msp-sales.d.ts.map +1 -0
  41. package/dist/operations/analytics-msp-sales.js +99 -0
  42. package/dist/operations/analytics-msp-sales.js.map +1 -0
  43. package/dist/operations/analytics-msp-schedule.d.ts +3 -0
  44. package/dist/operations/analytics-msp-schedule.d.ts.map +1 -0
  45. package/dist/operations/analytics-msp-schedule.js +339 -0
  46. package/dist/operations/analytics-msp-schedule.js.map +1 -0
  47. package/dist/operations/analytics-msp-team.d.ts +3 -0
  48. package/dist/operations/analytics-msp-team.d.ts.map +1 -0
  49. package/dist/operations/analytics-msp-team.js +195 -0
  50. package/dist/operations/analytics-msp-team.js.map +1 -0
  51. package/dist/operations/analytics-msp-tickets.d.ts +3 -0
  52. package/dist/operations/analytics-msp-tickets.d.ts.map +1 -0
  53. package/dist/operations/analytics-msp-tickets.js +578 -0
  54. package/dist/operations/analytics-msp-tickets.js.map +1 -0
  55. package/dist/operations/analytics-msp-time.d.ts +3 -0
  56. package/dist/operations/analytics-msp-time.d.ts.map +1 -0
  57. package/dist/operations/analytics-msp-time.js +485 -0
  58. package/dist/operations/analytics-msp-time.js.map +1 -0
  59. package/dist/operations/analytics-msp-utils.d.ts +49 -0
  60. package/dist/operations/analytics-msp-utils.d.ts.map +1 -0
  61. package/dist/operations/analytics-msp-utils.js +157 -0
  62. package/dist/operations/analytics-msp-utils.js.map +1 -0
  63. package/dist/operations/analytics.d.ts +9 -0
  64. package/dist/operations/analytics.d.ts.map +1 -0
  65. package/dist/operations/analytics.js +742 -0
  66. package/dist/operations/analytics.js.map +1 -0
  67. package/dist/operations/executor.d.ts +10 -0
  68. package/dist/operations/executor.d.ts.map +1 -0
  69. package/dist/operations/executor.js +243 -0
  70. package/dist/operations/executor.js.map +1 -0
  71. package/dist/operations/registry.d.ts +16 -0
  72. package/dist/operations/registry.d.ts.map +1 -0
  73. package/dist/operations/registry.js +847 -0
  74. package/dist/operations/registry.js.map +1 -0
  75. package/dist/services/api-database.d.ts +38 -0
  76. package/dist/services/api-database.d.ts.map +1 -0
  77. package/dist/services/api-database.js +191 -0
  78. package/dist/services/api-database.js.map +1 -0
  79. package/dist/services/cache.d.ts +12 -0
  80. package/dist/services/cache.d.ts.map +1 -0
  81. package/dist/services/cache.js +32 -0
  82. package/dist/services/cache.js.map +1 -0
  83. package/dist/services/connectwise-api.d.ts +43 -0
  84. package/dist/services/connectwise-api.d.ts.map +1 -0
  85. package/dist/services/connectwise-api.js +198 -0
  86. package/dist/services/connectwise-api.js.map +1 -0
  87. package/dist/services/db-builder.d.ts +11 -0
  88. package/dist/services/db-builder.d.ts.map +1 -0
  89. package/dist/services/db-builder.js +237 -0
  90. package/dist/services/db-builder.js.map +1 -0
  91. package/dist/services/fast-memory.d.ts +39 -0
  92. package/dist/services/fast-memory.d.ts.map +1 -0
  93. package/dist/services/fast-memory.js +147 -0
  94. package/dist/services/fast-memory.js.map +1 -0
  95. package/dist/services/load-env.d.ts +15 -0
  96. package/dist/services/load-env.d.ts.map +1 -0
  97. package/dist/services/load-env.js +59 -0
  98. package/dist/services/load-env.js.map +1 -0
  99. package/dist/tools/batch.d.ts +9 -0
  100. package/dist/tools/batch.d.ts.map +1 -0
  101. package/dist/tools/batch.js +159 -0
  102. package/dist/tools/batch.js.map +1 -0
  103. package/dist/tools/composite.d.ts +9 -0
  104. package/dist/tools/composite.d.ts.map +1 -0
  105. package/dist/tools/composite.js +353 -0
  106. package/dist/tools/composite.js.map +1 -0
  107. package/dist/tools/discovery.d.ts +9 -0
  108. package/dist/tools/discovery.d.ts.map +1 -0
  109. package/dist/tools/discovery.js +245 -0
  110. package/dist/tools/discovery.js.map +1 -0
  111. package/dist/tools/execution.d.ts +9 -0
  112. package/dist/tools/execution.d.ts.map +1 -0
  113. package/dist/tools/execution.js +130 -0
  114. package/dist/tools/execution.js.map +1 -0
  115. package/dist/tools/memory.d.ts +9 -0
  116. package/dist/tools/memory.d.ts.map +1 -0
  117. package/dist/tools/memory.js +152 -0
  118. package/dist/tools/memory.js.map +1 -0
  119. package/dist/tools/operations.d.ts +9 -0
  120. package/dist/tools/operations.d.ts.map +1 -0
  121. package/dist/tools/operations.js +214 -0
  122. package/dist/tools/operations.js.map +1 -0
  123. package/dist/tools/pagination.d.ts +9 -0
  124. package/dist/tools/pagination.d.ts.map +1 -0
  125. package/dist/tools/pagination.js +133 -0
  126. package/dist/tools/pagination.js.map +1 -0
  127. package/dist/tools/validation.d.ts +9 -0
  128. package/dist/tools/validation.d.ts.map +1 -0
  129. package/dist/tools/validation.js +705 -0
  130. package/dist/tools/validation.js.map +1 -0
  131. package/dist/types/index.d.ts +145 -0
  132. package/dist/types/index.d.ts.map +1 -0
  133. package/dist/types/index.js +3 -0
  134. package/dist/types/index.js.map +1 -0
  135. package/dist/types/operations.d.ts +30 -0
  136. package/dist/types/operations.d.ts.map +1 -0
  137. package/dist/types/operations.js +3 -0
  138. package/dist/types/operations.js.map +1 -0
  139. package/dist/utils/conditions.d.ts +20 -0
  140. package/dist/utils/conditions.d.ts.map +1 -0
  141. package/dist/utils/conditions.js +78 -0
  142. package/dist/utils/conditions.js.map +1 -0
  143. package/dist/utils/formatters.d.ts +35 -0
  144. package/dist/utils/formatters.d.ts.map +1 -0
  145. package/dist/utils/formatters.js +337 -0
  146. package/dist/utils/formatters.js.map +1 -0
  147. package/package.json +46 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fast-memory.js","sourceRoot":"","sources":["../../src/services/fast-memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Zero-dependency .env file loader.
3
+ * Reads KEY=VALUE pairs from the plugin root's .env file and populates
4
+ * process.env as a fallback — existing environment variables take precedence.
5
+ */
6
+ /** Resolve the plugin root directory. */
7
+ export declare function getPluginRoot(): string;
8
+ /**
9
+ * Load .env file into process.env (existing vars take precedence).
10
+ * Search order:
11
+ * 1. ~/.config/connectwise-psa/.env (user config — always writable, works in Cowork)
12
+ * 2. <plugin_root>/.env (legacy — may be read-only in Cowork sandbox)
13
+ */
14
+ export declare function loadEnv(): void;
15
+ //# sourceMappingURL=load-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-env.d.ts","sourceRoot":"","sources":["../../src/services/load-env.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,yCAAyC;AACzC,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAyC9B"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Zero-dependency .env file loader.
3
+ * Reads KEY=VALUE pairs from the plugin root's .env file and populates
4
+ * process.env as a fallback — existing environment variables take precedence.
5
+ */
6
+ import { readFileSync, existsSync } from 'fs';
7
+ import { dirname, join, resolve } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ /** Resolve the plugin root directory. */
12
+ export function getPluginRoot() {
13
+ // When built, __dirname is dist/services/ — 2 levels up to package root
14
+ return process.env['CLAUDE_PLUGIN_ROOT'] ?? resolve(__dirname, '../..');
15
+ }
16
+ /**
17
+ * Load .env file into process.env (existing vars take precedence).
18
+ * Search order:
19
+ * 1. ~/.config/connectwise-psa/.env (user config — always writable, works in Cowork)
20
+ * 2. <plugin_root>/.env (legacy — may be read-only in Cowork sandbox)
21
+ */
22
+ export function loadEnv() {
23
+ const homeDir = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '';
24
+ const candidates = [
25
+ join(homeDir, '.config', 'connectwise-psa', '.env'), // primary (always writable)
26
+ join(getPluginRoot(), '.env'), // legacy fallback
27
+ ];
28
+ const envPath = candidates.find(p => existsSync(p));
29
+ if (!envPath)
30
+ return;
31
+ let content;
32
+ try {
33
+ content = readFileSync(envPath, 'utf-8');
34
+ }
35
+ catch {
36
+ return;
37
+ }
38
+ for (const line of content.split('\n')) {
39
+ const trimmed = line.trim();
40
+ if (!trimmed || trimmed.startsWith('#'))
41
+ continue;
42
+ const eqIndex = trimmed.indexOf('=');
43
+ if (eqIndex <= 0)
44
+ continue;
45
+ const key = trimmed.slice(0, eqIndex).trim();
46
+ let value = trimmed.slice(eqIndex + 1).trim();
47
+ // Strip surrounding quotes
48
+ if ((value.startsWith('"') && value.endsWith('"')) ||
49
+ (value.startsWith("'") && value.endsWith("'"))) {
50
+ value = value.slice(1, -1);
51
+ }
52
+ // Set if not already in environment, or if current value is empty/unresolved placeholder
53
+ const current = process.env[key];
54
+ if (!current || current.startsWith('${')) {
55
+ process.env[key] = value;
56
+ }
57
+ }
58
+ }
59
+ //# sourceMappingURL=load-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-env.js","sourceRoot":"","sources":["../../src/services/load-env.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,yCAAyC;AACzC,MAAM,UAAU,aAAa;IAC3B,wEAAwE;IACxE,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IACxE,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAI,4BAA4B;QACnF,IAAI,CAAC,aAAa,EAAE,EAAE,MAAM,CAAC,EAA2B,kBAAkB;KAC3E,CAAC;IAEF,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,OAAO;QAAE,OAAO;IAErB,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAElD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,IAAI,CAAC;YAAE,SAAS;QAE3B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAE9C,2BAA2B;QAC3B,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;YACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QAED,yFAAyF;QACzF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC3B,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare const batchTools: Tool[];
3
+ export declare function handleBatchTool(toolName: string, args: Record<string, unknown>): Promise<{
4
+ content: Array<{
5
+ type: 'text';
6
+ text: string;
7
+ }>;
8
+ }>;
9
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AA+B1D,eAAO,MAAM,UAAU,EAAE,IAAI,EA2C5B,CAAC;AAMF,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CA0G7D"}
@@ -0,0 +1,159 @@
1
+ // ConnectWise PSA MCP Server — Batch Update Tool
2
+ import { getAPI } from '../services/connectwise-api.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Helper
5
+ // ---------------------------------------------------------------------------
6
+ function text(t) {
7
+ return { content: [{ type: 'text', text: t }] };
8
+ }
9
+ /** Narrow an unknown value to a BatchOperation, returning null on invalid input. */
10
+ function parseBatchOperation(raw) {
11
+ if (typeof raw !== 'object' || raw === null)
12
+ return null;
13
+ const obj = raw;
14
+ const path = typeof obj['path'] === 'string' ? obj['path'] : null;
15
+ const method = obj['method'];
16
+ if (!path)
17
+ return null;
18
+ if (method !== 'PATCH' && method !== 'POST' && method !== 'DELETE')
19
+ return null;
20
+ return {
21
+ path,
22
+ method,
23
+ body: obj['body'],
24
+ };
25
+ }
26
+ // ---------------------------------------------------------------------------
27
+ // Tool definition
28
+ // ---------------------------------------------------------------------------
29
+ export const batchTools = [
30
+ {
31
+ name: 'cw_batch_update',
32
+ description: 'Execute one or more PATCH / POST / DELETE operations against the ConnectWise API. ' +
33
+ 'When confirm is false (the default) the tool performs a DRY RUN and shows exactly what would be sent without touching any data. ' +
34
+ 'Set confirm to true to execute the operations sequentially and receive a pass/fail summary.',
35
+ inputSchema: {
36
+ type: 'object',
37
+ properties: {
38
+ operations: {
39
+ type: 'array',
40
+ description: 'Ordered list of operations to execute',
41
+ items: {
42
+ type: 'object',
43
+ properties: {
44
+ path: {
45
+ type: 'string',
46
+ description: 'API path (e.g. /service/tickets/12345)',
47
+ },
48
+ method: {
49
+ type: 'string',
50
+ enum: ['PATCH', 'POST', 'DELETE'],
51
+ description: 'HTTP method',
52
+ },
53
+ body: {
54
+ type: 'object',
55
+ description: 'Optional request body (for PATCH / POST)',
56
+ },
57
+ },
58
+ required: ['path', 'method'],
59
+ },
60
+ minItems: 1,
61
+ },
62
+ confirm: {
63
+ type: 'boolean',
64
+ description: 'Set to true to actually execute the operations. Defaults to false (dry run).',
65
+ },
66
+ },
67
+ required: ['operations'],
68
+ },
69
+ },
70
+ ];
71
+ // ---------------------------------------------------------------------------
72
+ // Handler
73
+ // ---------------------------------------------------------------------------
74
+ export async function handleBatchTool(toolName, args) {
75
+ if (toolName !== 'cw_batch_update') {
76
+ return text(`Unknown batch tool: ${toolName}`);
77
+ }
78
+ // ------------------------------------------------------------------
79
+ // Parse and validate operations
80
+ // ------------------------------------------------------------------
81
+ const rawOps = args['operations'];
82
+ if (!Array.isArray(rawOps) || rawOps.length === 0) {
83
+ return text('Error: operations must be a non-empty array.');
84
+ }
85
+ const ops = [];
86
+ const parseErrors = [];
87
+ for (let i = 0; i < rawOps.length; i++) {
88
+ const op = parseBatchOperation(rawOps[i]);
89
+ if (!op) {
90
+ parseErrors.push(`Operation[${i}] is invalid — must have a string path and method of PATCH | POST | DELETE`);
91
+ }
92
+ else {
93
+ ops.push(op);
94
+ }
95
+ }
96
+ if (parseErrors.length > 0) {
97
+ return text(`Validation errors:\n${parseErrors.map((e) => ` - ${e}`).join('\n')}`);
98
+ }
99
+ // ------------------------------------------------------------------
100
+ // Dry run (confirm === false or omitted)
101
+ // ------------------------------------------------------------------
102
+ const confirm = args['confirm'] === true;
103
+ if (!confirm) {
104
+ const preview = ops
105
+ .map((op, i) => {
106
+ const bodyStr = op.body !== undefined ? `\n body: ${JSON.stringify(op.body, null, 2)}` : '';
107
+ return ` [${i + 1}] ${op.method} ${op.path}${bodyStr}`;
108
+ })
109
+ .join('\n\n');
110
+ return text(`DRY RUN — ${ops.length} operation${ops.length !== 1 ? 's' : ''} would be executed:\n\n${preview}\n\n` +
111
+ 'Set confirm=true to execute.');
112
+ }
113
+ // ------------------------------------------------------------------
114
+ // Live execution — sequential to avoid race conditions on related records
115
+ // ------------------------------------------------------------------
116
+ const api = getAPI();
117
+ const result = {
118
+ total: ops.length,
119
+ succeeded: 0,
120
+ failed: 0,
121
+ results: [],
122
+ };
123
+ for (const op of ops) {
124
+ try {
125
+ await api.requestWithRetry({
126
+ path: op.path,
127
+ method: op.method,
128
+ body: op.body,
129
+ });
130
+ result.succeeded++;
131
+ result.results.push({
132
+ path: op.path,
133
+ status: 'success',
134
+ message: `${op.method} ${op.path} — OK`,
135
+ });
136
+ }
137
+ catch (err) {
138
+ const message = err instanceof Error ? err.message : String(err);
139
+ result.failed++;
140
+ result.results.push({
141
+ path: op.path,
142
+ status: 'error',
143
+ message: `${op.method} ${op.path} — ${message}`,
144
+ });
145
+ }
146
+ }
147
+ // ------------------------------------------------------------------
148
+ // Format summary
149
+ // ------------------------------------------------------------------
150
+ const statusIcon = (s) => (s === 'success' ? 'OK' : 'FAIL');
151
+ const rows = result.results
152
+ .map((r) => ` [${statusIcon(r.status)}] ${r.message}`)
153
+ .join('\n');
154
+ const summary = `Batch complete — ${result.total} operation${result.total !== 1 ? 's' : ''}: ` +
155
+ `${result.succeeded} succeeded, ${result.failed} failed.\n\n` +
156
+ rows;
157
+ return text(summary);
158
+ }
159
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare const compositeTools: Tool[];
3
+ export declare function handleCompositeTool(toolName: string, args: Record<string, unknown>): Promise<{
4
+ content: Array<{
5
+ type: 'text';
6
+ text: string;
7
+ }>;
8
+ }>;
9
+ //# sourceMappingURL=composite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../src/tools/composite.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AA8B1D,eAAO,MAAM,cAAc,EAAE,IAAI,EAwEhC,CAAC;AAMF,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CAiT7D"}
@@ -0,0 +1,353 @@
1
+ // ConnectWise PSA MCP Server — Composite Query Tools
2
+ //
3
+ // Each tool fans out multiple API calls in parallel via Promise.allSettled so
4
+ // partial failures are handled gracefully — the caller always gets whatever
5
+ // data was successfully retrieved.
6
+ import { getAPI } from '../services/connectwise-api.js';
7
+ import { formatTicketContext, formatMemberDashboard, formatAgreementHealth } from '../utils/formatters.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+ function text(t) {
12
+ return { content: [{ type: 'text', text: t }] };
13
+ }
14
+ /**
15
+ * Settle a named parallel fetch. Returns the resolved value on success or
16
+ * an object containing an `_error` key describing the failure on rejection.
17
+ */
18
+ async function settle(label, promise) {
19
+ const [result] = await Promise.allSettled([promise]);
20
+ if (result.status === 'fulfilled')
21
+ return result.value;
22
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
23
+ return { _error: `${label} failed: ${reason}` };
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Tool definitions
27
+ // ---------------------------------------------------------------------------
28
+ export const compositeTools = [
29
+ {
30
+ name: 'cw_ticket_context',
31
+ description: 'Fetch the complete context for a single ticket in one call: the ticket record, internal notes, time entries, tasks, attached documents, and schedule entries. ' +
32
+ 'Partial failures are surfaced inline — you still receive whatever succeeded.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ ticket_id: {
37
+ type: 'number',
38
+ description: 'Numeric ConnectWise ticket ID',
39
+ },
40
+ },
41
+ required: ['ticket_id'],
42
+ },
43
+ },
44
+ {
45
+ name: 'cw_member_dashboard',
46
+ description: 'Return a workload snapshot for a single member: their profile, open tickets, scheduled hours, and time-entry hours — all in one call.',
47
+ inputSchema: {
48
+ type: 'object',
49
+ properties: {
50
+ identifier: {
51
+ type: 'string',
52
+ description: 'ConnectWise member identifier (login name)',
53
+ },
54
+ days_back: {
55
+ type: 'number',
56
+ description: 'How many calendar days to look back for schedule and time entries (default 30)',
57
+ },
58
+ },
59
+ required: ['identifier'],
60
+ },
61
+ },
62
+ {
63
+ name: 'cw_agreement_health',
64
+ description: 'Return the full health picture for a single agreement: the agreement record, all additions, configured work roles, and configured work types — in one call.',
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: {
68
+ agreement_id: {
69
+ type: 'number',
70
+ description: 'Numeric ConnectWise agreement ID',
71
+ },
72
+ },
73
+ required: ['agreement_id'],
74
+ },
75
+ },
76
+ {
77
+ name: 'cw_audit_ticket',
78
+ description: 'Audit a ticket for completeness, compliance, and quality. Checks: missing fields, ' +
79
+ 'incomplete tasks, missing resolution note, time entries without notes, SLA compliance, ' +
80
+ 'customer notification status, excessive time vs ticket complexity, and billing coherence. ' +
81
+ 'Returns a compliance score with detailed findings.',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ ticket_id: {
86
+ type: 'number',
87
+ description: 'Numeric ConnectWise ticket ID to audit',
88
+ },
89
+ },
90
+ required: ['ticket_id'],
91
+ },
92
+ },
93
+ ];
94
+ // ---------------------------------------------------------------------------
95
+ // Handler
96
+ // ---------------------------------------------------------------------------
97
+ export async function handleCompositeTool(toolName, args) {
98
+ const api = getAPI();
99
+ // -------------------------------------------------------------------------
100
+ switch (toolName) {
101
+ case 'cw_ticket_context': {
102
+ const ticketId = Number(args['ticket_id']);
103
+ if (!Number.isInteger(ticketId) || ticketId <= 0) {
104
+ return text('Error: ticket_id must be a positive integer.');
105
+ }
106
+ const base = `/service/tickets/${ticketId}`;
107
+ const [ticket, notes, timeEntries, tasks, documents, scheduleEntries] = await Promise.all([
108
+ settle('ticket', api.request({ path: base, method: 'GET' })),
109
+ settle('notes', api.request({ path: `${base}/notes`, method: 'GET', params: { pageSize: 250, orderBy: 'dateCreated asc' } })),
110
+ settle('timeEntries', api.request({ path: `${base}/timeentries`, method: 'GET', params: { pageSize: 250 } })),
111
+ settle('tasks', api.request({ path: `${base}/tasks`, method: 'GET', params: { pageSize: 250 } })),
112
+ settle('documents', api.request({ path: `${base}/documents`, method: 'GET', params: { pageSize: 250 } })),
113
+ settle('scheduleEntries', api.request({ path: `${base}/scheduleentries`, method: 'GET', params: { pageSize: 250 } })),
114
+ ]);
115
+ const result = { ticket, notes, timeEntries, tasks, documents, scheduleEntries };
116
+ return text(formatTicketContext(result));
117
+ }
118
+ // -------------------------------------------------------------------------
119
+ case 'cw_member_dashboard': {
120
+ const identifier = String(args['identifier'] ?? '').trim();
121
+ if (!identifier) {
122
+ return text('Error: identifier is required.');
123
+ }
124
+ const daysBack = Number(args['days_back'] ?? 30);
125
+ const startDate = new Date();
126
+ startDate.setDate(startDate.getDate() - (Number.isFinite(daysBack) && daysBack > 0 ? daysBack : 30));
127
+ const startIso = startDate.toISOString().split('T')[0]; // YYYY-MM-DD
128
+ const [memberResult, openTicketsResult, scheduleResult, timeResult] = await Promise.all([
129
+ settle('member', api.request({
130
+ path: '/system/members',
131
+ method: 'GET',
132
+ params: { conditions: `identifier='${identifier}'` },
133
+ })),
134
+ settle('openTickets', api.request({
135
+ path: '/service/tickets',
136
+ method: 'GET',
137
+ params: {
138
+ conditions: `resources like '%${identifier}%' AND closedFlag=false`,
139
+ pageSize: 50,
140
+ fields: 'id,summary,status,company,priority,dateEntered',
141
+ },
142
+ })),
143
+ settle('scheduleEntries', api.request({
144
+ path: '/schedule/entries',
145
+ method: 'GET',
146
+ params: {
147
+ conditions: `member/identifier='${identifier}' AND dateStart>=[${startIso}]`,
148
+ pageSize: 250,
149
+ },
150
+ })),
151
+ settle('timeEntries', api.request({
152
+ path: '/time/entries',
153
+ method: 'GET',
154
+ params: {
155
+ conditions: `member/identifier='${identifier}' AND dateEntered>=[${startIso}]`,
156
+ pageSize: 250,
157
+ },
158
+ })),
159
+ ]);
160
+ // Derive summary metrics from successfully fetched data
161
+ const openTicketCount = Array.isArray(openTicketsResult) ? openTicketsResult.length : null;
162
+ let totalTimeHours = null;
163
+ if (Array.isArray(timeResult)) {
164
+ totalTimeHours = timeResult.reduce((sum, e) => sum + (e.actualHours ?? 0), 0);
165
+ }
166
+ let scheduledHours = null;
167
+ if (Array.isArray(scheduleResult)) {
168
+ scheduledHours = scheduleResult.reduce((sum, e) => sum + (e.hours ?? 0), 0);
169
+ }
170
+ const summary = {
171
+ member: memberResult,
172
+ openTickets: openTicketsResult,
173
+ scheduleEntries: scheduleResult,
174
+ timeEntries: timeResult,
175
+ _computed: {
176
+ periodStart: startIso,
177
+ daysBack,
178
+ openTicketCount,
179
+ totalTimeHours: totalTimeHours !== null ? Math.round(totalTimeHours * 100) / 100 : null,
180
+ scheduledHours: scheduledHours !== null ? Math.round(scheduledHours * 100) / 100 : null,
181
+ },
182
+ };
183
+ return text(formatMemberDashboard(summary));
184
+ }
185
+ // -------------------------------------------------------------------------
186
+ case 'cw_agreement_health': {
187
+ const agreementId = Number(args['agreement_id']);
188
+ if (!Number.isInteger(agreementId) || agreementId <= 0) {
189
+ return text('Error: agreement_id must be a positive integer.');
190
+ }
191
+ const base = `/finance/agreements/${agreementId}`;
192
+ const [agreement, additions, workRoles, workTypes] = await Promise.all([
193
+ settle('agreement', api.request({ path: base, method: 'GET' })),
194
+ settle('additions', api.request({ path: `${base}/additions`, method: 'GET', params: { pageSize: 250 } })),
195
+ settle('workRoles', api.request({ path: `${base}/workRoles`, method: 'GET', params: { pageSize: 250 } })),
196
+ settle('workTypes', api.request({ path: `${base}/workTypes`, method: 'GET', params: { pageSize: 250 } })),
197
+ ]);
198
+ const result = { agreement, additions, workRoles, workTypes };
199
+ return text(formatAgreementHealth(result));
200
+ }
201
+ // -------------------------------------------------------------------------
202
+ case 'cw_audit_ticket': {
203
+ const ticketId = Number(args['ticket_id']);
204
+ if (!Number.isInteger(ticketId) || ticketId <= 0) {
205
+ return text('Error: ticket_id must be a positive integer.');
206
+ }
207
+ const base = `/service/tickets/${ticketId}`;
208
+ // Parallel fetch all ticket data
209
+ const [ticket, notes, timeEntries, tasks, documents] = await Promise.all([
210
+ settle('ticket', api.request({ path: base, method: 'GET' })),
211
+ settle('notes', api.request({ path: `${base}/notes`, method: 'GET', params: { pageSize: 250, orderBy: 'dateCreated asc' } })),
212
+ settle('timeEntries', api.request({ path: `${base}/timeentries`, method: 'GET', params: { pageSize: 250 } })),
213
+ settle('tasks', api.request({ path: `${base}/tasks`, method: 'GET', params: { pageSize: 250 } })),
214
+ settle('documents', api.request({ path: `${base}/documents`, method: 'GET', params: { pageSize: 250 } })),
215
+ ]);
216
+ const findings = [];
217
+ let score = 100;
218
+ const t = ticket;
219
+ const noteArr = Array.isArray(notes) ? notes : [];
220
+ const timeArr = Array.isArray(timeEntries) ? timeEntries : [];
221
+ const taskArr = Array.isArray(tasks) ? tasks : [];
222
+ if ('_error' in t) {
223
+ return text(`Error fetching ticket #${ticketId}: ${t._error}`);
224
+ }
225
+ // 1. Required fields
226
+ const requiredFields = [
227
+ { field: 'company', label: 'Company' },
228
+ { field: 'contact', label: 'Contact' },
229
+ { field: 'type', label: 'Type' },
230
+ { field: 'subType', label: 'SubType' },
231
+ { field: 'priority', label: 'Priority' },
232
+ ];
233
+ for (const { field, label } of requiredFields) {
234
+ if (!t[field] || (typeof t[field] === 'object' && !t[field]?.id)) {
235
+ findings.push({ check: `Missing ${label}`, status: 'fail', detail: `${label} is not set on this ticket` });
236
+ score -= 10;
237
+ }
238
+ else {
239
+ findings.push({ check: `${label} set`, status: 'pass', detail: `${label}: ${t[field]?.name ?? 'set'}` });
240
+ }
241
+ }
242
+ // 2. Tasks completeness
243
+ if (taskArr.length > 0) {
244
+ const incomplete = taskArr.filter(task => !task.closedFlag);
245
+ if (incomplete.length > 0) {
246
+ findings.push({ check: 'Incomplete tasks', status: 'fail', detail: `${incomplete.length} of ${taskArr.length} tasks are not complete` });
247
+ score -= 15;
248
+ }
249
+ else {
250
+ findings.push({ check: 'All tasks complete', status: 'pass', detail: `${taskArr.length} tasks all done` });
251
+ }
252
+ }
253
+ // 3. Resolution note
254
+ const hasResolution = noteArr.some(n => n.internalAnalysisFlag === false && n.resolutionFlag === true) ||
255
+ noteArr.some(n => n.resolutionFlag === true);
256
+ const isClosed = (t.closedFlag === true);
257
+ if (isClosed && !hasResolution) {
258
+ findings.push({ check: 'Missing resolution note', status: 'fail', detail: 'Ticket is closed but has no resolution note' });
259
+ score -= 15;
260
+ }
261
+ else if (hasResolution) {
262
+ findings.push({ check: 'Resolution note present', status: 'pass', detail: 'Resolution documented' });
263
+ }
264
+ // 4. Time entries with notes
265
+ if (timeArr.length > 0) {
266
+ const noNotes = timeArr.filter(e => {
267
+ const hours = Number(e.actualHours ?? 0);
268
+ const notes = String(e.notes ?? '').trim();
269
+ return hours > 0 && notes.length === 0;
270
+ });
271
+ if (noNotes.length > 0) {
272
+ findings.push({ check: 'Time entries missing notes', status: 'fail', detail: `${noNotes.length} of ${timeArr.length} entries have hours but no description` });
273
+ score -= 10;
274
+ }
275
+ else {
276
+ findings.push({ check: 'All time entries have notes', status: 'pass', detail: `${timeArr.length} entries documented` });
277
+ }
278
+ }
279
+ else if (isClosed) {
280
+ findings.push({ check: 'No time logged', status: 'warn', detail: 'Ticket is closed but has no time entries' });
281
+ score -= 5;
282
+ }
283
+ // 5. Customer notification
284
+ const hasExternalNote = noteArr.some(n => n.detailDescriptionFlag === true || n.externalFlag === true);
285
+ if (!hasExternalNote) {
286
+ findings.push({ check: 'No customer communication', status: 'warn', detail: 'No external notes found — customer may not have been notified' });
287
+ score -= 5;
288
+ }
289
+ else {
290
+ findings.push({ check: 'Customer notified', status: 'pass', detail: 'External notes present' });
291
+ }
292
+ // 6. Excessive time check
293
+ const totalHours = timeArr.reduce((s, e) => s + (Number(e.actualHours) || 0), 0);
294
+ const budgetHours = Number(t.budgetHours ?? 0);
295
+ if (budgetHours > 0 && totalHours > budgetHours * 1.5) {
296
+ findings.push({ check: 'Excessive time', status: 'warn', detail: `${Math.round(totalHours * 100) / 100}h logged vs ${budgetHours}h budget (${Math.round(totalHours / budgetHours * 100)}%)` });
297
+ score -= 10;
298
+ }
299
+ else if (budgetHours > 0) {
300
+ findings.push({ check: 'Time within budget', status: 'pass', detail: `${Math.round(totalHours * 100) / 100}h of ${budgetHours}h budget (${Math.round(totalHours / budgetHours * 100)}%)` });
301
+ }
302
+ // 7. SLA check (basic — response time)
303
+ const dateEntered = t.dateEntered;
304
+ const firstNote = noteArr.find(n => n.internalAnalysisFlag === true || n.detailDescriptionFlag === true);
305
+ if (dateEntered && firstNote) {
306
+ const created = new Date(dateEntered).getTime();
307
+ const responded = new Date(firstNote.dateCreated).getTime();
308
+ const responseMinutes = Math.round((responded - created) / 60000);
309
+ if (responseMinutes > 240) {
310
+ findings.push({ check: 'Slow first response', status: 'warn', detail: `First response after ${responseMinutes} minutes (${Math.round(responseMinutes / 60)}h)` });
311
+ score -= 5;
312
+ }
313
+ else {
314
+ findings.push({ check: 'First response time OK', status: 'pass', detail: `Responded in ${responseMinutes} minutes` });
315
+ }
316
+ }
317
+ // 8. Agreement/billing coherence
318
+ const agreement = t.agreement;
319
+ const billableEntries = timeArr.filter(e => String(e.billableOption ?? '').toLowerCase().includes('billable'));
320
+ if (!agreement && billableEntries.length > 0) {
321
+ findings.push({ check: 'Billing without agreement', status: 'warn', detail: `${billableEntries.length} billable entries but no agreement on ticket` });
322
+ }
323
+ // Build report
324
+ score = Math.max(0, score);
325
+ const passCount = findings.filter(f => f.status === 'pass').length;
326
+ const failCount = findings.filter(f => f.status === 'fail').length;
327
+ const warnCount = findings.filter(f => f.status === 'warn').length;
328
+ const lines = [];
329
+ lines.push(`## Ticket Audit: #${ticketId} — ${t.summary ?? 'N/A'}`);
330
+ lines.push('');
331
+ lines.push(`**Compliance Score: ${score}/100** | ${passCount} pass | ${failCount} fail | ${warnCount} warn`);
332
+ lines.push('');
333
+ lines.push('| Check | Status | Detail |');
334
+ lines.push('|-------|--------|--------|');
335
+ for (const f of findings) {
336
+ const icon = f.status === 'pass' ? 'PASS' : f.status === 'fail' ? 'FAIL' : 'WARN';
337
+ lines.push(`| ${f.check} | ${icon} | ${f.detail} |`);
338
+ }
339
+ if (failCount > 0) {
340
+ lines.push('');
341
+ lines.push('### Action Required');
342
+ for (const f of findings.filter(f => f.status === 'fail')) {
343
+ lines.push(`- **${f.check}**: ${f.detail}`);
344
+ }
345
+ }
346
+ return text(lines.join('\n'));
347
+ }
348
+ // -------------------------------------------------------------------------
349
+ default:
350
+ return text(`Unknown composite tool: ${toolName}`);
351
+ }
352
+ }
353
+ //# sourceMappingURL=composite.js.map