@sassoftware/sas-score-mcp-serverjs 0.4.1-15 → 0.4.1-18

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/cli.js CHANGED
@@ -41,24 +41,45 @@ const args = parseArgs({
41
41
  short: 'p',
42
42
  description: 'Port to run the server on'
43
43
  },
44
- https: {
45
- type: 'string',
46
- description: 'Use HTTPS for the server (default: false)'
47
- },
48
44
  mcptype: {
49
45
  type: 'string',
50
46
  short: 'm',
51
47
  description: 'MCP server type (http or stdio)'
52
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
+
53
59
  viya: {
54
60
  type: 'string',
55
61
  short: 'v',
56
62
  description: 'Viya server URL'
57
63
  },
64
+ mcphost: {
65
+ type: 'string',
66
+ short: 'm',
67
+ description: 'MCP server host (default: http://localhost:8080)'
68
+ },
58
69
  authflow: {
59
70
  type: 'string',
60
71
  short: 'a',
61
- description: 'Authentication flow (sascli, code, token, bearer,auth)'
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'
62
83
  },
63
84
  profile: {
64
85
  type: 'string',
@@ -68,10 +89,22 @@ const args = parseArgs({
68
89
  type: 'string',
69
90
  description: 'SAS CLI config directory'
70
91
  },
71
- 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: {
72
101
  type: 'string',
73
102
  short: 'e',
74
- 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\''
75
108
  },
76
109
  help: {
77
110
  type: 'boolean',
@@ -80,14 +113,9 @@ const args = parseArgs({
80
113
  },
81
114
  version: {
82
115
  type: 'boolean',
83
- short: 'v',
84
116
  description: 'Show version'
85
117
  },
86
- 'install-skills': {
87
- type: 'string',
88
- short: 's',
89
- description: 'Install bundled skills/'
90
- }
118
+
91
119
  },
92
120
  strict: false,
93
121
  allowPositionals: false
@@ -99,26 +127,81 @@ if (args.values.help) {
99
127
  Usage: sas-score-mcp-serverjs [options]
100
128
 
101
129
  Options:
102
- -p, --port <port> Port to run the server on (default: 8080)
103
- -m, --mcptype <type> MCP server type: http or stdio (default: http)
104
- -v, --viya <url> Viya server URL
105
- -c, --mcpclient <name> Name of the MCP client (for token management)
106
- -a, --authflow <flow> Authentication flow: sascli, code, or token
107
- --provider <name> Authentication by external provider, e.g. AZURE.
108
- --profile <name> SAS CLI profile name
109
- --config <path> SAS CLI config directory
110
- -e, --envfile <path> Environment file path
111
- -h, --help Show this help message
112
- --version Show version
113
- -s, --install-skills <client>Install bundled skills to ~/.claude/skills/
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
+
114
154
 
115
155
  Environment Variables:
116
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.
117
158
  See README.md for more information.
118
159
  `);
119
160
  process.exit(0);
120
161
  }
121
162
 
163
+ // read env file and then override with command line arguments
164
+ if (args.values.env) {
165
+ console.error('Working Directory', process.cwd());
166
+ let envf = process.cwd() + '\\' + args.values.env;
167
+ //__dirname + '\\.env';
168
+ console.error('Env file:', envf);
169
+ if (fs.existsSync(envf)) {
170
+ console.error(`Loading environment variables from ${envf}...`);
171
+ let e = iconfig(envf); // avoid dotenv since it writes to console.error
172
+ console.error('[Note]: Environment variables loaded from .env file...');
173
+ console.error('Loaded env variables:', e);
174
+ // dotenvExpand.expand(e);
175
+ } else {
176
+ console.error(
177
+ '[Note]: No env file found, Using default environment variables...'
178
+ );
179
+ }
180
+ }
181
+ // Apply command line arguments to override environment variables
182
+
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';
198
+
199
+
200
+
201
+ process.env.SAMESITE = 'Lax,secure';
202
+ process.env.APPHOST = '0.0.0.0';
203
+ process.env.APPNAME = 'sas-score-mcp-serverjs';
204
+
122
205
  // Handle version flag
123
206
  if (args.values.version) {
124
207
  let pkgJson = JSON.parse(pkg);
@@ -126,17 +209,19 @@ if (args.values.version) {
126
209
  process.exit(0);
127
210
  }
128
211
 
129
- // Handle install-skills flag
130
- if (args.values['install-skills']) {
131
- let destdir = '.' + args.values['install-skills'];
132
- const skillsSrc = join(__dirname, 'skills');
133
- const skillsDest = join(os.homedir(), destdir, 'skills');
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}`);
134
215
 
216
+ let client = process.env.CLIENT;
217
+ if (client !== 'none') {
218
+ let destdir = '.' + client;
219
+ let skillsDest = join(os.homedir(), destdir,'skills');
220
+ const skillsSrc = join(__dirname, 'skills');
135
221
  if (!fs.existsSync(skillsSrc)) {
136
222
  console.error('No skills directory found in this package.');
137
223
  process.exit(1);
138
224
  }
139
-
140
225
  fs.mkdirSync(skillsDest, { recursive: true });
141
226
 
142
227
  const skills = fs.readdirSync(skillsSrc, { withFileTypes: true })
@@ -144,7 +229,7 @@ if (args.values['install-skills']) {
144
229
  .map(d => d.name);
145
230
 
146
231
  if (skills.length === 0) {
147
- console.error('No skills found to install.');
232
+ console.error('[Note]No skills found to install.');
148
233
  } else {
149
234
  console.error(`Installing ${skills.length} skill(s) to ${skillsDest}...`);
150
235
  for (const skill of skills) {
@@ -153,76 +238,16 @@ if (args.values['install-skills']) {
153
238
  fs.cpSync(src, dest, { recursive: true });
154
239
  console.error(` installed: ${skill}`);
155
240
  }
156
-
241
+ console.error(`\n installed in cli. ${client}`);
157
242
  console.error(`\n${skills.length} skill(s) installed to ${skillsDest}`);
158
- console.error('Restart Client to activate the new skills.');
159
- process.exit(0);
160
- }
161
- }
243
+ console.error('[Note] Skills are ready for use.');
162
244
 
163
- if (process.env.ENVFILE === 'FALSE') {
164
- //use this when using remote mcp server and no .env file is desired
165
- console.error('[Note]: Skipping .env file as ENVFILE is set to FALSE...');
166
- } else {
167
- console.error('Working Directory', process.cwd());
168
- let envf = process.env.ENVFILE || (process.cwd() + '\\.env');
169
- //__dirname + '\\.env';
170
- console.error('Env file:', envf);
171
- if (fs.existsSync(envf)) {
172
- console.error(`Loading environment variables from ${envf}...`);
173
- let e = iconfig(envf); // avoid dotenv since it writes to console.error
174
- console.error('[Note]: Environment variables loaded from .env file...');
175
- console.error('Loaded env variables:', e);
176
- // dotenvExpand.expand(e);
177
- } else {
178
- console.error(
179
- '[Note]: No .env file found, Using default environment variables...'
180
- );
181
245
  }
182
246
  }
183
247
 
184
- // Apply command line arguments to override environment variables
185
- if (args.values.port) {
186
- process.env.PORT = args.values.port;
187
- console.error(`[Note] PORT set from command line: ${args.values.port}`);
188
- }
189
-
190
- if (args.values.mcptype) {
191
- process.env.MCPTYPE = args.values.mcptype;
192
- console.error(`[Note] MCPTYPE set from command line: ${args.values.mcptype}`);
193
- }
194
- if (args.values.mcpclient) {
195
- process.env.MCPCLIENT = args.values.mcpclient;
196
- console.error(`[Note] MCPCLIENT set from command line: ${args.values.mcpclient}`);
197
- }
198
- if (args.values.viya) {
199
- process.env.VIYA_SERVER = args.values.viya;
200
- console.error(`[Note] VIYA_SERVER set from command line: ${args.values.viya}`);
201
- }
202
-
203
- if (args.values.mcpserver) {
204
- process.env.MCPSHOST = args.values.mcphost;
205
- console.error(`[Note] MCPHOST set from command line: ${args.values.mcpserver}`);
206
- }
207
-
208
- if (args.values.authflow) {
209
- process.env.AUTHFLOW = args.values.authflow;
210
- console.error(`[Note] AUTHFLOW set from command line: ${args.values.authflow}`);
211
- }
212
248
 
213
- if (args.values.profile) {
214
- process.env.SAS_CLI_PROFILE = args.values.profile;
215
- console.error(`[Note] SAS_CLI_PROFILE set from command line: ${args.values.profile}`);
216
- }
217
249
 
218
- if (args.values.config) {
219
- process.env.SAS_CLI_CONFIG = args.values.config;
220
- console.error(`[Note] SAS_CLI_CONFIG set from command line: ${args.values.config}`);
221
- }
222
250
 
223
- if (process.env.APPHOST == null) {
224
- process.env.APPHOST = 'localhost';
225
- }
226
251
  /********************************* */
227
252
  const BRAND = 'sas-score'
228
253
  /********************************* */
@@ -231,9 +256,9 @@ let version = pkgJson.version;
231
256
  let mcpType = process.env.MCPTYPE || 'http';
232
257
  console.error(
233
258
  `\nStarting MCP ServerJS - Version: ${pkgJson.version} - ${new Date().toISOString()}\n
234
- brand: ${process.env.BRAND || BRAND}\n
235
- mcpType: ${mcpType}\n
236
- viyaServer: ${process.env.VIYA_SERVER}\n`
259
+ brand: ${process.env.BRAND || BRAND}
260
+ mcpType: ${mcpType}
261
+ viyaServer: ${process.env.VIYA_SERVER}`
237
262
  );
238
263
  // session sessionCache
239
264
  // For more robust caching consider products like Redis
@@ -248,51 +273,25 @@ let sessionCache = new NodeCache({ stdTTL: 24 * 60 * 60, checkperiod: 2 * 60, us
248
273
  // stdio: set the env in the mcp config
249
274
  // http: use dotenv-cli to load env before starting the mcp server
250
275
 
251
-
252
- // need to tell core what transport to use(http or stdio)
253
-
254
- // subclasses for sasQuery tool (special use case)
255
- // to be replaced by the planned adding external tool definition capability
256
-
257
- let subclassJson = [];
258
- if (process.env.SUBCLASS != null) {
259
- console.error(`Using subclass: ${process.env.SUBCLASS}`);
260
- let subclass = process.env.SUBCLASS;
261
- if (fs.existsSync(subclass)) {
262
- console.error(`Loading subclass information from ${subclass}...`);
263
- let s = fs.readFileSync(subclass, 'utf8');
264
- subclassJson = JSON.parse(s);
265
- console.error(`Loaded subclass: ${JSON.stringify(subclassJson, null, 2)}`);
266
- }
267
- }
268
276
  // setup base appEnv
269
277
  // for stdio this is the _appContext
270
278
  // for http each session a copy of this as appEnvTemplate is created in corehttp
271
279
 
272
- // backward compability variables
273
- let clientID = process.env.CLIENTID || process.env.CLIENTIDPW || null;
274
- let clientSecret = process.env.CLIENTSECRET || process.env.CLIENTSECRETPW || null;
275
280
  let https = process.env.HTTPS != null ? process.env.HTTPS.toUpperCase() : "FALSE";
276
281
  let authExternal = false;
277
282
  let authFlow = process.env.AUTHFLOW;
278
283
  let mcpHost = process.env.MCPHOST;
279
284
 
280
- if (authFlow === 'oauth') {
285
+ if (authFlow === 'oauth' || authFlow === 'oauthclient') {
281
286
  authFlow = 'bearer';
282
- authExternal = true;
287
+ authExternal = (authFlow === 'oauthclient') ? true : false;
283
288
  }
284
- if (authFlow === 'oauthproxy') {
285
- authFlow = 'bearer';
286
- authExternal = false;
287
- }
288
-
289
- console.error(`[Note] Authentication flow: ${authFlow}, External provider: ${authExternal}`);
290
289
  let autoLogon = process.env.AUTOLOGON != null ? process.env.AUTOLOGON.toUpperCase() : "FALSE";
291
290
  const appEnvBase = {
292
291
  version: version,
293
292
  mcpType: mcpType,
294
- mcpClient: process.env.MCPCLIENT || 'vscode',
295
- mcpHost: mcpHost,
293
+ mcpClient: process.env.MCPCLIENT || 'github',
294
+ mcpHost: (process.env.MCPHOST == null) ? 'http://localhost:8080' : process.env.MCPHOST,
296
295
  brand: (process.env.BRAND == null) ? BRAND : process.env.BRAND,
297
296
  HTTPS: https,
298
297
  SAS_CLI_PROFILE: process.env.SAS_CLI_PROFILE || 'Default',
@@ -308,21 +307,17 @@ const appEnvBase = {
308
307
  PORT: process.env.PORT || 8080,
309
308
  USERNAME: process.env.USERNAME || null,
310
309
  PASSWORD: process.env.PASSWORD || null,
311
- CLIENTID: clientID,
312
- CLIENTSECRET: clientSecret,
310
+ CLIENTID: process.env.CLIENTID || null,
311
+ CLIENTSECRET: process.env.CLIENTSECRET || null,
313
312
  PKCE: process.env.PKCE || null,
314
313
 
315
314
  TOKEN: process.env.TOKEN || null,
316
315
  REFRESH_TOKEN: process.env.REFRESH_TOKEN || null,
317
316
  TOKENFILE: process.env.TOKENFILE || null,
318
317
  TLS_CREATE: process.env.TLS_CREATE || null,
319
- SUBCLASS: process.env.SUBCLASS || null,
320
- subclassJson: subclassJson,
321
- // future use for controlling tool list using env variable
322
- toolsets:
323
- process.env.TOOLSETS != null
324
- ? process.env.TOOLSETS.split(',')
325
- : ['default'],
318
+ CASSERVER: process.env.CASSERVER,
319
+ COMPUTECONTEXT: process.env.COMPUTECONTEXT,
320
+
326
321
  // command line arguments
327
322
  cliArgs: args.values,
328
323
  // user defined tools
@@ -352,8 +347,8 @@ const appEnvBase = {
352
347
  store: null, /* for restaf users */
353
348
  storeConfig: {},
354
349
  oauthInfo: null,
355
- CLIENTID: clientID,
356
- CLIENTSECRET: clientSecret,
350
+ CLIENTID: process.env.CLIENTID || null,
351
+ CLIENTSECRET: process.env.CLIENTSECRET || null,
357
352
  pkce: process.env.PKCE || null,
358
353
  casSession: null, /* restaf cas session object */
359
354
  computeSession: null, /* restaf compute session object */
@@ -373,12 +368,9 @@ let useHapi = process.env.USEHAPI === 'TRUE' ? true : false;
373
368
  appEnvBase.useHapi = useHapi;
374
369
 
375
370
  // setup TLS options for viya calls
376
- console.error('[Note]Viya SSL dir set to: ' + appEnvBase.VIYACERT);
377
371
  appEnvBase.contexts.viyaCert = readCerts(appEnvBase.VIYACERT); /* appEnvBase.contexts.viyaCert is set here */
378
372
 
379
373
  // setup TLS options for app server (expressMcpServer or hapiMcpServer)
380
-
381
- console.error('[Note]App SSL dir set to: ' + appEnvBase.SSLCERT);
382
374
  appEnvBase.tlsOpts = readCerts(appEnvBase.SSLCERT);
383
375
  appEnvBase.contexts.appCert = appEnvBase.tlsOpts; /* just for completeness */
384
376
 
@@ -428,11 +420,41 @@ sessionCache.set('tokenlist', tokenlist);
428
420
  // set this for stdio transport use
429
421
  // dummy sessionId for use in the tools
430
422
 
431
- ;
432
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
433
424
  let sessionId = randomUUID();
434
425
  sessionCache.set(sessionId, appEnvBase);
435
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
436
458
  debugger;
437
459
  if (mcpType === 'stdio') {
438
460
  console.error('[Note] Setting up stdio transport with sessionId:', sessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sassoftware/sas-score-mcp-serverjs",
3
- "version": "0.4.1-15",
3
+ "version": "0.4.1-18",
4
4
  "description": "A mcp server for SAS Viya",
5
5
  "author": "Deva Kumar <deva.kumar@sas.com>",
6
6
  "license": "Apache-2.0",
@@ -42,7 +42,8 @@
42
42
  "cli.js",
43
43
  "openApi.json",
44
44
  "openApi.yaml",
45
- "skills"
45
+ "skills",
46
+ "scripts"
46
47
  ],
47
48
  "dependencies": {
48
49
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -73,7 +74,6 @@
73
74
  "@babel/preset-env": "^7.28.5",
74
75
  "@types/debug": "^4.1.12",
75
76
  "@types/node": "^25.0.3",
76
- "npm-check-updates": "^19.2.0",
77
77
  "rimraf": "^6.1.2",
78
78
  "typescript": "^5.9.3"
79
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.