@sassoftware/sas-score-mcp-serverjs 1.0.1-9 → 1.1.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.
Files changed (118) hide show
  1. package/.skills/.claude-plugin/plugin.json +59 -0
  2. package/.skills/agents/sas-score-mcp-serverjs-agent.md +26 -0
  3. package/.skills/copilot-instructions.md +62 -0
  4. package/.skills/skills/README.md +204 -0
  5. package/.skills/skills/detail-strategy/SKILL.md +316 -0
  6. package/.skills/skills/find-library-server/SKILL.md +62 -0
  7. package/.skills/skills/find-resources/SKILL.md +66 -0
  8. package/.skills/skills/list-library/SKILL.md +30 -0
  9. package/.skills/skills/list-mas-job-jobdef/SKILL.md +31 -0
  10. package/.skills/skills/list-tables/SKILL.md +30 -0
  11. package/.skills/skills/read-strategy/SKILL.md +87 -0
  12. package/.skills/skills/request-routing/SKILL.md +112 -0
  13. package/.skills/skills/score-cas/SKILL.md +95 -0
  14. package/.skills/skills/score-job-jobdef/SKILL.md +58 -0
  15. package/.skills/skills/score-mas-scr/SKILL.md +58 -0
  16. package/.skills/skills/score-program/SKILL.md +59 -0
  17. package/.skills/skills/score-strategy/SKILL.md +39 -0
  18. package/README.md +96 -54
  19. package/cli.js +11 -13
  20. package/openApi.yaml +121 -121
  21. package/package.json +16 -14
  22. package/scripts/docs/SCORE_SKILL_REFERENCE.md +17 -16
  23. package/scripts/docs/TOOL_DESCRIPTION_TEMPLATE.md +3 -3
  24. package/scripts/docs/TOOL_UPDATES_SUMMARY.md +65 -63
  25. package/scripts/docs/oauth-http-transport.md +2 -2
  26. package/scripts/docs/sas-mcp-tools-reference.md +43 -32
  27. package/scripts/plot_msrp_usa.py +49 -0
  28. package/scripts/refreshtoken.js +58 -0
  29. package/scripts/runListScr.mjs +16 -0
  30. package/src/createMcpServer.js +4 -1
  31. package/src/expressMcpServer.js +47 -49
  32. package/src/oauthHandlers/authorize.js +4 -1
  33. package/src/oauthHandlers/baseUrl.js +4 -0
  34. package/src/oauthHandlers/callback.js +4 -0
  35. package/src/oauthHandlers/getMetadata.js +4 -0
  36. package/src/oauthHandlers/index.js +4 -0
  37. package/src/oauthHandlers/token.js +4 -0
  38. package/src/openApi.yaml +121 -121
  39. package/src/processHeaders.js +10 -7
  40. package/src/setupSkills.js +1 -18
  41. package/src/toolHelpers/_casScore.js +32 -0
  42. package/src/toolHelpers/_desc.js +14 -0
  43. package/src/toolHelpers/_findJob.js +12 -0
  44. package/src/toolHelpers/_findJobdef.js +10 -0
  45. package/src/toolHelpers/_findLibrary.js +11 -0
  46. package/src/toolHelpers/_findMas.js +13 -0
  47. package/src/toolHelpers/_findScr.js +36 -0
  48. package/src/toolHelpers/_findTable.js +11 -0
  49. package/src/toolHelpers/_listJobdefs.js +12 -2
  50. package/src/toolHelpers/_listJobs.js +19 -8
  51. package/src/toolHelpers/{_listModels.js → _listMas.js} +4 -4
  52. package/src/toolHelpers/_listScr.js +13 -0
  53. package/src/toolHelpers/{_scrInfo.js → _scrDescribe.js} +4 -4
  54. package/src/toolHelpers/_scrScore.js +2 -2
  55. package/src/toolHelpers/_submitCasl.js +19 -17
  56. package/src/toolHelpers/{_tableInfo.js → _tableDescribe.js} +2 -2
  57. package/src/toolHelpers/getLogonPayload.js +2 -2
  58. package/src/toolSet/casModelScore.js +93 -0
  59. package/src/toolSet/casProgramScore.js +105 -0
  60. package/src/toolSet/devaScore.js +11 -6
  61. package/src/toolSet/findJob.js +74 -59
  62. package/src/toolSet/findJobdef.js +67 -64
  63. package/src/toolSet/findLibrary.js +28 -23
  64. package/src/toolSet/findMas.js +72 -0
  65. package/src/toolSet/findScr.js +69 -0
  66. package/src/toolSet/findTable.js +34 -27
  67. package/src/toolSet/getEnv.js +57 -57
  68. package/src/toolSet/jobDescribe.js +65 -0
  69. package/src/toolSet/jobScore.js +90 -0
  70. package/src/toolSet/jobdefDescribe.js +67 -0
  71. package/src/toolSet/jobdefScore.js +85 -0
  72. package/src/toolSet/listJobdefs.js +17 -8
  73. package/src/toolSet/listJobs.js +15 -8
  74. package/src/toolSet/listLibraries.js +16 -10
  75. package/src/toolSet/listMas.js +71 -0
  76. package/src/toolSet/listScr.js +62 -0
  77. package/src/toolSet/listTables.js +78 -66
  78. package/src/toolSet/{runMacro.js → macroScore.js} +86 -82
  79. package/src/toolSet/makeTools.js +39 -25
  80. package/src/toolSet/masDescribe.js +67 -0
  81. package/src/toolSet/masScore.js +95 -0
  82. package/src/toolSet/{runProgram.js → programScore.js} +96 -93
  83. package/src/toolSet/readTable.js +43 -26
  84. package/src/toolSet/sasQuery.js +24 -18
  85. package/src/toolSet/scrDescribe.js +55 -0
  86. package/src/toolSet/scrScore.js +63 -70
  87. package/src/toolSet/searchAssets.js +1 -1
  88. package/src/toolSet/setContext.js +8 -3
  89. package/src/toolSet/superstat.js +61 -61
  90. package/src/toolSet/tableDescribe.js +65 -0
  91. package/.agents/sas-score-mcp-serverjs-agent.md +0 -58
  92. package/.instructions/copilot-instructions.md +0 -201
  93. package/.instructions/enforce-find-resource-strategy.md +0 -35
  94. package/.skills/sas-find-library-smart/SKILL.md +0 -155
  95. package/.skills/sas-find-resource-strategy/SKILL.md +0 -105
  96. package/.skills/sas-list-resource-strategy/SKILL.md +0 -124
  97. package/.skills/sas-list-tables-smart/SKILL.md +0 -128
  98. package/.skills/sas-read-and-score-strategy/SKILL.md +0 -113
  99. package/.skills/sas-read-strategy/SKILL.md +0 -154
  100. package/.skills/sas-request-classifier/SKILL.md +0 -74
  101. package/.skills/sas-score-workflow-strategy/SKILL.md +0 -314
  102. package/scripts/optimize_final.py +0 -140
  103. package/scripts/optimize_tools.py +0 -99
  104. package/scripts/setup-skills.js +0 -34
  105. package/scripts/update_descriptions.py +0 -46
  106. package/src/authpkce.js +0 -219
  107. package/src/handleGetDelete.js +0 -34
  108. package/src/handleRequest.js +0 -112
  109. package/src/hapiMcpServer.js +0 -241
  110. package/src/toolSet/findModel.js +0 -60
  111. package/src/toolSet/listModels.js +0 -56
  112. package/src/toolSet/modelInfo.js +0 -55
  113. package/src/toolSet/modelScore.js +0 -89
  114. package/src/toolSet/runCasProgram.js +0 -98
  115. package/src/toolSet/runJob.js +0 -81
  116. package/src/toolSet/runJobdef.js +0 -82
  117. package/src/toolSet/scrInfo.js +0 -52
  118. package/src/toolSet/tableInfo.js +0 -58
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env python3
2
- import re
3
- import os
4
-
5
- os.chdir('c:/dev/github/sas-score-mcp-serverjs')
6
-
7
- # Update runJob.js
8
- file_path = 'src/toolSet/runJob.js'
9
- with open(file_path, 'r', encoding='utf-8') as f:
10
- content = f.read()
11
-
12
- new_desc = """run-job — execute a deployed SAS Viya job.
13
-
14
- USE when: run job, execute job, run with parameters
15
- DO NOT USE for: arbitrary SAS code (use run-sas-program), macros (use run-macro), list/find jobs
16
-
17
- PARAMETERS
18
- - name: string — job name (required)
19
- - scenario: string | object — input parameters. Accepts: "x=1, y=2" or {x:1, y:2}
20
-
21
- ROUTING RULES
22
- - "run job xyz" → { name: "xyz" }
23
- - "run job xyz with param1=10, param2=val2" → { name: "xyz", scenario: {param1:10, param2:"val2"} }
24
-
25
- EXAMPLES
26
- - "run job xyz" → { name: "xyz" }
27
- - "run job monthly_etl with month=10, year=2025" → { name: "monthly_etl", scenario: {month:10, year:2025} }
28
-
29
- NEGATIVE EXAMPLES (do not route here)
30
- - "run SAS code" (use run-sas-program)
31
- - "run macro X" (use run-macro)
32
- - "list jobs" (use list-jobs)
33
- - "find job X" (use find-job)
34
-
35
- ERRORS
36
- Returns log output, listings, tables from job. Error if job not found."""
37
-
38
- # Use a more flexible pattern
39
- pattern = r'let description = `\n## run-job.*?`;\n'
40
- replacement = f'let description = `\n{new_desc}\n`;\n'
41
- updated = re.sub(pattern, replacement, content, flags=re.DOTALL)
42
-
43
- with open(file_path, 'w', encoding='utf-8') as f:
44
- f.write(updated)
45
-
46
- print(f"✓ Updated {file_path}")
package/src/authpkce.js DELETED
@@ -1,219 +0,0 @@
1
- /**
2
- * SAS Viya PKCE Authorization Flow
3
- *
4
- * Uses the Authorization Code flow with PKCE (RFC 7636).
5
- * Starts a local HTTP server to capture the redirect callback.
6
- *
7
- * Usage:
8
- * node sas-viya-pkce-auth.js
9
- *
10
- * Prerequisites:
11
- * - A SAS Viya client registered with:
12
- * grant_types: authorization_code
13
- * redirect_uri: http://localhost:3000/callback
14
- * PKCE enabled (no client_secret required)
15
- */
16
-
17
- import http from "http";
18
- import https from "https";
19
- import crypto from "crypto";
20
- import url from "url";
21
-
22
- let VIYA_SERVER=process.env.VIYA_SERVER;
23
- let PORT=8080;
24
- let CLIENTID='pkcemcp';
25
- // ── Configuration ────────────────────────────────────────────────────────────
26
-
27
- const CONFIG = {
28
- authBaseUrl: `${VIYA_SERVER}/oauth/SASLogon`,
29
- clientId: CLIENTID, // <-- replace with your client ID
30
- redirectUri: `http://localhost:${PORT}/callback`,
31
- scopes: "openid profile",
32
- localPort: PORT,
33
- };
34
-
35
- main().catch((err) => {
36
- console.error("Unexpected error:", err);
37
- process.exit(1);
38
- });
39
-
40
- // ─────────────────────────────────────────────────────────────────────────────
41
-
42
- // Generate a cryptographically random code verifier (43–128 chars, base64url)
43
- function generateCodeVerifier() {
44
- return crypto.randomBytes(64).toString("base64url");
45
- }
46
-
47
- // Derive the code challenge: BASE64URL(SHA-256(verifier))
48
- function generateCodeChallenge(verifier) {
49
- return crypto.createHash("sha256").update(verifier).digest("base64url");
50
- }
51
-
52
- // Build the SASLogon authorization URL
53
- function buildAuthUrl(codeChallenge, state) {
54
- const params = new URLSearchParams({
55
- response_type: "code",
56
- client_id: CONFIG.clientId,
57
- redirect_uri: CONFIG.redirectUri,
58
- scope: CONFIG.scopes,
59
- state,
60
- code_challenge: codeChallenge,
61
- code_challenge_method: "S256",
62
- });
63
- return `${CONFIG.authBaseUrl}/authorize?${params}`;
64
- }
65
-
66
- // Exchange the authorization code for tokens
67
- function exchangeCodeForTokens(code, codeVerifier) {
68
- return new Promise((resolve, reject) => {
69
- const body = new URLSearchParams({
70
- grant_type: "authorization_code",
71
- client_id: CONFIG.clientId,
72
- redirect_uri: CONFIG.redirectUri,
73
- code,
74
- code_verifier: codeVerifier,
75
- }).toString();
76
-
77
- const tokenUrl = new URL(`${CONFIG.authBaseUrl}/token`);
78
-
79
- const options = {
80
- hostname: tokenUrl.hostname,
81
- port: tokenUrl.port || 443,
82
- path: tokenUrl.pathname,
83
- method: "POST",
84
- headers: {
85
- "Content-Type": "application/x-www-form-urlencoded",
86
- "Content-Length": Buffer.byteLength(body),
87
- },
88
- // Remove the line below if your Viya instance has a valid TLS cert
89
- rejectUnauthorized: false,
90
- };
91
-
92
- const req = https.request(options, (res) => {
93
- let data = "";
94
- res.on("data", (chunk) => (data += chunk));
95
- res.on("end", () => {
96
- try {
97
- const parsed = JSON.parse(data);
98
- if (res.statusCode >= 400) {
99
- reject(new Error(`Token error (${res.statusCode}): ${data}`));
100
- } else {
101
- resolve(parsed);
102
- }
103
- } catch {
104
- reject(new Error(`Failed to parse token response: ${data}`));
105
- }
106
- });
107
- });
108
-
109
- req.on("error", reject);
110
- req.write(body);
111
- req.end();
112
- });
113
- }
114
-
115
- // Wait for the browser redirect and extract code + state
116
- function waitForCallback(expectedState) {
117
- return new Promise((resolve, reject) => {
118
- const server = http.createServer((req, res) => {
119
- const parsed = url.parse(req.url, true);
120
-
121
- if (parsed.pathname !== "/callback") {
122
- res.writeHead(404);
123
- res.end("Not found");
124
- return;
125
- }
126
-
127
- const { code, state, error, error_description } = parsed.query;
128
-
129
- res.writeHead(200, { "Content-Type": "text/html" });
130
- res.end(`
131
- <html><body>
132
- <h2>${error ? "Authorization failed" : "Authorization successful!"}</h2>
133
- <p>You may close this tab.</p>
134
- </body></html>
135
- `);
136
-
137
- server.close();
138
-
139
- if (error) {
140
- reject(new Error(`OAuth error: ${error} — ${error_description}`));
141
- return;
142
- }
143
- if (state !== expectedState) {
144
- reject(new Error("State mismatch — possible CSRF attack"));
145
- return;
146
- }
147
-
148
- resolve(code);
149
- });
150
-
151
- server.listen(CONFIG.localPort, () => {
152
- console.error(`Listening on http://localhost:${CONFIG.localPort}/callback`);
153
- });
154
-
155
- server.on("error", reject);
156
- });
157
- }
158
-
159
- // Main flow
160
- async function main() {
161
- const codeVerifier = generateCodeVerifier();
162
- const codeChallenge = generateCodeChallenge(codeVerifier);
163
- const state = crypto.randomBytes(16).toString("hex");
164
-
165
- const authUrl = buildAuthUrl(codeChallenge, state);
166
-
167
- console.error("\nOpen this URL in your browser to log in:\n");
168
- console.error(authUrl);
169
- console.error();
170
-
171
- // Try to auto-open in the default browser (best-effort)
172
- try {
173
- const { exec } = require("child_process");
174
- const cmd =
175
- process.platform === "win32"
176
- ? `start "" "${authUrl}"`
177
- : process.platform === "darwin"
178
- ? `open "${authUrl}"`
179
- : `xdg-open "${authUrl}"`;
180
- exec(cmd);
181
- } catch {
182
- // ignore — user can open manually
183
- }
184
-
185
- let code;
186
- try {
187
- code = await waitForCallback(state);
188
- } catch (err) {
189
- console.error("Callback error:", err.message);
190
- return null;
191
- }
192
-
193
- console.error("Authorization code received. Exchanging for tokens...");
194
-
195
- let tokens;
196
- try {
197
- tokens = await exchangeCodeForTokens(code, codeVerifier);
198
- } catch (err) {
199
- console.error("Token exchange error:", err.message);
200
- return null;
201
- }
202
-
203
- console.error("\nTokens received:");
204
- console.error(" access_token :", tokens.access_token?.slice(0, 40) + "...");
205
- console.error(" token_type :", tokens.token_type);
206
- console.error(" expires_in :", tokens.expires_in, "seconds");
207
- if (tokens.refresh_token) {
208
- console.error(" refresh_token:", tokens.refresh_token.slice(0, 20) + "...");
209
- }
210
- if (tokens.id_token) {
211
- console.error(" id_token :", tokens.id_token.slice(0, 40) + "...");
212
- }
213
-
214
- // tokens.access_token is ready to use as a Bearer token for Viya REST APIs
215
- return tokens.access_token
216
- }
217
-
218
-
219
-
@@ -1,34 +0,0 @@
1
- /*
2
- * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- async function handleGetDelete(mcpServer, cache, req, h) {
7
- const sessionId = req.headers["mcp-session-id"];
8
- console.error("=======================================================");
9
- console.error(`[Note] Handling ${req.method} for session ID:`, sessionId);
10
- let transports = cache.get("transports");
11
- let transport = transports[sessionId];
12
- if (!sessionId || transport == null) {
13
- console.error('[Note] Looks like a fresh start - no session id or transport found');
14
- return h.abandon;
15
- }
16
-
17
- if (req.method === "GET") {
18
- // You can customize the response as needed
19
- console.error("[Note] Payload:", req.payload);
20
- console.error("======================================================");
21
- await transport.handleRequest(req.raw.req, req.raw.res, req.payload);
22
- return h.abandon;
23
- }
24
-
25
- if (req.method === "DELETE") {
26
- console.error("[Note] Deleting transport and cache for session ID:", sessionId);
27
- delete transports[sessionId];
28
- cache.del(sessionId);
29
- return h.response(`[Info] In DELETE: Session ID ${sessionId} deleted`).code(201);
30
- }
31
-
32
-
33
- }
34
- export default handleGetDelete;
@@ -1,112 +0,0 @@
1
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2
- import { randomUUID } from "node:crypto";
3
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
4
-
5
- async function handleRequest(mcpServer, cache, req, h, credentials) {
6
- let headerCache = {};
7
- let transport;
8
- let transports = cache.get("transports");
9
- try {
10
-
11
- headerCache = customHeaders(req, h);
12
- let sessionId = req.headers["mcp-session-id"];
13
-
14
- // we have session id, get existing transport
15
-
16
- if (sessionId != null) {
17
- /* existing transport */
18
- transport = transports[sessionId];
19
- if (transport == null) {
20
- h.response({ isError: true, content: [{ type: 'text', text: 'Session not found. Please re-initialize the MCP client.' }] }).code(400).type('application/json');
21
- return h.abandon;
22
- }
23
- }
24
-
25
- if (sessionId != null && transport != null) {
26
- // post the curren session - used to pass _appContext to tools
27
- cache.set("currentId", sessionId);
28
-
29
- // get app context for session
30
- let _appContext = cache.get(sessionId);
31
-
32
- //if first prompt on a sessionid, create app context
33
-
34
- if (_appContext == null) {
35
- console.error("[Note] Creating new app context for session ID:", sessionId);
36
- let appEnvTemplate = cache.get("appEnvTemplate");
37
- _appContext = Object.assign({}, appEnvTemplate, headerCache);
38
- if (headerCache.AUTHFLOW === 'bearer') {
39
- _appContext.contexts.AUTHFLOW = 'bearer';
40
- _appContext.contexts.bearerToken = headerCache.bearerToken;
41
- }
42
- _appContext.contexts.oauthInfo = credentials;
43
- cache.set(sessionId, _appContext);
44
- }
45
- console.error("[Note] Using existing transport for session ID:", sessionId);
46
- return await transport.handleRequest(req.raw.req, req.raw.res, req.payload);
47
- }
48
-
49
- // initialize request
50
- else if (!sessionId && isInitializeRequest(req.payload)) {
51
- // create transport
52
- console.error("[Note] Initializing new transport for MCP request...");
53
- transport = new StreamableHTTPServerTransport({
54
- sessionIdGenerator: () => randomUUID(),
55
- enableJsonResponse: true,
56
- onsessioninitialized: (sessionId) => {
57
- // Store the transport by session ID
58
- transports[sessionId] = transport;
59
- },
60
- });
61
- // Clean up transport when closed
62
- transport.onclose = () => {
63
- if (transport.sessionId) {
64
- delete transports[transport.sessionId];
65
- }
66
- };
67
- console.error("[Note] Connecting mcpServer to new transport...");
68
- await mcpServer.connect(transport);
69
-
70
- // Save transport data and app context for use in tools
71
- cache.set("transports", transports);
72
- return await transport.handleRequest(req.raw.req, req.raw.res, req.payload);
73
- // cache transport
74
-
75
-
76
- }
77
- }
78
- catch (error) {
79
- console.error("Error handling MCP request:", error);
80
- let r = { isError: true, content: [{ type: 'text', text: 'Internal server error occurred while processing the request.' }] };
81
- return h.response(r).code(500).type('application/json');
82
- }
83
- function customHeaders(req, h) {
84
-
85
- // process any new header information
86
-
87
- // Allow different VIYA server per sessionid(user)
88
- let headerCache = {};
89
- if (req.headers["X-VIYA-SERVER"] != null) {
90
- console.error("[Note] Using user supplied VIYA server");
91
- headerCache.VIYA_SERVER = req.header("X-VIYA-SERVER");
92
- }
93
-
94
- // used when doing autorization via mcp client
95
- // ideal for production use
96
- const hdr = req.headers["Authorization"];
97
- if (hdr != null) {
98
- headerCache.bearerToken = hdr.slice(7);
99
- headerCache.AUTHFLOW = "bearer";
100
- }
101
-
102
- // faking out api key since Viya does not support
103
- // not ideal for production
104
- const hdr2 = req.headers["X-REFRESH-TOKEN"];
105
- if (hdr2 != null) {
106
- headerCache.refreshToken = hdr2;
107
- headerCache.AUTHFLOW = "refresh";
108
- }
109
- return headerCache;
110
- }
111
- };
112
- export default handleRequest;
@@ -1,241 +0,0 @@
1
- /*
2
- * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
- import appServer from "@sassoftware/viya-serverjs";
6
- import handleRequest from "./handleRequest.js";
7
- import handleGetDelete from "./handleGetDelete.js";
8
- import urlOpen from "./urlOpen.js";
9
- import fs from "fs";
10
-
11
- async function hapiMcpServer(mcpServer, cache, baseAppEnvContext) {
12
-
13
- console.error('Starting Hapi MCP server...');
14
- console.error("[Note]: Hapi MCP server started...", baseAppEnvContext.AUTHFLOW);
15
- process.env.REDIRECT = `/status`;
16
- process.env.APPHOST = '0.0.0.0';
17
- let r = await appServer.asyncCore(mcpHandlers, true, 'app', null);
18
- console.error('Hapi server running result:', r);
19
- if (baseAppEnvContext.AUTHFLOW === 'code' && baseAppEnvContext.AUTOLOGON === 'TRUE') {
20
- await urlOpen(r);
21
- }
22
- return r;
23
-
24
- // add MCP handlers to the app server
25
-
26
- function mcpHandlers() {
27
- function getHtml() {
28
- return `
29
- <!DOCTYPE html>
30
- <html lang="en">
31
- <head>
32
- <meta charset="UTF-8">
33
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
34
- <title>sas-score-mcp-server</title>
35
- <style>
36
- * {
37
- margin: 0;
38
- padding: 0;
39
- box-sizing: border-box;
40
- }
41
-
42
- body {
43
- font-family: Arial, sans-serif;
44
- height: 100vh;
45
- overflow: hidden;
46
- }
47
-
48
- dialog {
49
- position: fixed;
50
- left: 50%;
51
- right: auto;
52
- top: 0;
53
- transform: translateX(-50%);
54
- width: fit-content;
55
- height: fit-content;
56
- border: none;
57
- border-radius: 4px;
58
- padding: 16px;
59
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
60
- }
61
-
62
- dialog::backdrop {
63
- background-color: rgba(0, 0, 0, 0.1);
64
- }
65
-
66
- dialog h2 {
67
- font-size: 18px;
68
- margin-bottom: 12px;
69
- color: #000;
70
- }
71
-
72
- dialog p {
73
- font-family: 'Courier New', monospace;
74
- font-size: 12px;
75
- width: fit-content;
76
- line-height: 1.6;
77
- color: #333;
78
- word-wrap: break-word;
79
- white-space: pre-wrap;
80
- }
81
-
82
- /* Window styling to show it's 10px larger than dialog */
83
- body::before {
84
- display: none;
85
- }
86
- </style>
87
- </head>
88
- <body>
89
- <dialog open>
90
- <h2>sas-score-mcp-server</h2>
91
- <p>The mcp server is now ready for use. </p>
92
- <p>You can close this window</p>
93
- <p>For information on the tools see this documentation link:</p>
94
- <a href="https://github.com/sassoftware/sas-score-mcp-serverjs/wiki " target="_blank">Summary of tools </a>
95
- </p>
96
- </dialog>
97
- </body>
98
- </html>
99
- `;
100
-
101
- }
102
- let routes = [
103
- {
104
- method: ["GET"],
105
- path: "/health",
106
- options: {
107
- handler: async (req, h) => {
108
- let health = {
109
- name: "@sassoftware/mcp-server",
110
- version: baseAppEnvContext.version,
111
- description: "SAS Viya Sample MCP Server",
112
- endpoints: {
113
- mcp: "/mcp",
114
- health: "/health",
115
- apiMeta: "/apiMeta"
116
- },
117
- usage:
118
- "Use with MCP Inspector or compatible MCP clients like vscode or your own MCP client",
119
- };
120
- console.error("Health check requested, returning:", health);
121
- return h.response(health).code(200).type('application/json');
122
- },
123
- auth: false,
124
- description: "Help",
125
- notes: "Help",
126
- tags: ["mcp"],
127
- }
128
- },
129
- {
130
- method: ["GET"],
131
- path: "/ready",
132
- options: {
133
- handler: async (req, h) => {
134
- let status = {status: 1};
135
- console.error("Ready check requested, returning:", status);
136
- return h.response(status).code(200).type('application/json');
137
- },
138
- auth: false,
139
- description: "probe readiness of the server",
140
- notes: "Help",
141
- tags: ["mcp"],
142
- }
143
- },
144
- {
145
- method: ["GET"],
146
- path: "/StartUp",
147
- options: {
148
- handler: async (req, h) => {
149
- let status = { status: 1 };
150
- console.error("Startup check requested, returning:", status);
151
- return h.response(status).code(200).type('application/json');
152
- },
153
- auth: false,
154
- description: "probe startup of the server",
155
- notes: "Help",
156
- tags: ["mcp"],
157
- }
158
- },
159
- {
160
- method: ["GET"],
161
- path: "/apiMeta",
162
- options: {
163
- handler: async (req, h) => {
164
- let spec = openAPIJson();
165
- return h.response(spec).code(200).type('application/json');
166
- },
167
- auth: false,
168
- description: "API Metadata"
169
- }
170
- },
171
- {
172
- method: ["GET"],
173
- path: "/openapi.json",
174
- options: {
175
- handler: async (req, h) => {
176
- let spec = openAPIJson();
177
- return h.response(spec).code(200).type('application/json');
178
- },
179
- auth: false,
180
- description: "API Metadata"
181
- }
182
- },
183
- {
184
- method: ["GET"],
185
- path: `/${baseAppEnvContext.contexts.APPNAME}/status`,
186
- options: {
187
- handler: async (req, h) => {
188
- let ht = getHtml();
189
- return h.response(ht).code(200).type('text/html');
190
- // return h.abandon;
191
- },
192
- auth: false,
193
- description: "Help",
194
- notes: "Help",
195
- tags: ["mcp"],
196
- }
197
- },
198
- {
199
- method: ["POST"],
200
- path: `/mcp`,
201
- options: {
202
- handler: async (req, h) => {
203
- let precontext = req.pre.context;
204
- let oauthInfo = (precontext != null) ? precontext.credentials : null;
205
- await handleRequest(mcpServer, cache, req, h, oauthInfo);
206
- return h.abandon;
207
- },
208
-
209
- auth: {
210
- strategy: "sas",
211
- mode: 'required'
212
- },
213
- description: "The main route for MCP requests",
214
- notes: "Requires a valid session",
215
- tags: ["mcp"],
216
- },
217
- },
218
- {
219
- method: ["GET", "DELETE"],
220
- path: `/mcp`,
221
-
222
- options: {
223
- handler: async (req, h) => {
224
- await handleGetDelete(mcpServer, cache, req, h);
225
- return h.abandon;
226
- },
227
- auth: {
228
- strategy: "session",
229
- mode: 'try'
230
- },
231
- description: "Handle GET and DELETE requests",
232
- notes: "Will fail if no valid session",
233
- tags: ["mcp"],
234
- },
235
- }
236
-
237
- ];
238
- return routes;
239
- }
240
- }
241
- export default hapiMcpServer;