@sassoftware/sas-score-mcp-serverjs 0.4.1 → 1.0.1-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.
Files changed (72) hide show
  1. package/.skills/agents/sas-viya-scoring-expert.md +58 -0
  2. package/.skills/copilot-instructions.md +155 -0
  3. package/.skills/skills/sas-find-library-smart/SKILL.md +154 -0
  4. package/.skills/skills/sas-list-tables-smart/SKILL.md +127 -0
  5. package/.skills/skills/sas-read-and-score/SKILL.md +111 -0
  6. package/.skills/skills/sas-read-strategy/SKILL.md +156 -0
  7. package/.skills/skills/sas-request-classifier/SKILL.md +69 -0
  8. package/.skills/skills/sas-score-workflow/SKILL.md +314 -0
  9. package/cli.js +311 -70
  10. package/package.json +7 -7
  11. package/scripts/docs/SCORE_SKILL_REFERENCE.md +142 -0
  12. package/scripts/docs/TOOL_DESCRIPTION_TEMPLATE.md +157 -0
  13. package/scripts/docs/TOOL_UPDATES_SUMMARY.md +208 -0
  14. package/scripts/docs/mcp-localhost-config-guide.md +184 -0
  15. package/scripts/docs/oauth-http-transport.md +96 -0
  16. package/scripts/docs/sas-mcp-tools-reference.md +600 -0
  17. package/scripts/getViyaca.sh +1 -0
  18. package/scripts/optimize_final.py +140 -0
  19. package/scripts/optimize_tools.py +99 -0
  20. package/scripts/setup-skills.js +34 -0
  21. package/scripts/update_descriptions.py +46 -0
  22. package/scripts/viyatls.sh +3 -0
  23. package/src/authpkce.js +219 -0
  24. package/src/createMcpServer.js +16 -5
  25. package/src/expressMcpServer.js +350 -308
  26. package/src/handleGetDelete.js +6 -3
  27. package/src/hapiMcpServer.js +10 -18
  28. package/src/oauthHandlers/authorize.js +46 -0
  29. package/src/oauthHandlers/baseUrl.js +8 -0
  30. package/src/oauthHandlers/callback.js +96 -0
  31. package/src/oauthHandlers/getMetadata.js +27 -0
  32. package/src/oauthHandlers/index.js +7 -0
  33. package/src/oauthHandlers/token.js +37 -0
  34. package/src/processHeaders.js +88 -0
  35. package/src/setupSkills.js +46 -0
  36. package/src/toolHelpers/_jobSubmit.js +2 -0
  37. package/src/toolHelpers/_listLibrary.js +55 -39
  38. package/src/toolHelpers/getLogonPayload.js +7 -1
  39. package/src/toolHelpers/readCerts.js +4 -4
  40. package/src/toolHelpers/refreshToken.js +3 -2
  41. package/src/toolHelpers/refreshTokenOauth.js +3 -3
  42. package/src/toolSet/.claude/settings.local.json +13 -0
  43. package/src/toolSet/devaScore.js +61 -69
  44. package/src/toolSet/findJob.js +38 -71
  45. package/src/toolSet/findJobdef.js +28 -59
  46. package/src/toolSet/findLibrary.js +68 -100
  47. package/src/toolSet/findModel.js +35 -58
  48. package/src/toolSet/findTable.js +31 -60
  49. package/src/toolSet/getEnv.js +30 -45
  50. package/src/toolSet/listJobdefs.js +61 -96
  51. package/src/toolSet/listJobs.js +61 -110
  52. package/src/toolSet/listLibraries.js +78 -90
  53. package/src/toolSet/listModels.js +56 -83
  54. package/src/toolSet/listTables.js +66 -95
  55. package/src/toolSet/makeTools.js +1 -0
  56. package/src/toolSet/modelInfo.js +22 -54
  57. package/src/toolSet/modelScore.js +35 -77
  58. package/src/toolSet/readTable.js +63 -104
  59. package/src/toolSet/runCasProgram.js +32 -52
  60. package/src/toolSet/runJob.js +24 -24
  61. package/src/toolSet/runJobdef.js +26 -29
  62. package/src/toolSet/runMacro.js +82 -82
  63. package/src/toolSet/runProgram.js +32 -84
  64. package/src/toolSet/sasQuery.js +77 -126
  65. package/src/toolSet/sasQueryTemplate.js +4 -5
  66. package/src/toolSet/sasQueryTemplate2.js +4 -5
  67. package/src/toolSet/scrInfo.js +4 -7
  68. package/src/toolSet/scrScore.js +69 -70
  69. package/src/toolSet/searchAssets.js +5 -6
  70. package/src/toolSet/setContext.js +65 -92
  71. package/src/toolSet/superstat.js +61 -60
  72. package/src/toolSet/tableInfo.js +58 -102
