@sassoftware/sas-score-mcp-serverjs 1.0.1-19 → 1.0.1-21

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,234 @@
1
+ ---
2
+ name: detail-strategy
3
+ description: >
4
+ Unified detail/information retrieval strategy. Handles MAS models, SCR models, and tables.
5
+ Always verify resources exist using find-resources skill before retrieving details.
6
+ ---
7
+
8
+ # Detail Strategy
9
+
10
+ Use this strategy when the user requests information about a resource: model details, schema, metadata, inputs/outputs, or any descriptive information.
11
+
12
+ ## Prerequisites
13
+
14
+ 1. Verify the resource exists using find-resources skill
15
+ 2. Determine resource type (MAS model, SCR model, or table)
16
+
17
+ ---
18
+
19
+ ## Step 1: Identify Resource Type
20
+
21
+ Classify the resource based on naming convention or context:
22
+
23
+ ```
24
+ model X.mas → MAS model (default for "model X")
25
+ model X.scr → SCR model
26
+ table X in library Y → CAS or SAS table
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Step 2: Verify Resource Exists
32
+
33
+ Use find-resources skill to confirm resource exists before retrieval:
34
+
35
+ ### Find MAS Model
36
+ ```
37
+ find-resources skill → find-model
38
+ Tool: sas-score-find-model({ name: "<model>" })
39
+ ```
40
+
41
+ ### Find SCR Model
42
+ ```
43
+ find-resources skill → find-scr
44
+ Tool: sas-score-find-scr({ url: "<scr-endpoint>" })
45
+ ```
46
+
47
+ ### Find Table
48
+ ```
49
+ find-resources skill → find-table
50
+ Tool: sas-score-find-table({ lib: "<library>", name: "<table>", server: "<cas|sas>" })
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Step 3: Get Details
56
+
57
+ ### Option A: MAS Model Details
58
+
59
+ **Trigger phrases**: "what inputs does model X need", "describe model X", "show variables for model X", "model X metadata", "model X information"
60
+
61
+ **Tool**: `sas-score-model-info`
62
+
63
+ **Parameters**:
64
+ ```
65
+ sas-score-model-info({
66
+ model: "<model name>"
67
+ })
68
+ ```
69
+
70
+ **Returns**:
71
+ - Input variables (name, type, role)
72
+ - Output variables (name, type, possible_values)
73
+ - Model type
74
+ - Description
75
+
76
+ **Example**:
77
+ ```
78
+ User: "What inputs does model churnRisk need?"
79
+
80
+ 1. Find: sas-score-find-model({ name: "churnRisk" })
81
+ 2. Get info: sas-score-model-info({ model: "churnRisk" })
82
+ 3. Return: { inputs: [...], outputs: [...], description: "..." }
83
+ ```
84
+
85
+ ---
86
+
87
+ ### Option B: SCR Model Details
88
+
89
+ **Trigger phrases**: "what does SCR model X need", "describe SCR model X", "SCR model X inputs", "SCR model X schema"
90
+
91
+ **Tool**: `sas-score-scr-info`
92
+
93
+ **Parameters**:
94
+ ```
95
+ sas-score-scr-info({
96
+ url: "<scr endpoint>"
97
+ })
98
+ ```
99
+
100
+ **Returns**:
101
+ - Input schema (variable names, types, required/optional)
102
+ - Output schema (prediction, probabilities, scores)
103
+ - Model metadata
104
+
105
+ **Example**:
106
+ ```
107
+ User: "Show inputs for SCR model at https://scr-host/models/loan"
108
+
109
+ 1. Get info: sas-score-scr-info({ url: "https://scr-host/models/loan" })
110
+ 2. Return: { inputs: [...], outputs: [...] }
111
+ ```
112
+
113
+ **Note**: SCR models typically do not require pre-verification (can call scr-info directly)
114
+
115
+ ---
116
+
117
+ ### Option C: Table Details
118
+
119
+ **Trigger phrases**: "what columns in table X", "describe table X", "show schema for table X", "table X structure", "table X metadata"
120
+
121
+ **Tool**: `sas-score-table-info`
122
+
123
+ **Parameters**:
124
+ ```
125
+ sas-score-table-info({
126
+ lib: "<library>",
127
+ table: "<table name>",
128
+ server: "<cas|sas>"
129
+ })
130
+ ```
131
+
132
+ **Returns**:
133
+ - Columns array (name, type, label, format, length)
134
+ - Table info (rowCount, fileSize, created, modified)
135
+
136
+ **Example**:
137
+ ```
138
+ User: "What columns are in the customers table in Public?"
139
+
140
+ 1. Find: sas-score-find-table({ lib: "Public", name: "customers", server: "cas" })
141
+ 2. Get info: sas-score-table-info({ lib: "Public", table: "customers", server: "cas" })
142
+ 3. Return: { columns: [...], tableInfo: {...} }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Decision Tree
148
+
149
+ ```
150
+ User requests information/details
151
+ ├─ About a MAS model?
152
+ │ → Find model (find-resources)
153
+ │ → Call: sas-score-model-info
154
+
155
+ ├─ About a SCR model?
156
+ │ → Call: sas-score-scr-info (can skip verification)
157
+
158
+ └─ About a table?
159
+ → Find table (find-resources, determine server)
160
+ → Call: sas-score-table-info
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Implementation Checklist
166
+
167
+ For each detail/information request:
168
+
169
+ - [ ] **Classify** resource type (MAS/SCR/table)
170
+ - [ ] **Verify** resource exists (use find-resources skill, except SCR)
171
+ - [ ] **Determine** server for tables (CAS or SAS)
172
+ - [ ] **Execute** appropriate detail tool
173
+ - [ ] **Format** results (column alignment, readable structure)
174
+ - [ ] **Append** Strategy Summary to response
175
+
176
+ ---
177
+
178
+ ## Response Format
179
+
180
+ Always append a **Strategy Summary** to responses:
181
+
182
+ ```
183
+ ---
184
+
185
+ **Strategy Summary:**
186
+ - **Classification**: [Resource type identified]
187
+ - **Verification**: [Resource found or skipped (SCR)]
188
+ - **Tool Used**: [Detail tool invoked]
189
+ - **Server**: [CAS/SAS for tables, N/A for models]
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Error Recovery
195
+
196
+ If a request fails:
197
+
198
+ 1. **Resource not found** → Ask user to verify name/spelling
199
+ 2. **Server mismatch** → Re-verify server location with find-resources
200
+ 3. **Invalid URL (SCR)** → Ask for correct SCR endpoint URL
201
+ 4. **Tool error** → Return error message verbatim and ask for clarification
202
+
203
+ ---
204
+
205
+ ## Examples
206
+
207
+ ### Example 1: Model Information
208
+
209
+ **User**: "Describe model creditScore"
210
+
211
+ **Workflow**:
212
+ 1. Classify: MAS model detail request
213
+ 2. Verify: Find model creditScore → Found ✓
214
+ 3. Execute: `sas-score-model-info({ model: "creditScore" })`
215
+ 4. Return: Model inputs, outputs, and description
216
+
217
+ ### Example 2: SCR Model Schema
218
+
219
+ **User**: "What inputs does the SCR loan model need?"
220
+
221
+ **Workflow**:
222
+ 1. Classify: SCR model detail request
223
+ 2. Execute: `sas-score-scr-info({ url: "https://scr-host/models/loan" })`
224
+ 3. Return: Input schema and output schema
225
+
226
+ ### Example 3: Table Columns
227
+
228
+ **User**: "Show columns for customers in Public"
229
+
230
+ **Workflow**:
231
+ 1. Classify: Table detail request
232
+ 2. Verify: Find table customers in Public → CAS ✓
233
+ 3. Execute: `sas-score-table-info({ lib: "Public", table: "customers", server: "cas" })`
234
+ 4. Return: Column names, types, and table metadata
@@ -9,6 +9,8 @@ description: >
9
9
 
10
10
  Use this strategy to verify that a resource exists before executing any action.
11
11
 
12
+ Do **not** use list tools for finding specific resources. List tools are for listing available resources and exploration, not verification.
13
+
12
14
  ## Resource Types and Find Tools
13
15
 
14
16
  ### 1. Find Library
@@ -12,7 +12,7 @@ Use this strategy when the user requests model scoring, predictions, or running
12
12
  ## Prerequisites
13
13
 
14
14
  1. Verify the model/job exists using find-resources skill
15
- 2. If scoring table rows: Verify the table exists and determine server
15
+ 2. If scoring table rows: Verify the table exists and determine server using find-resources skill
16
16
  3. If scoring with inline scenario: Parse the scenario data
17
17
 
18
18
  ---
@@ -50,12 +50,12 @@ score results of query... → Query results
50
50
 
51
51
  **Tools**:
52
52
  - MAS: `sas-score-mas-score`
53
- - Job: `sas-score-run-jobdef`
53
+ - Job: `sas-score-run-job`
54
54
  - JobDef: `sas-score-run-jobdef`
55
55
  - SCR: `sas-score-scr-score`
56
56
 
57
57
  **Flow**:
58
- 1. Find model/job (find-resources)
58
+ 1. Find model (find-resources)
59
59
  2. Score with inline data
60
60
  3. Return prediction + input data merged
61
61
 
@@ -66,11 +66,18 @@ sas-score-mas-score({
66
66
  model: "<model name>",
67
67
  scenario: { a: 1, b: 2 }
68
68
  ```
