@sassoftware/sas-score-mcp-serverjs 1.0.1-7 → 1.0.1-9

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.
@@ -0,0 +1,314 @@
1
+ ---
2
+ name: sas-score-workflow-strategy
3
+ description: >
4
+ Guide the full model scoring workflow: validate model familiarity, route to appropriate scoring tool
5
+ based on model type, invoke scoring with scenario data, and present merged results. Use this skill
6
+ when the user wants to run predictions on data (already fetched or user-supplied). Supports generic
7
+ syntax: "score with model <name>.<type> scenario =<params>" where type is job|jobdef|mas|scr|sas.
8
+ Trigger phrases: "score these records", "predict using model", "run model on", "score with model X.mas".
9
+ ---
10
+
11
+ # SAS Score Workflow
12
+
13
+ Orchestrates model validation, type-based routing, scoring invocation, and result presentation.
14
+ Handles both MAS models and alternative scoring engines (jobs, jobdefs, SCR, SAS programs).
15
+
16
+ ---
17
+
18
+ ## Generic Scoring Syntax
19
+
20
+ Users can invoke scoring with a unified syntax that automatically routes to the correct tool:
21
+
22
+ ```
23
+ score with model <name>.<type> [scenario =<key=value pairs>]
24
+ score <name>.<type> [scenario =<key=value pairs>]
25
+ ```
26
+
27
+ **Type determines the routing:**
28
+ - `.job` → route to `sas-score-run-job` with scoring parameters
29
+ - `.jobdef` → route to `sas-score-run-jobdef` with scoring parameters
30
+ - `.mas` → route to `sas-score-model-score` (Model Analytical Service — default)
31
+ - `.scr` → route to `sas-score-scr-score` (SAS Container Runtime)
32
+ - `.sas` → route to `sas-score-run-sas-program` to run sas program in folder
33
+
34
+ If no type is specified (bare model name), assume `.mas` (MAS model).
35
+
36
+ ---
37
+
38
+ ## Type-Based Routing
39
+
40
+ ### Parse and Strip Model Type
41
+
42
+ When a user provides a model name with a type suffix (e.g., `simplejon.job`, `churn.mas`):
43
+
44
+ 1. **Extract the type:** Split on the last dot to identify the type suffix
45
+ - `simplejon.job` → type = `job`, base name = `simplejon`
46
+ - `churn.mas` → type = `mas`, base name = `churn`
47
+ - `fraud_detector.jobdef` → type = `jobdef`, base name = `fraud_detector`
48
+
49
+ 2. **Validate the type:** Confirm it matches one of the supported types: `job`, `jobdef`, `mas`, `scr`, `sas`
50
+ - If type is unrecognized, assume `.mas` (default MAS model) and treat the entire input as the model name
51
+
52
+ 3. **Strip the type suffix:** Remove the `.type` from the model name before passing to the routing tool
53
+ - **Critical:** Always pass the base name (without the dot and type) to the invoked tool
54
+ - `simplejon.job` → pass `simplejon` to `sas-score-run-job`
55
+ - `churn.mas` → pass `churn` to `sas-score-model-score`
56
+ - `fraud_detector.jobdef` → pass `fraud_detector` to `sas-score-run-jobdef`
57
+
58
+ ### Type: `.mas` (Model Aggregation Service)
59
+ - **Tool**: `sas-score-model-score`
60
+ - **Use for**: Standard MAS-deployed predictive models
61
+ - **Example**: `score with model churn.mas scenario =age=45,income=60000`
62
+ - **Invocation**: `sas-score-model-score({ model: "churn", scenario: {...} })`
63
+
64
+ ### Type: `.job` (SAS Viya Job)
65
+ - **Tool**: `sas-score-run-job`
66
+ - **Use for**: Pre-built scoring jobs with parameters
67
+ - **Example**: `score with model monthly_scorer.job scenario =month=10,year=2025`
68
+ - **Invocation**: `sas-score-run-job({ name: "monthly_scorer", scenario: {...} })`
69
+
70
+ ### Type: `.jobdef` (SAS Viya Job Definition)
71
+ - **Tool**: `sas-score-run-jobdef`
72
+ - **Use for**: Job definitions that perform scoring logic
73
+ - **Example**: `score with model fraud_detector.jobdef using amount=500,merchant=online`
74
+ - **Invocation**: `sas-score-run-jobdef({ name: "fraud_detector", scenario: {...} })`
75
+
76
+ ### Type: `.scr` (Score Code Runtime)
77
+ - **Tool**: `sas-score-scr-score`
78
+ - **Use for**: Models deployed in SCR containers (REST endpoints)
79
+ - **Example**: `score https://scr-host/models/loan.scr using age=45,credit=700`
80
+ - **Invocation**: `sas-score-scr-score({ url: "https://scr-host/models/loan", scenario: {...} })`
81
+
82
+ ### Type: `.sas` (SAS Program / SQL)
83
+ - **Tool**: `sas-score-run-sas-program`
84
+ - **Use for**: Custom SAS or SQL scoring code
85
+ - **Example**: `score my_scoring_code.sas using x=1,y=2`
86
+ - **Invocation**: `sas-score-run-sas-program({ folder: "my_scoring_code", scenario: {...} })`
87
+
88
+
89
+
90
+ ---
91
+
92
+ ## Scenario Parsing
93
+
94
+ The scenario parameter (comma-separated key=value pairs) is parsed into an object:
95
+
96
+ ```
97
+ scenario =age=45,income=60000,region=South
98
+ ↓ parsed as:
99
+ { age: "45", income: "60000", region: "South" }
100
+ ```
101
+
102
+ Accepted formats:
103
+ - **String**: `age=45,income=60000`
104
+ - **Object**: `{ age: 45, income: 60000 }`
105
+ - **Array** (batch): `[ {age:45, income:60000}, {age:50, income:75000} ]`
106
+
107
+ ---
108
+
109
+ ## Integration with other skills
110
+
111
+ - **Before scoring table data**: Use `sas-find-resource-strategy` to verify library/table/model resources, then `sas-read-strategy` to fetch records
112
+ - **For read + score workflows**: Use `sas-read-and-score-strategy` for the complete end-to-end pattern
113
+
114
+ ---
115
+
116
+ ## Step 1 — Check model familiarity before scoring
117
+
118
+ Score immediately if:
119
+ - The user names a specific model they've used before in this session, OR
120
+ - The model name matches a previously confirmed model in the conversation
121
+
122
+ Pause and suggest investigation if:
123
+ - The model name is new, vague, or misspelled-looking (e.g. "the churn one", "that cancer model")
124
+ - The user seems unsure of the required input variable names
125
+
126
+ **Suggested message:**
127
+ > "I don't recognize that model — want me to use `sas-find-resource-strategy` to confirm it exists,
128
+ > or `model-info` to check its required inputs first?"
129
+
130
+ ---
131
+
132
+ ## Step 2 — Prepare the scenario data
133
+
134
+ **For a single record** (one object):
135
+ ```javascript
136
+ scenario = { field1: value1, field2: value2, ... }
137
+ ```
138
+
139
+ **For batch scoring** (multiple records — the typical case):
140
+ ```javascript
141
+ scenario = [
142
+ { field1: val1, field2: val2, ... },
143
+ { field1: val3, field2: val4, ... },
144
+ ...
145
+ ]
146
+ ```
147
+
148
+ **Critical rules:**
149
+ - Loop or call sas-score-model-score **once per row**.
150
+ - Field names in the scenario must match the model's expected input variable names **exactly**.
151
+ - If table column names differ from model input names, **flag this to the user** and ask for confirmation before scoring.
152
+ - Example: Table has `age_years`, but model expects `age` → ask user which column maps to which input.
153
+ - Do not add units, labels, or extra metadata — raw field values only.
154
+
155
+ ---
156
+
157
+ ## Step 3 — Invoke the appropriate scoring tool
158
+
159
+ Based on the type extracted from the model name, invoke the corresponding tool:
160
+
161
+ **For `.mas` (default):**
162
+ ```javascript
163
+ sas-score-model-score({
164
+ model: "<modelname>",
165
+ scenario: scenario, // object or array
166
+ uflag: false // set true if you need field names prefixed with _
167
+ })
168
+ ```
169
+
170
+ **For `.job`:**
171
+ ```javascript
172
+ sas-score-run-job({
173
+ name: "<jobname>",
174
+ scenario: scenario
175
+ })
176
+ ```
177
+
178
+ **For `.jobdef`:**
179
+ ```javascript
180
+ sas-score-run-jobdef({
181
+ name: "<jobdefname>",
182
+ scenario: scenario
183
+ })
184
+ ```
185
+
186
+ **For `.scr`:**
187
+ ```javascript
188
+ sas-score-scr-score({
189
+ url: "<scr_endpoint_url>",
190
+ scenario: scenario
191
+ })
192
+ ```
193
+
194
+ **For `.sas`:**
195
+ ```javascript
196
+ sas-score-run-sas-program({
197
+ src: "<sas_or_sql_code>",
198
+ scenario: scenario
199
+ })
200
+ ```
201
+
202
+ **Rules:**
203
+ - Pass the full batch in one call; do not loop over rows
204
+ - If scoring fails, return the structured error and suggest troubleshooting
205
+ - For MAS models, include uflag parameter if underscore-prefixed output is needed
206
+ - For jobs/jobdefs, scenario becomes parameter arguments
207
+ - For SCR, include full URL endpoint
208
+
209
+ ---
210
+
211
+ ## Step 4 — Present the results
212
+
213
+ Merge the scoring output back with the input records and present as a table where possible.
214
+
215
+ **Always surface:**
216
+ - The key prediction/score field(s) (e.g. `P_churn`, `score`, `prediction`, `P_risk`)
217
+ - Any probability/confidence fields for classification models (e.g. `P_class0`, `P_class1`)
218
+ - Selected input fields that drove the prediction, so the user can see context
219
+
220
+ **Formatting:**
221
+ - Present results in a table for clarity
222
+ - If results exceed 10 rows, show the first 10 and ask: *"Want to see more results or export the full set?"*
223
+ - Round numeric predictions to 2–4 decimal places for readability
224
+
225
+ ---
226
+
227
+ ## Common flows
228
+
229
+ **Flow A — Score rows with MAS model**
230
+ > "Score the first 10 customers in Public.customers with the churn model"
231
+
232
+ 1. `sas-score-read-table` → { table: "Public.customers", limit: 10 }
233
+ 2. `sas-score-model-score` → { model: "churn", scenario: [ ...10 row objects ] }
234
+ 3. Present merged results with prediction + key inputs
235
+
236
+ **Flow B — Score with a scoring job**
237
+ > "Score December sales with the monthly_scorer job using month=12,year=2025"
238
+
239
+ 1. `sas-score-run-job` → { name: "monthly_scorer", scenario: { month: "12", year: "2025" } }
240
+ 2. Capture job output and tables
241
+ 3. Present results
242
+
243
+ **Flow C — Score with a job definition**
244
+ > "Run fraud detection jobdef on transaction amount=500, merchant=online"
245
+
246
+ 1. `sas-score-run-jobdef` → { name: "fraud_detection", scenario: { amount: "500", merchant: "online" } }
247
+ 2. Capture log, listings, and tables
248
+ 3. Present results
249
+
250
+ **Flow D — Score with SCR endpoint**
251
+ > "Score with the loan model at https://scr-host/models/loan using age=45, credit_score=700"
252
+
253
+ 1. `sas-score-scr-score` → { url: "https://scr-host/models/loan", scenario: { age: "45", credit_score: "700" } }
254
+ 2. Capture prediction response
255
+ 3. Present result
256
+
257
+ **Flow E — Score results of an analytical query with MAS**
258
+ > "Score high-value customers (spend > 5000) in mylib.sales with the fraud model"
259
+
260
+ 1. `sas-score-sas-query` → { table: "mylib.sales", sql: "SELECT * FROM mylib.sales WHERE spend > 5000" }
261
+ 2. `sas-score-model-score` → { model: "fraud", scenario: [ ...result rows ] }
262
+ 3. Present merged results
263
+
264
+ **Flow F — User supplies scenario data directly**
265
+ > "Score age=45, income=60000, region=South with the churn model"
266
+
267
+ 1. Skip read step
268
+ 2. `sas-score-model-score` → { model: "churn", scenario: { age: "45", income: "60000", region: "South" } }
269
+ 3. Present result
270
+
271
+ **Flow G — Model unfamiliar, need to confirm**
272
+ > "Score Public.applicants with the creditRisk2 model"
273
+
274
+ 1. Pause — "creditRisk2" is new
275
+ 2. Suggest: `sas-find-resource-strategy` to confirm resource existence, `model-info` to get input variables
276
+ 3. Once confirmed → `sas-score-read-table` + `sas-score-model-score`
277
+
278
+ **Flow H — Generic score syntax with type routing**
279
+ > "score with model churn.mas scenario =age=45,income=60000"
280
+ > "score fraud_detector.jobdef where scenario =amount=500"
281
+ > "score monthly_report.job using month=10,year=2025"
282
+
283
+ 1. Parse model name to extract type (.mas, .job, .jobdef, .scr, .sas)
284
+ 2. Route to appropriate tool based on type
285
+ 3. Parse scenario and invoke tool with parameters
286
+ 4. Present results from routed tool
287
+
288
+ ---
289
+
290
+ ## Error handling
291
+
292
+ | Problem | Action |
293
+ |---|---|
294
+ | Model not found | Use `sas-find-resource-strategy` to verify the model (or job/jobdef type) exists |
295
+ | Input field name mismatch | Show the mismatch (table has X, model expects Y), ask user to confirm mapping |
296
+ | Scoring error / invalid inputs | Return structured error, suggest `model-info` to check required inputs and data types |
297
+ | Empty read result | Tell user, ask if they want to adjust the query/filter before scoring |
298
+ | Missing input fields | Ask which table columns map to the required model inputs |
299
+
300
+ ---
301
+
302
+ ## Tips
303
+
304
+ - **Batch is better:** Always pass the full set of records in one `sas-score-model-score` call. Do not loop.
305
+ - **Confirm mappings:** If column names don't match model inputs, ask before scoring.
306
+ - **Show context:** Include key input columns in the result output so predictions make sense.
307
+ - **Limit output:** For large result sets (>10 rows), ask before showing all.
308
+
309
+ ---
310
+
311
+ ## Integration with other skills
312
+
313
+ - **Before scoring table data**: Use `sas-find-resource-strategy` to verify resources, then `sas-read-strategy` to fetch records
314
+ - **For read + score workflows**: Use `sas-read-and-score-strategy` for the complete end-to-end pattern
package/cli.js CHANGED
@@ -16,8 +16,6 @@ import createMcpServer from './src/createMcpServer.js';
16
16
  import fs from 'fs';
