@sassoftware/sas-score-mcp-serverjs 0.4.1-1 → 0.4.1-17

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 (61) hide show
  1. package/cli.js +210 -108
  2. package/package.json +6 -4
  3. package/scripts/docs/SCORE_SKILL_REFERENCE.md +142 -0
  4. package/scripts/docs/TOOL_DESCRIPTION_TEMPLATE.md +157 -0
  5. package/scripts/docs/TOOL_UPDATES_SUMMARY.md +208 -0
  6. package/scripts/docs/mcp-localhost-config-guide.md +184 -0
  7. package/scripts/docs/oauth-http-transport.md +96 -0
  8. package/scripts/docs/sas-mcp-tools-reference.md +600 -0
  9. package/scripts/getViyaca.sh +1 -0
  10. package/scripts/optimize_final.py +140 -0
  11. package/scripts/optimize_tools.py +99 -0
  12. package/scripts/setup-skills.js +78 -0
  13. package/scripts/update_descriptions.py +46 -0
  14. package/scripts/viyatls.sh +3 -0
  15. package/skills/sas-find-library-smart/SKILL.md +154 -0
  16. package/skills/sas-list-tables-smart/SKILL.md +127 -0
  17. package/skills/sas-read-and-score/SKILL.md +71 -51
  18. package/skills/sas-read-strategy/SKILL.md +43 -30
  19. package/skills/sas-score-workflow/SKILL.md +65 -34
  20. package/skills/sas-spec-migration/SKILL.md +303 -0
  21. package/src/authpkce.js +219 -0
  22. package/src/createMcpServer.js +11 -8
  23. package/src/expressMcpServer.js +354 -338
  24. package/src/oauthHandlers/authorize.js +46 -0
  25. package/src/oauthHandlers/baseUrl.js +8 -0
  26. package/src/oauthHandlers/callback.js +96 -0
  27. package/src/oauthHandlers/getMetadata.js +27 -0
  28. package/src/oauthHandlers/index.js +7 -0
  29. package/src/oauthHandlers/token.js +37 -0
  30. package/src/processHeaders.js +88 -0
  31. package/src/toolHelpers/_listLibrary.js +0 -1
  32. package/src/toolHelpers/getLogonPayload.js +5 -1
  33. package/src/toolHelpers/refreshToken.js +3 -2
  34. package/src/toolHelpers/refreshTokenOauth.js +3 -3
  35. package/src/toolSet/.claude/settings.local.json +13 -0
  36. package/src/toolSet/devaScore.js +61 -61
  37. package/src/toolSet/findJob.js +1 -1
  38. package/src/toolSet/findJobdef.js +2 -2
  39. package/src/toolSet/findLibrary.js +68 -67
  40. package/src/toolSet/findModel.js +2 -2
  41. package/src/toolSet/findTable.js +3 -2
  42. package/src/toolSet/getEnv.js +8 -4
  43. package/src/toolSet/listJobdefs.js +61 -61
  44. package/src/toolSet/listJobs.js +61 -61
  45. package/src/toolSet/listLibraries.js +78 -78
  46. package/src/toolSet/listModels.js +56 -56
  47. package/src/toolSet/listTables.js +66 -65
  48. package/src/toolSet/modelInfo.js +2 -2
  49. package/src/toolSet/modelScore.js +6 -5
  50. package/src/toolSet/readTable.js +63 -65
  51. package/src/toolSet/runCasProgram.js +7 -6
  52. package/src/toolSet/runJob.js +81 -81
  53. package/src/toolSet/runJobdef.js +82 -82
  54. package/src/toolSet/runMacro.js +81 -80
  55. package/src/toolSet/runProgram.js +4 -8
  56. package/src/toolSet/sasQuery.js +77 -78
  57. package/src/toolSet/scrInfo.js +1 -1
  58. package/src/toolSet/scrScore.js +69 -68
  59. package/src/toolSet/setContext.js +65 -65
  60. package/src/toolSet/superstat.js +61 -59
  61. package/src/toolSet/tableInfo.js +58 -57
package/cli.js CHANGED
@@ -21,10 +21,12 @@ 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';
25
26
  import { parseArgs } from "node:util";
26
27
 
27
28
  import NodeCache from 'node-cache';
29
+ import { be } from 'zod/locales';
28
30
  //import getOpts from './src/toolHelpers/getOpts.js';
29
31
 
