-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmcp_server.mjs
More file actions
190 lines (168 loc) · 6.94 KB
/
mcp_server.mjs
File metadata and controls
190 lines (168 loc) · 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { readFileSync } from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const Mustache = require("mustache");
const { processTemplateData, renderCustomGenSpecs, getDefaultSetObjectiveCode } = require("./processTemplateData.js");
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const server = new Server(
{
name: "script-creator",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
// Load generators.json to build enum constraints
let generators = {};
let genModuleEnum = [];
let genFunctionEnum = [];
try {
const generatorsPath = path.join(__dirname, 'data/generators.json');
generators = JSON.parse(readFileSync(generatorsPath, 'utf8'));
// Build enum arrays from generators.json
genModuleEnum = Object.keys(generators);
genFunctionEnum = [];
for (const module in generators) {
if (Array.isArray(generators[module].generators)) {
genFunctionEnum.push(...generators[module].generators);
}
}
// Remove duplicates
genFunctionEnum = [...new Set(genFunctionEnum)];
} catch (e) {
// If file doesn't exist, this is a critical error - the tool cannot function without it
throw new Error(`Failed to load generators.json: ${e.message}`);
}
return {
tools: [
{
name: "CreateLibEnsembleScripts",
description: "Render script using existing script-creator templates",
inputSchema: {
type: "object",
properties: {
app_ref: { type: "string", description: "Application reference name" },
num_workers: { type: "string", description: "Number of workers" },
sim_app: { type: "string", description: "Path to simulation application" },
input_path: { type: "string", description: "Path to input file or directory" },
dimension: { type: "string", description: "Number of parameters" },
max_sims: { type: "string", description: "Maximum simulations" },
input_type: { type: "string", description: "Input type: file or directory" },
templated_enable: { type: "boolean", description: "Enable templated input file" },
templated_filename: { type: "string", description: "Template filename" },
template_vars: { type: "array", description: "Template variable names", items: { type: "string" } },
cluster_enable: { type: "boolean", description: "Enable cluster mode" },
cluster_total_nodes: { type: "string", description: "Total nodes for cluster" },
scheduler_type: { type: "string", enum: ["slurm", "pbs"], description: "Scheduler type (slurm or pbs)" },
gen_module: {
type: "string",
enum: genModuleEnum,
description: `Generator module. Valid options: ${genModuleEnum.join(", ")}`
},
gen_function: {
type: "string",
enum: genFunctionEnum,
description: `Generator function. Valid options: ${genFunctionEnum.join(", ")}`
},
nodes: { type: "string", description: "Number of nodes" },
procs: { type: "string", description: "Number of processes" },
gpus: { type: "string", description: "Number of GPUs" },
input_usage: { type: "string", enum: ["directory", "cmdline"], description: "Input usage: directory or cmdline" },
output_file_name: { type: "string", description: "Output file name to read objective from (defaults to app_ref.stat)" },
custom_set_objective: { type: "boolean", description: "Use custom set_objective function" },
set_objective_code: { type: "string", description: "Custom set_objective_value() function code" },
},
additionalProperties: true,
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== "CreateLibEnsembleScripts") {
throw new Error(`Unknown tool: ${request.params.name}`);
}
const params = request.params.arguments || {};
try {
// Process parameters using shared logic
const data = { ...params };
// Load generator specs
let generatorSpecs = {};
try {
const generatorSpecsPath = path.join(__dirname, 'data/generator_specs.json');
generatorSpecs = JSON.parse(readFileSync(generatorSpecsPath, 'utf8'));
} catch (e) {
// If file doesn't exist, use empty object
generatorSpecs = {};
}
// Process template data using shared function
processTemplateData(data, generatorSpecs);
// Disable HTML escaping for Mustache (needed for both custom_gen_specs and template rendering)
Mustache.escape = text => text;
// Render custom gen_specs using shared function
renderCustomGenSpecs(data, Mustache.render.bind(Mustache));
// Set default objective code
if (!data.set_objective_code) {
data.set_objective_code = getDefaultSetObjectiveCode(data);
}
// Load templates
const runTpl = readFileSync(path.join(__dirname, 'templates/run_libe.py.j2'), 'utf8');
const simfTpl = readFileSync(path.join(__dirname, 'templates/simf.py.j2'), 'utf8');
// Render templates
const runRendered = Mustache.render(runTpl, data);
const simfRendered = Mustache.render(simfTpl, data);
let output = `=== run_libe.py ===\n${runRendered}\n\n=== simf.py ===\n${simfRendered}`;
// Render batch script if cluster is enabled
if (data.cluster_enabled) {
const batchPath = data.scheduler_type === 'slurm'
? 'templates/submit_slurm.sh.j2'
: 'templates/submit_pbs.sh.j2';
const batchTpl = readFileSync(path.join(__dirname, batchPath), 'utf8');
const batchRendered = Mustache.render(batchTpl, data);
const batchFilename = data.scheduler_type === 'slurm' ? 'submit_slurm.sh' : 'submit_pbs.sh';
output += `\n\n=== ${batchFilename} ===\n${batchRendered}`;
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error rendering templates: ${error.message}`,
},
],
isError: true,
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Script Creator MCP server running on stdio");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});