@access-mcp/shared 0.5.0 → 0.7.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/dist/__tests__/interserver.integration.test.js +3 -1
- package/dist/base-server.d.ts +2 -0
- package/dist/base-server.js +34 -31
- package/dist/drupal-auth.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/logger.d.ts +42 -0
- package/dist/logger.js +82 -0
- package/dist/taxonomies.js +19 -90
- package/dist/utils.d.ts +40 -0
- package/dist/utils.js +59 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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();
|
package/dist/base-server.d.ts
CHANGED
|
@@ -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?;
|
package/dist/base-server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
244
|
+
this._httpServer.get("/health", (req, res) => {
|
|
242
245
|
res.json({
|
|
243
246
|
server: this.serverName,
|
|
244
247
|
version: this.version,
|
|
245
|
-
status:
|
|
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(
|
|
251
|
-
|
|
252
|
-
const transport = new SSEServerTransport(
|
|
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(
|
|
257
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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,
|
|
328
|
+
this._httpServer.listen(this._httpPort, "0.0.0.0", () => {
|
|
326
329
|
resolve();
|
|
327
|
-
}).on(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
}
|
package/dist/drupal-auth.js
CHANGED
|
@@ -74,10 +74,10 @@ export class DrupalAuthProvider {
|
|
|
74
74
|
throw new Error("Not authenticated. Call ensureAuthenticated() first.");
|
|
75
75
|
}
|
|
76
76
|
return {
|
|
77
|
-
|
|
77
|
+
Cookie: this.sessionCookie,
|
|
78
78
|
"X-CSRF-Token": this.csrfToken,
|
|
79
79
|
"Content-Type": "application/vnd.api+json",
|
|
80
|
-
|
|
80
|
+
Accept: "application/vnd.api+json",
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
/**
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/logger.d.ts
ADDED
|
@@ -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
|
+
};
|
package/dist/taxonomies.js
CHANGED
|
@@ -75,7 +75,7 @@ export const FIELDS_OF_SCIENCE = {
|
|
|
75
75
|
typical: 300000,
|
|
76
76
|
},
|
|
77
77
|
},
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
543
|
+
Jetstream2: {
|
|
615
544
|
name: "Jetstream2",
|
|
616
545
|
organization: "Indiana University",
|
|
617
546
|
description: "Cloud computing platform for interactive science and gateways",
|
package/dist/utils.d.ts
CHANGED
|
@@ -72,3 +72,43 @@ export declare const CommonNextSteps: {
|
|
|
72
72
|
description: string;
|
|
73
73
|
};
|
|
74
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Resource ID Resolution Utilities
|
|
77
|
+
*
|
|
78
|
+
* These utilities help resolve human-readable resource names (e.g., "Anvil", "Delta")
|
|
79
|
+
* to full resource IDs (e.g., "anvil.purdue.access-ci.org").
|
|
80
|
+
*/
|
|
81
|
+
export interface ResourceMatch {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
}
|
|
85
|
+
export type ResolveResult = {
|
|
86
|
+
success: true;
|
|
87
|
+
id: string;
|
|
88
|
+
} | {
|
|
89
|
+
success: false;
|
|
90
|
+
error: string;
|
|
91
|
+
suggestion?: string;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a human-readable name to a resource ID.
|
|
95
|
+
*
|
|
96
|
+
* @param input - The input string (name or ID)
|
|
97
|
+
* @param searchFn - A function that searches for resources by name and returns matches
|
|
98
|
+
* @returns ResolveResult with either the resolved ID or an error message
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const result = await resolveResourceId("Anvil", async (query) => {
|
|
103
|
+
* const resources = await searchResources({ query });
|
|
104
|
+
* return resources.map(r => ({ id: r.id, name: r.name }));
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* if (result.success) {
|
|
108
|
+
* console.log(result.id); // "anvil.purdue.access-ci.org"
|
|
109
|
+
* } else {
|
|
110
|
+
* console.log(result.error); // "Multiple resources match..."
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export declare function resolveResourceId(input: string, searchFn: (query: string) => Promise<ResourceMatch[]>): Promise<ResolveResult>;
|
package/dist/utils.js
CHANGED
|
@@ -82,3 +82,62 @@ export const CommonNextSteps = {
|
|
|
82
82
|
description: `Try these refinements: ${suggestions.join(", ")}`,
|
|
83
83
|
}),
|
|
84
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Resolve a human-readable name to a resource ID.
|
|
87
|
+
*
|
|
88
|
+
* @param input - The input string (name or ID)
|
|
89
|
+
* @param searchFn - A function that searches for resources by name and returns matches
|
|
90
|
+
* @returns ResolveResult with either the resolved ID or an error message
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const result = await resolveResourceId("Anvil", async (query) => {
|
|
95
|
+
* const resources = await searchResources({ query });
|
|
96
|
+
* return resources.map(r => ({ id: r.id, name: r.name }));
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* if (result.success) {
|
|
100
|
+
* console.log(result.id); // "anvil.purdue.access-ci.org"
|
|
101
|
+
* } else {
|
|
102
|
+
* console.log(result.error); // "Multiple resources match..."
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export async function resolveResourceId(input, searchFn) {
|
|
107
|
+
// If it already looks like a full resource ID (contains dots), return as-is
|
|
108
|
+
if (input.includes(".")) {
|
|
109
|
+
return { success: true, id: input };
|
|
110
|
+
}
|
|
111
|
+
// Search for the resource by name
|
|
112
|
+
const items = await searchFn(input);
|
|
113
|
+
if (items.length === 0) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: `No resource found matching '${input}'`,
|
|
117
|
+
suggestion: "Use the search tool to find valid resource names.",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Find exact name match first (case-insensitive)
|
|
121
|
+
const inputLower = input.toLowerCase();
|
|
122
|
+
const exactMatch = items.find((item) => item.name?.toLowerCase() === inputLower);
|
|
123
|
+
if (exactMatch && exactMatch.id) {
|
|
124
|
+
return { success: true, id: exactMatch.id };
|
|
125
|
+
}
|
|
126
|
+
// Multiple partial matches - ask user to be more specific
|
|
127
|
+
if (items.length > 1) {
|
|
128
|
+
const names = items.map((i) => i.name).join(", ");
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
error: `Multiple resources match '${input}': ${names}`,
|
|
132
|
+
suggestion: "Please specify the exact resource name.",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Single partial match - use it
|
|
136
|
+
if (items[0].id) {
|
|
137
|
+
return { success: true, id: items[0].id };
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: `Could not resolve resource '${input}'`,
|
|
142
|
+
};
|
|
143
|
+
}
|