@@ -13,18 +13,20 @@ import openAPIJson from "./openAPIJson.js";
13
13
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
14
14
  import { randomUUID } from "node:crypto";
15
15
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
16
+
16
17
  import tlogon from "./toolHelpers/tlogon.js";
17
18
 
19
+ import { getMetadata, authorize, callback, token, baseUrl } from "./oauthHandlers/index.js";
20
+ import processHeaders from "./processHeaders.js";
18
21
 
19
22
  // setup express server
20
23
 
21
24
  async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
22
25
  // setup for change to persistence session
23
- cache.set("headerCache", {});
24
-
26
+ cache.del("headerCache");
25
27
  const app = express();
26
28
  let appStatus = false;
27
-
29
+ app.use(express.urlencoded({ extended: true })); // MUST be before your routes
28
30
  app.use(express.json({ limit: "50mb" }));
29
31
  app.use(
30
32
  cors({
@@ -44,7 +46,73 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
44
46
  // app.use(helmet());
45
47
  app.use(bodyParser.json({ limit: process.env.JSON_LIMIT ?? "50mb" }));
46
48
 
49
+ // In-memory stores for the OAuth PKCE proxy flow (cleared on server restart)
50
+ const pkceStore = new Map(); // ourState -> { codeVerifier, clientRedirectUri, clientState }
51
+ const codeStore = new Map(); // ourCode -> { access_token, refresh_token, expires_in }
52
+
53
+ app.get('/.well-known/oauth-protected-resource', (req, res) => {
54
+ let payload = {
55
+ resource: `${baseAppEnvContext.mcpHost}/mcp`,
56
+ authorization_servers: [`${baseAppEnvContext.VIYA_SERVER}`],
57
+ scopes_supported: ['openid'],
58
+ bearer_methods_supported: ["header"]
59
+ }
60
+ console.error("[Note]>>>>>>>>>>>>>>>>>>>>>>>>>> protected resource metadata ", payload );
61
+ return res.json(payload);
62
+ });
63
+
64
+ app.get("/.well-known/oauth-authorization-server", async (req, res) => {
65
+ console.error("[Note] Received request for OAuth authorization server metadata");
66
+ let metadata = getMetadata(req, res, baseAppEnvContext);
67
+ console.error("[Note]>>>>>>>>>>>>>>>>>>>>>>>>> metadata ", metadata);
68
+ return res.status(200).json(metadata);
69
+ });
70
+
71
+ // OAuth authorize — generates PKCE params, stores state, redirects to SAS Viya
72
+ app.get("/oauth/authorize", async (req, res) => {
73
+ console.error("[Note] Received request for /oauth/authorize");
74
+ return authorize(req, res, baseAppEnvContext, pkceStore, codeStore);
75
+ });
76
+ app.get("/authorize", async (req, res) => {
77
+ console.error("[Note] Received request for /authorize");
78
+ return authorize(req, res, baseAppEnvContext, pkceStore, codeStore);
79
+ });
80
+
81
+ // OAuth callback — receives code from SAS Viya, exchanges for tokens
82
+ app.get("/callback", async (req, res) => {
83
+ console.error("[Note] Received request for /callback with query parameters:");
84
+ return await callback(req, res, pkceStore, codeStore, baseAppEnvContext);
85
+ });
86
+
87
+ // OAuth token endpoint — MCP client exchanges intermediate code for access token
88
+ app.post("/oauth/token", (req, res) => {
89
+ console.error("[Note] Received request for /oauth/token");
90
+ return token(req, res, baseAppEnvContext, codeStore, cache);
91
+ });
92
+
93
+ app.post("/token", (req, res) => {
94
+ console.error("[Note] Received request for /token");
95
+ return token(req, res, baseAppEnvContext, codeStore, cache);
96
+ });
47
97
  // setup routes
98
+
99
+ // Root endpoint info
100
+
101
+ app.get("/", (req, res) => {
102
+ res.json({
103
+ name: "SAS Viya Sample MCP Server",
104
+ version: baseAppEnvContext.version,
105
+ description: "SAS Viya Sample MCP Server",
106
+ endpoints: {
107
+ mcp: "/mcp",
108
+ health: "/health",
109
+ apiMeta: "/apiMeta"
110
+ },
111
+ usage: "Use with MCP Inspector or compatible MCP clients",
112
+ });
113
+ });
114
+
115
+ // health endpoint
48
116
  app.get("/health", (req, res) => {
49
117
  console.error("Received request for health endpoint");
50
118
  if (appStatus === false) {
@@ -64,340 +132,314 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
64
132
  };
65
133
  res.json(health);
66
134
  });
67
-
68
- // Root endpoint info
69
-
70
- app.get("/", (req, res) => {
71
- res.json({
72
- name: "SAS Viya Sample MCP Server",
73
- version: baseAppEnvContext.version,
74
- description: "SAS Viya Sample MCP Server",
75
- endpoints: {
76
- mcp: "/mcp",
77
- health: "/health",
78
- apiMeta: "/apiMeta"
79
- },
80
- usage: "Use with MCP Inspector or compatible MCP clients",
81
- });
82
- });
83
135
 
84
- // api metadata endpoint(for sas specs)
85
- app.get("/apiMeta", (req, res) => {
86
- let spec = openAPIJson(baseAppEnvContext.version);
87
- res.json(spec);
88
- });
136
+ // api metadata endpoint(for sas specs)
137
+ app.get("/apiMeta", (req, res) => {
138
+ let spec = openAPIJson(baseAppEnvContext.version);
139
+ res.json(spec);
140
+ });
89
141
 
90
- // for azure container apps
91
- app.get("/openapi.json", (req, res) => {
92
- let spec = openAPIJson(baseAppEnvContext.version);
93
- res.json(spec);
94
- });
142
+ // for azure container apps
143
+ app.get("/openapi.json", (req, res) => {
144
+ let spec = openAPIJson(baseAppEnvContext.version);
145
+ res.json(spec);
146
+ });
95
147
 
96
- // handle processing of information in header.
97
- function requireBearer(req, res, next) {
148
+ // handle processing of information in header.
149
+ function requireBearer(req, res, next) {
150
+ return processHeaders(req, res, next, cache, baseAppEnvContext);
151
+ }
98
152
 
153
+ // process mcp endpoint requests
154
+ const handleRequest = async (req, res) => {
155
+ let transport = null;
156
+ let transports = cache.get("transports");
157
+ console.error("=========================================================");
158
+ console.error("Processing POST /mcp request");
159
+ if (transports == null) {
160
+ console.error("[Error] ***** transports cache is null. This is an error");
161
+ transports = {};
162
+ cache.set("transports", transports);
163
+ }
99
164
 
100
- // process any new header information
165
+ console.error("current transports in cache:", Object.keys(transports));
166
+ try {
167
+
168
+ let sessionId = req.headers["mcp-session-id"];
169
+ console.error("[Note]Incoming session ID:", sessionId);
170
+ let body = (req.body == null) ? 'no body' : JSON.stringify(req.body);
171
+ console.error('[Note] Payload is ', body);
172
+ if (/*!sessionId &&*/ isInitializeRequest(req.body)) {
173
+ // create transport
174
+ console.error("[Note] Initializing new transport for MCP session...");
175
+
176
+ transport = new StreamableHTTPServerTransport({
177
+ sessionIdGenerator: () => randomUUID(),
178
+ enableJsonResponse: true,
179
+ enableDnsRebindingProtection: true,
180
+ onsessioninitialized: (sessionId) => {
181
+ // Store the transport by session ID
182
+ console.error('Session initialized');
183
+ console.error("[Note] Transport initialized with ID:", sessionId);
184
+ transports[sessionId] = transport;
185
+ },
186
+ });
187
+ // Clean up transport when closed
188
+ transport.onclose = () => {
189
+ if (transport.sessionId && transports[transport.sessionId]) {
190
+ delete transports[transport.sessionId];
191
+ }
192
+ };
193
+ console.error("[Note] Connecting mcpServer to new transport...");
194
+ await mcpServer.connect(transport);
195
+
196
+ // Save transport data and app context for use in tools
197
+ console.error('[Note] Connected to mcpServer');
198
+ cache.set("transports", transports);
199
+ console.error("=======================================================");
200
+ return await transport.handleRequest(req, res, req.body);
201
+
202
+ // cache transport
203
+
204
+ } else if (sessionId != null) {
205
+ console.error('[Note] Incoming session ID:', sessionId);
206
+ transport = transports[sessionId];
207
+ console.error("[Note] Found transport:", transport != null);
208
+ if (transport == null) {
209
+ // this can happen if client is holding on to old session id
210
+ console.error("[Error] No transport found for session ID:", sessionId, "Returning a 404 error with instructions for the user");
211
+ res.status(404).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.`);
212
+ return;
213
+ }
101
214
 
102
- // Allow different VIYA server per sessionid(user)
103
- let headerCache = {};
104
- if (req.header("X-VIYA-SERVER") != null) {
105
- console.error("[Note] Using user supplied VIYA server");
106
- headerCache.VIYA_SERVER = req.header("X-VIYA-SERVER");
107
- }
215
+ // post the curren session - used to pass _appContext to tools
216
+ cache.set("currentId", sessionId);
108
217
 
109
- // used when doing autorization via mcp client
110
- // ideal for production use
111
- const hdr = req.header("Authorization");
112
- if (hdr != null) {
113
- headerCache.bearerToken = hdr.slice(7);
114
- headerCache.AUTHFLOW = "bearer";
115
- console.error("[Note] Using user supplied bearer token for authorization");
116
- }
218
+ // get app context for session
219
+ let _appContext = cache.get(sessionId);
117
220
 
118
- // faking out api key since Viya does not support
119
- // not ideal for production
120
- const hdr2 = req.header("X-REFRESH-TOKEN");
121
- if (hdr2 != null) {
122
- headerCache.REFRESH_TOKEN = hdr2;
123
- headerCache.AUTHFLOW = "refresh";
124
- console.error("[Note] Using user supplied refresh token for authorization");
125
- }
126
- cache.set("headerCache", headerCache);
127
- next();
128
- }
221
+ //if first prompt on a sessionid, create app context
222
+ if (_appContext == null) {
129
223
 
130
- // process mcp endpoint requests
131
- const handleRequest = async (req, res) => {
132
- let transport;
133
- let transports = cache.get("transports");
134
- if (transports == null) {
135
- console.error("[Error] No transports found in cache. Initializing empty transports object.");
136
- transports = {'dummy': null };
137
- }
138
- console.error("current transports in cache:", Object.keys(transports));
139
- try {
140
-
141
- let sessionId = req.headers["mcp-session-id"];
142
- console.error("========================================================");
143
- console.error("post /mcp called with session ID:", sessionId);
144
- let body = (req.body == null) ? 'no body' : JSON.stringify(req.body);
145
- console.error('[Note] Payload is ', body);
146
- if (/*!sessionId &&*/ isInitializeRequest(req.body)) {
147
- // create transport
148
- console.error("[Note] Initializing new transport for MCP session...");
149
-
150
- transport = new StreamableHTTPServerTransport({
151
- sessionIdGenerator: () => randomUUID(),
152
- enableJsonResponse: true,
153
- enableDnsRebindingProtection: true,
154
- onsessioninitialized: (sessionId) => {
155
- // Store the transport by session ID
156
- console.error('Session initialized');
157
- console.error("[Note] Transport initialized with ID:", sessionId);
158
- transports[sessionId] = transport;
159
- },
160
- });
161
- // Clean up transport when closed
162
- transport.onclose = () => {
163
- if (transport.sessionId) {
164
- delete transports[transport.sessionId];
224
+ let appEnvTemplate = cache.get("appEnvTemplate");
225
+ let headerCache = cache.get("headerCache");
226
+ _appContext = Object.assign({}, appEnvTemplate, headerCache);
227
+ cache.set(sessionId, _appContext);
228
+ } else {
229
+ let headerCache = cache.get("headerCache");
230
+ _appContext = Object.assign(_appContext, headerCache);
231
+ cache.set(sessionId, _appContext);
165
232
  }
166
- };
167
- console.error("[Note] Connecting mcpServer to new transport...");
168
- await mcpServer.connect(transport);
169
-
170
- // Save transport data and app context for use in tools
171
- console.error('connected mcpServer');
172
- cache.set("transports", transports);
173
- return await transport.handleRequest(req, res, req.body);
174
- // cache transport
175
-
176
- } else if (sessionId != null) {
177
- console.error('[Note] Incoming session ID:', sessionId);
178
- transport = transports[sessionId];
179
- console.error("[Note] Found transport:", transport != null);
180
- if (transport == null) {
181
- // this can happen if client is holding on to old session id
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.`);
233
+ console.error("[Note] Using existing transport for session ID:", sessionId);
234
+ console.error("==========================================================");
235
+ await transport.handleRequest(req, res, req.body);
184
236
  return;
185
237
  }
186
238
 
187
- // post the curren session - used to pass _appContext to tools
188
- cache.set("currentId", sessionId);
189
-
190
- // get app context for session
191
- let _appContext = cache.get(sessionId);
192
-
193
- //if first prompt on a sessionid, create app context
194
- if (_appContext == null) {
239
+ // initialize request
195
240
 
196
- let appEnvTemplate = cache.get("appEnvTemplate");
197
- let headerCache = cache.get("headerCache");
198
- _appContext = Object.assign({}, appEnvTemplate, headerCache);
199
- cache.set(sessionId, _appContext);
241
+ }
242
+ catch (error) {
243
+ console.error("Error handling MCP request:", error);
244
+ if (!res.headersSent) {
245
+ res.status(500).json({
246
+ jsonrpc: "2.0",
247
+ error: {
248
+ code: -32603,
249
+ message: JSON.stringify(error),
250
+ },
251
+ id: null,
252
+ });
200
253
  }
201
- console.error("[Note] Using existing transport for session ID:", sessionId);
202
-
203
- await transport.handleRequest(req, res, req.body);
204
254
  return;
205
255
  }
206
-
207
- // initialize request
208
-
209
- }
210
- catch (error) {
211
- console.error("Error handling MCP request:", error);
212
- if (!res.headersSent) {
213
- res.status(500).json({
214
- jsonrpc: "2.0",
215
- error: {
216
- code: -32603,
217
- message: JSON.stringify(error),
218
- },
219
- id: null,
220
- });
256
+ };
257
+ const handleGetDelete = async (req, res) => {
258
+ console.error("=========================================================");
259
+ console.error(`[Note] ${req.method} /mcp called`);
260
+ const sessionId = req.headers["mcp-session-id"];
261
+ console.error("[Note] SessionId:", sessionId);
262
+
263
+ let transports = cache.get("transports");
264
+ let transport = (sessionId == null) ? null : transports[sessionId];
265
+ console.error("[Note] Transport found:", transport != null);
266
+ if (!sessionId || transport == null) {
267
+ res.status(404).send(`[Error] In ${req.method}: Invalid or missing session ID ${sessionId}`);
268
+ return;
269
+ }
270
+ if (req.method === "GET") {
271
+ await transport.handleRequest(req, res);
272
+ return;
273
+ }
274
+ if (req.method === "DELETE" && sessionId != null) {
275
+ console.error("[Note] Deleting transport and cache for session ID:", sessionId);
276
+ delete transports[sessionId];
277
+ cache.del(sessionId);
278
+ res.status(201).send(`[Info] Deleted session ${sessionId}`);
221
279
  }
222
- return;
223
- }
224
- };
225
- const handleGetDelete = async (req, res) => {
226
- console.error(req.method, "/mcp called");
227
-
228
- const sessionId = req.headers["mcp-session-id"];
229
- console.error("Headers:", sessionId);
230
- console.error("Handling GET/DELETE for session ID:", sessionId);
231
- let transports = cache.get("transports");
232
- let transport = (sessionId == null) ? null : transports[sessionId];
233
- console.error("Found transport:", transport != null);
234
- if (!sessionId || transport == null) {
235
- res.status(200).send(`[Error] In ${req.method}: Invalid or missing session ID ${sessionId}`);
236
- return;
237
- }
238
- await transport.handleRequest(req, res);
239
- if (req.method === "DELETE" && sessionId != null) {
240
- console.error("Deleting transport and cache for session ID:", sessionId);
241
- delete transports[sessionId];
242
- cache.del(sessionId);
243
280
  }
244
- }
245
281
 
246
- app.options("/mcp", (_, res) => res.sendStatus(204));
247
- app.post("/mcp", requireBearer, handleRequest);
248
- app.get("/mcp", handleGetDelete);
249
- app.delete("/mcp", handleGetDelete);
250
- app.get("/startup", (_req, res) => {
251
- console.error("Received request for startup endpoint");
252
- if (appStatus === false) {
253
- return res.status(500).json({ status: "starting" });
254
- }
255
- return res.status(200).json({ status: "started" });
256
- });
257
- app.get("/tlogon", async (_req, res) => {
258
- console.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Testing logon");
259
- if (appStatus === false) {
260
- return res.status(500).json({ status: "not ready" });
261
- }
262
- let r = await tlogon(baseAppEnvContext);
263
- console.error(r);
264
- return res.status(200).json(r);
265
- });
266
- app.get("/status", (_req, res) => {
267
- console.error("Received request for status endpoint. Current app status:", appStatus);
268
- if (appStatus === false) {
269
- return res.status(500).json({ status: "not ready" });
270
- }
271
- return res.status(200).json({ status: "ready" });
272
- });
282
+ app.options("/mcp", (_, res) => res.sendStatus(204));
283
+ app.post("/mcp", requireBearer, handleRequest);
284
+ app.get("/mcp", handleGetDelete);
285
+ app.delete("/mcp", handleGetDelete);
286
+ app.get("/StartUp", (_req, res) => {
287
+ console.error("===================================================================")
288
+ console.error("Received request for Startup endpoint. Current app status:", appStatus);
289
+ console.error("===================================================================");
290
+ if (appStatus === false) {
291
+ return res.status(503).json({ status: "starting" });
292
+ }
293
+ return res.status(200).json({ status: "started" });
294
+ });
295
+ app.get("/tlogon", async (_req, res) => {
296
+ console.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Testing logon");
297
+ if (appStatus === false) {
298
+ return res.status(503).json({ status: "not ready" });
299
+ }
300
+ let r = await tlogon(baseAppEnvContext);
301
+ console.error(r);
302
+ return res.status(200).json(r);
303
+ });
304
+ app.get("/status", (_req, res) => {
305
+ console.error("===================================================================")
306
+ console.error("Received request for status endpoint. Current app status:", appStatus);
307
+ console.error("===================================================================");
308
+ if (appStatus === false) {
309
+ return res.status(503).json({ status: "not ready" });
310
+ }
311
+ return res.status(200).json({ status: "ready" });
312
+ });
273
313
 
274
- app.get("/ready", (_req, res) => {
275
- console.error("Received request for ready endpoint. Current app status:", appStatus);
276
- if (appStatus === false) {
277
- return res.status(500).json({ status: "not ready" });
278
- }
279
- return res.status(200).json({ status: "ready" });
280
- });
281
- // Start the server
282
- let appEnvBase = cache.get("appEnvBase");
283
-
284
- const PORT = appEnvBase.PORT;
285
-
286
- // get user specified TLS options
287
- let appServer;
288
-
289
- // get TLS options
290
- if (appEnvBase.HTTPS === 'TRUE') {
291
- if (appEnvBase.tlsOpts == null) {
292
- appEnvBase.tlsOpts = await getTls(appEnvBase);
293
- console.error(Object.keys(appEnvBase.tlsOpts));
294
- appEnvBase.tlsOpts.requestCert = false;
295
- appEnvBase.tlsOpts.rejectUnauthorized = false;
296
- appEnvBase.contexts.appCert = appEnvBase.tlsOpts; /* just for completeness */
297
- }
314
+ app.get("/ready", (_req, res) => {
315
+ console.error("===================================================================")
316
+ console.error("Received request for ready endpoint. Current app status:", appStatus);
317
+ console.error("===================================================================")
318
+ if (appStatus === false) {
319
+ return res.status(503).json({ status: "not ready" });
320
+ }
321
+ return res.status(200).json({ status: "ready" });
322
+ });
323
+ // Start the server
324
+ let appEnvBase = cache.get("appEnvBase");
325
+
326
+ const PORT = appEnvBase.PORT;
327
+
328
+ // get user specified TLS options
329
+ let appServer;
330
+
331
+ // get TLS options
332
+ if (appEnvBase.HTTPS === 'TRUE') {
333
+ if (appEnvBase.tlsOpts == null) {
334
+ appEnvBase.tlsOpts = await getTls(appEnvBase);
335
+ console.error(Object.keys(appEnvBase.tlsOpts));
336
+ appEnvBase.tlsOpts.requestCert = false;
337
+ appEnvBase.tlsOpts.rejectUnauthorized = false;
338
+ appEnvBase.contexts.appCert = appEnvBase.tlsOpts; /* just for completeness */
339
+ }
298
340
 
299
- cache.set("appEnvBase", appEnvBase);
341
+ cache.set("appEnvBase", appEnvBase);
300
342
 
301
- console.error(`[Note] MCP Server listening on port ${PORT}`);
302
- console.error(
303
- "[Note] Visit https://localhost:8080/health for health check"
304
- );
305
- console.error(
306
- "[Note] Configure your mcp host to use https://localhost:8080/mcp to interact with the MCP server"
307
- );
308
- console.error("[Note] Press Ctrl+C to stop the server");
343
+ console.error(`[Note] MCP Server listening on port ${PORT}`);
344
+ console.error(
345
+ "[Note] Visit https://localhost:8080/health for health check"
346
+ );
347
+ console.error(
348
+ "[Note] Configure your mcp host to use https://localhost:8080/mcp to interact with the MCP server"
349
+ );
350
+ console.error("[Note] Press Ctrl+C to stop the server");
309
351
 
310
- appServer = https.createServer(appEnvBase.tlsOpts, app);
311
- appServer.listen(PORT, "0.0.0.0", () => {
312
- console.error( `[Note] Express server successfully bound to 0.0.0.0:${PORT}` );
313
- appStatus= true;
314
- });
315
- } else {
316
- console.error(`[Note] MCP Server listening on port ${PORT}`);
317
- console.error("[Note] Visit http://localhost:8080/health for health check");
318
- console.error(
319
- "[Note] Configure your mcp host to use http://localhost:8080/mcp to interact with the MCP server"
320
- );
321
- console.error("[Note] Press Ctrl+C to stop the server");
322
- try {
323
- appServer = app.listen(PORT, "0.0.0.0", () => {
352
+ appServer = https.createServer(appEnvBase.tlsOpts, app);
353
+ appServer.listen(PORT, "0.0.0.0", () => {
354
+ console.error(`[Note] Express server successfully bound to 0.0.0.0:${PORT}`);
324
355
  appStatus = true;
325
- console.error(
326
- `[Note] Express server successfully bound to 0.0.0.0:${PORT}`
327
- );
328
-
329
356
  });
330
- } catch (error) {
331
- console.error("Error starting server:", error);
332
- }
333
- }
334
- process.on("SIGTERM", () => {
335
- console.error("Server closed");
336
- if (appServer != null) {
337
- appServer.close(() => { });
338
- }
339
- process.exit(0);
340
- });
341
- process.on("SIGINT", () => {
342
- console.error("Server closed");
343
- if (appServer != null) {
344
- appServer.close(() => { });
357
+ } else {
358
+ console.error(`[Note] MCP Server listening on port ${PORT}`);
359
+ console.error("[Note] Visit http://localhost:8080/health for health check");
360
+ console.error(
361
+ "[Note] Configure your mcp host to use http://localhost:8080/mcp to interact with the MCP server"
362
+ );
363
+ console.error("[Note] Press Ctrl+C to stop the server");
364
+ try {
365
+ appServer = app.listen(PORT, "0.0.0.0", () => {
366
+ appStatus = true;
367
+ console.error(
368
+ `[Note] Express server successfully bound to 0.0.0.0:${PORT}`
369
+ );
370
+
371
+ });
372
+ } catch (error) {
373
+ console.error("Error starting server:", error);
374
+ }
345
375
  }
346
- process.exit(0);
347
- });
348
-
349
- // create unsigned TLS cert
350
- async function getTls(appEnv) {
351
- let tlscreate =
352
- appEnv.TLS_CREATE == null
353
- ? "TLS_CREATE=C:US,ST:NC,L:Cary,O:SAS Institute,OU:STO,CN:localhost,ALT:na.sas.com"
354
- : appEnv.TLS_CREATE;
355
- let subjt = tlscreate.replaceAll('"', "").trim();
356
- let subj = subjt.split(",");
357
-
358
- let d = {};
359
- subj.map((c) => {
360
- let r = c.split(":");
361
- d[r[0]] = r[1];
362
- return { value: r[1] };
376
+ process.on("SIGTERM", () => {
377
+ console.error("Server closed");
378
+ if (appServer != null) {
379
+ appServer.close(() => { });
380
+ }
381
+ process.exit(0);
382
+ });
383
+ process.on("SIGINT", () => {
384
+ console.error("Server closed");
385
+ if (appServer != null) {
386
+ appServer.close(() => { });
387
+ }
388
+ process.exit(0);
363
389
  });
364
390
 
365
- let attr = [
366
- {
367
- name: "commonName",
368
- value: d.CN,
369
- },
370
- {
371
- name: "countryName",
372
- value: d.C,
373
- },
374
- {
375
- shortName: "ST",
376
- value: d.ST,
377
- },
378
- {
379
- name: "localityName",
380
- value: d.L,
381
- },
382
- {
383
- name: "organizationName",
384
- value: d.O,
385
- },
386
- {
387
- shortName: "OU",
388
- value: d.OU,
389
- },
390
- ];
391
-
392
- let pems = selfsigned.generate(attr);
393
- // selfsigned generates a new keypair
394
- let tls = {
395
- cert: pems.cert,
396
- key: pems.private,
397
- };
398
- console.error("Generated self-signed TLS certificate");
399
- return tls;
400
- }
391
+ // create unsigned TLS cert
392
+ async function getTls(appEnv) {
393
+ let tlscreate =
394
+ appEnv.TLS_CREATE == null
395
+ ? "TLS_CREATE=C:US,ST:NC,L:Cary,O:SAS Institute,OU:STO,CN:localhost,ALT:na.sas.com"
396
+ : appEnv.TLS_CREATE;
397
+ let subjt = tlscreate.replaceAll('"', "").trim();
398
+ let subj = subjt.split(",");
399
+
400
+ let d = {};
401
+ subj.map((c) => {
402
+ let r = c.split(":");
403
+ d[r[0]] = r[1];
404
+ return { value: r[1] };
405
+ });
406
+
407
+ let attr = [
408
+ {
409
+ name: "commonName",
410
+ value: d.CN,
411
+ },
412
+ {
413
+ name: "countryName",
414
+ value: d.C,
415
+ },
416
+ {
417
+ shortName: "ST",
418
+ value: d.ST,
419
+ },
420
+ {
421
+ name: "localityName",
422
+ value: d.L,
423
+ },
424
+ {
425
+ name: "organizationName",
426
+ value: d.O,
427
+ },
428
+ {
429
+ shortName: "OU",
430
+ value: d.OU,
431
+ },
432
+ ];
433
+
434
+ let pems = selfsigned.generate(attr);
435
+ // selfsigned generates a new keypair
436
+ let tls = {
437
+ cert: pems.cert,
438
+ key: pems.private,
439
+ };
440
+ console.error("Generated self-signed TLS certificate");
441
+ return tls;
442
+ }
401
443
  }
402
444
 
403
445
  export default expressMcpServer;