@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.
- package/.agents/sas-score-mcp-serverjs-agent.md +58 -0
- package/.instructions/copilot-instructions.md +201 -0
- package/.instructions/enforce-find-resource-strategy.md +35 -0
- package/.skills/sas-find-library-smart/SKILL.md +155 -0
- package/.skills/sas-find-resource-strategy/SKILL.md +105 -0
- package/.skills/sas-list-resource-strategy/SKILL.md +124 -0
- package/.skills/sas-list-tables-smart/SKILL.md +128 -0
- package/.skills/sas-read-and-score-strategy/SKILL.md +113 -0
- package/.skills/sas-read-strategy/SKILL.md +154 -0
- package/.skills/sas-request-classifier/SKILL.md +74 -0
- package/.skills/sas-score-workflow-strategy/SKILL.md +314 -0
- package/cli.js +24 -17
- package/package.json +4 -2
- package/src/setupSkills.js +22 -3
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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-
|
|
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",
|
package/src/setupSkills.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|