@sassoftware/sas-score-mcp-serverjs 0.0.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 (96) hide show
  1. package/.babelrc +6 -0
  2. package/.env +13 -0
  3. package/.env.http +29 -0
  4. package/CHANGES.md +2 -0
  5. package/CONTRIBUTING.md +14 -0
  6. package/ContributorAgreement.txt +56 -0
  7. package/LICENSE +205 -0
  8. package/LICENSES.json +105 -0
  9. package/QUICK_REFERENCE.md +378 -0
  10. package/README.md +267 -0
  11. package/SECURITY.md +31 -0
  12. package/SUPPORT.md +3 -0
  13. package/TOOL_DESCRIPTION_TEMPLATE.md +157 -0
  14. package/TOOL_UPDATES_SUMMARY.md +208 -0
  15. package/cli.js +214 -0
  16. package/labs/.subclass.json +13 -0
  17. package/labs/README.md +4 -0
  18. package/mcpConfigurations/README.md +3 -0
  19. package/mcpConfigurations/http.json +8 -0
  20. package/mcpConfigurations/stdio.json +20 -0
  21. package/mcpConfigurations/stdiodev.json +20 -0
  22. package/mcpserver.png +0 -0
  23. package/openApi.json +106 -0
  24. package/openApi.yaml +84 -0
  25. package/package.json +72 -0
  26. package/sas-mcp-tools-reference.md +600 -0
  27. package/sasCode/sas-sql-query.sas +33 -0
  28. package/sasCode/sas_sql_tool.json +237 -0
  29. package/scripts/getViyaca.sh +8 -0
  30. package/src/core.js +19 -0
  31. package/src/coreSSE.js +14 -0
  32. package/src/corehttp.js +335 -0
  33. package/src/createHttpTransport.js +26 -0
  34. package/src/createMcpServer.js +76 -0
  35. package/src/db/scrModels.js +23 -0
  36. package/src/toolSet/devaScore.js +69 -0
  37. package/src/toolSet/findJob.js +90 -0
  38. package/src/toolSet/findJobdef.js +95 -0
  39. package/src/toolSet/findLibrary.js +100 -0
  40. package/src/toolSet/findModel.js +83 -0
  41. package/src/toolSet/findTable.js +94 -0
  42. package/src/toolSet/getEnv.js +72 -0
  43. package/src/toolSet/listJobdefs.js +96 -0
  44. package/src/toolSet/listJobs.js +110 -0
  45. package/src/toolSet/listLibraries.js +90 -0
  46. package/src/toolSet/listModels.js +83 -0
  47. package/src/toolSet/listTables.js +95 -0
  48. package/src/toolSet/makeTools.js +75 -0
  49. package/src/toolSet/mcp server .png +0 -0
  50. package/src/toolSet/modelInfo.js +87 -0
  51. package/src/toolSet/modelScore.js +131 -0
  52. package/src/toolSet/readTable.js +104 -0
  53. package/src/toolSet/runCasProgram.js +118 -0
  54. package/src/toolSet/runJob.js +81 -0
  55. package/src/toolSet/runJobdef.js +85 -0
  56. package/src/toolSet/runMacro.js +82 -0
  57. package/src/toolSet/runProgram.js +145 -0
  58. package/src/toolSet/sasQuery.js +126 -0
  59. package/src/toolSet/sasQueryTemplate.js +148 -0
  60. package/src/toolSet/sasQueryTemplate2.js +140 -0
  61. package/src/toolSet/scrInfo.js +55 -0
  62. package/src/toolSet/scrScore.js +71 -0
  63. package/src/toolSet/searchAssets.js +52 -0
  64. package/src/toolSet/setContext.js +98 -0
  65. package/src/toolSet/superstat.js +60 -0
  66. package/src/toolSet/tableInfo.js +102 -0
  67. package/src/toolhelpers/_catalogSearch.js +87 -0
  68. package/src/toolhelpers/_getEnv.js +10 -0
  69. package/src/toolhelpers/_itemsData.js +28 -0
  70. package/src/toolhelpers/_jobSubmit.js +78 -0
  71. package/src/toolhelpers/_listJobdefs.js +59 -0
  72. package/src/toolhelpers/_listJobs.js +63 -0
  73. package/src/toolhelpers/_listLibrary.js +56 -0
  74. package/src/toolhelpers/_listModels.js +41 -0
  75. package/src/toolhelpers/_listTables.js +52 -0
  76. package/src/toolhelpers/_masDescribe.js +27 -0
  77. package/src/toolhelpers/_masScoring.js +64 -0
  78. package/src/toolhelpers/_readTable.js +69 -0
  79. package/src/toolhelpers/_scrInfo.js +32 -0
  80. package/src/toolhelpers/_scrScore.js +49 -0
  81. package/src/toolhelpers/_submitCasl.js +34 -0
  82. package/src/toolhelpers/_submitCode.js +96 -0
  83. package/src/toolhelpers/_submitMacro.js +24 -0
  84. package/src/toolhelpers/_tableColumns.js +61 -0
  85. package/src/toolhelpers/_tableInfo.js +72 -0
  86. package/src/toolhelpers/deleteSession.js +13 -0
  87. package/src/toolhelpers/getLogonPayload.js +100 -0
  88. package/src/toolhelpers/getOpts.js +43 -0
  89. package/src/toolhelpers/getOptsViya.js +38 -0
  90. package/src/toolhelpers/getStoreOpts.js +18 -0
  91. package/src/toolhelpers/getToken.js +40 -0
  92. package/src/toolhelpers/refreshToken.js +48 -0
  93. package/test/README.md +63 -0
  94. package/test/listLibraries.test.js +245 -0
  95. package/tool-developer-guide.md +80 -0
  96. package/types.js +25 -0
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import restafedit from '@sassoftware/restafedit';
6
+ import getLogonPayload from './getLogonPayload.js';
7
+ import deleteSession from './deleteSession.js';
8
+
9
+ async function _tableColumns(table, server) {
10
+ let logonPayload = await getLogonPayload();
11
+
12
+ let [lib, name] = table.split('.');
13
+ let itable = { name: name };
14
+ if (server === 'cas') {
15
+ itable.caslib = lib;
16
+ } else {
17
+ itable.libref = lib;
18
+ }
19
+ let config = {
20
+ source: (server === 'sas') ? 'compute' : server,
21
+ table: itable,
22
+
23
+ initialFetch: {
24
+ qs: {
25
+ start: 0, // Adjust for 0-based index
26
+ limit: 1,
27
+ format: true,
28
+ where: ''
29
+ }
30
+ }
31
+ };
32
+
33
+
34
+ let appControl = {};
35
+ try {
36
+ appControl = await restafedit.setup(
37
+ logonPayload,
38
+ config,
39
+ null,/* create a sessiion */
40
+ {},
41
+ 'user',
42
+ {}
43
+ );
44
+
45
+ //let tableSummary = await restafedit.getTableSummary(appControl);
46
+
47
+ await restafedit.scrollTable('first', appControl);
48
+ console.error(`Fetched columns for table ${table} from ${server}`);
49
+ let columns = {...appControl.state.columns};
50
+ delete columns._rowIndex;
51
+ delete columns._modified;
52
+ delete columns._index_;
53
+ console.error(columns);
54
+ await deleteSession(appControl);
55
+ return JSON.stringify(columns);
56
+ } catch (e) {
57
+ await deleteSession(appControl);
58
+ return outdata = null;
59
+ };
60
+ }
61
+ export default _tableColumns;
@@ -0,0 +1,72 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import restafedit from '@sassoftware/restafedit';
6
+ import deleteSession from './deleteSession.js';
7
+
8
+ async function _tableInfo(params) {
9
+ let { table, lib, server, _appContext } = params;
10
+
11
+
12
+ if (table.includes('.')) {
13
+ let parts = table.split('.');
14
+ lib = parts[0];
15
+ table = parts[1];
16
+ }
17
+ let itable = { name: table };
18
+ if (server === 'cas') {
19
+ itable.caslib = lib;
20
+ } else {
21
+ itable.libref = lib;
22
+ }
23
+ let config = {
24
+ casServerName: _appContext.cas,
25
+ computeContext: _appContext.sas,
26
+ source: (server === 'sas') ? 'compute' : server,
27
+ table: itable,
28
+
29
+ initialFetch: {
30
+ qs: {
31
+ start: 0, // Adjust for 0-based index
32
+ limit: 1,
33
+ format: true,
34
+ where: ''
35
+ }
36
+ }
37
+ };
38
+
39
+
40
+ let appControl = {};
41
+ try {
42
+ appControl = await restafedit.setup(
43
+ _appContext.logonPayload,
44
+ config,
45
+ null,/* create a sessiion */
46
+ {},
47
+ 'user',
48
+ _appContext.storeConfig
49
+ );
50
+
51
+ //let tableSummary = await restafedit.getTableSummary(appControl);
52
+
53
+ await restafedit.scrollTable('first', appControl);
54
+
55
+ let outdata = appControl.state.data.map((d) => {
56
+ delete d._rowIndex;
57
+ delete d._modified;
58
+ delete d._index_;
59
+ return d;
60
+ });
61
+ let columns = appControl.state.columns;
62
+ let structuredContent = { columns: columns, sampleData: outdata };
63
+
64
+ await deleteSession(appControl);
65
+ return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent: structuredContent };
66
+
67
+ } catch (err) {;
68
+ await deleteSession(appControl);
69
+ return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
70
+ }
71
+ }
72
+ export default _tableInfo;
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ async function deleteSession(appControl) {
6
+ let {store, session} = appControl;
7
+ if (store != null && session != null) {
8
+ await store.apiCall(session.links('delete'));
9
+ }
10
+ return;
11
+
12
+ }
13
+ export default deleteSession;
@@ -0,0 +1,100 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import getToken from "./getToken.js";
7
+ import refreshToken from "./refreshToken.js";
8
+
9
+ async function getLogonPayload(_appContext) {
10
+ _appContext.contexts.logonPayload = await igetLogonPayload(_appContext);
11
+ return _appContext.contexts.logonPayload;
12
+ }
13
+
14
+ async function igetLogonPayload(_appContext) {
15
+
16
+ // Use cached logonPayload if available
17
+ // This will cause timeouts if the token expires
18
+ if (_appContext.logonPayload != null && _appContext.tokenRefresh !== true) {
19
+ console.error("[Note] Using cached logonPayload information");
20
+ return _appContext.contexts.logonPayload;
21
+ }
22
+
23
+ // Use user supplied bearer token
24
+ if (_appContext.AUTHFLOW === "bearer") {
25
+ console.error("[Note] Using user suplied bearer token ");
26
+ let logonPayload = {
27
+ host: _appContext.VIYA_SERVER,
28
+ authType: "server",
29
+ token: _appContext.bearerToken,
30
+ tokenType: "Bearer",
31
+ };
32
+ return logonPayload;
33
+ }
34
+
35
+ // Use user supplied refresh token-
36
+ if (_appContext.AUTHFLOW === "refresh") {
37
+ console.error("[Note] Using user supplied refresh token");
38
+ let token = await refreshToken(_appContext,{token: _appContext.refreshToken, host: _appContext.VIYA_SERVER});
39
+ let logonPayload = {
40
+ host: _appContext.VIYA_SERVER,
41
+ authType: "server",
42
+ token: token,
43
+ tokenType: "Bearer",
44
+ };
45
+
46
+ return logonPayload;
47
+ }
48
+
49
+ if (_appContext.AUTHFLOW === "token") {
50
+ console.error("[Note] Using token supplied by user");
51
+ let logonPayload = {
52
+ host: _appContext.VIYA_SERVER,
53
+ authType: "server",
54
+ token: _appContext.TOKEN,
55
+ tokenType: "Bearer",
56
+ };
57
+ return logonPayload;
58
+ }
59
+
60
+ if (_appContext.AUTHFLOW === "none") {
61
+ console.error(
62
+ "[Note] No authentication flow selected. Proceeding without authentication."
63
+ );
64
+ let logonPayload = {
65
+ host: _appContext.VIYA_SERVER,
66
+ authType: "none",
67
+ };
68
+ return logonPayload;
69
+ }
70
+
71
+ if (_appContext.PASSWORDAUTHFLOW === "password") {
72
+ let logonPayload = {
73
+ host: _appContext.VIYA_SERVER,
74
+ authType: "password",
75
+ user: _appContext.USERNAME,
76
+ password: _appContext.PASSWORD,
77
+ clientID: _appContext.CLIENTIDPW,
78
+ clientSecret: _appContext.CLIENTSECRETPW,
79
+ };
80
+
81
+ return logonPayload;
82
+ }
83
+
84
+ // sascli auth flow - create from credentials file
85
+ try {
86
+ let { host, token } = await getToken(_appContext)
87
+ console.error("[Note] got refresh token from getToken() for host ", host);
88
+ let logonPayload = {
89
+ host: host,
90
+ authType: "server",
91
+ token: token,
92
+ tokenType: "Bearer",
93
+ };
94
+ return logonPayload;
95
+ } catch (e) {
96
+ console.error("[Error].... Error getting token: ", e);
97
+ return null;
98
+ }
99
+ }
100
+ export default getLogonPayload;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Helper function to get TLS options(for the app server) from specified directory
7
+ * signed certificates
8
+ * for testing you can use mkcert
9
+ * if this function return a null, coreehttp will create unsigned certs
10
+ * @param {Object} _appContext - Application context containing SSLCERT property
11
+ */
12
+ import fs from 'fs';
13
+ function getOpts(_appContext) {
14
+
15
+ if (_appContext.tlsOpts != null) {
16
+ return _appContext.tlsOpts;
17
+ }
18
+ let tlsdir = _appContext.SSLCERT;
19
+ if (tlsdir == null || tlsdir === 'NONE') {
20
+ return null;
21
+ }
22
+
23
+ console.error("[Note] Using TLS dir: " + tlsdir);
24
+ if (fs.existsSync(tlsdir) === false) {
25
+ console.error("[Warning] Specified TLS dir does not exist: " + tlsdir);
26
+ return null;
27
+ }
28
+
29
+ let listOfFiles = fs.readdirSync(tlsdir);
30
+ console.error("[Note] TLS/SSL files found: " + listOfFiles);
31
+ let options = {};
32
+ for(let i=0; i < listOfFiles.length; i++) {
33
+ let fname = listOfFiles[i];
34
+ let name = tlsdir + '/' + listOfFiles[i];
35
+ let key = fname.split('.')[0];
36
+ options[key] = fs.readFileSync(name, { encoding: 'utf8' });
37
+ }
38
+ console.error('TLS FILES', Object.keys(options));
39
+ _appContext.tlsOpts = options;
40
+ return options;
41
+
42
+ }
43
+ export default getOpts;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import fs from 'fs';
6
+ function getOptsViya(_appContext) {
7
+
8
+ if (_appContext.contexts.viyaCert != null) {
9
+ console.error('[Note] Using cached viyaOpts');
10
+ return _appContext.contexts.viyaCert;
11
+ }
12
+ let tlsdir = _appContext.VIYACERT;
13
+ if (tlsdir == null || tlsdir === 'NONE') {
14
+ return {};
15
+ }
16
+
17
+ console.error(`[Note] Using VIYACERT dir: ` + tlsdir);
18
+ if (fs.existsSync(tlsdir) === false) {
19
+ console.error("[Warning] Specified VIYACERT dir does not exist: " + tlsdir);
20
+ return {};
21
+ }
22
+
23
+ let listOfFiles = fs.readdirSync(tlsdir);
24
+ console.error("[Note] TLS/SSL files found: " + listOfFiles);
25
+ let options = {};
26
+ for(let i=0; i < listOfFiles.length; i++) {
27
+ let fname = listOfFiles[i];
28
+ let name = tlsdir + '/' + listOfFiles[i];
29
+ let key = fname.split('.')[0];
30
+ console.error('Reading TLS file: ' + name + ' as key: ' + key);
31
+ options[key] = fs.readFileSync(name, { encoding: 'utf8' });
32
+ }
33
+ console.error('VIYACERT FILES', Object.keys(options));
34
+ _appContext.contexts.viyaCert = options;
35
+ return options;
36
+
37
+ }
38
+ export default getOptsViya;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import getOptsViya from './getOptsViya.js';
6
+
7
+ function getStoreOpts(_appContext) {
8
+
9
+ let opts = getOptsViya(_appContext);
10
+
11
+
12
+ let storeOpts = {
13
+ casProxy: true,
14
+ httpOptions: { ...opts, rejectUnauthorized: true }
15
+ }
16
+ return storeOpts;
17
+ }
18
+ export default getStoreOpts;
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import fs from 'fs';
6
+ import os from 'os';
7
+ import refreshToken from './refreshToken.js';
8
+ async function getToken(_appContext) {
9
+ let homedir = os.homedir();
10
+
11
+ if (_appContext.SAS_CLI_CONFIG != null) {
12
+ homedir = _appContext.SAS_CLI_CONFIG;
13
+ }
14
+
15
+ let sep = (os.platform() === 'win32') ? '\\' : '/';
16
+ let credentials = homedir + sep + '.sas' + sep + 'credentials.json';
17
+ let url = homedir + sep + '.sas' + sep + 'config.json';
18
+ console.error('[Note] Using credentials file: ' + credentials);
19
+ console.error('[Note] Using config file: ' + url);
20
+
21
+ try {
22
+
23
+ let j = fs.readFileSync(credentials, 'utf8');
24
+ let js = JSON.parse(j);
25
+ let profile = (_appContext.SAS_CLI_PROFILE == null || _appContext.SAS_CLI_PROFILE.toLowerCase() === 'default')
26
+ ? 'Default' : _appContext.SAS_CLI_PROFILE;
27
+ let refresh_token = js[profile]['refresh-token'];
28
+ j = fs.readFileSync(url, 'utf8');
29
+ js = JSON.parse(j);
30
+ let host = js[profile]['sas-endpoint'];
31
+
32
+ let token = await refreshToken(_appContext, { token: refresh_token, host: host });
33
+ return { host, token };
34
+ } catch (e) {
35
+ console.error(e);
36
+ throw '[Error] Failed to read credentials/config file: ' + e;
37
+ }
38
+
39
+ }
40
+ export default getToken;
@@ -0,0 +1,48 @@
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 getOpts from './getOpts.js';
7
+ async function refreshToken(_appContext,params) {
8
+ let {host, token} = params;
9
+ const url = `${host}/SASLogon/oauth/token`;
10
+ let opts = getOpts(_appContext);
11
+
12
+ const agent = new Agent({
13
+ connect: opts
14
+ });
15
+
16
+ const body = new URLSearchParams({
17
+ grant_type: 'refresh_token',
18
+ refresh_token: token,
19
+ client_id: 'sas.cli'
20
+ });
21
+
22
+ try {
23
+ const response = await fetch(url, {
24
+ method: 'POST',
25
+ headers: {
26
+ 'Accept': 'application/json',
27
+ 'Content-Type': 'application/x-www-form-urlencoded',
28
+ dispatcher: agent
29
+ },
30
+ body: body.toString()
31
+ });
32
+
33
+ if (!response.ok) {
34
+ const error = await response.text();
35
+ console.error('[Error] Failed to refresh token: ', error);
36
+ throw new Error(error);
37
+ }
38
+
39
+ const data = await response.json();
40
+
41
+ return data.access_token;
42
+ } catch (err) {
43
+ console.error('[Error] Failed to refresh token: ', err);
44
+ throw err;
45
+ }
46
+ }
47
+
48
+ export default refreshToken;
package/test/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # MCP API Tests
2
+
3
+ This directory contains tests that use direct HTTP calls to test the MCP protocol and tools.
4
+
5
+ ## Running Tests
6
+
7
+ ### List Libraries Test
8
+
9
+ Test the `list-libraries` tool using raw HTTP POST requests to the MCP endpoint:
10
+
11
+ **Prerequisites:**
12
+ 1. Start the MCP server in HTTP mode:
13
+ ```bash
14
+ npm start
15
+ # Server should be running at http://localhost:8080/mcp
16
+ ```
17
+
18
+ 2. In a separate terminal, run the test:
19
+ ```bash
20
+ node test/listLibraries.test.js
21
+ ```
22
+
23
+ This test will:
24
+ 1. Make HTTP POST requests to http://localhost:8080/mcp
25
+ 2. Use JSON-RPC 2.0 protocol format
26
+ 3. Call the `list-libraries` tool with various parameters
27
+ 4. Verify the responses
28
+
29
+ ### Test Scenarios
30
+
31
+ The test covers:
32
+ - ✓ Connecting to MCP server
33
+ - ✓ Listing available tools
34
+ - ✓ List CAS libraries with default parameters
35
+ - ✓ List CAS libraries with custom limit
36
+ - ✓ List SAS libraries
37
+ - ✓ List libraries with pagination
38
+ - ✓ Server parameter normalization (CAS → cas)
39
+
40
+ ## Prerequisites
41
+
42
+ Make sure you have:
43
+ - Node.js >= 22.16.0
44
+ - Valid `.env` file with SAS Viya credentials
45
+ - `@modelcontextprotocol/sdk` installed
46
+
47
+ ## Environment Setup
48
+
49
+ The test requires environment variables for SAS Viya connection:
50
+ ```
51
+ VIYA_SERVER=https://your-viya-server.com
52
+ CLIENTID=your-client-id
53
+ CLIENTSECRET=your-client-secret
54
+ ```
55
+
56
+ ## Output
57
+
58
+ The test will display:
59
+ - Connection status
60
+ - Tool discovery results
61
+ - Each test scenario with pass/fail status
62
+ - Response data from each call
63
+ - Final summary of passed/failed tests