@sassoftware/sas-score-mcp-serverjs 0.4.1-21 → 0.4.1-22

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.
@@ -1,401 +0,0 @@
1
- ---
2
- name: sas-score-workflow
3
- description: >
4
- MANDATORY routing logic for all scoring requests. Extract model.type suffix, route to correct tool
5
- (run-job|run-jobdef|model-score|scr-score|run-program). Complete routing decision BEFORE invoking
6
- any tool. Handles both MAS models and alternative scoring engines (jobs, jobdefs, SCR, SAS programs).
7
- Trigger phrases: "score with model X.job", "score X.jobdef scenario", "score with model X.mas",
8
- "score with model X.scr", any request with "score" + model name containing a dot (.) + type suffix.
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
- ## ⚠️ MANDATORY: ROUTING DECISION (DO THIS FIRST)
39
-
40
- **CRITICAL: This decision must be completed BEFORE invoking ANY scoring tool.**
41
-
42
- When a user requests scoring with a model name (e.g., `score a=10,b=20 with model simplejob.job`):
43
-
44
- ### Step 1: Extract the Type Suffix
45
- Check if the model name contains a dot:
46
- - **YES** (e.g., `simplejob.job`) → Split on the last dot to extract type: `job`
47
- - **NO** (e.g., `churn`) → Assume type is `.mas` (default MAS model)
48
-
49
- ### Step 2: Validate the Type
50
- Confirm the extracted type is one of: `job`, `jobdef`, `mas`, `scr`, `sas`
51
- - **VALID** → Proceed to Step 3
52
- - **INVALID** (e.g., `.xyz`) → Assume `.mas` and treat entire input as model name
53
-
54
- ### Step 3: Strip the Type Suffix
55
- Remove the `.type` from the model name:
56
- - `simplejob.job` → base name: `simplejob`
57
- - `churn.mas` → base name: `churn`
58
- - `fraud_detector.jobdef` → base name: `fraud_detector`
59
-
60
- ### Step 4: Route to the Correct Tool
61
- Invoke the tool that matches the type:
62
- - **`.job`** → `sas-score-run-job`
63
- - **`.jobdef`** → `sas-score-run-jobdef`
64
- - **`.mas`** → `sas-score-model-score`
65
- - **`.scr`** → `sas-score-scr-score`
66
- - **`.sas`** → `sas-score-run-sas-program`
67
-
68
- ### Step 5: Invoke with Correct Parameters
69
- Pass the base name (without type) to the routed tool.
70
-
71
- ---
72
-
73
- ## Routing Decision Tree
74
-
75
- ```
76
- User request: "score a=10,b=20 with model simplejob.job"
77
-
78
- [Extract model reference] → "simplejob.job"
79
-
80
- [Split on last dot] → base="simplejob", type="job"
81
-
82
- [Validate type] → "job" ∈ [job, jobdef, mas, scr, sas]? YES
83
-
84
- [Route by type]
85
- └─ type="job" → call sas-score-run-job()
86
-
87
- [Invoke tool] → sas-score-run-job({ name: "simplejob", scenario: {a: "10", b: "20"} })
88
-
89
- [Return results]
90
- ```
91
-
92
- ---
93
-
94
- ## ❌ Anti-Patterns (DO NOT DO THIS)
95
-
96
- These mistakes cause incorrect routing:
97
-
98
- | Request | Wrong Tool Called | Why It's Wrong | Correct Tool |
99
- |---------|-------------------|----------------|---------------|
100
- | `score simplejob.job scenario =a=10,b=20` | `deva-score` | `.job` suffix ignored; generic scorer used | `sas-score-run-job` |
101
- | `score churn.mas where age=45` | `run-job` | Type mismatch; treated as job instead of MAS | `sas-score-model-score` |
102
- | `score fraud.jobdef using amount=500` | `model-score` | Wrong tool for jobdef type | `sas-score-run-jobdef` |
103
- | `score model.scr with scenario` | `run-jobdef` | SCR endpoint ignored | `sas-score-scr-score` |
104
- | `score code.sas using x=1` | `model-score` | SAS program treated as MAS model | `sas-score-run-sas-program` |
105
-
106
- ---
107
-
108
- ## ✅ Checkpoint Verification
109
-
110
- Before invoking ANY tool, verify all checkpoints:
111
-
112
- - [ ] **Did I extract the type correctly?** (Check: is it one of the 5 types: `job`, `jobdef`, `mas`, `scr`, `sas`?)
113
- - [ ] **Did I strip the type suffix from the model name?** (Pass base name only: `simplejob`, not `simplejob.job`)
114
- - [ ] **Does the routing match the type?** (e.g., `.job` → `run-job`, `.mas` → `model-score`)
115
- - [ ] **Am I using the correct parameter name?** (e.g., `name:` for jobs/jobdefs, `model:` for MAS, `url:` for SCR)
116
- - [ ] **Is the scenario parsed correctly?** (e.g., `{ a: "10", b: "20" }` from `a=10,b=20`)
117
- - [ ] **Have I considered if this is the default MAS case?** (If no type, assume `.mas` and use `model-score`)
118
-
119
- ⚠️ **All checkboxes must be ✓ before invoking the tool.**
120
-
121
- ---
122
-
123
- ## Type-Based Routing
124
-
125
- ### Parse and Strip Model Type
126
-
127
- When a user provides a model name with a type suffix (e.g., `simplejon.job`, `churn.mas`):
128
-
129
- 1. **Extract the type:** Split on the last dot to identify the type suffix
130
- - `simplejon.job` → type = `job`, base name = `simplejon`
131
- - `churn.mas` → type = `mas`, base name = `churn`
132
- - `fraud_detector.jobdef` → type = `jobdef`, base name = `fraud_detector`
133
-
134
- 2. **Validate the type:** Confirm it matches one of the supported types: `job`, `jobdef`, `mas`, `scr`, `sas`
135
- - If type is unrecognized, assume `.mas` (default MAS model) and treat the entire input as the model name
136
-
137
- 3. **Strip the type suffix:** Remove the `.type` from the model name before passing to the routing tool
138
- - **Critical:** Always pass the base name (without the dot and type) to the invoked tool
139
- - `simplejon.job` → pass `simplejon` to `sas-score-run-job`
140
- - `churn.mas` → pass `churn` to `sas-score-model-score`
141
- - `fraud_detector.jobdef` → pass `fraud_detector` to `sas-score-run-jobdef`
142
-
143
- ### Type: `.mas` (Model Aggregation Service)
144
- - **Tool**: `sas-score-model-score`
145
- - **Use for**: Standard MAS-deployed predictive models
146
- - **Example**: `score with model churn.mas scenario =age=45,income=60000`
147
- - **Invocation**: `sas-score-model-score({ model: "churn", scenario: {...} })`
148
-
149
- ### Type: `.job` (SAS Viya Job)
150
- - **Tool**: `sas-score-run-job`
151
- - **Use for**: Pre-built scoring jobs with parameters
152
- - **Example**: `score with model monthly_scorer.job scenario =month=10,year=2025`
153
- - **Invocation**: `sas-score-run-job({ name: "monthly_scorer", scenario: {...} })`
154
-
155
- ### Type: `.jobdef` (SAS Viya Job Definition)
156
- - **Tool**: `sas-score-run-jobdef`
157
- - **Use for**: Job definitions that perform scoring logic
158
- - **Example**: `score with model fraud_detector.jobdef using amount=500,merchant=online`
159
- - **Invocation**: `sas-score-run-jobdef({ name: "fraud_detector", scenario: {...} })`
160
-
161
- ### Type: `.scr` (Score Code Runtime)
162
- - **Tool**: `sas-score-scr-score`
163
- - **Use for**: Models deployed in SCR containers (REST endpoints)
164
- - **Example**: `score https://scr-host/models/loan.scr using age=45,credit=700`
165
- - **Invocation**: `sas-score-scr-score({ url: "https://scr-host/models/loan", scenario: {...} })`
166
-
167
- ### Type: `.sas` (SAS Program / SQL)
168
- - **Tool**: `sas-score-run-sas-program`
169
- - **Use for**: Custom SAS or SQL scoring code
170
- - **Example**: `score my_scoring_code.sas using x=1,y=2`
171
- - **Invocation**: `sas-score-run-sas-program({ folder: "my_scoring_code", scenario: {...} })`
172
-
173
-
174
-
175
- ---
176
-
177
- ## Scenario Parsing
178
-
179
- The scenario parameter (comma-separated key=value pairs) is parsed into an object:
180
-
181
- ```
182
- scenario =age=45,income=60000,region=South
183
- ↓ parsed as:
184
- { age: "45", income: "60000", region: "South" }
185
- ```
186
-
187
- Accepted formats:
188
- - **String**: `age=45,income=60000`
189
- - **Object**: `{ age: 45, income: 60000 }`
190
- - **Array** (batch): `[ {age:45, income:60000}, {age:50, income:75000} ]`
191
-
192
- ---
193
-
194
- ## Integration with other skills
195
-
196
- - **Before scoring table data**: Use `sas-find-library-smart` to verify the library, then `sas-read-strategy` to fetch records
197
- - **For read + score workflows**: Use `sas-read-and-score` for the complete end-to-end pattern
198
-
199
- ---
200
-
201
- ## Step 1 — Check model familiarity before scoring
202
-
203
- Score immediately if:
204
- - The user names a specific model they've used before in this session, OR
205
- - The model name matches a previously confirmed model in the conversation
206
-
207
- Pause and suggest investigation if:
208
- - The model name is new, vague, or misspelled-looking (e.g. "the churn one", "that cancer model")
209
- - The user seems unsure of the required input variable names
210
-
211
- **Suggested message:**
212
- > "I don't recognize that model — want me to run `find-model` to confirm it exists,
213
- > or `model-info` to check its required inputs first?"
214
-
215
- ---
216
-
217
- ## Step 2 — Prepare the scenario data
218
-
219
- **For a single record** (one object):
220
- ```javascript
221
- scenario = { field1: value1, field2: value2, ... }
222
- ```
223
-
224
- **For batch scoring** (multiple records — the typical case):
225
- ```javascript
226
- scenario = [
227
- { field1: val1, field2: val2, ... },
228
- { field1: val3, field2: val4, ... },
229
- ...
230
- ]
231
- ```
232
-
233
- **Critical rules:**
234
- - Loop or call sas-score-model-score **once per row**.
235
- - Field names in the scenario must match the model's expected input variable names **exactly**.
236
- - If table column names differ from model input names, **flag this to the user** and ask for confirmation before scoring.
237
- - Example: Table has `age_years`, but model expects `age` → ask user which column maps to which input.
238
- - Do not add units, labels, or extra metadata — raw field values only.
239
-
240
- ---
241
-
242
- ## Step 3 — Invoke the appropriate scoring tool
243
-
244
- Based on the type extracted from the model name, invoke the corresponding tool:
245
-
246
- **For `.mas` (default):**
247
- ```javascript
248
- sas-score-model-score({
249
- model: "<modelname>",
250
- scenario: scenario, // object or array
251
- uflag: false // set true if you need field names prefixed with _
252
- })
253
- ```
254
-
255
- **For `.job`:**
256
- ```javascript
257
- sas-score-run-job({
258
- name: "<jobname>",
259
- scenario: scenario
260
- })
261
- ```
262
-
263
- **For `.jobdef`:**
264
- ```javascript
265
- sas-score-run-jobdef({
266
- name: "<jobdefname>",
267
- scenario: scenario
268
- })
269
- ```
270
-
271
- **For `.scr`:**
272
- ```javascript
273
- sas-score-scr-score({
274
- url: "<scr_endpoint_url>",
275
- scenario: scenario
276
- })
277
- ```
278
-
279
- **For `.sas`:**
280
- ```javascript
281
- sas-score-run-sas-program({
282
- src: "<sas_or_sql_code>",
283
- scenario: scenario
284
- })
285
- ```
286
-
287
- **Rules:**
288
- - Pass the full batch in one call; do not loop over rows
289
- - If scoring fails, return the structured error and suggest troubleshooting
290
- - For MAS models, include uflag parameter if underscore-prefixed output is needed
291
- - For jobs/jobdefs, scenario becomes parameter arguments
292
- - For SCR, include full URL endpoint
293
-
294
- ---
295
-
296
- ## Step 4 — Present the results
297
-
298
- Merge the scoring output back with the input records and present as a table where possible.
299
-
300
- **Always surface:**
301
- - The key prediction/score field(s) (e.g. `P_churn`, `score`, `prediction`, `P_risk`)
302
- - Any probability/confidence fields for classification models (e.g. `P_class0`, `P_class1`)
303
- - Selected input fields that drove the prediction, so the user can see context
304
-
305
- **Formatting:**
306
- - Present results in a table for clarity
307
- - If results exceed 10 rows, show the first 10 and ask: *"Want to see more results or export the full set?"*
308
- - Round numeric predictions to 2–4 decimal places for readability
309
-
310
- ---
311
-
312
- ## Common flows
313
-
314
- **Flow A — Score rows with MAS model**
315
- > "Score the first 10 customers in Public.customers with the churn model"
316
-
317
- 1. `sas-score-read-table` → { table: "Public.customers", limit: 10 }
318
- 2. `sas-score-model-score` → { model: "churn", scenario: [ ...10 row objects ] }
319
- 3. Present merged results with prediction + key inputs
320
-
321
- **Flow B — Score with a scoring job**
322
- > "Score December sales with the monthly_scorer job using month=12,year=2025"
323
-
324
- 1. `sas-score-run-job` → { name: "monthly_scorer", scenario: { month: "12", year: "2025" } }
325
- 2. Capture job output and tables
326
- 3. Present results
327
-
328
- **Flow C — Score with a job definition**
329
- > "Run fraud detection jobdef on transaction amount=500, merchant=online"
330
-
331
- 1. `sas-score-run-jobdef` → { name: "fraud_detection", scenario: { amount: "500", merchant: "online" } }
332
- 2. Capture log, listings, and tables
333
- 3. Present results
334
-
335
- **Flow D — Score with SCR endpoint**
336
- > "Score with the loan model at https://scr-host/models/loan using age=45, credit_score=700"
337
-
338
- 1. `sas-score-scr-score` → { url: "https://scr-host/models/loan", scenario: { age: "45", credit_score: "700" } }
339
- 2. Capture prediction response
340
- 3. Present result
341
-
342
- **Flow E — Score results of an analytical query with MAS**
343
- > "Score high-value customers (spend > 5000) in mylib.sales with the fraud model"
344
-
345
- 1. `sas-score-sas-query` → { table: "mylib.sales", sql: "SELECT * FROM mylib.sales WHERE spend > 5000" }
346
- 2. `sas-score-model-score` → { model: "fraud", scenario: [ ...result rows ] }
347
- 3. Present merged results
348
-
349
- **Flow F — User supplies scenario data directly**
350
- > "Score age=45, income=60000, region=South with the churn model"
351
-
352
- 1. Skip read step
353
- 2. `sas-score-model-score` → { model: "churn", scenario: { age: "45", income: "60000", region: "South" } }
354
- 3. Present result
355
-
356
- **Flow G — Model unfamiliar, need to confirm**
357
- > "Score Public.applicants with the creditRisk2 model"
358
-
359
- 1. Pause — "creditRisk2" is new
360
- 2. Suggest: `find-model` to confirm it exists, `model-info` to get input variables
361
- 3. Once confirmed → `sas-score-read-table` + `sas-score-model-score`
362
-
363
- **Flow H — Generic score syntax with type routing**
364
- > "score with model churn.mas scenario =age=45,income=60000"
365
- > "score fraud_detector.jobdef where scenario =amount=500"
366
- > "score monthly_report.job using month=10,year=2025"
367
-
368
- 1. Parse model name to extract type (.mas, .job, .jobdef, .scr, .sas)
369
- 2. Route to appropriate tool based on type
370
- 3. Parse scenario and invoke tool with parameters
371
- 4. Present results from routed tool
372
-
373
- ---
374
-
375
- ## Error handling
376
-
377
- | Problem | Action |
378
- |---|---|
379
- | Model not found | Suggest `find-model` to verify the model is deployed |
380
- | Input field name mismatch | Show the mismatch (table has X, model expects Y), ask user to confirm mapping |
381
- | Scoring error / invalid inputs | Return structured error, suggest `model-info` to check required inputs and data types |
382
- | Empty read result | Tell user, ask if they want to adjust the query/filter before scoring |
383
- | Missing input fields | Ask which table columns map to the required model inputs |
384
- | **Type routing error (wrong tool called)** | **Check: did I complete the MANDATORY routing decision? Did I extract the type correctly? Did I route to the right tool based on type?** |
385
- | **Type suffix not recognized** | **Assume `.mas` (default MAS model) and treat entire input as model name** |
386
-
387
- ---
388
-
389
- ## Tips
390
-
391
- - **Batch is better:** Always pass the full set of records in one `sas-score-model-score` call. Do not loop.
392
- - **Confirm mappings:** If column names don't match model inputs, ask before scoring.
393
- - **Show context:** Include key input columns in the result output so predictions make sense.
394
- - **Limit output:** For large result sets (>10 rows), ask before showing all.
395
-
396
- ---
397
-
398
- ## Integration with other skills
399
-
400
- - **Before scoring table data**: Use `sas-find-library-smart` to verify the library, then `sas-read-strategy` to fetch records
401
- - **For read + score workflows**: Use `sas-read-and-score` for the complete end-to-end pattern
@@ -1,303 +0,0 @@
1
- ---
2
- name: sas-spec-migration
3
- description: >
4
- Migrate or clean up SAS MCP tool spec objects. Use this skill whenever the user asks
5
- to migrate, convert, update, or clean up tool specs. Covers two patterns:
6
- (1) Old Zod-based format (z.string(), z.number(), top-level schema/required) → new
7
- JSON Schema inputSchema format.
8
- (2) Existing JSON Schema specs that need cleanup: removing $schema, fixing required
9
- arrays (optional fields like "where" should not be in required), typing untyped fields
10
- like scenario. Also trigger on: "migrate my tools", "update my specs", "clean up my
11
- specs", "remove $schema", "fix required", or any request to standardize tool specs.
12
- ---
13
-
14
- # SAS Tool Spec Migration
15
-
16
- Covers two migration scenarios:
17
-
18
- - **Zod → JSON Schema**: Old format using `z.string()`, `z.number()` etc. with top-level `schema` and `required`
19
- - **JSON Schema cleanup**: Existing `inputSchema` specs that need `$schema` removed, `required` fixed, or untyped fields corrected
20
-
21
- Identify which pattern applies before proceeding.
22
-
23
- ---
24
-
25
- ## Pattern 1 — JSON Schema cleanup
26
-
27
- Use when the tool already has `inputSchema` but needs tidying. Apply all of these fixes:
28
-
29
- ### Remove `$schema`
30
-
31
- Drop the `$schema` declaration entirely — the MCP SDK and LLMs ignore it at runtime and
32
- it wastes tokens.
33
-
34
- ```js
35
- // Before
36
- inputSchema: {
37
- $schema: "http://json-schema.org/draft-07/schema#",
38
- type: "object",
39
- ...
40
- }
41
-
42
- // After
43
- inputSchema: {
44
- type: "object",
45
- ...
46
- }
47
- ```
48
-
49
- ### Fix `required` arrays
50
-
51
- Only include a field in `required` if it is truly mandatory. Common mistakes:
52
-
53
- | Field | Rule |
54
- |---|---|
55
- | `where` (filter expression) | Optional — remove from `required`, default to `""` in handler |
56
- | `scenario` (job params) | Optional — not all jobs need parameters; remove from `required` |
57
- | `name` | Required — always include |
58
- | `limit`, `start` | Required for list tools — include |
59
-
60
- ### Type untyped fields
61
-
62
- If a field has `{}` or no type, infer the correct type:
63
-
64
- | Field | Type |
65
- |---|---|
66
- | `scenario` | `{ type: "string" }` — handler already parses it as JSON |
67
- | `where` | `{ type: "string" }` |
68
- | Any flexible input | `{ type: "string" }` with note to parse in handler |
69
-
70
- ### Full cleanup example
71
-
72
- **Before:**
73
- ```js
74
- inputSchema: {
75
- $schema: "http://json-schema.org/draft-07/schema#",
76
- type: "object",
77
- properties: {
78
- name: { type: "string" },
79
- scenario: {}
80
- },
81
- required: ["name", "scenario"]
82
- }
83
- ```
84
-
85
- **After:**
86
- ```js
87
- inputSchema: {
88
- type: "object",
89
- properties: {
90
- name: { type: "string" },
91
- scenario: { type: "string" }
92
- },
93
- required: ["name"]
94
- }
95
- ```
96
-
97
- ---
98
-
99
- ## Pattern 2 — Zod → JSON Schema
100
-
101
- ## What changes
102
-
103
- **Old format:**
104
- ```js
105
- let spec = {
106
- name: 'find-job',
107
- aliases: [...],
108
- description: description,
109
- schema: {
110
- name: z.string(),
111
- limit: z.number().optional(),
112
- },
113
- required: ['name'],
114
- handler: async (params) => { ... }
115
- }
116
- ```
117
-
118
- **New format:**
119
- ```js
120
- let spec = {
121
- name: 'find-job',
122
- aliases: [...],
123
- description: description,
124
- inputSchema: {
125
- type: "object",
126
- properties: {
127
- name: { type: "string", description: "Job name to locate" },
128
- limit: { type: "number", description: "Max results to return" }
129
- },
130
- required: ['name']
131
- },
132
- handler: async (params) => { ... }
133
- }
134
- ```
135
-
136
- **Summary of changes:**
137
- - `schema` → `inputSchema`
138
- - `inputSchema` gains `type: "object"` and `properties: {}`
139
- - Each Zod field moves inside `properties` as a JSON Schema object
140
- - Top-level `required` array moves inside `inputSchema`
141
- - Fields with `.optional()` are NOT included in `required`
142
- - Fields without `.optional()` ARE included in `required` (unless already excluded)
143
-
144
- ---
145
-
146
- ## Zod → JSON Schema type mapping
147
-
148
- | Zod | JSON Schema |
149
- |---|---|
150
- | `z.string()` | `{ type: "string" }` |
151
- | `z.number()` | `{ type: "number" }` |
152
- | `z.boolean()` | `{ type: "boolean" }` |
153
- | `z.array(z.string())` | `{ type: "array", items: { type: "string" } }` |
154
- | `z.array(z.number())` | `{ type: "array", items: { type: "number" } }` |
155
- | `z.object({...})` | `{ type: "object", properties: {...} }` |
156
- | `z.enum(['a','b'])` | `{ type: "string", enum: ["a", "b"] }` |
157
- | `.describe("text")` | Add `description: "text"` to the property |
158
- | `.optional()` | Omit field from `required` array |
159
- | `.optional().describe("text")` | Omit from `required`, add `description` |
160
-
161
- ---
162
-
163
- ## Step-by-step migration
164
-
165
- ### Step 1 — Identify the fields
166
-
167
- Read the existing `schema` object. For each key, note:
168
- - Its Zod type (string, number, boolean, array, enum, object)
169
- - Whether it has `.optional()`
170
- - Whether it has `.describe("...")` — extract the description text
171
- - Whether it appears in the top-level `required` array
172
-
173
- ### Step 2 — Build `properties`
174
-
175
- For each field in `schema`, create a JSON Schema property object:
176
-
177
- ```js
178
- // Old
179
- fieldName: z.string().describe("The name of the thing")
180
-
181
- // New
182
- fieldName: { type: "string", description: "The name of the thing" }
183
- ```
184
-
185
- If no `.describe()` is present, infer a short description from the field name and tool context.
186
- Keep descriptions concise — one sentence, imperative style ("Job name to locate", not "This is the name of the job").
187
-
188
- ### Step 3 — Build `required`
189
-
190
- Include a field in `required` if:
191
- - It appeared in the old top-level `required` array, OR
192
- - It has no `.optional()` in its Zod definition
193
-
194
- Exclude a field from `required` if:
195
- - It has `.optional()` in its Zod definition
196
-
197
- ### Step 4 — Assemble `inputSchema`
198
-
199
- ```js
200
- inputSchema: {
201
- type: "object",
202
- properties: {
203
- // one entry per field from Step 2
204
- },
205
- required: [/* field names from Step 3 */]
206
- }
207
- ```
208
-
209
- If `required` would be empty, omit it entirely rather than including `required: []`.
210
-
211
- ### Step 5 — Remove old fields
212
-
213
- Remove `schema` and the top-level `required` from the spec object.
214
- Leave everything else unchanged: `name`, `aliases`, `description`, `handler`.
215
-
216
- ---
217
-
218
- ## Edge cases
219
-
220
- **Field in `required` but also `.optional()` in Zod:**
221
- Trust `.optional()` — exclude from `required`. The old `required` array may be stale.
222
-
223
- **Nested `z.object()`:**
224
- ```js
225
- // Old
226
- config: z.object({ host: z.string(), port: z.number() })
227
-
228
- // New
229
- config: {
230
- type: "object",
231
- properties: {
232
- host: { type: "string", description: "Host address" },
233
- port: { type: "number", description: "Port number" }
234
- }
235
- }
236
- ```
237
-
238
- **`z.union()` / `z.any()` / `z.unknown()`:**
239
- Use `{}` (empty schema, accepts anything) or `{ type: "string" }` with a note that the
240
- field accepts flexible input. Flag this to the user for review.
241
-
242
- **No `schema` field at all:**
243
- The tool takes no inputs. Set `inputSchema: { type: "object", properties: {} }` or omit
244
- `inputSchema` entirely — both are valid. Omitting is cleaner for zero-input tools.
245
-
246
- **`schema` is already a plain JSON object (not Zod):**
247
- Check if values use `z.` prefix. If not, the schema may already be partially migrated —
248
- wrap it in `{ type: "object", properties: { ... } }` and move `required` inside.
249
-
250
- ---
251
-
252
- ## Output format
253
-
254
- - Preserve the existing code style (quote style, indentation, trailing commas)
255
- - Output the full updated spec object, not just the diff
256
- - If migrating multiple specs at once, process them all and output each in full
257
- - After outputting, note any fields that used `z.union()`, `z.any()`, or other ambiguous
258
- types that the user should review manually
259
-
260
- ---
261
-
262
- ## Example — full migration
263
-
264
- **Input:**
265
- ```js
266
- let spec = {
267
- name: 'find-job',
268
- aliases: ['findJob', 'find job'],
269
- description: description,
270
- schema: {
271
- name: z.string(),
272
- caslib: z.string().optional().describe("CAS library name"),
273
- limit: z.number().optional(),
274
- },
275
- required: ['name'],
276
- handler: async (params) => {
277
- let r = await _listJobs(params);
278
- return r;
279
- }
280
- }
281
- ```
282
-
283
- **Output:**
284
- ```js
285
- let spec = {
286
- name: 'find-job',
287
- aliases: ['findJob', 'find job'],
288
- description: description,
289
- inputSchema: {
290
- type: "object",
291
- properties: {
292
- name: { type: "string", description: "Job name to locate" },
293
- caslib: { type: "string", description: "CAS library name" },
294
- limit: { type: "number", description: "Max results to return" }
295
- },
296
- required: ['name']
297
- },
298
- handler: async (params) => {
299
- let r = await _listJobs(params);
300
- return r;
301
- }
302
- }
303
- ```