@xalantis/mcp-server 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 ADDED
@@ -0,0 +1,86 @@
1
+ # @xalantis/mcp-server
2
+
3
+ MCP server for Xalantis — manage support tickets from Claude, Cursor, and other AI tools.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @xalantis/mcp-server
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ ### Claude Desktop
14
+
15
+ Add to your `claude_desktop_config.json`:
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "xalantis": {
21
+ "command": "npx",
22
+ "args": ["-y", "@xalantis/mcp-server"],
23
+ "env": {
24
+ "XALANTIS_API_KEY": "sk_live_..."
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ ### Claude Code
32
+
33
+ ```bash
34
+ claude mcp add xalantis -- npx -y @xalantis/mcp-server
35
+ ```
36
+
37
+ Then set the environment variable `XALANTIS_API_KEY` in your shell.
38
+
39
+ ### Cursor
40
+
41
+ Add to `.cursor/mcp.json`:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "xalantis": {
47
+ "command": "npx",
48
+ "args": ["-y", "@xalantis/mcp-server"],
49
+ "env": {
50
+ "XALANTIS_API_KEY": "sk_live_..."
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Available Tools
58
+
59
+ | Tool | Description |
60
+ |------|-------------|
61
+ | `list_tickets` | List tickets with filters (status, priority, search, pagination) |
62
+ | `get_ticket` | Get a single ticket by UUID |
63
+ | `create_ticket` | Create a new support ticket |
64
+ | `update_ticket` | Update ticket status, priority, subject, etc. |
65
+ | `reply_to_ticket` | Add a reply or internal note |
66
+ | `list_ticket_replies` | List all replies for a ticket |
67
+
68
+ ## Examples
69
+
70
+ Once connected, you can ask your AI assistant:
71
+
72
+ - "List all open high-priority tickets"
73
+ - "Create a ticket about the login bug reported by user@example.com"
74
+ - "Mark ticket abc-123 as resolved"
75
+ - "Reply to ticket abc-123 saying the fix has been deployed"
76
+ - "Show me the replies on ticket abc-123"
77
+
78
+ ## Environment Variables
79
+
80
+ | Variable | Required | Description |
81
+ |----------|----------|-------------|
82
+ | `XALANTIS_API_KEY` | Yes | Your Xalantis API key |
83
+
84
+ ## License
85
+
86
+ MIT
package/build/api.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Lightweight HTTP client for the Xalantis API.
3
+ * Used internally by the MCP server tools.
4
+ */
5
+ export declare class ApiError extends Error {
6
+ code: string;
7
+ status: number;
8
+ details?: Record<string, string[]> | undefined;
9
+ constructor(message: string, code: string, status: number, details?: Record<string, string[]> | undefined);
10
+ }
11
+ export declare function assertUuid(value: string, label: string): void;
12
+ export declare function apiRequest(apiKey: string, method: string, path: string, body?: unknown, params?: Record<string, unknown>): Promise<unknown>;
package/build/api.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Lightweight HTTP client for the Xalantis API.
3
+ * Used internally by the MCP server tools.
4
+ */
5
+ const BASE_URL = 'https://xalantis.com/api/v1';
6
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7
+ export class ApiError extends Error {
8
+ code;
9
+ status;
10
+ details;
11
+ constructor(message, code, status, details) {
12
+ super(message);
13
+ this.code = code;
14
+ this.status = status;
15
+ this.details = details;
16
+ this.name = 'ApiError';
17
+ }
18
+ }
19
+ export function assertUuid(value, label) {
20
+ if (!UUID_RE.test(value)) {
21
+ throw new Error(`${label} must be a valid UUID (got "${value}")`);
22
+ }
23
+ }
24
+ export async function apiRequest(apiKey, method, path, body, params) {
25
+ const url = new URL(`${BASE_URL}${path}`);
26
+ if (params) {
27
+ for (const [key, value] of Object.entries(params)) {
28
+ if (value !== undefined && value !== null) {
29
+ url.searchParams.set(key, String(value));
30
+ }
31
+ }
32
+ }
33
+ const response = await fetch(url.toString(), {
34
+ method,
35
+ headers: {
36
+ 'Authorization': `Bearer ${apiKey}`,
37
+ 'Accept': 'application/json',
38
+ 'Content-Type': 'application/json',
39
+ },
40
+ body: body ? JSON.stringify(body) : undefined,
41
+ });
42
+ let json;
43
+ try {
44
+ json = await response.json();
45
+ }
46
+ catch {
47
+ throw new ApiError(`Server returned non-JSON response (HTTP ${response.status})`, 'INVALID_RESPONSE', response.status);
48
+ }
49
+ if (!response.ok) {
50
+ const err = json;
51
+ throw new ApiError(err?.error?.message || `Request failed (HTTP ${response.status})`, err?.error?.code || 'UNKNOWN_ERROR', response.status, err?.error?.details);
52
+ }
53
+ return json;
54
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/build/index.js ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod';
5
+ import { apiRequest, assertUuid, ApiError } from './api.js';
6
+ const API_KEY = process.env.XALANTIS_API_KEY || '';
7
+ if (!API_KEY) {
8
+ console.error('Error: XALANTIS_API_KEY environment variable is required');
9
+ process.exit(1);
10
+ }
11
+ const server = new McpServer({
12
+ name: 'xalantis',
13
+ version: '0.1.0',
14
+ });
15
+ // ─── Helper ────────────────────────────────────────────────
16
+ function text(data) {
17
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
18
+ }
19
+ function error(message) {
20
+ return { content: [{ type: 'text', text: message }], isError: true };
21
+ }
22
+ // ─── list_tickets ──────────────────────────────────────────
23
+ server.tool('list_tickets', 'List support tickets with optional filters and pagination', {
24
+ status: z.enum(['new', 'open', 'pending', 'on_hold', 'resolved', 'closed']).optional().describe('Filter by status'),
25
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().describe('Filter by priority'),
26
+ search: z.string().optional().describe('Search in subject and description'),
27
+ requester_email: z.string().optional().describe('Filter by requester email'),
28
+ sort_by: z.enum(['created_at', 'updated_at', 'priority', 'status', 'due_at', 'reference']).optional().describe('Sort field'),
29
+ sort_dir: z.enum(['asc', 'desc']).optional().describe('Sort direction'),
30
+ page: z.number().int().min(1).optional().describe('Page number'),
31
+ per_page: z.number().int().min(1).max(100).optional().describe('Results per page (max 100)'),
32
+ }, async (params) => {
33
+ try {
34
+ const result = await apiRequest(API_KEY, 'GET', '/tickets', undefined, params);
35
+ return text(result);
36
+ }
37
+ catch (e) {
38
+ return error(e instanceof Error ? e.message : 'Unknown error');
39
+ }
40
+ });
41
+ // ─── get_ticket ────────────────────────────────────────────
42
+ server.tool('get_ticket', 'Get a single ticket by UUID', {
43
+ uuid: z.string().describe('Ticket UUID'),
44
+ }, async ({ uuid }) => {
45
+ try {
46
+ assertUuid(uuid, 'ticket uuid');
47
+ const result = await apiRequest(API_KEY, 'GET', `/tickets/${uuid}`);
48
+ return text(result);
49
+ }
50
+ catch (e) {
51
+ return error(e instanceof Error ? e.message : 'Unknown error');
52
+ }
53
+ });
54
+ // ─── create_ticket ─────────────────────────────────────────
55
+ server.tool('create_ticket', 'Create a new support ticket', {
56
+ subject: z.string().min(1).max(255).describe('Ticket subject'),
57
+ description: z.string().min(1).describe('Ticket description'),
58
+ requester_email: z.string().email().describe('Email of the person reporting the issue'),
59
+ requester_name: z.string().optional().describe('Name of the requester'),
60
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().describe('Priority level (default: medium)'),
61
+ category_slug: z.string().optional().describe('Category slug'),
62
+ channel: z.enum(['api', 'email', 'phone', 'chat', 'web', 'widget']).optional().describe('Channel source'),
63
+ }, async (params) => {
64
+ try {
65
+ const result = await apiRequest(API_KEY, 'POST', '/tickets', params);
66
+ return text(result);
67
+ }
68
+ catch (e) {
69
+ if (e instanceof ApiError && e.details) {
70
+ return error(`Validation failed: ${JSON.stringify(e.details)}`);
71
+ }
72
+ return error(e instanceof Error ? e.message : 'Unknown error');
73
+ }
74
+ });
75
+ // ─── update_ticket ─────────────────────────────────────────
76
+ server.tool('update_ticket', 'Update an existing ticket (status, priority, subject, etc.)', {
77
+ uuid: z.string().describe('Ticket UUID'),
78
+ subject: z.string().min(1).max(255).optional().describe('New subject'),
79
+ description: z.string().min(1).optional().describe('New description'),
80
+ status: z.enum(['new', 'open', 'pending', 'on_hold', 'resolved', 'closed']).optional().describe('New status'),
81
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().describe('New priority'),
82
+ category_slug: z.string().optional().describe('Category slug'),
83
+ assignee_id: z.number().int().optional().describe('Assign to agent by ID'),
84
+ due_at: z.string().optional().describe('SLA due date (ISO 8601)'),
85
+ }, async ({ uuid, ...data }) => {
86
+ try {
87
+ assertUuid(uuid, 'ticket uuid');
88
+ const result = await apiRequest(API_KEY, 'PATCH', `/tickets/${uuid}`, data);
89
+ return text(result);
90
+ }
91
+ catch (e) {
92
+ return error(e instanceof Error ? e.message : 'Unknown error');
93
+ }
94
+ });
95
+ // ─── reply_to_ticket ───────────────────────────────────────
96
+ server.tool('reply_to_ticket', 'Add a reply or internal note to a ticket', {
97
+ ticket_uuid: z.string().describe('Ticket UUID'),
98
+ content: z.string().min(1).describe('Reply content'),
99
+ is_internal: z.boolean().optional().describe('Set to true for an internal note (not visible to requester)'),
100
+ }, async ({ ticket_uuid, ...data }) => {
101
+ try {
102
+ assertUuid(ticket_uuid, 'ticket uuid');
103
+ const result = await apiRequest(API_KEY, 'POST', `/tickets/${ticket_uuid}/replies`, data);
104
+ return text(result);
105
+ }
106
+ catch (e) {
107
+ return error(e instanceof Error ? e.message : 'Unknown error');
108
+ }
109
+ });
110
+ // ─── list_ticket_replies ───────────────────────────────────
111
+ server.tool('list_ticket_replies', 'List all replies and notes for a ticket', {
112
+ ticket_uuid: z.string().describe('Ticket UUID'),
113
+ page: z.number().int().min(1).optional().describe('Page number'),
114
+ per_page: z.number().int().min(1).max(100).optional().describe('Results per page'),
115
+ }, async ({ ticket_uuid, ...params }) => {
116
+ try {
117
+ assertUuid(ticket_uuid, 'ticket uuid');
118
+ const result = await apiRequest(API_KEY, 'GET', `/tickets/${ticket_uuid}/replies`, undefined, params);
119
+ return text(result);
120
+ }
121
+ catch (e) {
122
+ return error(e instanceof Error ? e.message : 'Unknown error');
123
+ }
124
+ });
125
+ // ─── Start server ──────────────────────────────────────────
126
+ async function main() {
127
+ const transport = new StdioServerTransport();
128
+ await server.connect(transport);
129
+ console.error('Xalantis MCP server running');
130
+ }
131
+ main().catch((err) => {
132
+ console.error('Fatal error:', err);
133
+ process.exit(1);
134
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@xalantis/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Xalantis — manage support tickets from Claude, Cursor, and other AI tools",
5
+ "type": "module",
6
+ "bin": {
7
+ "xalantis-mcp": "./build/index.js"
8
+ },
9
+ "main": "build/index.js",
10
+ "files": ["build"],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "start": "node build/index.js",
15
+ "lint": "tsc --noEmit"
16
+ },
17
+ "keywords": ["xalantis", "mcp", "model-context-protocol", "tickets", "support", "ai"],
18
+ "author": "WAZMINE <dev@xalantis.com>",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.0.0",
22
+ "zod": "^3.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.0.0",
26
+ "typescript": "^5.7.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ }
31
+ }