@sassoftware/sas-score-mcp-serverjs 1.0.1-9 → 1.1.2

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 +66 -61
  61. package/src/toolSet/findJob.js +24 -9
  62. package/src/toolSet/findJobdef.js +22 -19
  63. package/src/toolSet/findLibrary.js +73 -68
  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 +6 -6
  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 +70 -61
  73. package/src/toolSet/listJobs.js +68 -61
  74. package/src/toolSet/listLibraries.js +84 -78
  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} +21 -18
  83. package/src/toolSet/readTable.js +80 -63
  84. package/src/toolSet/sasQuery.js +83 -77
  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 +70 -65
  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
@@ -0,0 +1,58 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { Agent, fetch } from 'undici';
6
+ import fs from "fs";
7
+
8
+ refreshToken()
9
+ .then (token => {
10
+ console.log(token);
11
+ fs.writeFileSync('token.txt', `"AUTHORIZATION": "Bearer ${token}"`, 'utf8');
12
+ })
13
+ .catch (err => {
14
+ console.error('[Error] Failed to refresh token: ', err);
15
+ });
16
+ async function refreshToken(){
17
+ let host = process.env.VIYA_SERVER;
18
+ let token = process.env.REFRESH_TOKEN;
19
+ let url = `${host}/SASLogon/oauth/token`;
20
+
21
+ let aconnect = {
22
+ rejectUnauthorized: false // or false, if you really want to bypass checks
23
+ }
24
+
25
+ const agent = new Agent(aconnect);
26
+
27
+ console.error('[Info] Refreshing token...', token);
28
+ const ibody = {
29
+ grant_type: 'refresh_token',
30
+ refresh_token: token,
31
+ client_id: 'sas.cli'
32
+ };
33
+
34
+ let body = new URLSearchParams(ibody);
35
+ try {
36
+ const response = await fetch(url, {
37
+ method: 'POST',
38
+ headers: {
39
+ 'Accept': 'application/json',
40
+ 'Content-Type': 'application/x-www-form-urlencoded',
41
+ dispatcher: agent
42
+ },
43
+ body: body.toString()
44
+ });
45
+
46
+ if (!response.ok) {
47
+ const error = await response.text();
48
+ console.error('[Error] Failed to refresh token: ', error);
49
+ throw new Error(error);
50
+ }
51
+
52
+ const data = await response.json();
53
+ return data.access_token;
54
+ } catch (err) {
55
+ console.error('[Error] Failed to refresh token: ', err);
56
+ throw err;
57
+ }
58
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import listScr from '../src/toolSet/listScr.js';
3
+
4
+ async function run() {
5
+ const appContext = { brand: 'sas-score' };
6
+ const spec = listScr(appContext);
7
+ try {
8
+ const res = await spec.handler({});
9
+ console.log(JSON.stringify(res, null, 2));
10
+ } catch (err) {
11
+ console.error('Error running listScr:', err);
12
+ process.exit(1);
13
+ }
14
+ }
15
+
16
+ run();
@@ -78,6 +78,7 @@ async function createMcpServer(cache, _appContext) {
78
78
  // Register the tools with brand prefix
79
79
  console.error(`[Note] Brand: ${_appContext.brand}`);
80
80
  let toolNames = [];
81
+ let totalDescription = 0;
81
82
  toolSet.forEach((tool, i) => {
82
83
  let toolName = _appContext.brand + '-' + tool.name;
83
84
  //tool.inputSchema.additionalProperties = false; // disallow extra properties
@@ -85,12 +86,14 @@ async function createMcpServer(cache, _appContext) {
85
86
  description: tool.description,
86
87
  inputSchema: tool.inputSchema
87
88
  }
89
+ totalDescription += tool.description.length;``
88
90
  let toolHandler = wrapf(cache, tool.handler);
89
- // console.error(`[Note] Registering tool ${toolName} with config: ${JSON.stringify(config)}`);
90
91
  let r = mcpServer.registerTool(toolName, config, toolHandler);
91
92
  toolNames.push(toolName);
92
93
  });
94
+ console.error(`[Note] Agent Mode: ${_appContext.agent}`);
93
95
  console.error(`[Note] Registered ${toolSet.length} tools: ${toolNames}`);
96
+ console.error(`[Note] Total description length: ${totalDescription} characters`);
94
97
  cache.set("mcpServer", mcpServer);
95
98
  return mcpServer;
96
99
  }
@@ -9,6 +9,7 @@ import cors from "cors";
9
9
  import bodyParser from "body-parser";
10
10
  import selfsigned from "selfsigned";
11
11
  import openAPIJson from "./openAPIJson.js";
12
+ import createMcpServer from "./createMcpServer.js";
12
13
 
13
14
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
14
15
  import { randomUUID } from "node:crypto";
@@ -21,7 +22,7 @@ import processHeaders from "./processHeaders.js";
21
22
 
22
23
  // setup express server
23
24
 
24
- async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
25
+ async function expressMcpServer(_mcpServer, cache, baseAppEnvContext) {
25
26
  // setup for change to persistence session
26
27
  cache.del("headerCache");
27
28
  const app = express();
@@ -50,6 +51,10 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
50
51
  const pkceStore = new Map(); // ourState -> { codeVerifier, clientRedirectUri, clientState }
51
52
  const codeStore = new Map(); // ourCode -> { access_token, refresh_token, expires_in }
52
53
 
54
+ // Per-session transports — each initialize creates its own transport
55
+ const transports = new Map(); // sessionId -> transport
56
+ cache.set("transports", transports);
57
+
53
58
  app.get('/.well-known/oauth-protected-resource', (req, res) => {
54
59
  let payload = {
55
60
  resource: `${baseAppEnvContext.mcpHost}/mcp`,
@@ -152,67 +157,54 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
152
157
 
153
158
  // process mcp endpoint requests
154
159
  const handleRequest = async (req, res) => {
155
- let transport = null;
156
- let transports = cache.get("transports");
157
160
  console.error("=========================================================");
158
161
  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
- }
164
-
165
- console.error("current transports in cache:", Object.keys(transports));
162
+ console.error("current active sessions:", transports.size);
166
163
  try {
167
164
 
168
165
  let sessionId = req.headers["mcp-session-id"];
169
166
  console.error("[Note]Incoming session ID:", sessionId);
170
167
  let body = (req.body == null) ? 'no body' : JSON.stringify(req.body);
171
168
  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({
169
+
170
+ if (isInitializeRequest(req.body)) {
171
+ console.error("[Note]>>>>>>>>>>>>>>>>>>>>>>>>> Creating new MCP server");
172
+ let mcpServer = await createMcpServer(cache, baseAppEnvContext);
173
+ // New session — create a dedicated transport
174
+ console.error("[Note] Initializing new session with fresh transport...");
175
+ const isInitRequest = req.body?.method === 'initialize';
176
+
177
+ const transport = new StreamableHTTPServerTransport({
177
178
  sessionIdGenerator: () => randomUUID(),
178
179
  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;
180
+ onsessioninitialized: (newSessionId) => {
181
+ console.error("[Note] Session initialized with ID:", newSessionId);
182
+ transports.set(newSessionId, transport);
183
+ cache.set("transports", transports);
184
+ console.error("[Note] Total active sessions:", Object.keys(transports));
185
185
  },
186
186
  });
187
- // Clean up transport when closed
188
187
  transport.onclose = () => {
189
- if (transport.sessionId && transports[transport.sessionId]) {
190
- delete transports[transport.sessionId];
188
+ if (transport.sessionId) {
189
+ console.error("[Note] Session closed, removing transport:", transport.sessionId);
190
+ transports.delete(transport.sessionId);
191
+ cache.del(transport.sessionId);
191
192
  }
192
193
  };
193
- console.error("[Note] Connecting mcpServer to new transport...");
194
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
195
  console.error("=======================================================");
200
196
  return await transport.handleRequest(req, res, req.body);
201
197
 
202
- // cache transport
203
-
204
198
  } 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;
199
+ const transport = transports.get(sessionId);
200
+ if (!transport) {
201
+ console.error("[Note] Unknown session ID:", sessionId);
202
+ return res.status(404).json({ jsonrpc: "2.0", error: { code: -32000, message: "Session not found. Please re-initialize." }, id: null });
213
203
  }
204
+ console.error('[Note] Incoming session ID:', sessionId);
205
+ console.error("[Note] Using transport for session ID:", sessionId);
214
206
 
215
- // post the curren session - used to pass _appContext to tools
207
+ // post the current session - used to pass _appContext to tools
216
208
  cache.set("currentId", sessionId);
217
209
 
218
210
  // get app context for session
@@ -230,7 +222,6 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
230
222
  _appContext = Object.assign(_appContext, headerCache);
231
223
  cache.set(sessionId, _appContext);
232
224
  }
233
- console.error("[Note] Using existing transport for session ID:", sessionId);
234
225
  console.error("==========================================================");
235
226
  await transport.handleRequest(req, res, req.body);
236
227
  return;
@@ -260,21 +251,28 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
260
251
  const sessionId = req.headers["mcp-session-id"];
261
252
  console.error("[Note] SessionId:", sessionId);
262
253
 
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;
254
+ if (!sessionId) {
255
+ console.error("[Note] No session ID on /DELETE rejecting");
256
+ return res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: Mcp-Session-Id header is required" }, id: null });
269
257
  }
258
+
259
+ const transport = transports.get(sessionId);
260
+ if (!transport) {
261
+ console.error("[Note] Unknown session ID:", sessionId);
262
+ return res.status(404).json({ jsonrpc: "2.0", error: { code: -32000, message: "Session not found. Please re-initialize." }, id: null });
263
+ }
264
+
270
265
  if (req.method === "GET") {
266
+ console.error("[Note] calling transport.handleRequest for GET /mcp");
271
267
  await transport.handleRequest(req, res);
272
268
  return;
273
269
  }
274
- if (req.method === "DELETE" && sessionId != null) {
270
+ if (req.method === "DELETE") {
275
271
  console.error("[Note] Deleting transport and cache for session ID:", sessionId);
276
- delete transports[sessionId];
272
+ transports.delete(sessionId);
277
273
  cache.del(sessionId);
274
+ console.error("[Note] Deleted session ID:", sessionId);
275
+ console.error("[Note] Total active sessions:", Object.keys(transports));
278
276
  res.status(201).send(`[Info] Deleted session ${sessionId}`);
279
277
  }
280
278
  }
@@ -1,4 +1,7 @@
1
- //authorize
1
+ /*
2
+ * Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
2
5
  import { randomBytes, createHash } from "node:crypto";
3
6
  import baseUrl from "./baseUrl.js";
4
7
 
@@ -1,3 +1,7 @@
1
+ /*
2
+ * Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
1
5
  function baseUrl(appContext) {
2
6
  const protocol = appContext.HTTPS === "TRUE" ? "https" : "http";
3
7
  //const host = "localhost";
@@ -1,3 +1,7 @@
1
+ /*
2
+ * Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
1
5
  import baseUrl from "./baseUrl.js";
2
6
  import { Agent, fetch as undiciFetch } from "undici";
3
7
  import { randomUUID } from "node:crypto";
@@ -1,3 +1,7 @@
1
+ /*
2
+ * Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
1
5
  import baseUrl from "./baseUrl.js";
2
6
  function getMetadata(req, res, appEnvContext) {
3
7
  let base = '';
@@ -1,3 +1,7 @@
1
+ /*
2
+ * Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
1
5
  import authorize from "./authorize.js";
2
6
  import callback from "./callback.js";
3
7
  import token from "./token.js";
@@ -1,3 +1,7 @@
1
+ /*
2
+ * Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
1
5
  function token(req, res, appContext, codeStore, cache) {
2
6
  console.error("===============================================================");
3
7
  console.error("[Note] at /token endpoint");
package/src/openApi.yaml CHANGED
@@ -1,121 +1,121 @@
1
- swagger: "2.0"
2
- info:
3
- title: SAS Viya Sample MCP Server API
4
- version: "1.0.0"
5
- description: API for interacting with the SAS Viya Sample MCP Server.
6
- host: localhost:8080
7
- basePath: /
8
- schemes:
9
- - http
10
- - https
11
- consumes:
12
- - application/json
13
- produces:
14
- - application/json
15
- paths:
16
- /health:
17
- get:
18
- summary: Health check
19
- description: Returns health and version information.
20
- responses:
21
- 200:
22
- description: Health information
23
- schema:
24
- type: object
25
- properties:
26
- name:
27
- type: string
28
- version:
29
- type: string
30
- description:
31
- type: string
32
- endpoints:
33
- type: object
34
- usage:
35
- type: string
36
- /apiMeta:
37
- get:
38
- summary: API metadata
39
- description: Returns the OpenAPI specification for this server.
40
- responses:
41
- 200:
42
- description: OpenAPI document
43
- schema:
44
- type: object
45
- /mcp:
46
- options:
47
- summary: CORS preflight
48
- description: CORS preflight endpoint.
49
- responses:
50
- 204:
51
- description: No Content
52
- post:
53
- summary: MCP request
54
- description: Handles MCP JSON-RPC requests.
55
- parameters:
56
- - name: body
57
- in: body
58
- required: true
59
- schema:
60
- type: object
61
- - name: Authorization
62
- in: header
63
- required: false
64
- type: string
65
- description: Bearer token for authentication
66
- - name: X-VIYA-SERVER
67
- in: header
68
- required: false
69
- type: string
70
- description: Override VIYA server
71
- - name: X-REFRESH-TOKEN
72
- in: header
73
- required: false
74
- type: string
75
- description: Refresh token for authentication
76
- - name: mcp-session-id
77
- in: header
78
- required: false
79
- type: string
80
- description: Session ID
81
- responses:
82
- 200:
83
- description: MCP response
84
- schema:
85
- type: object
86
- 500:
87
- description: Server error
88
- schema:
89
- type: object
90
- get:
91
- summary: Get MCP session
92
- description: Retrieves information for an MCP session.
93
- parameters:
94
- - name: mcp-session-id
95
- in: header
96
- required: true
97
- type: string
98
- description: Session ID
99
- responses:
100
- 200:
101
- description: Session information
102
- schema:
103
- type: object
104
- 400:
105
- description: Invalid or missing session ID
106
- delete:
107
- summary: Delete MCP session
108
- description: Deletes an MCP session.
109
- parameters:
110
- - name: mcp-session-id
111
- in: header
112
- required: true
113
- type: string
114
- description: Session ID
115
- responses:
116
- 200:
117
- description: Session deleted
118
- schema:
119
- type: object
120
- 400:
121
- description: Invalid or missing session ID
1
+ swagger: "2.0"
2
+ info:
3
+ title: SAS Viya Sample MCP Server API
4
+ version: "1.0.0"
5
+ description: API for interacting with the SAS Viya Sample MCP Server.
6
+ host: localhost:8080
7
+ basePath: /
8
+ schemes:
9
+ - http
10
+ - https
11
+ consumes:
12
+ - application/json
13
+ produces:
14
+ - application/json
15
+ paths:
16
+ /health:
17
+ get:
18
+ summary: Health check
19
+ description: Returns health and version information.
20
+ responses:
21
+ 200:
22
+ description: Health information
23
+ schema:
24
+ type: object
25
+ properties:
26
+ name:
27
+ type: string
28
+ version:
29
+ type: string
30
+ description:
31
+ type: string
32
+ endpoints:
33
+ type: object
34
+ usage:
35
+ type: string
36
+ /apiMeta:
37
+ get:
38
+ summary: API metadata
39
+ description: Returns the OpenAPI specification for this server.
40
+ responses:
41
+ 200:
42
+ description: OpenAPI document
43
+ schema:
44
+ type: object
45
+ /mcp:
46
+ options:
47
+ summary: CORS preflight
48
+ description: CORS preflight endpoint.
49
+ responses:
50
+ 204:
51
+ description: No Content
52
+ post:
53
+ summary: MCP request
54
+ description: Handles MCP JSON-RPC requests.
55
+ parameters:
56
+ - name: body
57
+ in: body
58
+ required: true
59
+ schema:
60
+ type: object
61
+ - name: Authorization
62
+ in: header
63
+ required: false
64
+ type: string
65
+ description: Bearer token for authentication
66
+ - name: X-VIYA-SERVER
67
+ in: header
68
+ required: false
69
+ type: string
70
+ description: Override VIYA server
71
+ - name: X-REFRESH-TOKEN
72
+ in: header
73
+ required: false
74
+ type: string
75
+ description: Refresh token for authentication
76
+ - name: mcp-session-id
77
+ in: header
78
+ required: false
79
+ type: string
80
+ description: Session ID
81
+ responses:
82
+ 200:
83
+ description: MCP response
84
+ schema:
85
+ type: object
86
+ 500:
87
+ description: Server error
88
+ schema:
89
+ type: object
90
+ get:
91
+ summary: Get MCP session
92
+ description: Retrieves information for an MCP session.
93
+ parameters:
94
+ - name: mcp-session-id
95
+ in: header
96
+ required: true
97
+ type: string
98
+ description: Session ID
99
+ responses:
100
+ 200:
101
+ description: Session information
102
+ schema:
103
+ type: object
104
+ 400:
105
+ description: Invalid or missing session ID
106
+ delete:
107
+ summary: Delete MCP session
108
+ description: Deletes an MCP session.
109
+ parameters:
110
+ - name: mcp-session-id
111
+ in: header
112
+ required: true
113
+ type: string
114
+ description: Session ID
115
+ responses:
116
+ 200:
117
+ description: Session deleted
118
+ schema:
119
+ type: object
120
+ 400:
121
+ description: Invalid or missing session ID
@@ -1,5 +1,3 @@
1
- import { start } from "node:repl";
2
-
3
1
  /*
4
2
  * Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
5
3
  * SPDX-License-Identifier: Apache-2.0
@@ -32,20 +30,25 @@ function processHeaders(req, res, next, cache, appContext) {
32
30
  const hdr = req.header("Authorization");
33
31
  //for now, ignore Authorization if authflow is not bearer
34
32
  let token = (hdr != null) ? hdr.slice(7) : null;
35
- //console.error("[Note] Authorization token", token);
36
33
  debugger;
37
- console.error('>>>',appContext.AUTHFLOW);
34
+ console.error('[Note} AUTHFLOW=', appContext.AUTHFLOW);
35
+ console.error("[Note] External authorization :", appContext.AUTHEXTERNAL);
38
36
  if (appContext.AUTHFLOW === 'bearer') {
39
37
  debugger;
40
38
  let startAuth = false;
41
- console.error("[Note] appContext.AUTHEXTERNAL:", appContext.AUTHEXTERNAL);
39
+
42
40
  if (appContext.AUTHEXTERNAL === true) {
43
41
  console.error("[Note] Expecting external authorization");
44
42
  if (token != null) {
45
43
  console.error("[Note] Using user supplied token for authorization");
46
44
  headerCache.bearerToken = token;
47
45
  } else {
48
- startAuth = true;
46
+ console.error("[Note] No Authorization token provided in header for external authorization.");
47
+ console.error("[Note] Returning 404 since we are configured for external token and no token provided in header.");
48
+ return res.status(404).json({
49
+ error: "unauthorized",
50
+ error_description: "[Error] Missing token for external authorization."
51
+ });
49
52
  }
50
53
  } else if (token == null) {
51
54
  console.error("[Note] No Authorization token provided in header.");
@@ -55,7 +58,7 @@ function processHeaders(req, res, next, cache, appContext) {
55
58
  let tokenlist = cache.get("tokenlist");
56
59
  let tokenData = tokenlist[token];
57
60
  if (tokenData == null) {
58
- return res.status(403).json({
61
+ return res.status(401).json({
59
62
  error: "unauthorized",
60
63
  error_description: "[Error] Expired token. Clear token and try again."
61
64
  });
@@ -19,27 +19,10 @@ function setupSkills(clientName,agentFolder) {
19
19
  if (agentFolder) {
20
20
  destination = path.join(destination, agentFolder);
21
21
  }
22
- const source = path.join(__dirname, `../.skills` + '_' + clientName.toLowerCase());
22
+ const source = path.join(__dirname, `../.skills`);
23
23
  console.error("==================================================================");
24
24
  console.error(` Copying ${source} to ${destination}...`);
25
25
 
26
-
27
-
28
- // Copy agents folder if it exists
29
- let agentsFromPath = path.join(__dirname, `../.agents`);
30
- let agentsToPath = path.join(destination, 'agents');
31
- copyFolderSync(agentsFromPath, agentsToPath);
32
-
33
- // now copy the skills folder to the destination
34
- let toPath = path.join(destination, '.skills');
35
- let fromPath = path.join(__dirname, `../.skills`);
36
- copyFolderSync(fromPath, toPath);
37
-
38
- // Now copy instructions
39
- let instructionsFromPath = path.join(__dirname, `../.instructions`);
40
- let instructionsToPath = destination;
41
- copyFolderSync(instructionsFromPath, instructionsToPath);
42
-
43
26
  function copyFolderSync(from, to) {
44
27
  if (!fs.existsSync(from)) return [];
45
28
  if (!fs.existsSync(to)) fs.mkdirSync(to, { recursive: true });;