@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 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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './base-server';
2
+ export * from './types';
3
+ export * from './utils';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './base-server';
2
+ export * from './types';
3
+ export * from './utils';
@@ -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
+ });
@@ -0,0 +1,3 @@
1
+ export declare function sanitizeGroupId(groupId: string): string;
2
+ export declare function formatApiUrl(version: string, endpoint: string): string;
3
+ export declare function handleApiError(error: any): string;
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
+ }