@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 +164 -142
- package/package.json +3 -3
- 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 +78 -0
- package/scripts/update_descriptions.py +46 -0
- package/scripts/viyatls.sh +3 -0
- package/skills/sas-find-library-smart/SKILL.md +154 -0
- package/skills/sas-list-tables-smart/SKILL.md +127 -123
- package/skills/sas-read-and-score/SKILL.md +21 -2
- package/skills/sas-read-strategy/SKILL.md +35 -22
- package/skills/sas-score-workflow/SKILL.md +49 -35
- package/src/oauthHandlers/callback.js +6 -3
- package/src/toolHelpers/refreshToken.js +3 -2
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
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('
|
|
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}
|
|
235
|
-
mcpType: ${mcpType}
|
|
236
|
-
viyaServer: ${process.env.VIYA_SERVER}
|
|
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 || '
|
|
295
|
-
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:
|
|
312
|
-
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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:
|
|
356
|
-
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-
|
|
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.
|