69
+ **Parameters** (job): ):
70
+ ```
71
+ sas-score-run-job({
72
+ name: "<job name>",
73
+ scenario: { a: 1, b: 2 }
74
+ })
75
+ ```
69
76
 
70
- **Parameters** (Job/JobDef):
77
+ **Parameters** (jobdef):
71
78
  ```
72
79
  sas-score-run-jobdef({
73
- name: "<job or jobdef name>",
80
+ name: "<jobdef name>",
74
81
  scenario: { a: 1, b: 2 }
75
82
  })
76
83
  ```
@@ -178,20 +185,20 @@ If table columns don't match model input variable names:
178
185
 
179
186
  ### Example 1: Score with inline scenario
180
187
  **Request**: "score a=1, b=2 with model simplejob.job"
181
- 1. Find job simplejob Found
188
+ 1. Find job simplejob using find-resources strategy
182
189
  2. Run: `sas-score-run-jobdef({ name: "simplejob", scenario: { a: 1, b: 2 } })`
183
190
  3. Return: `{ c: 3 }`
184
191
 
185
192
  ### Example 2: Score with MAS model
186
193
  **Request**: "predict churn for age=45, income=60000 with model churn_predictor"
187
- 1. Find model churn_predictor MAS
194
+ 1. Find model churn_predictor.mas using find-resources strategy
188
195
  2. Score: `sas-score-mas-score({ model: "churn_predictor", scenario: { age: 45, income: 60000 } })`