17
17
  import { randomUUID } from 'node:crypto';
18
18
 
19
- //import refreshToken from './src/toolHelpers/refreshToken.js';
20
- //import getOptsViya from './src/toolHelpers/getOptsViya.js';
21
19
  import readCerts from './src/toolHelpers/readCerts.js';
22
20
 
23
21
  import { fileURLToPath } from 'url';
@@ -72,11 +70,6 @@ const args = parseArgs({
72
70
  short: 'c',
73
71
  description: 'Client ID for authentication'
74
72
  },
75
- clientsecret: {
76
- type: 'string',
77
- short: 's',
78
- description: 'Client Secret for authentication'
79
- },
80
73
  profile: {
81
74
  type: 'string',
82
75
  description: 'SAS CLI profile name'
@@ -107,12 +100,17 @@ const args = parseArgs({
107
100
  alias: 'mcpclient',
108
101
  description: 'MCP client name (github, claude...). Defaults to \'github\''
109
102
  },
110
- agentfolder: {
103
+ folder: {
111
104
  type: 'string',
112
105
  short: 'f',
113
106
  description: 'Subfolder under the client folder to copy the skills to, used to have different set of skills for different agents under the same client'
114
107
 
115
108
  },
109
+ skills: {
110
+ type: 'string',
111
+ short: 's',
112
+ description: 'Copies the skills for .github and .claude to the user home directory under .clientname (e.g. .github) or current directory if client name starts with dot (e.g. ./.github), used to have different set of skills for different clients'
113
+ },
116
114
  help: {
117
115
  type: 'boolean',
118
116
  short: 'h',
@@ -131,29 +129,28 @@ const args = parseArgs({
131
129
  // Handle help flag
132
130
  if (args.values.help) {
133
131
  console.error(`
134
- SAS Viya Scoring Expert Agent - Version: ${JSON.parse(pkg).version}
132
+ sas-score-mcp-serverjs - Version: ${JSON.parse(pkg).version}
135
133
 
136
134
  Usage: npx @sassoftware/sas-score-mcp-serverjs@dev [options]
137
135
 
138
136
  Options:
139
137
  Minimal options:
140
138
  -v, --viya <url> Viya server URL
141
- -c, --clientid <id> Client ID for oauth authentication(pkce preferred. default: vscodemcp)
142
139
 
143
140
  MCP server options:
144
141
  -t, --mcptype <type> MCP server type: http or stdio (default: http)
145
142
  -m, --mcphost <host> MCP server host - can be remote URL - (default: http://localhost:8080)
146
143
 
147
- Agent options:
148
- --agent Enable agent mode with a pre-configured set of skills based on the client specified (default: false)
149
- --client <name> MCP client name (github, claude...). Defaults to 'github'.Use to install skills
150
- --agentfolder <folder> Subfolder under the client folder to copy the skills to, used to have different set of skills for different agents under the same client
151
144
  Authentication options:
145
+ -c, --clientid <id> Client ID for oauth authentication(pkce preferred. default: vscodemcp)
152
146
  -a, --authflow <flow> Authentication flow: oauth, oauthclient, sascli, code, token(default oauth)
153
- -s, --clientsecret <secret> Client Secret for oauth authentication (not needed for pkce)
154
147
  --profile <name> SAS CLI profile name for sascli flow (default: Default)
155
148
  --config <path> SAS CLI config directory for sascli flow (default: user home directory)
156
149
 
150
+ Agent/skills options:
151
+ -s, --skills <name> Copies the skills for .github and .claude
152
+ -f, --folder <folder> Subfolder to copy the skills to. ex: ./github/<folder>, used to have different set of skills for different agents under the same client.
153
+
157
154
  Other options:
158
155
  -p, --port <port> Port to run the server on (default: 8080)
159
156
  --https Use HTTPS for the server (default: false)
@@ -172,6 +169,13 @@ Environment Variables:
172
169
  `);
173
170
  process.exit(0);
174
171
  }
172
+ if (args.values.skills) {
173
+ console.error(`[Note] Settings up skills for ${args.values.skills }`);
174
+ setupSkills(args.values.skills, args.values.folder);;
175
+ console.error(`[Note] Skills setup completed. `);
176
+ process.exit(0);
177
+ }
178
+
175
179
  console.error('Parsed command line arguments:', args.values);
176
180
  // read env file and then override with command line arguments
177
181
  if (args.values.env) {
@@ -201,7 +205,7 @@ process.env.AUTHFLOW = args.values.authflow || process.env.AUTHFLOW || 'oauth';
201
205
  process.env.MCPCLIENT = args.values.client || process.env.MCPCLIENT || 'github';
202
206
  process.env.VIYA_SERVER = args.values.viya || process.env.VIYA_SERVER;
203
207
  process.env.CLIENTID = args.values.clientid || process.env.CLIENTID || 'vscodemcp';
204
- process.env.CLIENTSECRET = args.values.clientsecret || process.env.CLIENTSECRET || null;
208
+ process.env.CLIENTSECRET = null;
205
209
  process.env.SAS_CLI_PROFILE = args.values.profile || process.env.SAS_CLI_PROFILE || 'Default';
206
210
  process.env.SAS_CLI_CONFIG = args.values.config || process.env.SAS_CLI_CONFIG || os.homedir(); // default to user home directory
207
211
  process.env.CASSERVER = args.values.casserver || process.env.CASSERVER || 'cas-shared-default';
@@ -381,10 +385,13 @@ if (appEnvBase.TOKENFILE != null) {
381
385
 
382
386
  // setup skills based on client before mcp initialization
383
387
  //
388
+
389
+
390
+
384
391
  if (process.env.AGENT === 'TRUE') {
385
392
  if (process.env.CLIENT !== 'none') {
386
393
  console.error(`[Note] Setting up skills for client: ${process.env.CLIENT}...`);
387
- setupSkills(process.env.CLIENT, args.values.agentfolder);
394
+ setupSkills(process.env.CLIENT, args.values.folder);
388
395
  }
389
396
  } else {
390
397
  console.error(`[Note] Agent mode not enabled`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sassoftware/sas-score-mcp-serverjs",
3
- "version": "1.0.1-7",
3
+ "version": "1.0.1-9",
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,9 @@
42
42
  "openApi.json",
43
43
  "openApi.yaml",
44
44
  "scripts",
45
- ".skills"
45
+ ".skills",
46
+ ".agents",
47
+ ".instructions"
46
48
  ],
47
49
  "dependencies": {
48
50
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -23,22 +23,41 @@ function setupSkills(clientName,agentFolder) {
23
23
  console.error("==================================================================");
24
24
  console.error(` Copying ${source} to ${destination}...`);
25
25
 
26
+
27
+
28
+ // Copy agents folder if it exists
29
+ let agentsFromPath = path.join(__dirname, `../.agents`);
30
+ let agentsToPath = path.join(destination, 'agents');
31
+ copyFolderSync(agentsFromPath, agentsToPath);
32
+
33
+ // now copy the skills folder to the destination
34
+ let toPath = path.join(destination, '.skills');
35
+ let fromPath = path.join(__dirname, `../.skills`);
36
+ copyFolderSync(fromPath, toPath);
37
+
38
+ // Now copy instructions
39
+ let instructionsFromPath = path.join(__dirname, `../.instructions`);
40
+ let instructionsToPath = destination;
41
+ copyFolderSync(instructionsFromPath, instructionsToPath);
42
+
26
43
  function copyFolderSync(from, to) {
27
44
  if (!fs.existsSync(from)) return [];
28
45
  if (!fs.existsSync(to)) fs.mkdirSync(to, { recursive: true });;
29
46
  fs.readdirSync(from).forEach(element => {
30
47
  const fromPath = path.join(from, element);
31
- const toPath = path.join(to, element);
48
+ let toPath = path.join(to, element);
49
+ if (clientName === 'claude' && element === 'copilot-instructions.md') {
50
+ toPath = path.join(to, 'CLAUDE.md');
51
+ }
32
52
  if (fs.lstatSync(fromPath).isFile()) {
33
53
  console.error(` 📄 Copying file: ${element}`);
34
54
  fs.copyFileSync(fromPath, toPath);
35
55
  } else if (fs.lstatSync(fromPath).isDirectory()) {
36
56
  console.error(`📂 Copying folder: ${element}`);
37
- copyFolderSync(fromPath, toPath) ;
57
+ copyFolderSync(fromPath, toPath);
38
58
  }
39
59
  });
40
60
  }
41
-
42
61
  function listExpandedFolder(dir, indent = "") {
43
62
  const entries = fs.readdirSync(dir, { withFileTypes: true });
44
63