superops-msp 1.0.6 → 1.1.12
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 +56 -18
- package/package.json +2 -3
- package/src/client.mjs +137 -0
- package/src/index.mjs +101 -2
- package/docs/introduction.json +0 -9
package/README.md
CHANGED
|
@@ -12,35 +12,37 @@ SuperOps MSP is for **Managed Service Providers** - IT companies that manage tec
|
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
|
+
We recommend using [bun](https://bun.sh) for faster startup times - MCP servers start on every request, so speed matters.
|
|
16
|
+
|
|
15
17
|
```bash
|
|
16
|
-
|
|
18
|
+
bunx superops-msp@latest
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
Or
|
|
21
|
+
Or with npx:
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
|
-
|
|
24
|
+
npx superops-msp@latest
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
Or install globally:
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
```bash
|
|
30
|
+
bun install -g superops-msp@latest
|
|
31
|
+
```
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
|
-
|
|
34
|
+
npm install -g superops-msp@latest
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
## Configuration
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
}
|
|
39
|
+
### Claude Code
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
claude mcp add superops-msp \
|
|
43
|
+
-e SUPEROPS_API_KEY=your-api-key \
|
|
44
|
+
-e SUPEROPS_SUBDOMAIN=your-subdomain \
|
|
45
|
+
-- bunx superops-msp@latest
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
### Claude Desktop
|
|
@@ -51,13 +53,29 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
|
51
53
|
{
|
|
52
54
|
"mcpServers": {
|
|
53
55
|
"superops-msp": {
|
|
54
|
-
"command": "
|
|
55
|
-
"args": ["
|
|
56
|
+
"command": "bunx",
|
|
57
|
+
"args": ["superops-msp@latest"],
|
|
58
|
+
"env": {
|
|
59
|
+
"SUPEROPS_API_KEY": "your-api-key",
|
|
60
|
+
"SUPEROPS_SUBDOMAIN": "your-subdomain"
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
```
|
|
60
66
|
|
|
67
|
+
### Environment Variables
|
|
68
|
+
|
|
69
|
+
| Variable | Required | Description |
|
|
70
|
+
|----------|----------|-------------|
|
|
71
|
+
| `SUPEROPS_API_KEY` | Yes | Your SuperOps API key |
|
|
72
|
+
| `SUPEROPS_SUBDOMAIN` | Yes | Your subdomain (e.g., `acme` from `acme.superops.ai`) |
|
|
73
|
+
| `SUPEROPS_REGION` | No | `us` (default) or `eu` |
|
|
74
|
+
| `SUPEROPS_TIMEOUT` | No | Request timeout in ms (default: 30000) |
|
|
75
|
+
| `SUPEROPS_READ_ONLY` | No | Set to `true` to block mutations |
|
|
76
|
+
|
|
77
|
+
Get your API key from **SuperOps Admin > API Settings**.
|
|
78
|
+
|
|
61
79
|
## Available Tools
|
|
62
80
|
|
|
63
81
|
### `search_superops_api`
|
|
@@ -96,6 +114,26 @@ list_superops_operations({ type: "mutations" })
|
|
|
96
114
|
list_superops_operations({ type: "all" })
|
|
97
115
|
```
|
|
98
116
|
|
|
117
|
+
### `execute_graphql`
|
|
118
|
+
|
|
119
|
+
Execute a GraphQL query or mutation against the SuperOps API. Requires environment variables (see [Configuration](#configuration)).
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
execute_graphql({
|
|
123
|
+
operation: "query { getTicket(id: \"123\") { id subject status } }"
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
execute_graphql({
|
|
127
|
+
operation: "mutation createTicket($input: CreateTicketInput!) { createTicket(input: $input) { id } }",
|
|
128
|
+
variables: { input: { subject: "New ticket", clientId: "456" } }
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**API limits and notes:**
|
|
133
|
+
- Maximum 800 API requests per minute
|
|
134
|
+
- Date/time values must be in UTC timezone with ISO format (e.g., `2022-04-10T10:15:30`)
|
|
135
|
+
- Use `null` to clear/reset attribute values
|
|
136
|
+
|
|
99
137
|
## API Endpoints
|
|
100
138
|
|
|
101
139
|
- **US**: `https://api.superops.ai/msp`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superops-msp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.12",
|
|
4
4
|
"description": "MCP server for SuperOps MSP GraphQL API documentation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"src/",
|
|
38
|
-
"docs/api-index.json"
|
|
39
|
-
"docs/introduction.json"
|
|
38
|
+
"docs/api-index.json"
|
|
40
39
|
]
|
|
41
40
|
}
|
package/src/client.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SuperOps MSP GraphQL Client
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Bearer token authentication
|
|
6
|
+
* - Configurable timeout with AbortController
|
|
7
|
+
* - Exponential backoff retry for transient failures
|
|
8
|
+
* - Read-only mode to block mutations
|
|
9
|
+
* - Handles GraphQL errors returned with 200 status
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
13
|
+
const MAX_RETRIES = 3;
|
|
14
|
+
const RETRY_DELAYS = [1000, 2000, 4000];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Structured error for SuperOps API failures
|
|
18
|
+
*/
|
|
19
|
+
export class SuperOpsAPIError extends Error {
|
|
20
|
+
constructor(status, body, context = {}) {
|
|
21
|
+
const message = SuperOpsAPIError.formatMessage(status, body);
|
|
22
|
+
super(message);
|
|
23
|
+
|
|
24
|
+
this.name = 'SuperOpsAPIError';
|
|
25
|
+
this.status = status;
|
|
26
|
+
this.body = body;
|
|
27
|
+
this.context = context;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static formatMessage(status, body) {
|
|
31
|
+
if (status === 200 && Array.isArray(body)) {
|
|
32
|
+
return body.map(e => e.message).join('; ');
|
|
33
|
+
}
|
|
34
|
+
return `HTTP ${status}: ${body?.message || body?.error || 'Request failed'}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isRateLimited() { return this.status === 429; }
|
|
38
|
+
isAuthError() { return this.status === 401 || this.status === 403; }
|
|
39
|
+
isServerError() { return this.status >= 500; }
|
|
40
|
+
isGraphQLError() { return this.status === 200 && Array.isArray(this.body); }
|
|
41
|
+
isRetryable() { return this.isRateLimited() || this.isServerError(); }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sleep(ms) {
|
|
45
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function withRetry(fn, maxRetries = MAX_RETRIES) {
|
|
49
|
+
let lastError;
|
|
50
|
+
|
|
51
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
52
|
+
try {
|
|
53
|
+
return await fn();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
lastError = error;
|
|
56
|
+
|
|
57
|
+
if (!(error instanceof SuperOpsAPIError) || !error.isRetryable()) {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (attempt < maxRetries - 1) {
|
|
62
|
+
await sleep(RETRY_DELAYS[attempt]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw lastError;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class SuperOpsClient {
|
|
71
|
+
constructor(config) {
|
|
72
|
+
if (!config.apiKey) {
|
|
73
|
+
throw new Error('SUPEROPS_API_KEY is required');
|
|
74
|
+
}
|
|
75
|
+
if (!config.subdomain) {
|
|
76
|
+
throw new Error('SUPEROPS_SUBDOMAIN is required');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.apiKey = config.apiKey;
|
|
80
|
+
this.subdomain = config.subdomain;
|
|
81
|
+
this.region = config.region || 'us';
|
|
82
|
+
this.timeout = config.timeout || DEFAULT_TIMEOUT_MS;
|
|
83
|
+
this.readOnly = config.readOnly ?? false;
|
|
84
|
+
|
|
85
|
+
const host = this.region === 'eu' ? 'euapi.superops.ai' : 'api.superops.ai';
|
|
86
|
+
this.endpoint = `https://${host}/msp`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async execute(operation, variables = {}) {
|
|
90
|
+
if (this.readOnly && operation.trim().toLowerCase().startsWith('mutation')) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
'Mutations are disabled in read-only mode. ' +
|
|
93
|
+
'Set SUPEROPS_READ_ONLY=false to enable mutations.'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const context = { operation, variables, endpoint: this.endpoint };
|
|
98
|
+
|
|
99
|
+
return withRetry(async () => {
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(this.endpoint, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
109
|
+
'CustomerSubDomain': this.subdomain,
|
|
110
|
+
'User-Agent': 'superops-msp-mcp/1.0'
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({ query: operation, variables }),
|
|
113
|
+
signal: controller.signal
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const body = await response.json();
|
|
117
|
+
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
throw new SuperOpsAPIError(response.status, body, context);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (body.errors?.length) {
|
|
123
|
+
throw new SuperOpsAPIError(200, body.errors, context);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return body.data;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error.name === 'AbortError') {
|
|
129
|
+
throw new Error(`Request timed out after ${this.timeout}ms`);
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
} finally {
|
|
133
|
+
clearTimeout(timeoutId);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -8,15 +8,36 @@ import {
|
|
|
8
8
|
import { readFile } from 'fs/promises';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import { dirname, join } from 'path';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
import { SuperOpsClient, SuperOpsAPIError } from './client.mjs';
|
|
11
13
|
|
|
12
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const pkg = require('../package.json');
|
|
17
|
+
|
|
13
18
|
const SERVER_NAME = 'superops-msp';
|
|
14
|
-
const SERVER_VERSION =
|
|
19
|
+
const SERVER_VERSION = pkg.version;
|
|
15
20
|
const PRODUCT_NAME = 'SuperOps MSP';
|
|
16
21
|
|
|
17
22
|
// API data cache
|
|
18
23
|
let apiData = null;
|
|
19
24
|
|
|
25
|
+
// API client (lazy initialization)
|
|
26
|
+
let client = null;
|
|
27
|
+
|
|
28
|
+
function getClient() {
|
|
29
|
+
if (!client && process.env.SUPEROPS_API_KEY && process.env.SUPEROPS_SUBDOMAIN) {
|
|
30
|
+
client = new SuperOpsClient({
|
|
31
|
+
apiKey: process.env.SUPEROPS_API_KEY,
|
|
32
|
+
subdomain: process.env.SUPEROPS_SUBDOMAIN,
|
|
33
|
+
region: process.env.SUPEROPS_REGION,
|
|
34
|
+
timeout: parseInt(process.env.SUPEROPS_TIMEOUT) || undefined,
|
|
35
|
+
readOnly: process.env.SUPEROPS_READ_ONLY === 'true'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return client;
|
|
39
|
+
}
|
|
40
|
+
|
|
20
41
|
async function loadApiData() {
|
|
21
42
|
if (apiData) return apiData;
|
|
22
43
|
|
|
@@ -33,7 +54,10 @@ async function loadApiData() {
|
|
|
33
54
|
// Create server
|
|
34
55
|
const server = new Server(
|
|
35
56
|
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
36
|
-
{
|
|
57
|
+
{
|
|
58
|
+
instructions: 'Use this server when the user needs help with the SuperOps MSP GraphQL API for managed service providers. Provides documentation for tickets, assets, clients, sites, contracts, billing, runbooks, and other MSP operations. Search for API queries, mutations, and type definitions.',
|
|
59
|
+
capabilities: { tools: {} }
|
|
60
|
+
}
|
|
37
61
|
);
|
|
38
62
|
|
|
39
63
|
// List available tools
|
|
@@ -96,6 +120,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
96
120
|
},
|
|
97
121
|
required: ['type']
|
|
98
122
|
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'execute_graphql',
|
|
126
|
+
description: `Execute a GraphQL query or mutation against the ${PRODUCT_NAME} API. Requires SUPEROPS_API_KEY environment variable.
|
|
127
|
+
|
|
128
|
+
IMPORTANT: Before constructing queries, you MUST look up the correct field names and syntax:
|
|
129
|
+
1. Call get_superops_operation to get the query template and see the input type name
|
|
130
|
+
2. Call get_superops_type for the input type (e.g., ListInfoInput) to see exact field names (pageSize not limit)
|
|
131
|
+
3. Call get_superops_type for any nested types (e.g., SortInput, SortOrder) to see enum values (use DESC not "desc")
|
|
132
|
+
4. Call get_superops_type for the return type (e.g., Ticket, Client) to see available fields (accountId not id)
|
|
133
|
+
|
|
134
|
+
Do NOT guess field names - they are non-standard. Always look them up first.`,
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties: {
|
|
138
|
+
operation: {
|
|
139
|
+
type: 'string',
|
|
140
|
+
description: 'The GraphQL query or mutation to execute'
|
|
141
|
+
},
|
|
142
|
+
variables: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
description: 'Variables for the operation (optional)'
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
required: ['operation']
|
|
148
|
+
}
|
|
99
149
|
}
|
|
100
150
|
]
|
|
101
151
|
};
|
|
@@ -239,6 +289,55 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
239
289
|
};
|
|
240
290
|
}
|
|
241
291
|
|
|
292
|
+
case 'execute_graphql': {
|
|
293
|
+
const apiClient = getClient();
|
|
294
|
+
|
|
295
|
+
if (!apiClient) {
|
|
296
|
+
return {
|
|
297
|
+
content: [{
|
|
298
|
+
type: 'text',
|
|
299
|
+
text: 'API execution requires SUPEROPS_API_KEY and SUPEROPS_SUBDOMAIN environment variables.\n\nGet your API key from SuperOps Admin > API Settings.\nYour subdomain is the prefix in your SuperOps URL (e.g., "acme" from acme.superops.ai).'
|
|
300
|
+
}],
|
|
301
|
+
isError: true
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const result = await apiClient.execute(
|
|
307
|
+
args.operation,
|
|
308
|
+
args.variables || {}
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
content: [{
|
|
313
|
+
type: 'text',
|
|
314
|
+
text: JSON.stringify(result, null, 2)
|
|
315
|
+
}]
|
|
316
|
+
};
|
|
317
|
+
} catch (error) {
|
|
318
|
+
let message;
|
|
319
|
+
|
|
320
|
+
if (error instanceof SuperOpsAPIError) {
|
|
321
|
+
if (error.isAuthError()) {
|
|
322
|
+
message = 'Authentication failed. Check your SUPEROPS_API_KEY.';
|
|
323
|
+
} else if (error.isRateLimited()) {
|
|
324
|
+
message = 'Rate limited after retries. Try again later.';
|
|
325
|
+
} else if (error.isGraphQLError()) {
|
|
326
|
+
message = `GraphQL Error: ${error.message}`;
|
|
327
|
+
} else {
|
|
328
|
+
message = `API Error (${error.status}): ${error.message}`;
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
message = error.message;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
content: [{ type: 'text', text: message }],
|
|
336
|
+
isError: true
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
242
341
|
default:
|
|
243
342
|
return {
|
|
244
343
|
content: [{
|
package/docs/introduction.json
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"title": "SuperOps MSP GraphQL API",
|
|
3
|
-
"description": "Welcome to the Superops MSP GraphQL API reference! This reference includes the complete set of GraphQL types, queries, mutations, and their parameters. For more tutorial-oriented API documentation, please check out our API Guide",
|
|
4
|
-
"contact": "support@superops.com",
|
|
5
|
-
"endpoints": {
|
|
6
|
-
"us": "https://api.superops.ai/msp",
|
|
7
|
-
"eu": "https://euapi.superops.ai/msp"
|
|
8
|
-
}
|
|
9
|
-
}
|