superops-it 1.0.1 → 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 CHANGED
@@ -12,40 +12,38 @@ SuperOps IT Teams is for **internal IT departments** - IT teams within a single
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
- npx superops-it
18
+ bunx superops-it@latest
17
19
  ```
18
20
 
19
- Or install globally:
21
+ Or with npx:
20
22
 
21
23
  ```bash
22
- npm install -g superops-it
24
+ npx superops-it@latest
23
25
  ```
24
26
 
25
- ## Configuration
27
+ Or install globally:
26
28
 
27
- ### Claude Code
29
+ ```bash
30
+ bun install -g superops-it@latest
31
+ ```
28
32
 
29
33
  ```bash
30
- claude mcp add superops-it -- npx -y superops-it
34
+ npm install -g superops-it@latest
31
35
  ```
32
36
 
33
- <details>
34
- <summary>Or manually add to settings</summary>
37
+ ## Configuration
35
38
 
36
- Add to `~/.claude/settings.json`:
39
+ ### Claude Code
37
40
 
38
- ```json
39
- {
40
- "mcpServers": {
41
- "superops-it": {
42
- "command": "npx",
43
- "args": ["-y", "superops-it"]
44
- }
45
- }
46
- }
41
+ ```bash
42
+ claude mcp add superops-it \
43
+ -e SUPEROPS_API_KEY=your-api-key \
44
+ -e SUPEROPS_SUBDOMAIN=your-subdomain \
45
+ -- bunx superops-it@latest
47
46
  ```
48
- </details>
49
47
 
50
48
  ### Claude Desktop
51
49
 
@@ -55,13 +53,29 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
55
53
  {
56
54
  "mcpServers": {
57
55
  "superops-it": {
58
- "command": "npx",
59
- "args": ["-y", "superops-it"]
56
+ "command": "bunx",
57
+ "args": ["superops-it@latest"],
58
+ "env": {
59
+ "SUPEROPS_API_KEY": "your-api-key",
60
+ "SUPEROPS_SUBDOMAIN": "your-subdomain"
61
+ }
60
62
  }
61
63
  }
62
64
  }
63
65
  ```
64
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
+
65
79
  ## Available Tools
66
80
 
67
81
  ### `search_superops_api`
@@ -100,6 +114,26 @@ list_superops_operations({ type: "mutations" })
100
114
  list_superops_operations({ type: "all" })
101
115
  ```
102
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", departmentId: "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
+
103
137
  ## API Endpoints
104
138
 
105
139
  - **US**: `https://api.superops.ai/it`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superops-it",
3
- "version": "1.0.1",
3
+ "version": "1.1.12",
4
4
  "description": "MCP server for SuperOps IT Teams GraphQL API documentation",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
@@ -23,19 +23,18 @@
23
23
  "license": "MIT",
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "git+https://github.com/Limehawk/superops-mcp.git",
26
+ "url": "git+https://github.com/limehawk/superops-mcp.git",
27
27
  "directory": "packages/it"
28
28
  },
29
- "homepage": "https://github.com/Limehawk/superops-mcp/tree/main/packages/it#readme",
29
+ "homepage": "https://github.com/limehawk/superops-mcp/tree/main/packages/it#readme",
30
30
  "bugs": {
31
- "url": "https://github.com/Limehawk/superops-mcp/issues"
31
+ "url": "https://github.com/limehawk/superops-mcp/issues"
32
32
  },
33
33
  "dependencies": {
34
34
  "@modelcontextprotocol/sdk": "^1.25.2"
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 IT Teams 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}/it`;
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-it-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-it';
14
- const SERVER_VERSION = '1.0.0';
19
+ const SERVER_VERSION = pkg.version;
15
20
  const PRODUCT_NAME = 'SuperOps IT Teams';
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
- { capabilities: { tools: {} } }
57
+ {
58
+ instructions: 'Use this server when the user needs help with the SuperOps IT Teams GraphQL API for internal IT departments. Provides documentation for tickets, assets, departments, users, and internal service desk 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: [{
@@ -1,9 +0,0 @@
1
- {
2
- "title": "SuperOps IT Teams GraphQL API",
3
- "description": "Welcome to the Superops IT 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/it",
7
- "eu": "https://euapi.superops.ai/it"
8
- }
9
- }