30
32
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -44,15 +46,40 @@ const args = parseArgs({
44
46
  short: 'm',
45
47
  description: 'MCP server type (http or stdio)'
46
48
  },
49
+ https: {
50
+ type: 'boolean',
51
+ description: 'Use HTTPS for the server (default: FALSE)'
52
+ },
53
+ 'skills-folder': {
54
+ type: 'string',
55
+ short: 'f',
56
+ description: 'Skills folder name'
57
+ },
58
+
47
59
  viya: {
48
60
  type: 'string',
49
61
  short: 'v',
50
62
  description: 'Viya server URL'
51
63
  },
64
+ mcphost: {
65
+ type: 'string',
66
+ short: 'm',
67
+ description: 'MCP server host (default: http://localhost:8080)'
68
+ },
52
69
  authflow: {
53
70
  type: 'string',
54
71
  short: 'a',
55
- description: 'Authentication flow (sascli, code, token)'
72
+ description: 'Authentication flow (sascli, code, token, oauth, oauth,oauthclient)'
73
+ },
74
+ clientid: {
75
+ type: 'string',
76
+ short: 'c',
77
+ description: 'Client ID for authentication'
78
+ },
79
+ clientsecret: {
80
+ type: 'string',
81
+ short: 's',
82
+ description: 'Client Secret for authentication'
56
83
  },
57
84
  profile: {
58
85
  type: 'string',
@@ -62,10 +89,22 @@ const args = parseArgs({
62
89
  type: 'string',
63
90
  description: 'SAS CLI config directory'
64
91
  },
65
- envfile: {
92
+ casserver: {
93
+ type: 'string',
94
+ description: 'CAS server name (default: cas-shared-default)'
95
+ },
96
+ computecontext: {
97
+ type: 'string',
98
+ description: 'Compute session name or context (default: SAS Job Execution compute context)'
99
+ },
100
+ env: {
66
101
  type: 'string',
67
102
  short: 'e',
68
- description: 'Environment file path'
103
+ description: 'Environment file path (default: .env in current working directory)'
104
+ },
105
+ client: {
106
+ type: 'string',
107
+ description: 'MCP client name (github, claude...). Defaults to \'github\''
69
108
  },
70
109
  help: {
71
110
  type: 'boolean',
@@ -74,9 +113,9 @@ const args = parseArgs({
74
113
  },
75
114
  version: {
76
115
  type: 'boolean',
77
- short: 'v',
78
116
  description: 'Show version'
79
- }
117
+ },
118
+
80
119
  },
81
120
  strict: false,
82
121
  allowPositionals: false
@@ -84,89 +123,131 @@ const args = parseArgs({
84
123
 
85
124
  // Handle help flag
86
125
  if (args.values.help) {
87
- console.log(`
126
+ console.error(`
88
127
  Usage: sas-score-mcp-serverjs [options]
89
128
 
90
129
  Options:
91
- -p, --port <port> Port to run the server on (default: 8080)
92
- -m, --mcptype <type> MCP server type: http or stdio (default: http)
93
- -v, --viya <url> Viya server URL
94
- -a, --authflow <flow> Authentication flow: sascli, code, or token
95
- --profile <name> SAS CLI profile name
96
- --config <path> SAS CLI config directory
97
- -e, --envfile <path> Environment file path
98
- -h, --help Show this help message
99
- --version Show version
130
+ Minimal options:
131
+ -v, --viya <url> Viya server URL
132
+ -c, --clientid <id> Client ID for oauth authentication(pkce preferred)
133
+
134
+ MCP server options:
135
+ -t, --mcptype <type> MCP server type: http or stdio (default: http)
136
+ -m, --mcphost <host> MCP server host - can be remote URL - (default: http://localhost:8080)
137
+ --client <name> MCP client name (github, claude...). Defaults to 'github'.Use to install skills
138
+ Authentication options:
139
+ -a, --authflow <flow> Authentication flow: oauth, oauthclient, sascli, code, token(default oauth)
140
+ -s, --clientsecret <secret> Client Secret for authentication(if necessary). See clientid option as well.
141
+ --profile <name> SAS CLI profile name for sascli flow (default: Default)
142
+ --config <path> SAS CLI config directory for sascli flow (default: user home directory)
143
+
144
+ Other options:
145
+ -p, --port <port> Port to run the server on (default: 8080)
146
+ --https Use HTTPS for the server (default: false)
147
+ --casserver <name> CAS server name (default: cas-shared-default)
148
+ --computecontext <name> Compute session name or context (default: SAS Job Execution compute context)
149
+
150
+ -e, --envfile <path> Environment file path
151
+ -h, --help Show this help message
152
+ --version Show version
153
+
100
154
 
101
155
  Environment Variables:
102
156
  Use .env file or set environment variables for configuration.
157
+ A alternative to cmd line arguments, and in some cases required for sensitive information like client secrets.
103
158
  See README.md for more information.
104
159
  `);
105
160
  process.exit(0);
106
161
  }
107
162
 
108
- // Handle version flag
109
- if (args.values.version) {
110
- let pkgJson = JSON.parse(pkg);
111
- console.log(pkgJson.version);
112
- process.exit(0);
113
- }
114
-
115
- if (process.env.ENVFILE === 'FALSE') {
116
- //use this when using remote mcp server and no .env file is desired
117
- console.error('[Note]: Skipping .env file as ENVFILE is set to FALSE...');
118
- } else {
163
+ // read env file and then override with command line arguments
164
+ if (args.values.env) {
119
165
  console.error('Working Directory', process.cwd());
120
- let envf = process.env.ENVFILE || (process.cwd() + '\\.env');
166
+ let envf = process.cwd() + '\\' + args.values.env;
121
167
  //__dirname + '\\.env';
122
- console.error('Env file:', envf);
168
+ console.error('Env file:', envf);
123
169
  if (fs.existsSync(envf)) {
124
170
  console.error(`Loading environment variables from ${envf}...`);
125
- let e = iconfig(envf); // avoid dotenv since it writes to console.log
171
+ let e = iconfig(envf); // avoid dotenv since it writes to console.error
126
172
  console.error('[Note]: Environment variables loaded from .env file...');
127
173
  console.error('Loaded env variables:', e);
128
174
  // dotenvExpand.expand(e);
129
175
  } else {
130
176
  console.error(
131
- '[Note]: No .env file found, Using default environment variables...'
177
+ '[Note]: No env file found, Using default environment variables...'
132
178
  );
133
179
  }
134
180
  }
135
-
136
181
  // Apply command line arguments to override environment variables
137
- if (args.values.port) {
138
- process.env.PORT = args.values.port;
139
- console.error(`[Note] PORT set from command line: ${args.values.port}`);
140
- }
141
182
 
142
- if (args.values.mcptype) {
143
- process.env.MCPTYPE = args.values.mcptype;
144
- console.error(`[Note] MCPTYPE set from command line: ${args.values.mcptype}`);
145
- }
183
+ process.env.PORT = process.env.PORT || '8080';
184
+ process.env.HTTPS = (args.values.https) ? 'TRUE' : 'FALSE';
185
+ process.env.MCPTYPE = args.values.mcptype || process.env.MCPTYPE || 'http';
186
+ process.env.MCPHOST = args.values.mcphost || process.env.MCPHOST || 'http://localhost:8080';
187
+ process.env.AUTHFLOW = args.values.authflow || process.env.AUTHFLOW || 'oauth';
188
+ process.env.MCPCLIENT = args.values.client || process.env.MCPCLIENT || 'github';
189
+ process.env.VIYA_SERVER = args.values.viya || process.env.VIYA_SERVER || null;
190
+ process.env.CLIENTID = args.values.clientid || process.env.CLIENTID || 'vscodemcp';
191
+ process.env.CLIENTSECRET = args.values.clientsecret || process.env.CLIENTSECRET || null;
192
+ process.env.SAS_CLI_PROFILE = args.values.profile || process.env.SAS_CLI_PROFILE || 'Default';
193
+ process.env.SAS_CLI_CONFIG = args.values.config || process.env.SAS_CLI_CONFIG || process.env.HOME; // default to user home directory
194
+ process.env.CASSERVER = args.values.casserver || process.env.CASSERVER || 'cas-shared-default';
195
+ process.env.COMPUTECONTEXT = args.values.computecontext || process.env.COMPUTECONTEXT || 'SAS Job Execution compute context';
196
+ process.env.APPHOST = 'localhost';
197
+ process.env.CLIENT = args.values.client || process.env.CLIENT || 'github';
146
198
 
147
- if (args.values.viya) {
148
- process.env.VIYA_SERVER = args.values.viya;
149
- console.error(`[Note] VIYA_SERVER set from command line: ${args.values.viya}`);
150
- }
151
199
 
152
- if (args.values.authflow) {
153
- process.env.AUTHFLOW = args.values.authflow;
154
- console.error(`[Note] AUTHFLOW set from command line: ${args.values.authflow}`);
155
- }
156
200
 
157
- if (args.values.profile) {
158
- process.env.SAS_CLI_PROFILE = args.values.profile;
159
- console.error(`[Note] SAS_CLI_PROFILE set from command line: ${args.values.profile}`);
201
+ process.env.SAMESITE = 'Lax,secure';
202
+ process.env.APPHOST = '0.0.0.0';
203
+ process.env.APPNAME = 'sas-score-mcp-serverjs';
204
+
205
+ // Handle version flag
206
+ if (args.values.version) {
207
+ let pkgJson = JSON.parse(pkg);
208
+ console.error(pkgJson.version);
209
+ process.exit(0);
160
210
  }
161
211
 
162
- if (args.values.config) {
163
- process.env.SAS_CLI_CONFIG = args.values.config;
164
- console.error(`[Note] SAS_CLI_CONFIG set from command line: ${args.values.config}`);
212
+ // copy the skills to directory based on the client name, so that different MCP clients can have different sets of skills if needed
213
+ // the -client indicates the current mcp client
214
+ console.error(`[Note] MCP client set to: ${process.env.CLIENT}`);
215
+
216
+ let client = process.env.CLIENT;
217
+ if (client != null) {
218
+ let destdir = '.' + client;
219
+ let skillsDest = join(os.homedir(), destdir,'skills');
220
+ const skillsSrc = join(__dirname, 'skills');
221
+ if (!fs.existsSync(skillsSrc)) {
222
+ console.error('No skills directory found in this package.');
223
+ process.exit(1);
224
+ }
225
+ fs.mkdirSync(skillsDest, { recursive: true });
226
+
227
+ const skills = fs.readdirSync(skillsSrc, { withFileTypes: true })
228
+ .filter(d => d.isDirectory())
229
+ .map(d => d.name);
230
+
231
+ if (skills.length === 0) {
232
+ console.error('[Note]No skills found to install.');
233
+ } else {
234
+ console.error(`Installing ${skills.length} skill(s) to ${skillsDest}...`);
235
+ for (const skill of skills) {
236
+ const src = join(skillsSrc, skill);
237
+ const dest = join(skillsDest, skill);
238
+ fs.cpSync(src, dest, { recursive: true });
239
+ console.error(` installed: ${skill}`);
240
+ }
241
+ console.error(`\n installed in cli. ${client}`);
242
+ console.error(`\n${skills.length} skill(s) installed to ${skillsDest}`);
243
+ console.error('[Note] Skills are ready for use.');
244
+
245
+ }
165
246
  }
166
247
 
167
- if (process.env.APPHOST == null) {
168
- process.env.APPHOST = 'localhost';
169
- }
248
+
249
+
250
+
170
251
  /********************************* */
171
252
  const BRAND = 'sas-score'
172
253
  /********************************* */
@@ -175,16 +256,16 @@ let version = pkgJson.version;
175
256
  let mcpType = process.env.MCPTYPE || 'http';
176
257
  console.error(
177
258
  `\nStarting MCP ServerJS - Version: ${pkgJson.version} - ${new Date().toISOString()}\n
178
- brand: ${process.env.BRAND || BRAND}\n
179
- mcpType: ${mcpType}\n
180
- viyaServer: ${process.env.VIYA_SERVER}\n`
259
+ brand: ${process.env.BRAND || BRAND}
260
+ mcpType: ${mcpType}
261
+ viyaServer: ${process.env.VIYA_SERVER}`
181
262
  );
182
263
  // session sessionCache
183
264
  // For more robust caching consider products like Redis
184
265
  // and storage provided by cloud providers
185
- console.error(process.env.COMPUTECONTEXT);
266
+
186
267
  debugger;
187
- let sessionCache = new NodeCache({ stdTTL: 24 *60*60, checkperiod: 2 * 60, useClones: false });
268
+ let sessionCache = new NodeCache({ stdTTL: 24 * 60 * 60, checkperiod: 2 * 60, useClones: false });
188
269
 
189
270
  //
190
271
  // Load environment variables from .env file if present
@@ -192,35 +273,25 @@ let sessionCache = new NodeCache({ stdTTL: 24 *60*60, checkperiod: 2 * 60, useCl
192
273
  // stdio: set the env in the mcp config
193
274
  // http: use dotenv-cli to load env before starting the mcp server
194
275
 
195
-
196
- // need to tell core what transport to use(http or stdio)
197
-
198
- // subclasses for sasQuery tool (special use case)
199
- // to be replaced by the planned adding external tool definition capability
200
-
201
- let subclassJson = [];
202
- if (process.env.SUBCLASS != null) {
203
- console.error(`Using subclass: ${process.env.SUBCLASS}`);
204
- let subclass = process.env.SUBCLASS;
205
- if (fs.existsSync(subclass)) {
206
- console.error(`Loading subclass information from ${subclass}...`);
207
- let s = fs.readFileSync(subclass, 'utf8');
208
- subclassJson = JSON.parse(s);
209
- console.error(`Loaded subclass: ${JSON.stringify(subclassJson, null, 2)}`);
210
- }
211
- }
212
276
  // setup base appEnv
213
277
  // for stdio this is the _appContext
214
278
  // for http each session a copy of this as appEnvTemplate is created in corehttp
215
279
 
216
- // backward compability variables
217
- let clientID = process.env.CLIENTID || process.env.CLIENTIDPW || null;
218
- let clientSecret = process.env.CLIENTSECRET || process.env.CLIENTSECRETPW || null;
219
280
  let https = process.env.HTTPS != null ? process.env.HTTPS.toUpperCase() : "FALSE";
281
+ let authExternal = false;
282
+ let authFlow = process.env.AUTHFLOW;
283
+ let mcpHost = process.env.MCPHOST;
284
+
285
+ if (authFlow === 'oauth' || authFlow === 'oauthclient') {
286
+ authFlow = 'bearer';
287
+ authExternal = (authFlow === 'oauthclient') ? true : false;
288
+ }
220
289
  let autoLogon = process.env.AUTOLOGON != null ? process.env.AUTOLOGON.toUpperCase() : "FALSE";
221
290
  const appEnvBase = {
222
291
  version: version,
223
- mcpType: mcpType,
292
+ mcpType: mcpType,
293
+ mcpClient: process.env.MCPCLIENT || 'github',
294
+ mcpHost: (process.env.MCPHOST == null) ? 'http://localhost:8080' : process.env.MCPHOST,
224
295
  brand: (process.env.BRAND == null) ? BRAND : process.env.BRAND,
225
296
  HTTPS: https,
226
297
  SAS_CLI_PROFILE: process.env.SAS_CLI_PROFILE || 'Default',
@@ -228,27 +299,25 @@ const appEnvBase = {
228
299
  SSLCERT: process.env.SSLCERT || null,
229
300
  VIYACERT: process.env.VIYACERT || null,
230
301
 
231
- AUTHFLOW: process.env.AUTHFLOW || 'sascli',
302
+ AUTHFLOW: authFlow,
303
+ AUTHEXTERNAL: authExternal,
304
+ BEARERTOKEN: null,
232
305
  AUTOLOGON: autoLogon,
233
306
  VIYA_SERVER: process.env.VIYA_SERVER,
234
307
  PORT: process.env.PORT || 8080,
235
308
  USERNAME: process.env.USERNAME || null,
236
309
  PASSWORD: process.env.PASSWORD || null,
237
- CLIENTID: clientID,
238
- CLIENTSECRET: clientSecret,
310
+ CLIENTID: process.env.CLIENTID || null,
311
+ CLIENTSECRET: process.env.CLIENTSECRET || null,
239
312
  PKCE: process.env.PKCE || null,
240
313
 
241
314
  TOKEN: process.env.TOKEN || null,
242
315
  REFRESH_TOKEN: process.env.REFRESH_TOKEN || null,
243
316
  TOKENFILE: process.env.TOKENFILE || null,
244
317
  TLS_CREATE: process.env.TLS_CREATE || null,
245
- SUBCLASS: process.env.SUBCLASS || null,
246
- subclassJson: subclassJson,
247
- // future use for controlling tool list using env variable
248
- toolsets:
249
- process.env.TOOLSETS != null
250
- ? process.env.TOOLSETS.split(',')
251
- : ['default'],
318
+ CASSERVER: process.env.CASSERVER,
319
+ COMPUTECONTEXT: process.env.COMPUTECONTEXT,
320
+
252
321
  // command line arguments
253
322
  cliArgs: args.values,
254
323
  // user defined tools
@@ -268,7 +337,8 @@ const appEnvBase = {
268
337
  tlsOpts: null,
269
338
  oauthInfo: null,
270
339
  contexts: {
271
- AUTHFLOW: process.env.AUTHFLOW || 'sascli',
340
+ AUTHFLOW: authFlow,
341
+ AUTHEXTERNAL: authExternal,
272
342
  host: process.env.VIYA_SERVER,
273
343
  APPHOST: process.env.APPHOST || 'localhost',
274
344
  APPNAME: process.env.APPNAME || 'sas-score-mcp-serverjs',
@@ -277,8 +347,8 @@ const appEnvBase = {
277
347
  store: null, /* for restaf users */
278
348
  storeConfig: {},
279
349
  oauthInfo: null,
280
- CLIENTID: clientID,
281
- CLIENTSECRET: clientSecret,
350
+ CLIENTID: process.env.CLIENTID || null,
351
+ CLIENTSECRET: process.env.CLIENTSECRET || null,
282
352
  pkce: process.env.PKCE || null,
283
353
  casSession: null, /* restaf cas session object */
284
354
  computeSession: null, /* restaf compute session object */
@@ -293,15 +363,14 @@ const appEnvBase = {
293
363
  }
294
364
  };
295
365
 
296
- process.env.APPPORT=appEnvBase.PORT;
366
+ process.env.APPPORT = appEnvBase.PORT;
367
+ let useHapi = process.env.USEHAPI === 'TRUE' ? true : false;
368
+ appEnvBase.useHapi = useHapi;
297
369
 
298
370
  // setup TLS options for viya calls
299
- console.error('[Note]Viya SSL dir set to: ' + appEnvBase.VIYACERT);
300
371
  appEnvBase.contexts.viyaCert = readCerts(appEnvBase.VIYACERT); /* appEnvBase.contexts.viyaCert is set here */
301
372
 
302
373
  // setup TLS options for app server (expressMcpServer or hapiMcpServer)
303
-
304
- console.error('[Note]App SSL dir set to: ' + appEnvBase.SSLCERT);
305
374
  appEnvBase.tlsOpts = readCerts(appEnvBase.SSLCERT);
306
375
  appEnvBase.contexts.appCert = appEnvBase.tlsOpts; /* just for completeness */
307
376
 
@@ -315,7 +384,7 @@ if (appEnvBase.TOKENFILE != null) {
315
384
  console.error(`[Note]Loading token from file: ${appEnvBase.TOKENFILE}...`);
316
385
  appEnvBase.TOKEN = fs.readFileSync(appEnvBase.TOKENFILE, { encoding: 'utf8' });
317
386
  appEnvBase.AUTHFLOW = 'token';
318
- appEnvBase.appContexts.logonPayload = {
387
+ appEnvBase.contexts.logonPayload = {
319
388
  host: appEnvBase.VIYA_SERVER,
320
389
  authType: 'server',
321
390
  token: appEnvBase.TOKEN,
@@ -328,10 +397,6 @@ if (appEnvBase.TOKENFILE != null) {
328
397
  }
329
398
 
330
399
 
331
-
332
- // if authflow is cli or code, postpone getting logonPayload until needed
333
-
334
-
335
400
  // setup mcpServer (both http and stdio use this)
336
401
  // this is singleton - best practices recommend this
337
402
 
@@ -342,18 +407,55 @@ let appEnvTemplate = Object.assign({}, appEnvBase);
342
407
 
343
408
  sessionCache.set('appEnvTemplate', appEnvTemplate);
344
409
 
410
+ // prime transport cache
345
411
  let transports = {
346
412
  "dummy": null
347
413
  };
348
414
  sessionCache.set('transports', transports);
415
+ let tokenlist = {
416
+ dummy: null
417
+ }
418
+ sessionCache.set('tokenlist', tokenlist);
349
419
 
350
420
  // set this for stdio transport use
351
421
  // dummy sessionId for use in the tools
352
- let useHapi = process.env.AUTHFLOW === 'code' ? true : false;
353
- console.error('[Note] appEnvBase is', JSON.stringify(appEnvBase, null,2));
422
+
354
423
  // 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
355
424
  let sessionId = randomUUID();
356
425
  sessionCache.set(sessionId, appEnvBase);
426
+ sessionCache.set('currentId', sessionId);
427
+
428
+ console.error('===================================================================');
429
+ console.error(`MCP ServerJS - Version: ${pkgJson.version} - ${new Date().toISOString()}`);
430
+ console.error(`
431
+ Usage: sas-score-mcp-serverjs [options]
432
+
433
+ Options:
434
+ Minimal options:
435
+ VIYA_SERVER ${appEnvBase.VIYA_SERVER}
436
+ CLIENTID ${appEnvBase.CLIENTID}
437
+
438
+ MCP server options:
439
+ MCPTYPE ${appEnvBase.mcpType}
440
+ MCPHOST ${appEnvBase.mcpHost}
441
+ PORT ${appEnvBase.PORT}
442
+ HTTPS ${appEnvBase.contexts.HTTPS}
443
+ CLIENT ${appEnvBase.mcpClient}
444
+
445
+ Authentication options:
446
+ AUTHFLOW ${process.env.AUTHFLOW}
447
+ CLIENTSECRET ${appEnvBase.CLIENTSECRET}
448
+ PROFILE ${appEnvBase.SAS_CLI_PROFILE}
449
+ CONFIG ${appEnvBase.SAS_CLI_CONFIG}
450
+
451
+ Other options:
452
+ CASSERVER ${appEnvBase.CASSERVER}
453
+ COMPUTECONTEXT ${appEnvBase.COMPUTECONTEXT}
454
+ }
455
+
456
+ `);
457
+ console
458
+ debugger;
357
459
  if (mcpType === 'stdio') {
358
460
  console.error('[Note] Setting up stdio transport with sessionId:', sessionId);
359
461
  console.error('[Note] Used in setting up tools and some persistence(not all).');
@@ -390,7 +492,7 @@ function iconfig(envFile) {
390
492
  });
391
493
  return envData;
392
494
  } catch (err) {
393
- console.log(err);
495
+ console.error(err);
394
496
  process.exit(0);
395
497
  }
396
498
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sassoftware/sas-score-mcp-serverjs",
3
- "version": "0.4.1-1",
3
+ "version": "0.4.1-17",
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,9 @@
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
+ "postinstall": "node scripts/setup-skills.js",
23
+ "setup-skills": "node scripts/setup-skills.js"
22
24
  },
23
25
  "repository": "https://github.com/sassoftware/sas-score-mcp-serverjs",
24
26
  "keywords": [
@@ -40,7 +42,8 @@
40
42
  "cli.js",
41
43
  "openApi.json",
42
44
  "openApi.yaml",
43
- "skills"
45
+ "skills",
46
+ "scripts"
44
47
  ],
45
48
  "dependencies": {
46
49
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -71,7 +74,6 @@
71
74
  "@babel/preset-env": "^7.28.5",
72
75
  "@types/debug": "^4.1.12",
73
76
  "@types/node": "^25.0.3",
74
- "npm-check-updates": "^19.2.0",
75
77
  "rimraf": "^6.1.2",
76
78
  "typescript": "^5.9.3"
77
79
  }
@@ -0,0 +1,142 @@
1
+ # Score Skill Documentation
2
+
3
+ ## Overview
4
+ The `score` skill is a generic scoring interface that automatically routes scoring requests to the appropriate tool based on the model type specified in the request.
5
+
6
+ ## Syntax
7
+ ```
8
+ score with model <name>.<type> [scenario =<key=value pairs>]
9
+ score <name>.<type> [scenario =<key=value pairs>]
10
+ ```
11
+
12
+ ## Supported Types
13
+ - **job** — Route to `run-job` for job-based scoring
14
+ - **jobdef** — Route to `run-jobdef` for job definition-based scoring
15
+ - **mas** — Route to `model-score` (Model Aggregation Service)
16
+ - **scr** — Route to `scr-score` (Score Code Runtime container)
17
+ - **sas** — Route to `run-sas-program` (arbitrary SAS/SQL scoring)
18
+
19
+ ## Usage Examples
20
+
21
+ ### MAS Model Scoring
22
+ ```
23
+ score with model churn.mas where scenario =age=45,income=60000
24
+ score mymodel.mas using age=45, income=60000
25
+ ```
26
+ Routes to: `model-score` with model name and scenario parameters
27
+
28
+ ### Job-Based Scoring
29
+ ```
30
+ score with model monthly_scorer.job scenario =month=10,year=2025
31
+ score mymodel.job with month=10, year=2025
32
+ ```
33
+ Routes to: `run-job` with job name and parameters
34
+
35
+ ### Job Definition Scoring
36
+ ```
37
+ score fraud_detector.jobdef using amount=500,merchant=online
38
+ score predictions.jobdef where scenario =x=1,y=2
39
+ ```
40
+ Routes to: `run-jobdef` with jobdef name and parameters
41
+
42
+ ### SCR (Score Code Runtime) Scoring
43
+ ```
44
+ score https://scr-host/models/loan.scr using age=45,credit_score=700
45
+ score mymodel.scr where scenario =age=45,income=60000
46
+ ```
47
+ Routes to: `scr-score` with SCR URL and scenario
48
+
49
+ ### SAS Program Scoring
50
+ ```
51
+ score predictions.sas where scenario =x=1,y=2
52
+ score my_scoring_code.sas using month=10,year=2025
53
+ ```
54
+ Routes to: `run-sas-program` with scenario parameters
55
+
56
+ ## Parameter Details
57
+
58
+ | Parameter | Required | Type | Description |
59
+ |-----------|----------|------|-------------|
60
+ | `model` | Yes | string | Model name with type suffix (e.g., `mymodel.mas`) |
61
+ | `scenario` | No | string\|object\|array | Input data as comma-separated key=value pairs |
62
+ | `type` | No | string | Type override (inferred from model name if not specified) |
63
+ | `prompt` | No | string | Full prompt for context |
64
+ | `context_data` | No | object | Contextual variables for fallback |
65
+
66
+ ## Scenario Format
67
+ The scenario parameter accepts multiple formats:
68
+
69
+ **String format** (comma-separated):
70
+ ```
71
+ age=45,income=60000,credit=700
72
+ ```
73
+
74
+ **Object format**:
75
+ ```javascript
76
+ {age: 45, income: 60000, credit: 700}
77
+ ```
78
+
79
+ **Array format** (batch scoring):
80
+ ```javascript
81
+ [
82
+ {age: 45, income: 60000},
83
+ {age: 50, income: 75000},
84
+ {age: 35, income: 55000}
85
+ ]
86
+ ```
87
+
88
+ ## Type Inference
89
+ The skill automatically infers the type from the model name:
90
+
91
+ ```
92
+ mymodel.mas → type = "mas"
93
+ scorer.job → type = "job"
94
+ detector.jobdef → type = "jobdef"
95
+ risk.scr → type = "scr"
96
+ predict.sas → type = "sas"
97
+ ```
98
+
99
+ If the type is not specified in the model name or as a parameter, the skill will ask for clarification:
100
+ ```
101
+ "Is this a mas, scr, job, jobdef, or sas model?"
102
+ ```
103
+
104
+ ## Context-Based Scoring
105
+ If no scenario is provided, the skill can extract relevant variables from the conversation context:
106
+
107
+ 1. Extracts available variables from context
108
+ 2. Asks user for confirmation: *"I found these variables: [list]. Should I use them for scoring?"*
109
+ 3. Proceeds with confirmed variables
110
+
111
+ ## Return Values
112
+ The scoring response varies by type:
113
+
114
+ | Type | Returns |
115
+ |------|---------|
116
+ | **job** | Log output, tables created by job |
117
+ | **jobdef** | Log output, tables created by jobdef |
118
+ | **mas** | Predictions, probabilities, scores |
119
+ | **scr** | Predictions and metadata from SCR endpoint |
120
+ | **sas** | SAS execution output with results |
121
+
122
+ All responses include metadata indicating which tool was invoked.
123
+
124
+ ## Error Handling
125
+
126
+ | Error | Message |
127
+ |-------|---------|
128
+ | Invalid type | "Unknown model type. Use: job, jobdef, mas, scr, or sas" |
129
+ | Missing model | "Please provide model name (e.g., score with model mymodel.mas)" |
130
+ | Invalid scenario | "Scenario must be key=value pairs separated by commas" |
131
+ | Routing failure | Backend error from invoked tool |
132
+
133
+ ## Implementation Details
134
+
135
+ The skill is defined in `src/toolSet/scoreSkill.js` and:
136
+ - Parses model name to extract type
137
+ - Normalizes type names (e.g., `jobs` → `job`)
138
+ - Routes to appropriate tool handler
139
+ - Attaches scoring metadata to response
140
+ - Handles errors from backend tools
141
+
142
+ The skill is automatically registered in `makeTools.js` and available alongside other MCP tools.