@access-mcp/shared 0.5.0 → 0.6.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.
@@ -16,6 +16,7 @@ describe("Inter-server Communication Integration Tests", () => {
16
16
  env: {
17
17
  ...process.env,
18
18
  PORT: String(NSF_PORT),
19
+ LOG_LEVEL: "info", // Enable info logging to see startup message
19
20
  },
20
21
  stdio: ["ignore", "pipe", "pipe"],
21
22
  });
@@ -24,7 +25,8 @@ describe("Inter-server Communication Integration Tests", () => {
24
25
  const timeout = setTimeout(() => {
25
26
  reject(new Error("Server startup timeout"));
26
27
  }, 10000);
27
- nsfServer.stdout?.on("data", (data) => {
28
+ // Logger writes to stderr, not stdout
29
+ nsfServer.stderr?.on("data", (data) => {
28
30
  if (data.toString().includes("HTTP server running")) {
29
31
  clearTimeout(timeout);
30
32
  resolve();
@@ -2,6 +2,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { Tool, Resource, Prompt, CallToolResult, ReadResourceResult, GetPromptResult, CallToolRequest, ReadResourceRequest, GetPromptRequest } from "@modelcontextprotocol/sdk/types.js";
4
4
  import { AxiosInstance } from "axios";
5
+ import { Logger } from "./logger.js";
5
6
  export type { Tool, Resource, Prompt, CallToolResult, ReadResourceResult, GetPromptResult };
6
7
  export declare abstract class BaseAccessServer {
7
8
  protected serverName: string;
@@ -9,6 +10,7 @@ export declare abstract class BaseAccessServer {
9
10
  protected baseURL: string;
10
11
  protected server: Server;
11
12
  protected transport: StdioServerTransport;
13
+ protected logger: Logger;
12
14
  private _httpClient?;
13
15
  private _httpServer?;
14
16
  private _httpPort?;
@@ -4,12 +4,14 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
4
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
5
  import axios from "axios";
6
6
  import express from "express";
7
+ import { createLogger } from "./logger.js";
7
8
  export class BaseAccessServer {
8
9
  serverName;
9
10
  version;
10
11
  baseURL;
11
12
  server;
12
13
  transport;
14
+ logger;
13
15
  _httpClient;
14
16
  _httpServer;
15
17
  _httpPort;
@@ -18,6 +20,7 @@ export class BaseAccessServer {
18
20
  this.serverName = serverName;
19
21
  this.version = version;
20
22
  this.baseURL = baseURL;
23
+ this.logger = createLogger(serverName);
21
24
  this.server = new Server({
22
25
  name: serverName,
23
26
  version: version,
@@ -75,13 +78,13 @@ export class BaseAccessServer {
75
78
  }
76
79
  catch (error) {
77
80
  const errorMessage = error instanceof Error ? error.message : String(error);
78
- console.error("Error handling tool call:", errorMessage);
81
+ this.logger.error("Error handling tool call", { error: errorMessage });
79
82
  return {
80
83
  content: [
81
84
  {
82
85
  type: "text",
83
86
  text: JSON.stringify({
84
- error: errorMessage
87
+ error: errorMessage,
85
88
  }),
86
89
  },
87
90
  ],
@@ -95,7 +98,7 @@ export class BaseAccessServer {
95
98
  }
96
99
  catch (error) {
97
100
  const errorMessage = error instanceof Error ? error.message : String(error);
98
- console.error("Error reading resource:", errorMessage);
101
+ this.logger.error("Error reading resource", { error: errorMessage });
99
102
  return {
100
103
  contents: [
101
104
  {
@@ -122,7 +125,7 @@ export class BaseAccessServer {
122
125
  }
123
126
  catch (error) {
124
127
  const errorMessage = error instanceof Error ? error.message : String(error);
125
- console.error("Error getting prompt:", errorMessage);
128
+ this.logger.error("Error getting prompt", { error: errorMessage });
126
129
  throw error;
127
130
  }
128
131
  });
@@ -220,7 +223,7 @@ export class BaseAccessServer {
220
223
  if (options?.httpPort) {
221
224
  this._httpPort = options.httpPort;
222
225
  await this.startHttpService();
223
- console.log(`${this.serverName} HTTP server running on port ${this._httpPort}`);
226
+ this.logger.info("HTTP server running", { port: this._httpPort });
224
227
  }
225
228
  else {
226
229
  // Only connect stdio transport when NOT in HTTP mode
@@ -238,23 +241,23 @@ export class BaseAccessServer {
238
241
  this._httpServer = express();
239
242
  this._httpServer.use(express.json());
240
243
  // Health check endpoint
241
- this._httpServer.get('/health', (req, res) => {
244
+ this._httpServer.get("/health", (req, res) => {
242
245
  res.json({
243
246
  server: this.serverName,
244
247
  version: this.version,
245
- status: 'healthy',
246
- timestamp: new Date().toISOString()
248
+ status: "healthy",
249
+ timestamp: new Date().toISOString(),
247
250
  });
248
251
  });
249
252
  // SSE endpoint for MCP remote connections
250
- this._httpServer.get('/sse', async (req, res) => {
251
- console.log(`[${this.serverName}] New SSE connection`);
252
- const transport = new SSEServerTransport('/messages', res);
253
+ this._httpServer.get("/sse", async (req, res) => {
254
+ this.logger.debug("New SSE connection");
255
+ const transport = new SSEServerTransport("/messages", res);
253
256
  const sessionId = transport.sessionId;
254
257
  this._sseTransports.set(sessionId, transport);
255
258
  // Clean up on disconnect
256
- res.on('close', () => {
257
- console.log(`[${this.serverName}] SSE connection closed: ${sessionId}`);
259
+ res.on("close", () => {
260
+ this.logger.debug("SSE connection closed", { sessionId });
258
261
  this._sseTransports.delete(sessionId);
259
262
  });
260
263
  // Create a new server instance for this SSE connection
@@ -273,33 +276,33 @@ export class BaseAccessServer {
273
276
  await sseServer.connect(transport);
274
277
  });
275
278
  // Messages endpoint for SSE POST messages
276
- this._httpServer.post('/messages', async (req, res) => {
279
+ this._httpServer.post("/messages", async (req, res) => {
277
280
  const sessionId = req.query.sessionId;
278
281
  const transport = this._sseTransports.get(sessionId);
279
282
  if (!transport) {
280
- res.status(404).json({ error: 'Session not found' });
283
+ res.status(404).json({ error: "Session not found" });
281
284
  return;
282
285
  }
283
286
  await transport.handlePostMessage(req, res, req.body);
284
287
  });
285
288
  // List available tools endpoint (for inter-server communication)
286
- this._httpServer.get('/tools', (req, res) => {
289
+ this._httpServer.get("/tools", (req, res) => {
287
290
  try {
288
291
  const tools = this.getTools();
289
292
  res.json({ tools });
290
293
  }
291
294
  catch (error) {
292
- res.status(500).json({ error: 'Failed to list tools' });
295
+ res.status(500).json({ error: "Failed to list tools" });
293
296
  }
294
297
  });
295
298
  // Tool execution endpoint (for inter-server communication)
296
- this._httpServer.post('/tools/:toolName', async (req, res) => {
299
+ this._httpServer.post("/tools/:toolName", async (req, res) => {
297
300
  try {
298
301
  const { toolName } = req.params;
299
302
  const { arguments: args = {} } = req.body;
300
303
  // Validate that the tool exists
301
304
  const tools = this.getTools();
302
- const tool = tools.find(t => t.name === toolName);
305
+ const tool = tools.find((t) => t.name === toolName);
303
306
  if (!tool) {
304
307
  res.status(404).json({ error: `Tool '${toolName}' not found` });
305
308
  return;
@@ -309,8 +312,8 @@ export class BaseAccessServer {
309
312
  method: "tools/call",
310
313
  params: {
311
314
  name: toolName,
312
- arguments: args
313
- }
315
+ arguments: args,
316
+ },
314
317
  };
315
318
  const result = await this.handleToolCall(request);
316
319
  res.json(result);
@@ -322,9 +325,9 @@ export class BaseAccessServer {
322
325
  });
323
326
  // Start HTTP server
324
327
  return new Promise((resolve, reject) => {
325
- this._httpServer.listen(this._httpPort, '0.0.0.0', () => {
328
+ this._httpServer.listen(this._httpPort, "0.0.0.0", () => {
326
329
  resolve();
327
- }).on('error', reject);
330
+ }).on("error", reject);
328
331
  });
329
332
  }
330
333
  /**
@@ -353,13 +356,13 @@ export class BaseAccessServer {
353
356
  }
354
357
  catch (error) {
355
358
  const errorMessage = error instanceof Error ? error.message : String(error);
356
- console.error("Error handling tool call:", errorMessage);
359
+ this.logger.error("Error handling tool call", { error: errorMessage });
357
360
  return {
358
361
  content: [
359
362
  {
360
363
  type: "text",
361
364
  text: JSON.stringify({
362
- error: errorMessage
365
+ error: errorMessage,
363
366
  }),
364
367
  },
365
368
  ],
@@ -373,7 +376,7 @@ export class BaseAccessServer {
373
376
  }
374
377
  catch (error) {
375
378
  const errorMessage = error instanceof Error ? error.message : String(error);
376
- console.error("Error reading resource:", errorMessage);
379
+ this.logger.error("Error reading resource", { error: errorMessage });
377
380
  return {
378
381
  contents: [
379
382
  {
@@ -399,7 +402,7 @@ export class BaseAccessServer {
399
402
  }
400
403
  catch (error) {
401
404
  const errorMessage = error instanceof Error ? error.message : String(error);
402
- console.error("Error getting prompt:", errorMessage);
405
+ this.logger.error("Error getting prompt", { error: errorMessage });
403
406
  throw error;
404
407
  }
405
408
  });
@@ -413,10 +416,10 @@ export class BaseAccessServer {
413
416
  throw new Error(`Service '${serviceName}' not found. Check ACCESS_MCP_SERVICES environment variable.`);
414
417
  }
415
418
  const response = await axios.post(`${serviceUrl}/tools/${toolName}`, {
416
- arguments: args
419
+ arguments: args,
417
420
  }, {
418
421
  timeout: 30000,
419
- validateStatus: () => true
422
+ validateStatus: () => true,
420
423
  });
421
424
  if (response.status !== 200) {
422
425
  throw new Error(`Remote server call failed: ${response.status} ${response.data?.error || response.statusText}`);
@@ -432,8 +435,8 @@ export class BaseAccessServer {
432
435
  if (!services)
433
436
  return null;
434
437
  const serviceMap = {};
435
- services.split(',').forEach(service => {
436
- const [name, url] = service.split('=');
438
+ services.split(",").forEach((service) => {
439
+ const [name, url] = service.split("=");
437
440
  if (name && url) {
438
441
  serviceMap[name.trim()] = url.trim();
439
442
  }
@@ -74,10 +74,10 @@ export class DrupalAuthProvider {
74
74
  throw new Error("Not authenticated. Call ensureAuthenticated() first.");
75
75
  }
76
76
  return {
77
- "Cookie": this.sessionCookie,
77
+ Cookie: this.sessionCookie,
78
78
  "X-CSRF-Token": this.csrfToken,
79
79
  "Content-Type": "application/vnd.api+json",
80
- "Accept": "application/vnd.api+json",
80
+ Accept: "application/vnd.api+json",
81
81
  };
82
82
  }
83
83
  /**
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export * from "./types.js";
3
3
  export * from "./utils.js";
4
4
  export * from "./taxonomies.js";
5
5
  export * from "./drupal-auth.js";
6
+ export * from "./logger.js";
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export * from "./types.js";
3
3
  export * from "./utils.js";
4
4
  export * from "./taxonomies.js";
5
5
  export * from "./drupal-auth.js";
6
+ export * from "./logger.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Structured logger for ACCESS-CI MCP servers.
3
+ *
4
+ * This logger writes to stderr to avoid interfering with MCP's JSON-RPC
5
+ * communication on stdout. It supports log levels that can be controlled
6
+ * via the LOG_LEVEL environment variable.
7
+ *
8
+ * Log levels (in order of severity):
9
+ * - error: Always shown, for critical errors
10
+ * - warn: Warnings that don't prevent operation
11
+ * - info: Important informational messages
12
+ * - debug: Detailed debugging information (disabled by default)
13
+ *
14
+ * Usage:
15
+ * import { createLogger } from "@access-mcp/shared";
16
+ * const logger = createLogger("my-server");
17
+ * logger.info("Server started");
18
+ * logger.error("Failed to connect", { url: "http://..." });
19
+ */
20
+ export type LogLevel = "error" | "warn" | "info" | "debug";
21
+ interface LogContext {
22
+ [key: string]: unknown;
23
+ }
24
+ export interface Logger {
25
+ error: (message: string, context?: LogContext) => void;
26
+ warn: (message: string, context?: LogContext) => void;
27
+ info: (message: string, context?: LogContext) => void;
28
+ debug: (message: string, context?: LogContext) => void;
29
+ }
30
+ /**
31
+ * Create a logger instance for a specific server.
32
+ *
33
+ * @param serverName - The name of the server (e.g., "access-mcp-events")
34
+ * @returns A logger instance with error, warn, info, and debug methods
35
+ */
36
+ export declare function createLogger(serverName: string): Logger;
37
+ /**
38
+ * A no-op logger that silently discards all log messages.
39
+ * Useful for testing or when logging should be completely disabled.
40
+ */
41
+ export declare const silentLogger: Logger;
42
+ export {};
package/dist/logger.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Structured logger for ACCESS-CI MCP servers.
3
+ *
4
+ * This logger writes to stderr to avoid interfering with MCP's JSON-RPC
5
+ * communication on stdout. It supports log levels that can be controlled
6
+ * via the LOG_LEVEL environment variable.
7
+ *
8
+ * Log levels (in order of severity):
9
+ * - error: Always shown, for critical errors
10
+ * - warn: Warnings that don't prevent operation
11
+ * - info: Important informational messages
12
+ * - debug: Detailed debugging information (disabled by default)
13
+ *
14
+ * Usage:
15
+ * import { createLogger } from "@access-mcp/shared";
16
+ * const logger = createLogger("my-server");
17
+ * logger.info("Server started");
18
+ * logger.error("Failed to connect", { url: "http://..." });
19
+ */
20
+ const LOG_LEVELS = {
21
+ error: 0,
22
+ warn: 1,
23
+ info: 2,
24
+ debug: 3,
25
+ };
26
+ function getLogLevel() {
27
+ const level = process.env.LOG_LEVEL?.toLowerCase();
28
+ if (level && level in LOG_LEVELS) {
29
+ return level;
30
+ }
31
+ // Default to 'warn' for production (errors and warnings only)
32
+ return "warn";
33
+ }
34
+ function shouldLog(level) {
35
+ const currentLevel = getLogLevel();
36
+ return LOG_LEVELS[level] <= LOG_LEVELS[currentLevel];
37
+ }
38
+ function formatMessage(serverName, level, message, context) {
39
+ const timestamp = new Date().toISOString();
40
+ const contextStr = context ? ` ${JSON.stringify(context)}` : "";
41
+ return `[${timestamp}] [${serverName}] [${level.toUpperCase()}] ${message}${contextStr}`;
42
+ }
43
+ /**
44
+ * Create a logger instance for a specific server.
45
+ *
46
+ * @param serverName - The name of the server (e.g., "access-mcp-events")
47
+ * @returns A logger instance with error, warn, info, and debug methods
48
+ */
49
+ export function createLogger(serverName) {
50
+ return {
51
+ error: (message, context) => {
52
+ if (shouldLog("error")) {
53
+ console.error(formatMessage(serverName, "error", message, context));
54
+ }
55
+ },
56
+ warn: (message, context) => {
57
+ if (shouldLog("warn")) {
58
+ console.error(formatMessage(serverName, "warn", message, context));
59
+ }
60
+ },
61
+ info: (message, context) => {
62
+ if (shouldLog("info")) {
63
+ console.error(formatMessage(serverName, "info", message, context));
64
+ }
65
+ },
66
+ debug: (message, context) => {
67
+ if (shouldLog("debug")) {
68
+ console.error(formatMessage(serverName, "debug", message, context));
69
+ }
70
+ },
71
+ };
72
+ }
73
+ /**
74
+ * A no-op logger that silently discards all log messages.
75
+ * Useful for testing or when logging should be completely disabled.
76
+ */
77
+ export const silentLogger = {
78
+ error: () => { },
79
+ warn: () => { },
80
+ info: () => { },
81
+ debug: () => { },
82
+ };
@@ -75,7 +75,7 @@ export const FIELDS_OF_SCIENCE = {
75
75
  typical: 300000,
76
76
  },
77
77
  },
78
- "Physics": {
78
+ Physics: {
79
79
  name: "Physics",
80
80
  description: "Research in high energy physics, astrophysics, condensed matter, and computational physics",
81
81
  keywords: [
@@ -106,7 +106,7 @@ export const FIELDS_OF_SCIENCE = {
106
106
  typical: 500000,
107
107
  },
108
108
  },
109
- "Chemistry": {
109
+ Chemistry: {
110
110
  name: "Chemistry",
111
111
  description: "Research in computational chemistry, molecular modeling, and materials science",
112
112
  keywords: [
@@ -120,23 +120,14 @@ export const FIELDS_OF_SCIENCE = {
120
120
  "ab initio",
121
121
  ],
122
122
  typical_resources: ["CPU clusters", "high memory", "GPU acceleration", "fast storage"],
123
- common_software: [
124
- "Gaussian",
125
- "GAMESS",
126
- "NWChem",
127
- "ORCA",
128
- "AMBER",
129
- "NAMD",
130
- "LAMMPS",
131
- "VMD",
132
- ],
123
+ common_software: ["Gaussian", "GAMESS", "NWChem", "ORCA", "AMBER", "NAMD", "LAMMPS", "VMD"],
133
124
  allocation_range: {
134
125
  min: 75000,
135
126
  max: 1000000,
136
127
  typical: 300000,
137
128
  },
138
129
  },
139
- "Engineering": {
130
+ Engineering: {
140
131
  name: "Engineering",
141
132
  description: "Research in computational engineering, CFD, structural analysis, and design optimization",
142
133
  keywords: [
@@ -151,15 +142,7 @@ export const FIELDS_OF_SCIENCE = {
151
142
  "aerospace",
152
143
  ],
153
144
  typical_resources: ["CPU clusters", "high memory", "GPU for visualization", "parallel I/O"],
154
- common_software: [
155
- "ANSYS",
156
- "OpenFOAM",
157
- "COMSOL",
158
- "ABAQUS",
159
- "LS-DYNA",
160
- "SU2",
161
- "ParaView",
162
- ],
145
+ common_software: ["ANSYS", "OpenFOAM", "COMSOL", "ABAQUS", "LS-DYNA", "SU2", "ParaView"],
163
146
  allocation_range: {
164
147
  min: 100000,
165
148
  max: 1500000,
@@ -180,16 +163,7 @@ export const FIELDS_OF_SCIENCE = {
180
163
  "environmental science",
181
164
  ],
182
165
  typical_resources: ["large storage", "CPU clusters", "high I/O", "data analytics"],
183
- common_software: [
184
- "WRF",
185
- "CESM",
186
- "NCAR",
187
- "netCDF",
188
- "GDAL",
189
- "Python",
190
- "R",
191
- "MATLAB",
192
- ],
166
+ common_software: ["WRF", "CESM", "NCAR", "netCDF", "GDAL", "Python", "R", "MATLAB"],
193
167
  allocation_range: {
194
168
  min: 150000,
195
169
  max: 2000000,
@@ -210,16 +184,7 @@ export const FIELDS_OF_SCIENCE = {
210
184
  "computational mathematics",
211
185
  ],
212
186
  typical_resources: ["CPU clusters", "high memory", "GPU for linear algebra"],
213
- common_software: [
214
- "MATLAB",
215
- "R",
216
- "Python",
217
- "Julia",
218
- "Mathematica",
219
- "SAS",
220
- "SPSS",
221
- "Octave",
222
- ],
187
+ common_software: ["MATLAB", "R", "Python", "Julia", "Mathematica", "SAS", "SPSS", "Octave"],
223
188
  allocation_range: {
224
189
  min: 50000,
225
190
  max: 500000,
@@ -239,15 +204,7 @@ export const FIELDS_OF_SCIENCE = {
239
204
  "survey analysis",
240
205
  ],
241
206
  typical_resources: ["data analytics", "storage", "CPU clusters for simulations"],
242
- common_software: [
243
- "R",
244
- "Python",
245
- "Stata",
246
- "SPSS",
247
- "NetLogo",
248
- "Gephi",
249
- "Julia",
250
- ],
207
+ common_software: ["R", "Python", "Stata", "SPSS", "NetLogo", "Gephi", "Julia"],
251
208
  allocation_range: {
252
209
  min: 25000,
253
210
  max: 300000,
@@ -268,16 +225,7 @@ export const FIELDS_OF_SCIENCE = {
268
225
  "simulation",
269
226
  ],
270
227
  typical_resources: ["large storage", "CPU clusters", "GPU for visualization", "data pipelines"],
271
- common_software: [
272
- "Gadget",
273
- "FLASH",
274
- "Enzo",
275
- "Athena",
276
- "IRAF",
277
- "DS9",
278
- "Python",
279
- "astropy",
280
- ],
228
+ common_software: ["Gadget", "FLASH", "Enzo", "Athena", "IRAF", "DS9", "Python", "astropy"],
281
229
  allocation_range: {
282
230
  min: 150000,
283
231
  max: 2500000,
@@ -365,12 +313,7 @@ export const RESOURCE_TYPES = {
365
313
  "Data processing",
366
314
  "Simulations",
367
315
  ],
368
- key_features: [
369
- "High core counts",
370
- "Good memory bandwidth",
371
- "MPI support",
372
- "Long-running jobs",
373
- ],
316
+ key_features: ["High core counts", "Good memory bandwidth", "MPI support", "Long-running jobs"],
374
317
  },
375
318
  GPU: {
376
319
  name: "GPU Accelerated",
@@ -382,12 +325,7 @@ export const RESOURCE_TYPES = {
382
325
  "Image processing",
383
326
  "CFD simulations",
384
327
  ],
385
- key_features: [
386
- "CUDA/ROCm support",
387
- "High memory bandwidth",
388
- "Tensor cores",
389
- "Fast training",
390
- ],
328
+ key_features: ["CUDA/ROCm support", "High memory bandwidth", "Tensor cores", "Fast training"],
391
329
  },
392
330
  "High Memory": {
393
331
  name: "High Memory Systems",
@@ -398,11 +336,7 @@ export const RESOURCE_TYPES = {
398
336
  "In-memory databases",
399
337
  "Large-scale graph analysis",
400
338
  ],
401
- key_features: [
402
- "1TB+ memory per node",
403
- "Fast memory access",
404
- "Large working sets",
405
- ],
339
+ key_features: ["1TB+ memory per node", "Fast memory access", "Large working sets"],
406
340
  },
407
341
  Storage: {
408
342
  name: "Storage Resources",
@@ -430,12 +364,7 @@ export const RESOURCE_TYPES = {
430
364
  "Interactive computing",
431
365
  "Elastic workloads",
432
366
  ],
433
- key_features: [
434
- "On-demand resources",
435
- "Virtual machines",
436
- "Container support",
437
- "API access",
438
- ],
367
+ key_features: ["On-demand resources", "Virtual machines", "Container support", "API access"],
439
368
  },
440
369
  };
441
370
  /**
@@ -525,10 +454,10 @@ export function getFeatureName(featureCode) {
525
454
  * Get feature names for an array of codes
526
455
  */
527
456
  export function getFeatureNames(featureCodes) {
528
- return featureCodes.map(code => getFeatureName(code));
457
+ return featureCodes.map((code) => getFeatureName(code));
529
458
  }
530
459
  export const ACCESS_SYSTEMS = {
531
- "Delta": {
460
+ Delta: {
532
461
  name: "Delta",
533
462
  organization: "NCSA (University of Illinois)",
534
463
  description: "GPU-focused system with NVIDIA A100 and A40 GPUs",
@@ -562,7 +491,7 @@ export const ACCESS_SYSTEMS = {
562
491
  ],
563
492
  experience_level: ["Beginner", "Intermediate", "Advanced"],
564
493
  },
565
- "Anvil": {
494
+ Anvil: {
566
495
  name: "Anvil",
567
496
  organization: "Purdue University",
568
497
  description: "Composable subsystem architecture with flexible resource allocation",
@@ -578,7 +507,7 @@ export const ACCESS_SYSTEMS = {
578
507
  ],
579
508
  experience_level: ["Beginner", "Intermediate", "Advanced"],
580
509
  },
581
- "Expanse": {
510
+ Expanse: {
582
511
  name: "Expanse",
583
512
  organization: "SDSC (San Diego Supercomputing Center)",
584
513
  description: "Balanced CPU and GPU computing with excellent storage",
@@ -595,7 +524,7 @@ export const ACCESS_SYSTEMS = {
595
524
  ],
596
525
  experience_level: ["Intermediate", "Advanced"],
597
526
  },
598
- "Stampede3": {
527
+ Stampede3: {
599
528
  name: "Stampede3",
600
529
  organization: "TACC (Texas Advanced Computing Center)",
601
530
  description: "Leadership-class supercomputer with NVIDIA Grace-Hopper",
@@ -611,7 +540,7 @@ export const ACCESS_SYSTEMS = {
611
540
  ],
612
541
  experience_level: ["Advanced", "Expert"],
613
542
  },
614
- "Jetstream2": {
543
+ Jetstream2: {
615
544
  name: "Jetstream2",
616
545
  organization: "Indiana University",
617
546
  description: "Cloud computing platform for interactive science and gateways",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@access-mcp/shared",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Shared utilities for ACCESS-CI MCP servers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",