@sassoftware/sas-score-mcp-serverjs 0.4.1 → 1.0.1-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.skills/agents/sas-viya-scoring-expert.md +58 -0
- package/.skills/copilot-instructions.md +155 -0
- package/.skills/skills/sas-find-library-smart/SKILL.md +154 -0
- package/.skills/skills/sas-list-tables-smart/SKILL.md +127 -0
- package/.skills/skills/sas-read-and-score/SKILL.md +111 -0
- package/.skills/skills/sas-read-strategy/SKILL.md +156 -0
- package/.skills/skills/sas-request-classifier/SKILL.md +69 -0
- package/.skills/skills/sas-score-workflow/SKILL.md +314 -0
- package/cli.js +311 -70
- package/package.json +7 -7
- package/scripts/docs/SCORE_SKILL_REFERENCE.md +142 -0
- package/scripts/docs/TOOL_DESCRIPTION_TEMPLATE.md +157 -0
- package/scripts/docs/TOOL_UPDATES_SUMMARY.md +208 -0
- package/scripts/docs/mcp-localhost-config-guide.md +184 -0
- package/scripts/docs/oauth-http-transport.md +96 -0
- package/scripts/docs/sas-mcp-tools-reference.md +600 -0
- package/scripts/getViyaca.sh +1 -0
- package/scripts/optimize_final.py +140 -0
- package/scripts/optimize_tools.py +99 -0
- package/scripts/setup-skills.js +34 -0
- package/scripts/update_descriptions.py +46 -0
- package/scripts/viyatls.sh +3 -0
- package/src/authpkce.js +219 -0
- package/src/createMcpServer.js +16 -5
- package/src/expressMcpServer.js +350 -308
- package/src/handleGetDelete.js +6 -3
- package/src/hapiMcpServer.js +10 -18
- package/src/oauthHandlers/authorize.js +46 -0
- package/src/oauthHandlers/baseUrl.js +8 -0
- package/src/oauthHandlers/callback.js +96 -0
- package/src/oauthHandlers/getMetadata.js +27 -0
- package/src/oauthHandlers/index.js +7 -0
- package/src/oauthHandlers/token.js +37 -0
- package/src/processHeaders.js +88 -0
- package/src/setupSkills.js +46 -0
- package/src/toolHelpers/_jobSubmit.js +2 -0
- package/src/toolHelpers/_listLibrary.js +55 -39
- package/src/toolHelpers/getLogonPayload.js +7 -1
- package/src/toolHelpers/readCerts.js +4 -4
- package/src/toolHelpers/refreshToken.js +3 -2
- package/src/toolHelpers/refreshTokenOauth.js +3 -3
- package/src/toolSet/.claude/settings.local.json +13 -0
- package/src/toolSet/devaScore.js +61 -69
- package/src/toolSet/findJob.js +38 -71
- package/src/toolSet/findJobdef.js +28 -59
- package/src/toolSet/findLibrary.js +68 -100
- package/src/toolSet/findModel.js +35 -58
- package/src/toolSet/findTable.js +31 -60
- package/src/toolSet/getEnv.js +30 -45
- package/src/toolSet/listJobdefs.js +61 -96
- package/src/toolSet/listJobs.js +61 -110
- package/src/toolSet/listLibraries.js +78 -90
- package/src/toolSet/listModels.js +56 -83
- package/src/toolSet/listTables.js +66 -95
- package/src/toolSet/makeTools.js +1 -0
- package/src/toolSet/modelInfo.js +22 -54
- package/src/toolSet/modelScore.js +35 -77
- package/src/toolSet/readTable.js +63 -104
- package/src/toolSet/runCasProgram.js +32 -52
- package/src/toolSet/runJob.js +24 -24
- package/src/toolSet/runJobdef.js +26 -29
- package/src/toolSet/runMacro.js +82 -82
- package/src/toolSet/runProgram.js +32 -84
- package/src/toolSet/sasQuery.js +77 -126
- package/src/toolSet/sasQueryTemplate.js +4 -5
- package/src/toolSet/sasQueryTemplate2.js +4 -5
- package/src/toolSet/scrInfo.js +4 -7
- package/src/toolSet/scrScore.js +69 -70
- package/src/toolSet/searchAssets.js +5 -6
- package/src/toolSet/setContext.js +65 -92
- package/src/toolSet/superstat.js +61 -60
- package/src/toolSet/tableInfo.js +58 -102
package/cli.js
CHANGED
|
@@ -21,39 +21,247 @@ import { randomUUID } from 'node:crypto';
|
|
|
21
21
|
import readCerts from './src/toolHelpers/readCerts.js';
|
|
22
22
|
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
24
|
-
import { dirname } from 'path';
|
|
24
|
+
import { dirname, join } from 'path';
|
|
25
|
+
import os from 'os';
|
|
26
|
+
import setupSkills from './src/setupSkills.js';
|
|
27
|
+
import { parseArgs } from "node:util";
|
|
25
28
|
|
|
26
29
|
import NodeCache from 'node-cache';
|
|
30
|
+
import { be } from 'zod/locales';
|
|
27
31
|
//import getOpts from './src/toolHelpers/getOpts.js';
|
|
28
32
|
|
|
29
33
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
34
|
|
|
31
35
|
let pkg = fs.readFileSync(__dirname + '/package.json', 'utf8');
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
// Parse command line arguments
|
|
38
|
+
const args = parseArgs({
|
|
39
|
+
options: {
|
|
40
|
+
port: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
short: 'p',
|
|
43
|
+
description: 'Port to run the server on'
|
|
44
|
+
},
|
|
45
|
+
mcptype: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
short: 'm',
|
|
48
|
+
description: 'MCP server type (http or stdio)'
|
|
49
|
+
},
|
|
50
|
+
https: {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
description: 'Use HTTPS for the server (default: FALSE)'
|
|
53
|
+
},
|
|
54
|
+
'skills-folder': {
|
|
55
|
+
type: 'string',
|
|
56
|
+
short: 'f',
|
|
57
|
+
description: 'Skills folder name'
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
viya: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
short: 'v',
|
|
63
|
+
description: 'Viya server URL'
|
|
64
|
+
},
|
|
65
|
+
mcphost: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
short: 'm',
|
|
68
|
+
description: 'MCP server host (default: http://localhost:8080)'
|
|
69
|
+
},
|
|
70
|
+
authflow: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
short: 'a',
|
|
73
|
+
description: 'Authentication flow (sascli, code, token, oauth, oauth,oauthclient)'
|
|
74
|
+
},
|
|
75
|
+
clientid: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
short: 'c',
|
|
78
|
+
description: 'Client ID for authentication'
|
|
79
|
+
},
|
|
80
|
+
clientsecret: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
short: 's',
|
|
83
|
+
description: 'Client Secret for authentication'
|
|
84
|
+
},
|
|
85
|
+
profile: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'SAS CLI profile name'
|
|
88
|
+
},
|
|
89
|
+
config: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: 'SAS CLI config directory'
|
|
92
|
+
},
|
|
93
|
+
casserver: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
description: 'CAS server name (default: cas-shared-default)'
|
|
96
|
+
},
|
|
97
|
+
computecontext: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: 'Compute session name or context (default: SAS Job Execution compute context)'
|
|
100
|
+
},
|
|
101
|
+
env: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
short: 'e',
|
|
104
|
+
description: 'Environment file path (default: .env in current working directory)'
|
|
105
|
+
},
|
|
106
|
+
client: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'MCP client name (github, claude...). Defaults to \'github\''
|
|
109
|
+
},
|
|
110
|
+
help: {
|
|
111
|
+
type: 'boolean',
|
|
112
|
+
short: 'h',
|
|
113
|
+
description: 'Show help message'
|
|
114
|
+
},
|
|
115
|
+
version: {
|
|
116
|
+
type: 'boolean',
|
|
117
|
+
description: 'Show version'
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
},
|
|
121
|
+
strict: false,
|
|
122
|
+
allowPositionals: false
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Handle help flag
|
|
126
|
+
if (args.values.help) {
|
|
127
|
+
console.error(`
|
|
128
|
+
SAS Viya Scoring Expert Agent - Version: ${JSON.parse(pkg).version}
|
|
129
|
+
|
|
130
|
+
Usage: npx @sassoftware/sas-score-mcp-serverjs@dev [options]
|
|
131
|
+
|
|
132
|
+
Options:
|
|
133
|
+
Minimal options:
|
|
134
|
+
-v, --viya <url> Viya server URL
|
|
135
|
+
-c, --clientid <id> Client ID for oauth authentication(pkce preferred. default: vscodemcp)
|
|
136
|
+
|
|
137
|
+
MCP server options:
|
|
138
|
+
-t, --mcptype <type> MCP server type: http or stdio (default: http)
|
|
139
|
+
-m, --mcphost <host> MCP server host - can be remote URL - (default: http://localhost:8080)
|
|
140
|
+
--client <name> MCP client name (github, claude...). Defaults to 'github'.Use to install skills
|
|
141
|
+
Authentication options:
|
|
142
|
+
-a, --authflow <flow> Authentication flow: oauth, oauthclient, sascli, code, token(default oauth)
|
|
143
|
+
-s, --clientsecret <secret> Client Secret for authentication(if necessary). See clientid option as well.
|
|
144
|
+
--profile <name> SAS CLI profile name for sascli flow (default: Default)
|
|
145
|
+
--config <path> SAS CLI config directory for sascli flow (default: user home directory)
|
|
146
|
+
|
|
147
|
+
Other options:
|
|
148
|
+
-p, --port <port> Port to run the server on (default: 8080)
|
|
149
|
+
--https Use HTTPS for the server (default: false)
|
|
150
|
+
--casserver <name> CAS server name (default: cas-shared-default)
|
|
151
|
+
--computecontext <name> Compute session name or context (default: SAS Job Execution compute context)
|
|
152
|
+
|
|
153
|
+
-e, --envfile <path> Environment file path
|
|
154
|
+
-h, --help Show this help message
|
|
155
|
+
--version Show version
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
Environment Variables:
|
|
159
|
+
Use .env file or set environment variables for configuration.
|
|
160
|
+
A alternative to cmd line arguments, and in some cases required for sensitive information like client secrets.
|
|
161
|
+
See README.md for more information.
|
|
162
|
+
`);
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// read env file and then override with command line arguments
|
|
167
|
+
if (args.values.env) {
|
|
37
168
|
console.error('Working Directory', process.cwd());
|
|
38
|
-
let envf = process.cwd() + '
|
|
169
|
+
let envf = process.cwd() + '\\' + args.values.env;
|
|
39
170
|
//__dirname + '\\.env';
|
|
40
|
-
console.error('Env file:', envf);
|
|
171
|
+
console.error('Env file:', envf);
|
|
41
172
|
if (fs.existsSync(envf)) {
|
|
42
|
-
console.error(`Loading environment variables
|
|
43
|
-
let e = iconfig(envf); // avoid dotenv since it writes to console.
|
|
173
|
+
console.error(`Loading environment variables from ${envf}...`);
|
|
174
|
+
let e = iconfig(envf); // avoid dotenv since it writes to console.error
|
|
44
175
|
console.error('[Note]: Environment variables loaded from .env file...');
|
|
45
176
|
console.error('Loaded env variables:', e);
|
|
46
177
|
// dotenvExpand.expand(e);
|
|
47
178
|
} else {
|
|
48
179
|
console.error(
|
|
49
|
-
'[Note]: No
|
|
180
|
+
'[Note]: No env file found, Using default environment variables...'
|
|
50
181
|
);
|
|
51
182
|
}
|
|
52
183
|
}
|
|
184
|
+
// Apply command line arguments to override environment variables
|
|
185
|
+
|
|
186
|
+
process.env.PORT = process.env.PORT || '8080';
|
|
187
|
+
process.env.HTTPS = (args.values.https) ? 'TRUE' : 'FALSE';
|
|
188
|
+
process.env.MCPTYPE = args.values.mcptype || process.env.MCPTYPE || 'http';
|
|
189
|
+
process.env.MCPHOST = args.values.mcphost || process.env.MCPHOST || 'http://localhost:8080';
|
|
190
|
+
process.env.AUTHFLOW = args.values.authflow || process.env.AUTHFLOW || 'oauth';
|
|
191
|
+
process.env.MCPCLIENT = args.values.client || process.env.MCPCLIENT || 'github';
|
|
192
|
+
process.env.VIYA_SERVER = args.values.viya || process.env.VIYA_SERVER || null;
|
|
193
|
+
process.env.CLIENTID = args.values.clientid || process.env.CLIENTID || 'vscodemcp';
|
|
194
|
+
process.env.CLIENTSECRET = args.values.clientsecret || process.env.CLIENTSECRET || null;
|
|
195
|
+
process.env.SAS_CLI_PROFILE = args.values.profile || process.env.SAS_CLI_PROFILE || 'Default';
|
|
196
|
+
process.env.SAS_CLI_CONFIG = args.values.config || process.env.SAS_CLI_CONFIG || process.env.HOME; // default to user home directory
|
|
197
|
+
process.env.CASSERVER = args.values.casserver || process.env.CASSERVER || 'cas-shared-default';
|
|
198
|
+
process.env.COMPUTECONTEXT = args.values.computecontext || process.env.COMPUTECONTEXT || 'SAS Job Execution compute context';
|
|
199
|
+
process.env.APPHOST = 'localhost';
|
|
200
|
+
process.env.CLIENT = args.values.client || process.env.CLIENT || 'github';
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
process.env.SAMESITE = 'Lax,secure';
|
|
205
|
+
process.env.APPHOST = '0.0.0.0';
|
|
206
|
+
process.env.APPNAME = 'sas-score-mcp-serverjs';
|
|
207
|
+
|
|
208
|
+
// Handle version flag
|
|
209
|
+
if (args.values.version) {
|
|
210
|
+
let pkgJson = JSON.parse(pkg);
|
|
211
|
+
console.error(pkgJson.version);
|
|
212
|
+
process.exit(0);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// copy the skills to directory based on the client name, so that different MCP clients can have different sets of skills if needed
|
|
216
|
+
// the -client indicates the current mcp client
|
|
217
|
+
console.error(`[Note] MCP client set to: ${process.env.CLIENT}`);
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
let client = process.env.CLIENT;
|
|
221
|
+
if (client !== 'none') {
|
|
222
|
+
console.error(`[Note] Setting up skills for client: ${client}...`);
|
|
223
|
+
setupSkills(client);
|
|
224
|
+
} else {
|
|
225
|
+
console.error(`[Note] No client specified, skipping skill setup...`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
/*
|
|
230
|
+
if (client !== 'none') {
|
|
231
|
+
let destdir = '.' + client;
|
|
232
|
+
let skillsDest = join(os.homedir(), destdir,'skills');
|
|
233
|
+
const skillsSrc = join(__dirname, '.github');
|
|
234
|
+
if (!fs.existsSync(skillsSrc)) {
|
|
235
|
+
console.error('No skills directory found in this package.');
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
239
|
+
|
|
240
|
+
const skills = fs.readdirSync(skillsSrc, { withFileTypes: true })
|
|
241
|
+
.filter(d => d.isDirectory())
|
|
242
|
+
.map(d => d.name);
|
|
243
|
+
|
|
244
|
+
if (skills.length === 0) {
|
|
245
|
+
console.error('[Note]No skills found to install.');
|
|
246
|
+
} else {
|
|
247
|
+
console.error(`Installing ${skills.length} skill(s) to ${skillsDest}...`);
|
|
248
|
+
for (const skill of skills) {
|
|
249
|
+
const src = join(skillsSrc, skill);
|
|
250
|
+
const dest = join(skillsDest, skill);
|
|
251
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
252
|
+
console.error(` installed: ${skill}`);
|
|
253
|
+
}
|
|
254
|
+
console.error(`\n installed in cli. ${client}`);
|
|
255
|
+
console.error(`\n${skills.length} skill(s) installed to ${skillsDest}`);
|
|
256
|
+
console.error('[Note] Skills are ready for use.');
|
|
257
|
+
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
53
264
|
|
|
54
|
-
if (process.env.APPHOST == null) {
|
|
55
|
-
process.env.APPHOST = 'localhost';
|
|
56
|
-
}
|
|
57
265
|
/********************************* */
|
|
58
266
|
const BRAND = 'sas-score'
|
|
59
267
|
/********************************* */
|
|
@@ -62,16 +270,16 @@ let version = pkgJson.version;
|
|
|
62
270
|
let mcpType = process.env.MCPTYPE || 'http';
|
|
63
271
|
console.error(
|
|
64
272
|
`\nStarting MCP ServerJS - Version: ${pkgJson.version} - ${new Date().toISOString()}\n
|
|
65
|
-
brand: ${process.env.BRAND || BRAND}
|
|
66
|
-
mcpType: ${mcpType}
|
|
67
|
-
viyaServer: ${process.env.VIYA_SERVER}
|
|
273
|
+
brand: ${process.env.BRAND || BRAND}
|
|
274
|
+
mcpType: ${mcpType}
|
|
275
|
+
viyaServer: ${process.env.VIYA_SERVER}`
|
|
68
276
|
);
|
|
69
277
|
// session sessionCache
|
|
70
278
|
// For more robust caching consider products like Redis
|
|
71
279
|
// and storage provided by cloud providers
|
|
72
|
-
|
|
280
|
+
|
|
73
281
|
debugger;
|
|
74
|
-
let sessionCache = new NodeCache({ stdTTL: 60*60, checkperiod: 2 * 60, useClones: false });
|
|
282
|
+
let sessionCache = new NodeCache({ stdTTL: 24 * 60 * 60, checkperiod: 2 * 60, useClones: false });
|
|
75
283
|
|
|
76
284
|
//
|
|
77
285
|
// Load environment variables from .env file if present
|
|
@@ -79,35 +287,25 @@ let sessionCache = new NodeCache({ stdTTL: 60*60, checkperiod: 2 * 60, useClones
|
|
|
79
287
|
// stdio: set the env in the mcp config
|
|
80
288
|
// http: use dotenv-cli to load env before starting the mcp server
|
|
81
289
|
|
|
82
|
-
|
|
83
|
-
// need to tell core what transport to use(http or stdio)
|
|
84
|
-
|
|
85
|
-
// subclasses for sasQuery tool (special use case)
|
|
86
|
-
// to be replaced by the planned adding external tool definition capability
|
|
87
|
-
|
|
88
|
-
let subclassJson = [];
|
|
89
|
-
if (process.env.SUBCLASS != null) {
|
|
90
|
-
console.error(`Using subclass: ${process.env.SUBCLASS}`);
|
|
91
|
-
let subclass = process.env.SUBCLASS;
|
|
92
|
-
if (fs.existsSync(subclass)) {
|
|
93
|
-
console.error(`Loading subclass information from ${subclass}...`);
|
|
94
|
-
let s = fs.readFileSync(subclass, 'utf8');
|
|
95
|
-
subclassJson = JSON.parse(s);
|
|
96
|
-
console.error(`Loaded subclass: ${JSON.stringify(subclassJson, null, 2)}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
290
|
// setup base appEnv
|
|
100
291
|
// for stdio this is the _appContext
|
|
101
292
|
// for http each session a copy of this as appEnvTemplate is created in corehttp
|
|
102
293
|
|
|
103
|
-
// backward compability variables
|
|
104
|
-
let clientID = process.env.CLIENTID || process.env.CLIENTIDPW || null;
|
|
105
|
-
let clientSecret = process.env.CLIENTSECRET || process.env.CLIENTSECRETPW || null;
|
|
106
294
|
let https = process.env.HTTPS != null ? process.env.HTTPS.toUpperCase() : "FALSE";
|
|
295
|
+
let authExternal = false;
|
|
296
|
+
let authFlow = process.env.AUTHFLOW;
|
|
297
|
+
let mcpHost = process.env.MCPHOST;
|
|
298
|
+
|
|
299
|
+
if (authFlow === 'oauth' || authFlow === 'oauthclient') {
|
|
300
|
+
authFlow = 'bearer';
|
|
301
|
+
authExternal = (authFlow === 'oauthclient') ? true : false;
|
|
302
|
+
}
|
|
107
303
|
let autoLogon = process.env.AUTOLOGON != null ? process.env.AUTOLOGON.toUpperCase() : "FALSE";
|
|
108
304
|
const appEnvBase = {
|
|
109
305
|
version: version,
|
|
110
|
-
mcpType: mcpType,
|
|
306
|
+
mcpType: mcpType,
|
|
307
|
+
mcpClient: process.env.MCPCLIENT || 'github',
|
|
308
|
+
mcpHost: (process.env.MCPHOST == null) ? 'http://localhost:8080' : process.env.MCPHOST,
|
|
111
309
|
brand: (process.env.BRAND == null) ? BRAND : process.env.BRAND,
|
|
112
310
|
HTTPS: https,
|
|
113
311
|
SAS_CLI_PROFILE: process.env.SAS_CLI_PROFILE || 'Default',
|
|
@@ -115,27 +313,27 @@ const appEnvBase = {
|
|
|
115
313
|
SSLCERT: process.env.SSLCERT || null,
|
|
116
314
|
VIYACERT: process.env.VIYACERT || null,
|
|
117
315
|
|
|
118
|
-
AUTHFLOW:
|
|
316
|
+
AUTHFLOW: authFlow,
|
|
317
|
+
AUTHEXTERNAL: authExternal,
|
|
318
|
+
BEARERTOKEN: null,
|
|
119
319
|
AUTOLOGON: autoLogon,
|
|
120
320
|
VIYA_SERVER: process.env.VIYA_SERVER,
|
|
121
321
|
PORT: process.env.PORT || 8080,
|
|
122
322
|
USERNAME: process.env.USERNAME || null,
|
|
123
323
|
PASSWORD: process.env.PASSWORD || null,
|
|
124
|
-
CLIENTID:
|
|
125
|
-
CLIENTSECRET:
|
|
324
|
+
CLIENTID: process.env.CLIENTID || null,
|
|
325
|
+
CLIENTSECRET: process.env.CLIENTSECRET || null,
|
|
126
326
|
PKCE: process.env.PKCE || null,
|
|
127
327
|
|
|
128
328
|
TOKEN: process.env.TOKEN || null,
|
|
129
329
|
REFRESH_TOKEN: process.env.REFRESH_TOKEN || null,
|
|
130
330
|
TOKENFILE: process.env.TOKENFILE || null,
|
|
131
331
|
TLS_CREATE: process.env.TLS_CREATE || null,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
? process.env.TOOLSETS.split(',')
|
|
138
|
-
: ['default'],
|
|
332
|
+
CASSERVER: process.env.CASSERVER,
|
|
333
|
+
COMPUTECONTEXT: process.env.COMPUTECONTEXT,
|
|
334
|
+
|
|
335
|
+
// command line arguments
|
|
336
|
+
cliArgs: args.values,
|
|
139
337
|
// user defined tools
|
|
140
338
|
//runtime variables
|
|
141
339
|
tokenRefresh: process.env.TOKENREFRESH === 'FALSE' ? false : true,
|
|
@@ -153,7 +351,8 @@ const appEnvBase = {
|
|
|
153
351
|
tlsOpts: null,
|
|
154
352
|
oauthInfo: null,
|
|
155
353
|
contexts: {
|
|
156
|
-
AUTHFLOW:
|
|
354
|
+
AUTHFLOW: authFlow,
|
|
355
|
+
AUTHEXTERNAL: authExternal,
|
|
157
356
|
host: process.env.VIYA_SERVER,
|
|
158
357
|
APPHOST: process.env.APPHOST || 'localhost',
|
|
159
358
|
APPNAME: process.env.APPNAME || 'sas-score-mcp-serverjs',
|
|
@@ -162,8 +361,8 @@ const appEnvBase = {
|
|
|
162
361
|
store: null, /* for restaf users */
|
|
163
362
|
storeConfig: {},
|
|
164
363
|
oauthInfo: null,
|
|
165
|
-
CLIENTID:
|
|
166
|
-
CLIENTSECRET:
|
|
364
|
+
CLIENTID: process.env.CLIENTID || null,
|
|
365
|
+
CLIENTSECRET: process.env.CLIENTSECRET || null,
|
|
167
366
|
pkce: process.env.PKCE || null,
|
|
168
367
|
casSession: null, /* restaf cas session object */
|
|
169
368
|
computeSession: null, /* restaf compute session object */
|
|
@@ -178,15 +377,14 @@ const appEnvBase = {
|
|
|
178
377
|
}
|
|
179
378
|
};
|
|
180
379
|
|
|
181
|
-
process.env.APPPORT=appEnvBase.PORT;
|
|
380
|
+
process.env.APPPORT = appEnvBase.PORT;
|
|
381
|
+
let useHapi = process.env.USEHAPI === 'TRUE' ? true : false;
|
|
382
|
+
appEnvBase.useHapi = useHapi;
|
|
182
383
|
|
|
183
384
|
// setup TLS options for viya calls
|
|
184
|
-
console.error('[Note]Viya SSL dir set to: ' + appEnvBase.VIYACERT);
|
|
185
385
|
appEnvBase.contexts.viyaCert = readCerts(appEnvBase.VIYACERT); /* appEnvBase.contexts.viyaCert is set here */
|
|
186
386
|
|
|
187
387
|
// setup TLS options for app server (expressMcpServer or hapiMcpServer)
|
|
188
|
-
|
|
189
|
-
console.error('[Note]App SSL dir set to: ' + appEnvBase.SSLCERT);
|
|
190
388
|
appEnvBase.tlsOpts = readCerts(appEnvBase.SSLCERT);
|
|
191
389
|
appEnvBase.contexts.appCert = appEnvBase.tlsOpts; /* just for completeness */
|
|
192
390
|
|
|
@@ -200,7 +398,7 @@ if (appEnvBase.TOKENFILE != null) {
|
|
|
200
398
|
console.error(`[Note]Loading token from file: ${appEnvBase.TOKENFILE}...`);
|
|
201
399
|
appEnvBase.TOKEN = fs.readFileSync(appEnvBase.TOKENFILE, { encoding: 'utf8' });
|
|
202
400
|
appEnvBase.AUTHFLOW = 'token';
|
|
203
|
-
appEnvBase.
|
|
401
|
+
appEnvBase.contexts.logonPayload = {
|
|
204
402
|
host: appEnvBase.VIYA_SERVER,
|
|
205
403
|
authType: 'server',
|
|
206
404
|
token: appEnvBase.TOKEN,
|
|
@@ -213,10 +411,6 @@ if (appEnvBase.TOKENFILE != null) {
|
|
|
213
411
|
}
|
|
214
412
|
|
|
215
413
|
|
|
216
|
-
|
|
217
|
-
// if authflow is cli or code, postpone getting logonPayload until needed
|
|
218
|
-
|
|
219
|
-
|
|
220
414
|
// setup mcpServer (both http and stdio use this)
|
|
221
415
|
// this is singleton - best practices recommend this
|
|
222
416
|
|
|
@@ -227,17 +421,64 @@ let appEnvTemplate = Object.assign({}, appEnvBase);
|
|
|
227
421
|
|
|
228
422
|
sessionCache.set('appEnvTemplate', appEnvTemplate);
|
|
229
423
|
|
|
230
|
-
|
|
424
|
+
// prime transport cache
|
|
425
|
+
let transports = {
|
|
426
|
+
"dummy": null
|
|
427
|
+
};
|
|
231
428
|
sessionCache.set('transports', transports);
|
|
429
|
+
let tokenlist = {
|
|
430
|
+
dummy: null
|
|
431
|
+
}
|
|
432
|
+
sessionCache.set('tokenlist', tokenlist);
|
|
232
433
|
|
|
233
434
|
// set this for stdio transport use
|
|
234
435
|
// dummy sessionId for use in the tools
|
|
235
|
-
|
|
236
|
-
|
|
436
|
+
|
|
437
|
+
// creat a dummy sessionId for stdio since there is only one session and transport in that case, and tools need a sessionId to access the appEnvBase and contexts
|
|
438
|
+
let sessionId = randomUUID();
|
|
439
|
+
sessionCache.set(sessionId, appEnvBase);
|
|
440
|
+
sessionCache.set('currentId', sessionId);
|
|
441
|
+
|
|
442
|
+
console.error('===================================================================');
|
|
443
|
+
console.error(`MCP ServerJS - Version: ${pkgJson.version} - ${new Date().toISOString()}`);
|
|
444
|
+
console.error(`
|
|
445
|
+
Usage: sas-score-mcp-serverjs [options]
|
|
446
|
+
|
|
447
|
+
Options:
|
|
448
|
+
Minimal options:
|
|
449
|
+
VIYA_SERVER ${appEnvBase.VIYA_SERVER}
|
|
450
|
+
CLIENTID ${appEnvBase.CLIENTID}
|
|
451
|
+
|
|
452
|
+
MCP server options:
|
|
453
|
+
MCPTYPE ${appEnvBase.mcpType}
|
|
454
|
+
MCPHOST ${appEnvBase.mcpHost}
|
|
455
|
+
PORT ${appEnvBase.PORT}
|
|
456
|
+
HTTPS ${appEnvBase.contexts.HTTPS}
|
|
457
|
+
CLIENT ${appEnvBase.mcpClient}
|
|
458
|
+
|
|
459
|
+
Authentication options:
|
|
460
|
+
AUTHFLOW ${process.env.AUTHFLOW}
|
|
461
|
+
CLIENTSECRET ${appEnvBase.CLIENTSECRET}
|
|
462
|
+
PROFILE ${appEnvBase.SAS_CLI_PROFILE}
|
|
463
|
+
CONFIG ${appEnvBase.SAS_CLI_CONFIG}
|
|
464
|
+
|
|
465
|
+
Other options:
|
|
466
|
+
CASSERVER ${appEnvBase.CASSERVER}
|
|
467
|
+
COMPUTECONTEXT ${appEnvBase.COMPUTECONTEXT}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
`);
|
|
471
|
+
|
|
472
|
+
console.error('===================================================================');
|
|
473
|
+
console.error(`
|
|
474
|
+
[Note] The SAS Viya Scoring Expert agent has been installed successfully.
|
|
475
|
+
Depending on the client you are using, the agent might not be active
|
|
476
|
+
If the agent does not appear in the agent dropdown list your options are:
|
|
477
|
+
- use the /subagent command
|
|
478
|
+
- exit this app and issue the npx command to restart the server
|
|
479
|
+
`);
|
|
480
|
+
|
|
237
481
|
if (mcpType === 'stdio') {
|
|
238
|
-
let sessionId = randomUUID();
|
|
239
|
-
sessionCache.set('currentId', sessionId);
|
|
240
|
-
sessionCache.set(sessionId, appEnvBase);
|
|
241
482
|
console.error('[Note] Setting up stdio transport with sessionId:', sessionId);
|
|
242
483
|
console.error('[Note] Used in setting up tools and some persistence(not all).');
|
|
243
484
|
await coreSSE(mcpServer);
|
|
@@ -245,12 +486,12 @@ if (mcpType === 'stdio') {
|
|
|
245
486
|
} else {
|
|
246
487
|
console.error('[Note] Starting HTTP MCP server...');
|
|
247
488
|
if (useHapi === true) {
|
|
248
|
-
process.env.
|
|
489
|
+
process.env.AUTHTYPE = null;
|
|
249
490
|
await hapiMcpServer(mcpServer, sessionCache, appEnvBase);
|
|
250
491
|
console.error('[Note] Using HAPI HTTP server...')
|
|
251
492
|
} else {
|
|
252
493
|
await expressMcpServer(mcpServer, sessionCache, appEnvBase);
|
|
253
|
-
console.error('[Note] MCP HTTP server started on port ' + appEnvBase.PORT);
|
|
494
|
+
console.error('[Note] MCP HTTP express server started on port ' + appEnvBase.PORT);
|
|
254
495
|
}
|
|
255
496
|
}
|
|
256
497
|
|
|
@@ -273,7 +514,7 @@ function iconfig(envFile) {
|
|
|
273
514
|
});
|
|
274
515
|
return envData;
|
|
275
516
|
} catch (err) {
|
|
276
|
-
console.
|
|
517
|
+
console.error(err);
|
|
277
518
|
process.exit(0);
|
|
278
519
|
}
|
|
279
520
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sassoftware/sas-score-mcp-serverjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1-0",
|
|
4
4
|
"description": "A mcp server for SAS Viya",
|
|
5
5
|
"author": "Deva Kumar <deva.kumar@sas.com>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"deploy": "bash ./deploy.sh",
|
|
19
19
|
"push2acr": "cd docker && bash ./push2acr.sh",
|
|
20
20
|
"bump": "npm version prerelease",
|
|
21
|
-
"pub": "npm publish --tag dev --access public"
|
|
21
|
+
"pub": "npm publish --tag dev --access public",
|
|
22
|
+
"setup-skills": "node scripts/setup-skills.js"
|
|
22
23
|
},
|
|
23
24
|
"repository": "https://github.com/sassoftware/sas-score-mcp-serverjs",
|
|
24
25
|
"keywords": [
|
|
@@ -39,10 +40,12 @@
|
|
|
39
40
|
"src",
|
|
40
41
|
"cli.js",
|
|
41
42
|
"openApi.json",
|
|
42
|
-
"openApi.yaml"
|
|
43
|
+
"openApi.yaml",
|
|
44
|
+
"scripts",
|
|
45
|
+
".skills"
|
|
43
46
|
],
|
|
44
47
|
"dependencies": {
|
|
45
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
49
|
"@sassoftware/restaf": "^5.6.0",
|
|
47
50
|
"@sassoftware/restafedit": "^3.11.1-10",
|
|
48
51
|
"@sassoftware/restaflib": "^5.6.0",
|
|
@@ -56,10 +59,8 @@
|
|
|
56
59
|
"express-list-endpoints": "^7.1.1",
|
|
57
60
|
"express-rate-limit": "^8.2.1",
|
|
58
61
|
"helmet": "^8.1.0",
|
|
59
|
-
"mcp-framework": "^0.2.16",
|
|
60
62
|
"node-cache": "^5.1.2",
|
|
61
63
|
"open": "^11.0.0",
|
|
62
|
-
"puppeteer": "^24.34.0",
|
|
63
64
|
"selfsigned": "^5.2.0",
|
|
64
65
|
"undici": "^7.16.0",
|
|
65
66
|
"uuid": "^13.0.0",
|
|
@@ -72,7 +73,6 @@
|
|
|
72
73
|
"@babel/preset-env": "^7.28.5",
|
|
73
74
|
"@types/debug": "^4.1.12",
|
|
74
75
|
"@types/node": "^25.0.3",
|
|
75
|
-
"npm-check-updates": "^19.2.0",
|
|
76
76
|
"rimraf": "^6.1.2",
|
|
77
77
|
"typescript": "^5.9.3"
|
|
78
78
|
}
|