189
196
  3. Return: `{ age: 45, income: 60000, churn_probability: 0.23, prediction: "no_churn" }`
190
197
 
191
198
  ### Example 3: Score table rows
192
- **Request**: "score all active customers with model risk_model"
193
- 1. Find model risk_model MAS
194
- 2. Find table customers in Public → CAS
199
+ **Request**: "score all active customers with model risk_model and table Public.customers"
200
+ 1. Find model risk_model.mad using find-resources strategy
201
+ 2. Find table Public.customers using find-resources strategy
195
202
  3. Read: `sas-score-read-table({ lib: "Public", table: "customers", server: "cas", where: "status='active'" })`
196
203
  4. Score each row with risk_model
197
204
  5. Return: customers with risk_score appended
package/README.md CHANGED
@@ -196,7 +196,15 @@ NODE_EXTRA_CA_CERTS=c:\Users\<your_username>\AppData\Local\mkcert\rootCA.pem
196
196
  ```
197
197
 
198
198
  ## License
199
- This project is licensed under the [Apache 2.0 license](LICENSE).
199
+
200
+
201
+ This project is licensed under the Apache License 2.0. See LICENSE.
202
+
203
+ The container image published from this repository also includes third-party software, each component under its own license:
204
+
205
+ The npm dependencies that ship with this project, along with their respective licenses, are listed in LICENSES.json.
206
+ The container is built from the 25-alpine base image; license texts for its included software ship inside the image itself.  License information for each Alpine package is available at pkgs.alpinelinux.org.
207
+ As with any container image, direct and indirect dependencies are governed by their own licenses. Users of the published container image are responsible for ensuring that their use complies with all applicable licenses.
200
208
 
201
209
  ## Additional Resources
202
210
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sassoftware/sas-score-mcp-serverjs",
3
- "version": "1.0.1-19",
3
+ "version": "1.0.1-21",
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,7 @@ function findJobdef(_appContext) {
18
18
  find-jobdef — locate a specific SAS Viya job definition.
19
19
 
20
20
  USE when: find jobdef, does jobdef exist, is there a jobdef named, lookup jobdef, verify jobdef exists
21
- DO NOT USE for: list jobdefs (use list-jobdefs), run jobdef (use run-jobdef), find job/lib/table/model (use respective tools)
21
+ DO NOT USE for: list jobdefs (use sas-score-list-jobdefs), run jobdef (use sas-score-run-jobdef), find job/lib/table/model (use respective tools)
22
22
 
23
23
  PARAMETERS
24
24
  - name: string (required) — jobdef name to locate; if multiple supplied, use first
@@ -38,10 +38,10 @@ EXAMPLES
38
38
  - "is there a jobdef named metricsRefresh" → { name: "metricsRefresh" }
39
39
 
40
40
  NEGATIVE EXAMPLES (do not route here)
41
- - "list jobdefs" (use list-jobdefs)
42
- - "run jobdef cars_job_v4" (use run-jobdef)
43
- - "find job ETL" (use find-job)
44
- - "find table cars" (use find-table)
41
+ - "list jobdefs" (use sas-score-list-jobdefs)
42
+ - "run jobdef cars_job_v4" (use sas-score-run-jobdef)
43
+ - "find job ETL" (use sas-score-find-job)
44
+ - "find table cars" (use sas-score-find-table)
45
45
 
46
46
  ERRORS
47
47
  Returns { jobdefs: [] } if not found; { jobdefs: [name, ...] } if found. Never hallucinate jobdef names.
@@ -44,16 +44,31 @@ Returns predictions, probabilities, scores merged with input data. Returns error
44
44
  description: description,
45
45
  inputSchema: z.object({
46
46
  model: z.string(),
47
- scenario: z.union([z.record(z.any()), z.string()]).optional()
47
+ scenario: z.any()
48
48
  }),
49
49
 
50
50
  handler: async (iparams) => {
51
51
  let params = {...iparams};
52
- if (typeof params.scenario === 'string') {
53
- try { params.scenario = JSON.parse(params.scenario); } catch { params.scenario = {}; }
52
+ let scenario = params.scenario;
53
+
54
+ // Convert the scenario string to an object
55
+ // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
56
+ let scenarioObj = {};
57
+ let count = 0;
58
+ if (typeof scenario === 'object') {
59
+ scenarioObj = scenario;
60
+ } else if (Array.isArray(scenario)) {
61
+ scenarioObj = scenario[0];
62
+ } else {
63
+ //console.error('Incoming scenario', scenario);
64
+ scenarioObj = scenario.split(',').reduce((acc, pair) => {
65
+ let [key, value] = pair.split('=');
66
+ acc[key.trim()] = value;
67
+ count++;
68
+ return acc;
69
+ }, {});
54
70
  }
55
- params.scenario = (params.scenario != null && typeof params.scenario === 'object') ? params.scenario : {};
56
-
71
+ params.scenario = scenarioObj;
57
72
  // Drop model extension (e.g., .job, .model)
58
73
  if (params.model && params.model.includes('.')) {
59
74
  params.model = params.model.substring(0, params.model.lastIndexOf('.'));
@@ -45,10 +45,10 @@ Log output and CAS results. If output table specified, returned as markdown tabl
45
45
  description: description,
46
46
  inputSchema:z.object({
47
47
  src: z.string(),
48
- scenario: z.string(),
49
- output: z.string().optional,
50
- folder: z.string().optional,
51
- limit: z.number().optional
48
+ scenario: z.any(),
49
+ output: z.string().optional(),
50
+ folder: z.string().optional(),
51
+ limit: z.number().optional()
52
52
  }),
53
53
 
54
54
  // NOTE: Previously 'required' incorrectly listed 'program' which does not
@@ -70,15 +70,26 @@ Log output and CAS results. If output table specified, returned as markdown tabl
70
70
  `;
