@access-mcp/shared 0.2.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 +43 -0
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +50 -0
- package/dist/base-server.d.ts +19 -0
- package/dist/base-server.js +107 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +21 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +15 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @access-mcp/shared
|
|
2
|
+
|
|
3
|
+
Shared utilities and base classes for ACCESS-CI MCP servers.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides the common infrastructure used by all ACCESS-CI MCP servers:
|
|
8
|
+
|
|
9
|
+
- **BaseAccessServer**: Abstract base class for creating MCP servers
|
|
10
|
+
- **Error handling utilities**: Consistent error handling across servers
|
|
11
|
+
- **Type definitions**: Shared TypeScript types
|
|
12
|
+
- **API utilities**: Helper functions for API interactions
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @access-mcp/shared
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { BaseAccessServer, handleApiError } from '@access-mcp/shared';
|
|
24
|
+
|
|
25
|
+
export class MyServer extends BaseAccessServer {
|
|
26
|
+
constructor() {
|
|
27
|
+
super('my-server', '1.0.0');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Implement required abstract methods
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- Automatic stdio transport setup
|
|
37
|
+
- Consistent error handling
|
|
38
|
+
- Built-in axios HTTP client with authentication support
|
|
39
|
+
- TypeScript support with full type definitions
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { sanitizeGroupId, formatApiUrl, handleApiError } from '../utils.js';
|
|
3
|
+
describe('Utils', () => {
|
|
4
|
+
describe('sanitizeGroupId', () => {
|
|
5
|
+
test('should remove invalid characters', () => {
|
|
6
|
+
expect(sanitizeGroupId('test@#$%')).toBe('test');
|
|
7
|
+
});
|
|
8
|
+
test('should keep valid characters', () => {
|
|
9
|
+
expect(sanitizeGroupId('test.group-123')).toBe('test.group-123');
|
|
10
|
+
});
|
|
11
|
+
test('should handle empty string', () => {
|
|
12
|
+
expect(sanitizeGroupId('')).toBe('');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('formatApiUrl', () => {
|
|
16
|
+
test('should format API URL correctly', () => {
|
|
17
|
+
expect(formatApiUrl('1.0', 'users')).toBe('/1.0/users');
|
|
18
|
+
});
|
|
19
|
+
test('should handle empty endpoint', () => {
|
|
20
|
+
expect(formatApiUrl('2.0', '')).toBe('/2.0/');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('handleApiError', () => {
|
|
24
|
+
test('should extract message from response data', () => {
|
|
25
|
+
const error = {
|
|
26
|
+
response: {
|
|
27
|
+
data: { message: 'Custom error message' }
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
expect(handleApiError(error)).toBe('Custom error message');
|
|
31
|
+
});
|
|
32
|
+
test('should handle status error', () => {
|
|
33
|
+
const error = {
|
|
34
|
+
response: {
|
|
35
|
+
status: 404,
|
|
36
|
+
statusText: 'Not Found'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
expect(handleApiError(error)).toBe('API error: 404 Not Found');
|
|
40
|
+
});
|
|
41
|
+
test('should handle error message', () => {
|
|
42
|
+
const error = { message: 'Network error' };
|
|
43
|
+
expect(handleApiError(error)).toBe('Network error');
|
|
44
|
+
});
|
|
45
|
+
test('should handle unknown error', () => {
|
|
46
|
+
const error = {};
|
|
47
|
+
expect(handleApiError(error)).toBe('Unknown API error');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { AxiosInstance } from 'axios';
|
|
4
|
+
export declare abstract class BaseAccessServer {
|
|
5
|
+
protected serverName: string;
|
|
6
|
+
protected version: string;
|
|
7
|
+
protected baseURL: string;
|
|
8
|
+
protected server: Server;
|
|
9
|
+
protected transport: StdioServerTransport;
|
|
10
|
+
private _httpClient?;
|
|
11
|
+
constructor(serverName: string, version: string, baseURL?: string);
|
|
12
|
+
protected get httpClient(): AxiosInstance;
|
|
13
|
+
private setupHandlers;
|
|
14
|
+
protected abstract getTools(): any[];
|
|
15
|
+
protected abstract getResources(): any[];
|
|
16
|
+
protected abstract handleToolCall(request: any): Promise<any>;
|
|
17
|
+
protected abstract handleResourceRead(request: any): Promise<any>;
|
|
18
|
+
start(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
export class BaseAccessServer {
|
|
6
|
+
serverName;
|
|
7
|
+
version;
|
|
8
|
+
baseURL;
|
|
9
|
+
server;
|
|
10
|
+
transport;
|
|
11
|
+
_httpClient;
|
|
12
|
+
constructor(serverName, version, baseURL = 'https://support.access-ci.org/api') {
|
|
13
|
+
this.serverName = serverName;
|
|
14
|
+
this.version = version;
|
|
15
|
+
this.baseURL = baseURL;
|
|
16
|
+
this.server = new Server({
|
|
17
|
+
name: serverName,
|
|
18
|
+
version: version,
|
|
19
|
+
}, {
|
|
20
|
+
capabilities: {
|
|
21
|
+
resources: {},
|
|
22
|
+
tools: {},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
this.transport = new StdioServerTransport();
|
|
26
|
+
this.setupHandlers();
|
|
27
|
+
}
|
|
28
|
+
get httpClient() {
|
|
29
|
+
if (!this._httpClient) {
|
|
30
|
+
const headers = {
|
|
31
|
+
'User-Agent': `${this.serverName}/${this.version}`,
|
|
32
|
+
};
|
|
33
|
+
// Add authentication if API key is provided
|
|
34
|
+
const apiKey = process.env.ACCESS_CI_API_KEY;
|
|
35
|
+
if (apiKey) {
|
|
36
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
37
|
+
}
|
|
38
|
+
this._httpClient = axios.create({
|
|
39
|
+
baseURL: this.baseURL,
|
|
40
|
+
timeout: 5000,
|
|
41
|
+
headers,
|
|
42
|
+
validateStatus: () => true, // Don't throw on HTTP errors
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return this._httpClient;
|
|
46
|
+
}
|
|
47
|
+
setupHandlers() {
|
|
48
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
49
|
+
try {
|
|
50
|
+
return { tools: this.getTools() };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Silent error handling for MCP compatibility
|
|
54
|
+
return { tools: [] };
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
58
|
+
try {
|
|
59
|
+
return { resources: this.getResources() };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Silent error handling for MCP compatibility
|
|
63
|
+
return { resources: [] };
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
67
|
+
try {
|
|
68
|
+
return await this.handleToolCall(request);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
72
|
+
console.error('Error handling tool call:', errorMessage);
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: `Error: ${errorMessage}`,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
84
|
+
try {
|
|
85
|
+
return await this.handleResourceRead(request);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
89
|
+
console.error('Error reading resource:', errorMessage);
|
|
90
|
+
return {
|
|
91
|
+
contents: [
|
|
92
|
+
{
|
|
93
|
+
uri: request.params.uri,
|
|
94
|
+
mimeType: 'text/plain',
|
|
95
|
+
text: `Error: ${errorMessage}`,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async start() {
|
|
103
|
+
await this.server.connect(this.transport);
|
|
104
|
+
// MCP servers should not output anything to stderr/stdout when running
|
|
105
|
+
// as it interferes with JSON-RPC communication
|
|
106
|
+
}
|
|
107
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const AffinityGroupSchema: z.ZodObject<{
|
|
3
|
+
group_id: z.ZodString;
|
|
4
|
+
name: z.ZodOptional<z.ZodString>;
|
|
5
|
+
description: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
group_id: string;
|
|
8
|
+
name?: string | undefined;
|
|
9
|
+
description?: string | undefined;
|
|
10
|
+
}, {
|
|
11
|
+
group_id: string;
|
|
12
|
+
name?: string | undefined;
|
|
13
|
+
description?: string | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const EventSchema: z.ZodObject<{
|
|
16
|
+
id: z.ZodString;
|
|
17
|
+
title: z.ZodString;
|
|
18
|
+
description: z.ZodOptional<z.ZodString>;
|
|
19
|
+
start_date: z.ZodOptional<z.ZodString>;
|
|
20
|
+
end_date: z.ZodOptional<z.ZodString>;
|
|
21
|
+
location: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
title: string;
|
|
24
|
+
id: string;
|
|
25
|
+
description?: string | undefined;
|
|
26
|
+
start_date?: string | undefined;
|
|
27
|
+
end_date?: string | undefined;
|
|
28
|
+
location?: string | undefined;
|
|
29
|
+
}, {
|
|
30
|
+
title: string;
|
|
31
|
+
id: string;
|
|
32
|
+
description?: string | undefined;
|
|
33
|
+
start_date?: string | undefined;
|
|
34
|
+
end_date?: string | undefined;
|
|
35
|
+
location?: string | undefined;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const KnowledgeBaseResourceSchema: z.ZodObject<{
|
|
38
|
+
id: z.ZodString;
|
|
39
|
+
title: z.ZodString;
|
|
40
|
+
content: z.ZodOptional<z.ZodString>;
|
|
41
|
+
category: z.ZodOptional<z.ZodString>;
|
|
42
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
title: string;
|
|
45
|
+
id: string;
|
|
46
|
+
content?: string | undefined;
|
|
47
|
+
category?: string | undefined;
|
|
48
|
+
tags?: string[] | undefined;
|
|
49
|
+
}, {
|
|
50
|
+
title: string;
|
|
51
|
+
id: string;
|
|
52
|
+
content?: string | undefined;
|
|
53
|
+
category?: string | undefined;
|
|
54
|
+
tags?: string[] | undefined;
|
|
55
|
+
}>;
|
|
56
|
+
export type AffinityGroup = z.infer<typeof AffinityGroupSchema>;
|
|
57
|
+
export type Event = z.infer<typeof EventSchema>;
|
|
58
|
+
export type KnowledgeBaseResource = z.infer<typeof KnowledgeBaseResourceSchema>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const AffinityGroupSchema = z.object({
|
|
3
|
+
group_id: z.string(),
|
|
4
|
+
name: z.string().optional(),
|
|
5
|
+
description: z.string().optional(),
|
|
6
|
+
});
|
|
7
|
+
export const EventSchema = z.object({
|
|
8
|
+
id: z.string(),
|
|
9
|
+
title: z.string(),
|
|
10
|
+
description: z.string().optional(),
|
|
11
|
+
start_date: z.string().optional(),
|
|
12
|
+
end_date: z.string().optional(),
|
|
13
|
+
location: z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
export const KnowledgeBaseResourceSchema = z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
title: z.string(),
|
|
18
|
+
content: z.string().optional(),
|
|
19
|
+
category: z.string().optional(),
|
|
20
|
+
tags: z.array(z.string()).optional(),
|
|
21
|
+
});
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function sanitizeGroupId(groupId) {
|
|
2
|
+
return groupId.replace(/[^a-zA-Z0-9.-]/g, '');
|
|
3
|
+
}
|
|
4
|
+
export function formatApiUrl(version, endpoint) {
|
|
5
|
+
return `/${version}/${endpoint}`;
|
|
6
|
+
}
|
|
7
|
+
export function handleApiError(error) {
|
|
8
|
+
if (error.response?.data?.message) {
|
|
9
|
+
return error.response.data.message;
|
|
10
|
+
}
|
|
11
|
+
if (error.response?.status) {
|
|
12
|
+
return `API error: ${error.response.status} ${error.response.statusText}`;
|
|
13
|
+
}
|
|
14
|
+
return error.message || 'Unknown API error';
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@access-mcp/shared",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared utilities for ACCESS-CI MCP servers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/**/*",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"access-ci",
|
|
20
|
+
"hpc",
|
|
21
|
+
"utilities"
|
|
22
|
+
],
|
|
23
|
+
"author": "ACCESS-CI",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/access-ci/access-mcp.git",
|
|
28
|
+
"directory": "packages/shared"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/access-ci/access-mcp#readme",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.16.0",
|
|
36
|
+
"axios": "^1.6.0",
|
|
37
|
+
"zod": "^3.22.0"
|
|
38
|
+
}
|
|
39
|
+
}
|