@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.
- package/.babelrc +6 -0
- package/.env +13 -0
- package/.env.http +29 -0
- package/CHANGES.md +2 -0
- package/CONTRIBUTING.md +14 -0
- package/ContributorAgreement.txt +56 -0
- package/LICENSE +205 -0
- package/LICENSES.json +105 -0
- package/QUICK_REFERENCE.md +378 -0
- package/README.md +267 -0
- package/SECURITY.md +31 -0
- package/SUPPORT.md +3 -0
- package/TOOL_DESCRIPTION_TEMPLATE.md +157 -0
- package/TOOL_UPDATES_SUMMARY.md +208 -0
- package/cli.js +214 -0
- package/labs/.subclass.json +13 -0
- package/labs/README.md +4 -0
- package/mcpConfigurations/README.md +3 -0
- package/mcpConfigurations/http.json +8 -0
- package/mcpConfigurations/stdio.json +20 -0
- package/mcpConfigurations/stdiodev.json +20 -0
- package/mcpserver.png +0 -0
- package/openApi.json +106 -0
- package/openApi.yaml +84 -0
- package/package.json +72 -0
- package/sas-mcp-tools-reference.md +600 -0
- package/sasCode/sas-sql-query.sas +33 -0
- package/sasCode/sas_sql_tool.json +237 -0
- package/scripts/getViyaca.sh +8 -0
- package/src/core.js +19 -0
- package/src/coreSSE.js +14 -0
- package/src/corehttp.js +335 -0
- package/src/createHttpTransport.js +26 -0
- package/src/createMcpServer.js +76 -0
- package/src/db/scrModels.js +23 -0
- package/src/toolSet/devaScore.js +69 -0
- package/src/toolSet/findJob.js +90 -0
- package/src/toolSet/findJobdef.js +95 -0
- package/src/toolSet/findLibrary.js +100 -0
- package/src/toolSet/findModel.js +83 -0
- package/src/toolSet/findTable.js +94 -0
- package/src/toolSet/getEnv.js +72 -0
- package/src/toolSet/listJobdefs.js +96 -0
- package/src/toolSet/listJobs.js +110 -0
- package/src/toolSet/listLibraries.js +90 -0
- package/src/toolSet/listModels.js +83 -0
- package/src/toolSet/listTables.js +95 -0
- package/src/toolSet/makeTools.js +75 -0
- package/src/toolSet/mcp server .png +0 -0
- package/src/toolSet/modelInfo.js +87 -0
- package/src/toolSet/modelScore.js +131 -0
- package/src/toolSet/readTable.js +104 -0
- package/src/toolSet/runCasProgram.js +118 -0
- package/src/toolSet/runJob.js +81 -0
- package/src/toolSet/runJobdef.js +85 -0
- package/src/toolSet/runMacro.js +82 -0
- package/src/toolSet/runProgram.js +145 -0
- package/src/toolSet/sasQuery.js +126 -0
- package/src/toolSet/sasQueryTemplate.js +148 -0
- package/src/toolSet/sasQueryTemplate2.js +140 -0
- package/src/toolSet/scrInfo.js +55 -0
- package/src/toolSet/scrScore.js +71 -0
- package/src/toolSet/searchAssets.js +52 -0
- package/src/toolSet/setContext.js +98 -0
- package/src/toolSet/superstat.js +60 -0
- package/src/toolSet/tableInfo.js +102 -0
- package/src/toolhelpers/_catalogSearch.js +87 -0
- package/src/toolhelpers/_getEnv.js +10 -0
- package/src/toolhelpers/_itemsData.js +28 -0
- package/src/toolhelpers/_jobSubmit.js +78 -0
- package/src/toolhelpers/_listJobdefs.js +59 -0
- package/src/toolhelpers/_listJobs.js +63 -0
- package/src/toolhelpers/_listLibrary.js +56 -0
- package/src/toolhelpers/_listModels.js +41 -0
- package/src/toolhelpers/_listTables.js +52 -0
- package/src/toolhelpers/_masDescribe.js +27 -0
- package/src/toolhelpers/_masScoring.js +64 -0
- package/src/toolhelpers/_readTable.js +69 -0
- package/src/toolhelpers/_scrInfo.js +32 -0
- package/src/toolhelpers/_scrScore.js +49 -0
- package/src/toolhelpers/_submitCasl.js +34 -0
- package/src/toolhelpers/_submitCode.js +96 -0
- package/src/toolhelpers/_submitMacro.js +24 -0
- package/src/toolhelpers/_tableColumns.js +61 -0
- package/src/toolhelpers/_tableInfo.js +72 -0
- package/src/toolhelpers/deleteSession.js +13 -0
- package/src/toolhelpers/getLogonPayload.js +100 -0
- package/src/toolhelpers/getOpts.js +43 -0
- package/src/toolhelpers/getOptsViya.js +38 -0
- package/src/toolhelpers/getStoreOpts.js +18 -0
- package/src/toolhelpers/getToken.js +40 -0
- package/src/toolhelpers/refreshToken.js +48 -0
- package/test/README.md +63 -0
- package/test/listLibraries.test.js +245 -0
- package/tool-developer-guide.md +80 -0
- 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
|