71
71
  }
72
72
  // figure out macros
73
-
74
- if (typeof scenario === 'string' && scenario.includes('=')) {
75
- scenario = scenario.split(',').reduce((acc, pair) => {
76
- const [k, ...rest] = pair.split('=');
77
- if (!k) return acc;
78
- acc[k.trim()] = rest.join('=').trim();
73
+
74
+ // Convert the scenario string to an object
75
+ // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
76
+ let scenarioObj = {};
77
+ let count = 0;
78
+ if (typeof scenario === 'object') {
79
+ scenarioObj = scenario;
80
+ } else if (Array.isArray(scenario)) {
81
+ scenarioObj = scenario[0];
82
+ } else {
83
+ //console.error('Incoming scenario', scenario);
84
+ scenarioObj = scenario.split(',').reduce((acc, pair) => {
85
+ let [key, value] = pair.split('=');
86
+ acc[key.trim()] = value;
87
+ count++;
79
88
  return acc;
80
89
  }, {});
81
90
  }
91
+ params.scenario = scenarioObj;
92
+
82
93
  let iparms = {
83
94
  args: scenario,
84
95
  output: params.output,
@@ -41,13 +41,29 @@ Returns log output, listings, tables from job. Error if job not found.
41
41
  description: description,
42
42
  inputSchema: z.object({
43
43
  name: z.string(),
44
- scenario: z.union([z.record(z.any()), z.string()]).optional()
44
+ scenario: z.any()
45
45
  }),
46
46
  handler: async (params) => {
47
- if (typeof params.scenario === 'string') {
48
- try { params.scenario = JSON.parse(params.scenario); } catch { params.scenario = {}; }
47
+ let scenario = params.scenario;
48
+
49
+ // Convert the scenario string to an object
50
+ // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
51
+ let scenarioObj = {};
52
+ let count = 0;
53
+ if (typeof scenario === 'object') {
54
+ scenarioObj = scenario;
55
+ } else if (Array.isArray(scenario)) {
56
+ scenarioObj = scenario[0];
57
+ } else {
58
+ //console.error('Incoming scenario', scenario);
59
+ scenarioObj = scenario.split(',').reduce((acc, pair) => {
60
+ let [key, value] = pair.split('=');
61
+ acc[key.trim()] = value;
62
+ count++;
63
+ return acc;
64
+ }, {});
49
65
  }
50
- params.scenario = (params.scenario != null && typeof params.scenario === 'object') ? params.scenario : {};
66
+ params.scenario = scenarioObj;
51
67
  params.type = 'job';
52
68
  // Provide runtime context for auth and server settings
53
69
  let r = await _jobSubmit(params);
@@ -42,13 +42,29 @@ Returns log output, listings, tables from jobdef. Error if jobdef not found.
42
42
  description: description,
43
43
  inputSchema: z.object({
44
44
  name: z.string(),
45
- scenario: z.union([z.record(z.any()), z.string()]).optional()
45
+ scenario: z.any()
46
46
  }),
47
47
  handler: async (params) => {
48
- if (typeof params.scenario === 'string') {
49
- try { params.scenario = JSON.parse(params.scenario); } catch { params.scenario = {}; }
48
+ let scenario = params.scenario;
49
+
50
+ // Convert the scenario string to an object
51
+ // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
52
+ let scenarioObj = {};
53
+ let count = 0;
54
+ if (typeof scenario === 'object') {
55
+ scenarioObj = scenario;
56
+ } else if (Array.isArray(scenario)) {
57
+ scenarioObj = scenario[0];
58
+ } else {
59
+ //console.error('Incoming scenario', scenario);
60
+ scenarioObj = scenario.split(',').reduce((acc, pair) => {
61
+ let [key, value] = pair.split('=');
62
+ acc[key.trim()] = value;
63
+ count++;
64
+ return acc;
65
+ }, {});
50
66
  }
51
- params.scenario = (params.scenario != null && typeof params.scenario === 'object') ? params.scenario : {};
67
+ params.scenario = scenarioObj;
52
68
  params.type = 'def';
53
69
  // Provide runtime context for auth and server settings
54
70
  let r = await _jobSubmit(params);