@sassoftware/sas-score-mcp-serverjs 0.4.0 → 0.4.1
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/cli.js +9 -127
- package/package.json +2 -3
- package/src/createMcpServer.js +0 -1
- package/src/expressMcpServer.js +27 -53
- package/src/handleGetDelete.js +3 -6
- package/src/hapiMcpServer.js +18 -10
- package/src/toolHelpers/_jobSubmit.js +0 -2
- package/src/toolHelpers/_listLibrary.js +39 -56
- package/src/toolHelpers/getLogonPayload.js +1 -3
- package/src/toolSet/devaScore.js +36 -28
- package/src/toolSet/findJob.js +49 -23
- package/src/toolSet/findJobdef.js +54 -24
- package/src/toolSet/findLibrary.js +57 -25
- package/src/toolSet/findModel.js +53 -31
- package/src/toolSet/findTable.js +54 -25
- package/src/toolSet/getEnv.js +38 -20
- package/src/toolSet/listJobdefs.js +58 -24
- package/src/toolSet/listJobs.js +72 -24
- package/src/toolSet/listLibraries.js +47 -37
- package/src/toolSet/listModels.js +47 -20
- package/src/toolSet/listTables.js +58 -29
- package/src/toolSet/makeTools.js +0 -3
- package/src/toolSet/modelInfo.js +49 -18
- package/src/toolSet/modelScore.js +69 -27
- package/src/toolSet/readTable.js +62 -25
- package/src/toolSet/runCasProgram.js +43 -23
- package/src/toolSet/runJob.js +19 -20
- package/src/toolSet/runJobdef.js +23 -21
- package/src/toolSet/runMacro.js +20 -20
- package/src/toolSet/runProgram.js +71 -24
- package/src/toolSet/sasQuery.js +70 -23
- package/src/toolSet/scrInfo.js +4 -3
- package/src/toolSet/setContext.js +48 -22
- package/src/toolSet/tableInfo.js +71 -28
- package/skills/mcp-tool-description-optimizer/SKILL.md +0 -129
- package/skills/mcp-tool-description-optimizer/references/examples.md +0 -123
- package/skills/sas-read-and-score/SKILL.md +0 -91
- package/skills/sas-read-strategy/SKILL.md +0 -143
- package/skills/sas-score-workflow/SKILL.md +0 -282
package/cli.js
CHANGED
|
@@ -22,7 +22,6 @@ import readCerts from './src/toolHelpers/readCerts.js';
|
|
|
22
22
|
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
24
24
|
import { dirname } from 'path';
|
|
25
|
-
import { parseArgs } from "node:util";
|
|
26
25
|
|
|
27
26
|
import NodeCache from 'node-cache';
|
|
28
27
|
//import getOpts from './src/toolHelpers/getOpts.js';
|
|
@@ -31,97 +30,16 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
31
30
|
|
|
32
31
|
let pkg = fs.readFileSync(__dirname + '/package.json', 'utf8');
|
|
33
32
|
|
|
34
|
-
// Parse command line arguments
|
|
35
|
-
const args = parseArgs({
|
|
36
|
-
options: {
|
|
37
|
-
port: {
|
|
38
|
-
type: 'string',
|
|
39
|
-
short: 'p',
|
|
40
|
-
description: 'Port to run the server on'
|
|
41
|
-
},
|
|
42
|
-
mcptype: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
short: 'm',
|
|
45
|
-
description: 'MCP server type (http or stdio)'
|
|
46
|
-
},
|
|
47
|
-
viya: {
|
|
48
|
-
type: 'string',
|
|
49
|
-
short: 'v',
|
|
50
|
-
description: 'Viya server URL'
|
|
51
|
-
},
|
|
52
|
-
authflow: {
|
|
53
|
-
type: 'string',
|
|
54
|
-
short: 'a',
|
|
55
|
-
description: 'Authentication flow (sascli, code, token)'
|
|
56
|
-
},
|
|
57
|
-
profile: {
|
|
58
|
-
type: 'string',
|
|
59
|
-
description: 'SAS CLI profile name'
|
|
60
|
-
},
|
|
61
|
-
config: {
|
|
62
|
-
type: 'string',
|
|
63
|
-
description: 'SAS CLI config directory'
|
|
64
|
-
},
|
|
65
|
-
envfile: {
|
|
66
|
-
type: 'string',
|
|
67
|
-
short: 'e',
|
|
68
|
-
description: 'Environment file path'
|
|
69
|
-
},
|
|
70
|
-
help: {
|
|
71
|
-
type: 'boolean',
|
|
72
|
-
short: 'h',
|
|
73
|
-
description: 'Show help message'
|
|
74
|
-
},
|
|
75
|
-
version: {
|
|
76
|
-
type: 'boolean',
|
|
77
|
-
short: 'v',
|
|
78
|
-
description: 'Show version'
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
strict: false,
|
|
82
|
-
allowPositionals: false
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Handle help flag
|
|
86
|
-
if (args.values.help) {
|
|
87
|
-
console.log(`
|
|
88
|
-
Usage: sas-score-mcp-serverjs [options]
|
|
89
|
-
|
|
90
|
-
Options:
|
|
91
|
-
-p, --port <port> Port to run the server on (default: 8080)
|
|
92
|
-
-m, --mcptype <type> MCP server type: http or stdio (default: http)
|
|
93
|
-
-v, --viya <url> Viya server URL
|
|
94
|
-
-a, --authflow <flow> Authentication flow: sascli, code, or token
|
|
95
|
-
--profile <name> SAS CLI profile name
|
|
96
|
-
--config <path> SAS CLI config directory
|
|
97
|
-
-e, --envfile <path> Environment file path
|
|
98
|
-
-h, --help Show this help message
|
|
99
|
-
--version Show version
|
|
100
|
-
|
|
101
|
-
Environment Variables:
|
|
102
|
-
Use .env file or set environment variables for configuration.
|
|
103
|
-
See README.md for more information.
|
|
104
|
-
`);
|
|
105
|
-
process.exit(0);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Handle version flag
|
|
109
|
-
if (args.values.version) {
|
|
110
|
-
let pkgJson = JSON.parse(pkg);
|
|
111
|
-
console.log(pkgJson.version);
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
33
|
if (process.env.ENVFILE === 'FALSE') {
|
|
116
34
|
//use this when using remote mcp server and no .env file is desired
|
|
117
35
|
console.error('[Note]: Skipping .env file as ENVFILE is set to FALSE...');
|
|
118
36
|
} else {
|
|
119
37
|
console.error('Working Directory', process.cwd());
|
|
120
|
-
let envf = process.
|
|
38
|
+
let envf = process.cwd() + '\\.env';
|
|
121
39
|
//__dirname + '\\.env';
|
|
122
40
|
console.error('Env file:', envf);
|
|
123
41
|
if (fs.existsSync(envf)) {
|
|
124
|
-
console.error(`Loading environment variables
|
|
42
|
+
console.error(`Loading environment variables rom ${envf}...`);
|
|
125
43
|
let e = iconfig(envf); // avoid dotenv since it writes to console.log
|
|
126
44
|
console.error('[Note]: Environment variables loaded from .env file...');
|
|
127
45
|
console.error('Loaded env variables:', e);
|
|
@@ -133,37 +51,6 @@ if (process.env.ENVFILE === 'FALSE') {
|
|
|
133
51
|
}
|
|
134
52
|
}
|
|
135
53
|
|
|
136
|
-
// Apply command line arguments to override environment variables
|
|
137
|
-
if (args.values.port) {
|
|
138
|
-
process.env.PORT = args.values.port;
|
|
139
|
-
console.error(`[Note] PORT set from command line: ${args.values.port}`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (args.values.mcptype) {
|
|
143
|
-
process.env.MCPTYPE = args.values.mcptype;
|
|
144
|
-
console.error(`[Note] MCPTYPE set from command line: ${args.values.mcptype}`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (args.values.viya) {
|
|
148
|
-
process.env.VIYA_SERVER = args.values.viya;
|
|
149
|
-
console.error(`[Note] VIYA_SERVER set from command line: ${args.values.viya}`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (args.values.authflow) {
|
|
153
|
-
process.env.AUTHFLOW = args.values.authflow;
|
|
154
|
-
console.error(`[Note] AUTHFLOW set from command line: ${args.values.authflow}`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (args.values.profile) {
|
|
158
|
-
process.env.SAS_CLI_PROFILE = args.values.profile;
|
|
159
|
-
console.error(`[Note] SAS_CLI_PROFILE set from command line: ${args.values.profile}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (args.values.config) {
|
|
163
|
-
process.env.SAS_CLI_CONFIG = args.values.config;
|
|
164
|
-
console.error(`[Note] SAS_CLI_CONFIG set from command line: ${args.values.config}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
54
|
if (process.env.APPHOST == null) {
|
|
168
55
|
process.env.APPHOST = 'localhost';
|
|
169
56
|
}
|
|
@@ -184,7 +71,7 @@ console.error(
|
|
|
184
71
|
// and storage provided by cloud providers
|
|
185
72
|
console.error(process.env.COMPUTECONTEXT);
|
|
186
73
|
debugger;
|
|
187
|
-
let sessionCache = new NodeCache({ stdTTL:
|
|
74
|
+
let sessionCache = new NodeCache({ stdTTL: 60*60, checkperiod: 2 * 60, useClones: false });
|
|
188
75
|
|
|
189
76
|
//
|
|
190
77
|
// Load environment variables from .env file if present
|
|
@@ -249,8 +136,6 @@ const appEnvBase = {
|
|
|
249
136
|
process.env.TOOLSETS != null
|
|
250
137
|
? process.env.TOOLSETS.split(',')
|
|
251
138
|
: ['default'],
|
|
252
|
-
// command line arguments
|
|
253
|
-
cliArgs: args.values,
|
|
254
139
|
// user defined tools
|
|
255
140
|
//runtime variables
|
|
256
141
|
tokenRefresh: process.env.TOKENREFRESH === 'FALSE' ? false : true,
|
|
@@ -342,20 +227,17 @@ let appEnvTemplate = Object.assign({}, appEnvBase);
|
|
|
342
227
|
|
|
343
228
|
sessionCache.set('appEnvTemplate', appEnvTemplate);
|
|
344
229
|
|
|
345
|
-
let transports = {
|
|
346
|
-
"dummy": null
|
|
347
|
-
};
|
|
230
|
+
let transports = {'dummy': null};
|
|
348
231
|
sessionCache.set('transports', transports);
|
|
349
232
|
|
|
350
233
|
// set this for stdio transport use
|
|
351
234
|
// dummy sessionId for use in the tools
|
|
352
|
-
let useHapi = process.env.
|
|
235
|
+
let useHapi = (process.env.USEHAPI === 'TRUE') ? true : false;
|
|
353
236
|
console.error('[Note] appEnvBase is', JSON.stringify(appEnvBase, null,2));
|
|
354
|
-
// creat a dummy sessionId for stdio since there is only one session and transport in that case, and tools need a sessionId to access the appEnvBase and contexts
|
|
355
|
-
let sessionId = randomUUID();
|
|
356
|
-
sessionCache.set(sessionId, appEnvBase);
|
|
357
|
-
useHapi = false; // for now disable hapi for authflow code since it is not working well, and code flow is not commonly used in server side applications, and http transport works better for code flow since it requires multiple back and forth calls to complete the auth process which is not ideal for stdio transport, this will be revisited in the future to either fix the issues with hapi or implement the code flow in a way that works better with stdio transport
|
|
358
237
|
if (mcpType === 'stdio') {
|
|
238
|
+
let sessionId = randomUUID();
|
|
239
|
+
sessionCache.set('currentId', sessionId);
|
|
240
|
+
sessionCache.set(sessionId, appEnvBase);
|
|
359
241
|
console.error('[Note] Setting up stdio transport with sessionId:', sessionId);
|
|
360
242
|
console.error('[Note] Used in setting up tools and some persistence(not all).');
|
|
361
243
|
await coreSSE(mcpServer);
|
|
@@ -363,7 +245,7 @@ if (mcpType === 'stdio') {
|
|
|
363
245
|
} else {
|
|
364
246
|
console.error('[Note] Starting HTTP MCP server...');
|
|
365
247
|
if (useHapi === true) {
|
|
366
|
-
process.env.
|
|
248
|
+
process.env.AUTHFLOW = null;;
|
|
367
249
|
await hapiMcpServer(mcpServer, sessionCache, appEnvBase);
|
|
368
250
|
console.error('[Note] Using HAPI HTTP server...')
|
|
369
251
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sassoftware/sas-score-mcp-serverjs",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "A mcp server for SAS Viya",
|
|
5
5
|
"author": "Deva Kumar <deva.kumar@sas.com>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,8 +39,7 @@
|
|
|
39
39
|
"src",
|
|
40
40
|
"cli.js",
|
|
41
41
|
"openApi.json",
|
|
42
|
-
"openApi.yaml"
|
|
43
|
-
"skills"
|
|
42
|
+
"openApi.yaml"
|
|
44
43
|
],
|
|
45
44
|
"dependencies": {
|
|
46
45
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
package/src/createMcpServer.js
CHANGED
|
@@ -77,7 +77,6 @@ async function createMcpServer(cache, _appContext) {
|
|
|
77
77
|
let toolHandler = wrapf(cache, tool.handler);
|
|
78
78
|
|
|
79
79
|
mcpServer.tool(toolName, tool.description, tool.schema, toolHandler);
|
|
80
|
-
|
|
81
80
|
toolNames.push(toolName);
|
|
82
81
|
});
|
|
83
82
|
console.error(`[Note] Registered ${toolSet.length} tools: ${toolNames}`);
|
package/src/expressMcpServer.js
CHANGED
|
@@ -95,9 +95,10 @@ app.get("/openapi.json", (req, res) => {
|
|
|
95
95
|
|
|
96
96
|
// handle processing of information in header.
|
|
97
97
|
function requireBearer(req, res, next) {
|
|
98
|
+
|
|
99
|
+
|
|
98
100
|
// process any new header information
|
|
99
|
-
|
|
100
|
-
console.error("Processing headers for incoming request to /mcp endpoint");
|
|
101
|
+
|
|
101
102
|
// Allow different VIYA server per sessionid(user)
|
|
102
103
|
let headerCache = {};
|
|
103
104
|
if (req.header("X-VIYA-SERVER") != null) {
|
|
@@ -112,10 +113,6 @@ function requireBearer(req, res, next) {
|
|
|
112
113
|
headerCache.bearerToken = hdr.slice(7);
|
|
113
114
|
headerCache.AUTHFLOW = "bearer";
|
|
114
115
|
console.error("[Note] Using user supplied bearer token for authorization");
|
|
115
|
-
console.error("[Debug] Bearer token starts with:", headerCache.bearerToken);
|
|
116
|
-
} else {
|
|
117
|
-
console.error("[Note] No bearer token supplied in Authorization header");
|
|
118
|
-
headerCache.bearerToken = null;
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
// faking out api key since Viya does not support
|
|
@@ -128,27 +125,22 @@ function requireBearer(req, res, next) {
|
|
|
128
125
|
}
|
|
129
126
|
cache.set("headerCache", headerCache);
|
|
130
127
|
next();
|
|
131
|
-
console.error("Finished processing headers for /mcp request");
|
|
132
|
-
console.log("=======================================================");
|
|
133
128
|
}
|
|
134
129
|
|
|
135
130
|
// process mcp endpoint requests
|
|
136
131
|
const handleRequest = async (req, res) => {
|
|
137
|
-
let transport
|
|
132
|
+
let transport;
|
|
138
133
|
let transports = cache.get("transports");
|
|
139
|
-
console.error("=========================================================");
|
|
140
|
-
console.error("Processing POST /mcp request");
|
|
141
134
|
if (transports == null) {
|
|
142
|
-
console.error("[Error]
|
|
143
|
-
transports = {};
|
|
144
|
-
cache.set("transports", transports);
|
|
135
|
+
console.error("[Error] No transports found in cache. Initializing empty transports object.");
|
|
136
|
+
transports = {'dummy': null };
|
|
145
137
|
}
|
|
146
|
-
|
|
147
138
|
console.error("current transports in cache:", Object.keys(transports));
|
|
148
139
|
try {
|
|
149
140
|
|
|
150
141
|
let sessionId = req.headers["mcp-session-id"];
|
|
151
|
-
console.error("
|
|
142
|
+
console.error("========================================================");
|
|
143
|
+
console.error("post /mcp called with session ID:", sessionId);
|
|
152
144
|
let body = (req.body == null) ? 'no body' : JSON.stringify(req.body);
|
|
153
145
|
console.error('[Note] Payload is ', body);
|
|
154
146
|
if (/*!sessionId &&*/ isInitializeRequest(req.body)) {
|
|
@@ -168,7 +160,7 @@ const handleRequest = async (req, res) => {
|
|
|
168
160
|
});
|
|
169
161
|
// Clean up transport when closed
|
|
170
162
|
transport.onclose = () => {
|
|
171
|
-
if (transport.sessionId
|
|
163
|
+
if (transport.sessionId) {
|
|
172
164
|
delete transports[transport.sessionId];
|
|
173
165
|
}
|
|
174
166
|
};
|
|
@@ -176,11 +168,9 @@ const handleRequest = async (req, res) => {
|
|
|
176
168
|
await mcpServer.connect(transport);
|
|
177
169
|
|
|
178
170
|
// Save transport data and app context for use in tools
|
|
179
|
-
console.error('
|
|
171
|
+
console.error('connected mcpServer');
|
|
180
172
|
cache.set("transports", transports);
|
|
181
|
-
console.error("=======================================================");
|
|
182
173
|
return await transport.handleRequest(req, res, req.body);
|
|
183
|
-
|
|
184
174
|
// cache transport
|
|
185
175
|
|
|
186
176
|
} else if (sessionId != null) {
|
|
@@ -189,8 +179,8 @@ const handleRequest = async (req, res) => {
|
|
|
189
179
|
console.error("[Note] Found transport:", transport != null);
|
|
190
180
|
if (transport == null) {
|
|
191
181
|
// this can happen if client is holding on to old session id
|
|
192
|
-
console.error("[Error] No transport found for session ID:", sessionId, "Returning a
|
|
193
|
-
res.status(
|
|
182
|
+
console.error("[Error] No transport found for session ID:", sessionId, "Returning a 400 error with instructions for the user");
|
|
183
|
+
res.status(400).send(`Invalid or missing session ID ${sessionId}. Please ensure your MCP client is configured to use the correct session ID returned in the 'mcp-session-id' header of the response from the /mcp endpoint.`);
|
|
194
184
|
return;
|
|
195
185
|
}
|
|
196
186
|
|
|
@@ -207,15 +197,9 @@ const handleRequest = async (req, res) => {
|
|
|
207
197
|
let headerCache = cache.get("headerCache");
|
|
208
198
|
_appContext = Object.assign({}, appEnvTemplate, headerCache);
|
|
209
199
|
cache.set(sessionId, _appContext);
|
|
210
|
-
} else {
|
|
211
|
-
let headerCache = cache.get("headerCache");
|
|
212
|
-
console.error('compare tokens', headerCache.bearerToken === _appContext.bearerToken);
|
|
213
|
-
_appContext = Object.assign(_appContext, headerCache);
|
|
214
|
-
console.error('New bearerToken:', _appContext.bearerToken);
|
|
215
|
-
cache.set(sessionId, _appContext);
|
|
216
200
|
}
|
|
217
201
|
console.error("[Note] Using existing transport for session ID:", sessionId);
|
|
218
|
-
|
|
202
|
+
|
|
219
203
|
await transport.handleRequest(req, res, req.body);
|
|
220
204
|
return;
|
|
221
205
|
}
|
|
@@ -239,27 +223,23 @@ const handleRequest = async (req, res) => {
|
|
|
239
223
|
}
|
|
240
224
|
};
|
|
241
225
|
const handleGetDelete = async (req, res) => {
|
|
242
|
-
console.error("
|
|
243
|
-
console.error(`[Note] ${req.method} /mcp called`);
|
|
244
|
-
const sessionId = req.headers["mcp-session-id"];
|
|
245
|
-
console.error("[Note] SessionId:", sessionId);
|
|
226
|
+
console.error(req.method, "/mcp called");
|
|
246
227
|
|
|
228
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
229
|
+
console.error("Headers:", sessionId);
|
|
230
|
+
console.error("Handling GET/DELETE for session ID:", sessionId);
|
|
247
231
|
let transports = cache.get("transports");
|
|
248
232
|
let transport = (sessionId == null) ? null : transports[sessionId];
|
|
249
|
-
console.error("
|
|
233
|
+
console.error("Found transport:", transport != null);
|
|
250
234
|
if (!sessionId || transport == null) {
|
|
251
|
-
res.status(
|
|
235
|
+
res.status(200).send(`[Error] In ${req.method}: Invalid or missing session ID ${sessionId}`);
|
|
252
236
|
return;
|
|
253
237
|
}
|
|
254
|
-
|
|
255
|
-
await transport.handleRequest(req, res);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
238
|
+
await transport.handleRequest(req, res);
|
|
258
239
|
if (req.method === "DELETE" && sessionId != null) {
|
|
259
|
-
console.error("
|
|
240
|
+
console.error("Deleting transport and cache for session ID:", sessionId);
|
|
260
241
|
delete transports[sessionId];
|
|
261
242
|
cache.del(sessionId);
|
|
262
|
-
res.status(201).send(`[Info] Deleted session ${sessionId}`);
|
|
263
243
|
}
|
|
264
244
|
}
|
|
265
245
|
|
|
@@ -267,40 +247,34 @@ app.options("/mcp", (_, res) => res.sendStatus(204));
|
|
|
267
247
|
app.post("/mcp", requireBearer, handleRequest);
|
|
268
248
|
app.get("/mcp", handleGetDelete);
|
|
269
249
|
app.delete("/mcp", handleGetDelete);
|
|
270
|
-
app.get("/
|
|
271
|
-
console.error("
|
|
272
|
-
console.error("Received request for Startup endpoint. Current app status:", appStatus);
|
|
273
|
-
console.error("===================================================================");
|
|
250
|
+
app.get("/startup", (_req, res) => {
|
|
251
|
+
console.error("Received request for startup endpoint");
|
|
274
252
|
if (appStatus === false) {
|
|
275
|
-
return res.status(
|
|
253
|
+
return res.status(500).json({ status: "starting" });
|
|
276
254
|
}
|
|
277
255
|
return res.status(200).json({ status: "started" });
|
|
278
256
|
});
|
|
279
257
|
app.get("/tlogon", async (_req, res) => {
|
|
280
258
|
console.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Testing logon");
|
|
281
259
|
if (appStatus === false) {
|
|
282
|
-
return res.status(
|
|
260
|
+
return res.status(500).json({ status: "not ready" });
|
|
283
261
|
}
|
|
284
262
|
let r = await tlogon(baseAppEnvContext);
|
|
285
263
|
console.error(r);
|
|
286
264
|
return res.status(200).json(r);
|
|
287
265
|
});
|
|
288
266
|
app.get("/status", (_req, res) => {
|
|
289
|
-
console.error("===================================================================")
|
|
290
267
|
console.error("Received request for status endpoint. Current app status:", appStatus);
|
|
291
|
-
console.error("===================================================================");
|
|
292
268
|
if (appStatus === false) {
|
|
293
|
-
return res.status(
|
|
269
|
+
return res.status(500).json({ status: "not ready" });
|
|
294
270
|
}
|
|
295
271
|
return res.status(200).json({ status: "ready" });
|
|
296
272
|
});
|
|
297
273
|
|
|
298
274
|
app.get("/ready", (_req, res) => {
|
|
299
|
-
console.error("===================================================================")
|
|
300
275
|
console.error("Received request for ready endpoint. Current app status:", appStatus);
|
|
301
|
-
console.error("===================================================================")
|
|
302
276
|
if (appStatus === false) {
|
|
303
|
-
return res.status(
|
|
277
|
+
return res.status(500).json({ status: "not ready" });
|
|
304
278
|
}
|
|
305
279
|
return res.status(200).json({ status: "ready" });
|
|
306
280
|
});
|
package/src/handleGetDelete.js
CHANGED
|
@@ -5,25 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
async function handleGetDelete(mcpServer, cache, req, h) {
|
|
7
7
|
const sessionId = req.headers["mcp-session-id"];
|
|
8
|
-
console.error(
|
|
9
|
-
console.error(`[Note] Handling ${req.method} for session ID:`, sessionId);
|
|
8
|
+
console.error(`Handling ${req.method} for session ID:`, sessionId);
|
|
10
9
|
let transports = cache.get("transports");
|
|
11
10
|
let transport = transports[sessionId];
|
|
12
11
|
if (!sessionId || transport == null) {
|
|
13
12
|
console.error('[Note] Looks like a fresh start - no session id or transport found');
|
|
14
|
-
|
|
13
|
+
h.abandon;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
if (req.method === "GET") {
|
|
18
17
|
// You can customize the response as needed
|
|
19
|
-
console.error("[Note] Payload:", req.payload);
|
|
20
|
-
console.log("======================================================");
|
|
21
18
|
await transport.handleRequest(req.raw.req, req.raw.res, req.payload);
|
|
22
19
|
return h.abandon;
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
if (req.method === "DELETE") {
|
|
26
|
-
console.error("
|
|
23
|
+
console.error("Deleting transport and cache for session ID:", sessionId);
|
|
27
24
|
delete transports[sessionId];
|
|
28
25
|
cache.del(sessionId);
|
|
29
26
|
return h.response(`[Info] In DELETE: Session ID ${sessionId} deleted`).code(201);
|
package/src/hapiMcpServer.js
CHANGED
|
@@ -128,30 +128,38 @@ async function hapiMcpServer(mcpServer, cache, baseAppEnvContext) {
|
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
method: ["GET"],
|
|
131
|
-
path: "/
|
|
131
|
+
path: "/startz",
|
|
132
132
|
options: {
|
|
133
133
|
handler: async (req, h) => {
|
|
134
|
-
let status = {status:
|
|
135
|
-
console.error("
|
|
136
|
-
|
|
134
|
+
let status = { status: (process.env.HEALTH === 'true') ? 'ready' : 'starting' };
|
|
135
|
+
console.error("startz check requested, returning:", status);
|
|
136
|
+
if (process.env.HEALTH !== 'true') {
|
|
137
|
+
return h.response(status).code(200).type('application/json');
|
|
138
|
+
} else {
|
|
139
|
+
return h.response(status).code(503).type('application/json');
|
|
140
|
+
}
|
|
137
141
|
},
|
|
138
142
|
auth: false,
|
|
139
|
-
description: "
|
|
143
|
+
description: "Help",
|
|
140
144
|
notes: "Help",
|
|
141
145
|
tags: ["mcp"],
|
|
142
146
|
}
|
|
143
147
|
},
|
|
144
148
|
{
|
|
145
149
|
method: ["GET"],
|
|
146
|
-
path: "/
|
|
150
|
+
path: "/readyz",
|
|
147
151
|
options: {
|
|
148
152
|
handler: async (req, h) => {
|
|
149
|
-
let status = { status:
|
|
150
|
-
console.error("
|
|
151
|
-
|
|
153
|
+
let status = { status: (process.env.HEALTH === 'true') ? 'ready' : 'starting' };
|
|
154
|
+
console.error("readyz check requested, returning:", status);
|
|
155
|
+
if (process.env.HEALTH !== 'true') {
|
|
156
|
+
return h.response(status).code(200).type('application/json');
|
|
157
|
+
} else {
|
|
158
|
+
return h.response(status).code(503).type('application/json');
|
|
159
|
+
}
|
|
152
160
|
},
|
|
153
161
|
auth: false,
|
|
154
|
-
description: "
|
|
162
|
+
description: "Help",
|
|
155
163
|
notes: "Help",
|
|
156
164
|
tags: ["mcp"],
|
|
157
165
|
}
|
|
@@ -6,70 +6,53 @@
|
|
|
6
6
|
import restafedit from '@sassoftware/restafedit';
|
|
7
7
|
import deleteSession from './deleteSession.js';
|
|
8
8
|
|
|
9
|
-
async function _listLibrary(params)
|
|
9
|
+
async function _listLibrary(params ){
|
|
10
10
|
|
|
11
11
|
let { server, limit, start, name, _appContext } = params;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
|
|
13
|
+
let config = {
|
|
14
|
+
casServerName: _appContext.cas,
|
|
15
|
+
computeContext: _appContext.sas,
|
|
16
|
+
source: (server === 'sas') ? 'compute' : server,
|
|
17
|
+
table: null
|
|
18
|
+
};
|
|
19
|
+
let appControl;
|
|
20
|
+
try {
|
|
21
|
+
// setup request control
|
|
22
|
+
appControl = await restafedit.setup(
|
|
23
|
+
_appContext.logonPayload,
|
|
24
|
+
config
|
|
25
|
+
,null,{},'user',{}, {}, _appContext.storeConfig
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// query parameters
|
|
29
|
+
let payload = {
|
|
30
|
+
qs: {
|
|
31
|
+
limit: (limit != null) ? limit : 10,
|
|
32
|
+
start: start - 1
|
|
33
|
+
}
|
|
21
34
|
};
|
|
22
|
-
let appControl;
|
|
23
|
-
try {
|
|
24
|
-
// setup request control
|
|
25
|
-
appControl = await restafedit.setup(
|
|
26
|
-
_appContext.logonPayload,
|
|
27
|
-
config
|
|
28
|
-
, null, {}, 'user', {}, {}, _appContext.storeConfig
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
// query parameters
|
|
32
|
-
let payload = {
|
|
33
|
-
qs: {
|
|
34
|
-
limit: (limit != null) ? limit : 10,
|
|
35
|
-
start: start - 1
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
36
|
+
if (name != null) {
|
|
37
|
+
payload.qs = {
|
|
38
|
+
filter: `eq(name, '${name}')`
|
|
43
39
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let items = await restafedit.getLibraryList(appControl, payload);
|
|
43
|
+
let response = {libraries: items};
|
|
44
|
+
await deleteSession(appControl);
|
|
45
|
+
|
|
46
|
+
return { content: [{ type: 'text', text: JSON.stringify(response) }],
|
|
47
|
+
structuredContent: response
|
|
48
|
+
};
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error(JSON.stringify(err));
|
|
51
|
+
if (appControl != null) {
|
|
47
52
|
await deleteSession(appControl);
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
content: [{ type: 'text', text: JSON.stringify(response) }],
|
|
51
|
-
structuredContent: response
|
|
52
|
-
};
|
|
53
|
-
} catch (err) {
|
|
54
|
-
console.error(JSON.stringify(err));
|
|
55
|
-
if (appControl != null) {
|
|
56
|
-
await deleteSession(appControl);
|
|
57
|
-
}
|
|
58
|
-
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
59
53
|
}
|
|
54
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
60
55
|
}
|
|
61
|
-
|
|
62
|
-
let source = (server === 'all') ? ['sas', 'cas'] : [server];
|
|
63
|
-
let response = {};
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < source.length; i++) {
|
|
66
|
-
let liblist = await _ilistLibrary({ server: source[i], limit, start, name, _appContext });
|
|
67
|
-
response[source[i]] = liblist.structuredContent;
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
content: [{ type: 'text', text: JSON.stringify(response) }],
|
|
71
|
-
structuredContent: response
|
|
72
|
-
};
|
|
73
56
|
}
|
|
74
57
|
|
|
75
58
|
export default _listLibrary;
|
|
@@ -16,11 +16,10 @@ async function igetLogonPayload(_appContext) {
|
|
|
16
16
|
console.error('[Info] Getting logon payload...',_appContext.AUTHFLOW);
|
|
17
17
|
// Use cached logonPayload if available
|
|
18
18
|
// This will cause timeouts if the token expires
|
|
19
|
-
|
|
19
|
+
if (_appContext.contexts.logonPayload != null && _appContext.tokenRefresh !== true) {
|
|
20
20
|
console.error("[Note] Using cached logonPayload information");
|
|
21
21
|
return _appContext.contexts.logonPayload;
|
|
22
22
|
}
|
|
23
|
-
*/
|
|
24
23
|
|
|
25
24
|
if (_appContext.AUTHFLOW === 'code') {
|
|
26
25
|
let oauthInfo = _appContext.contexts.oauthInfo;
|
|
@@ -53,7 +52,6 @@ async function igetLogonPayload(_appContext) {
|
|
|
53
52
|
token: _appContext.bearerToken,
|
|
54
53
|
tokenType: "Bearer",
|
|
55
54
|
};
|
|
56
|
-
console.error("[Note] Bearer token in logonPayload ", _appContext.bearerToken);
|
|
57
55
|
return logonPayload;
|
|
58
56
|
}
|
|
59
57
|
|