@sassoftware/sas-score-mcp-serverjs 0.4.1 → 1.0.1-0

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.
Files changed (72) hide show
  1. package/.skills/agents/sas-viya-scoring-expert.md +58 -0
  2. package/.skills/copilot-instructions.md +155 -0
  3. package/.skills/skills/sas-find-library-smart/SKILL.md +154 -0
  4. package/.skills/skills/sas-list-tables-smart/SKILL.md +127 -0
  5. package/.skills/skills/sas-read-and-score/SKILL.md +111 -0
  6. package/.skills/skills/sas-read-strategy/SKILL.md +156 -0
  7. package/.skills/skills/sas-request-classifier/SKILL.md +69 -0
  8. package/.skills/skills/sas-score-workflow/SKILL.md +314 -0
  9. package/cli.js +311 -70
  10. package/package.json +7 -7
  11. package/scripts/docs/SCORE_SKILL_REFERENCE.md +142 -0
  12. package/scripts/docs/TOOL_DESCRIPTION_TEMPLATE.md +157 -0
  13. package/scripts/docs/TOOL_UPDATES_SUMMARY.md +208 -0
  14. package/scripts/docs/mcp-localhost-config-guide.md +184 -0
  15. package/scripts/docs/oauth-http-transport.md +96 -0
  16. package/scripts/docs/sas-mcp-tools-reference.md +600 -0
  17. package/scripts/getViyaca.sh +1 -0
  18. package/scripts/optimize_final.py +140 -0
  19. package/scripts/optimize_tools.py +99 -0
  20. package/scripts/setup-skills.js +34 -0
  21. package/scripts/update_descriptions.py +46 -0
  22. package/scripts/viyatls.sh +3 -0
  23. package/src/authpkce.js +219 -0
  24. package/src/createMcpServer.js +16 -5
  25. package/src/expressMcpServer.js +350 -308
  26. package/src/handleGetDelete.js +6 -3
  27. package/src/hapiMcpServer.js +10 -18
  28. package/src/oauthHandlers/authorize.js +46 -0
  29. package/src/oauthHandlers/baseUrl.js +8 -0
  30. package/src/oauthHandlers/callback.js +96 -0
  31. package/src/oauthHandlers/getMetadata.js +27 -0
  32. package/src/oauthHandlers/index.js +7 -0
  33. package/src/oauthHandlers/token.js +37 -0
  34. package/src/processHeaders.js +88 -0
  35. package/src/setupSkills.js +46 -0
  36. package/src/toolHelpers/_jobSubmit.js +2 -0
  37. package/src/toolHelpers/_listLibrary.js +55 -39
  38. package/src/toolHelpers/getLogonPayload.js +7 -1
  39. package/src/toolHelpers/readCerts.js +4 -4
  40. package/src/toolHelpers/refreshToken.js +3 -2
  41. package/src/toolHelpers/refreshTokenOauth.js +3 -3
  42. package/src/toolSet/.claude/settings.local.json +13 -0
  43. package/src/toolSet/devaScore.js +61 -69
  44. package/src/toolSet/findJob.js +38 -71
  45. package/src/toolSet/findJobdef.js +28 -59
  46. package/src/toolSet/findLibrary.js +68 -100
  47. package/src/toolSet/findModel.js +35 -58
  48. package/src/toolSet/findTable.js +31 -60
  49. package/src/toolSet/getEnv.js +30 -45
  50. package/src/toolSet/listJobdefs.js +61 -96
  51. package/src/toolSet/listJobs.js +61 -110
  52. package/src/toolSet/listLibraries.js +78 -90
  53. package/src/toolSet/listModels.js +56 -83
  54. package/src/toolSet/listTables.js +66 -95
  55. package/src/toolSet/makeTools.js +1 -0
  56. package/src/toolSet/modelInfo.js +22 -54
  57. package/src/toolSet/modelScore.js +35 -77
  58. package/src/toolSet/readTable.js +63 -104
  59. package/src/toolSet/runCasProgram.js +32 -52
  60. package/src/toolSet/runJob.js +24 -24
  61. package/src/toolSet/runJobdef.js +26 -29
  62. package/src/toolSet/runMacro.js +82 -82
  63. package/src/toolSet/runProgram.js +32 -84
  64. package/src/toolSet/sasQuery.js +77 -126
  65. package/src/toolSet/sasQueryTemplate.js +4 -5
  66. package/src/toolSet/sasQueryTemplate2.js +4 -5
  67. package/src/toolSet/scrInfo.js +4 -7
  68. package/src/toolSet/scrScore.js +69 -70
  69. package/src/toolSet/searchAssets.js +5 -6
  70. package/src/toolSet/setContext.js +65 -92
  71. package/src/toolSet/superstat.js +61 -60
  72. package/src/toolSet/tableInfo.js +58 -102
@@ -1,69 +1,61 @@
1
- /*
2
- * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
- import {z} from 'zod';
6
-
7
- function devaScore(_appContext) {
8
-
9
- let description = `
10
- ## deva-score — compute a numeric score based on two input values
11
-
12
- LLM Invocation Guidance (When to use)
13
- Use THIS tool when:
14
- - User wants to calculate the deva score: "Calculate deva score for 5 and 10"
15
- - User provides two numbers for scoring: "Score these values: 3 and 7"
16
- - User wants to compute a score in a series: "Calculate scores for [list of numbers]"
17
-
18
- Do NOT use this tool for:
19
- - Scoring models (use model-score)
20
- - Statistical calculations beyond deva scoring
21
- - Looking up data or metadata
22
-
23
- Purpose
24
- Compute a numeric deva score by applying the formula (a + b) * 42 to two input numbers. For scoring more than two numbers, call this tool multiple times using the previous result as the first input (left-to-right fold).
25
-
26
- Parameters
27
- - a (number, required): First numeric input value
28
- - b (number, required): Second numeric input value
29
-
30
- Response Contract
31
- Returns a numeric result: (a + b) * 42
32
- The result is always a number representing the computed deva score.
33
-
34
- Disambiguation & Clarification
35
- - If user provides more than two numbers without clear instructions: "Do you want to calculate the deva score by combining these numbers left-to-right?"
36
- - If user provides non-numeric input: "Please provide numeric values"
37
-
38
- Examples ( mapped params)
39
- - "Calculate deva score for 5 and 10" → { a: 5, b: 10 } returns 630
40
- - "Score 1 and 2" → { a: 1, b: 2 } returns 126
41
- - For multiple numbers, chain calls: devaScore(1,2)→126, then devaScore(126,3)→5418
42
-
43
- Negative Examples (should NOT call deva-score)
44
- - "Score this customer with the credit model" (use model-score instead)
45
- - "Calculate the mean of these values" (use run-sas-program or sas-query instead)
46
-
47
- Related Tools
48
- - None directly related (this is a specialized scoring tool)
49
-
50
- Notes
51
- For sequences of numbers, use a left-to-right fold: call devaScore(first, second), then use that result as the first parameter for devaScore(result, third), and so on.
52
- `;
53
- let spec = {
54
- name: 'deva-score',
55
- aliases: ['devaScore','deva score','deva_score'],
56
- description: description,
57
- schema: {
58
- a: z.number(),
59
- b: z.number()
60
- },
61
- handler: async ({ a, b }) => {
62
- console.error( a, b);
63
- return `MagicScore ${ (a + b) * 42 }`;
64
- }
65
-
66
- }
67
- return spec;
68
- }
69
- export default devaScore;
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { z } from 'zod';
6
+
7
+ function devaScore(_appContext) {
8
+
9
+ let description = `
10
+ deva-score — compute a numeric score based on two input values.
11
+
12
+ USE when: calculate deva score, score these values, compute score for numbers
13
+ DO NOT USE for: model scoring (use model-score), statistical calculations, data lookup
14
+
15
+ PARAMETERS
16
+ - a: number (required) first input value
17
+ - b: number (required) — second input value
18
+
19
+ FORMULA: (a + b) * 42
20
+
21
+ ROUTING RULES
22
+ - "calculate deva score for 5 and 10" → { a: 5, b: 10 }
23
+ - "score 1 and 2" → { a: 1, b: 2 }
24
+ - "deva score a=3, b=7" { a: 3, b: 7 }
25
+ - Multiple numbers → chain calls left-to-right: call(first, second), then call(result, third)
26
+
27
+ EXAMPLES
28
+ - "Calculate deva score for 5 and 10" → { a: 5, b: 10 } returns 630
29
+ - "Score 1 and 2" → { a: 1, b: 2 } returns 126
30
+ - "Deva score 20 and 30" → { a: 20, b: 30 } returns 2100
31
+
32
+ NEGATIVE EXAMPLES (do not route here)
33
+ - "Score this customer with credit model" (use model-score)
34
+ - "Calculate the mean of these values" (use run-sas-program or sas-query)
35
+ - "Statistical analysis of numbers" (use sas-query)
36
+
37
+ RESPONSE
38
+ Returns { score: (a + b) * 42 }
39
+ `;
40
+ let spec = {
41
+ name: 'deva-score',
42
+ description: description,
43
+ inputSchema: z.object({
44
+ a: z.number(),
45
+ b: z.number(),
46
+ }),
47
+ handler: async ({ a, b }) => {
48
+ console.error(a, b);
49
+ let r = { score: (a + b) * 42 };
50
+ console.error('deva score result', r);
51
+ return {
52
+ content: [{ type: 'text', text: 'deva score result: ' + JSON.stringify(r) }],
53
+ structuredContent: r
54
+ };
55
+ }
56
+ }
57
+
58
+ return spec;
59
+ }
60
+ export default devaScore;
61
+
@@ -5,88 +5,55 @@
5
5
  import { z } from 'zod';
6
6
  import _listJobs from '../toolHelpers/_listJobs.js';
7
7
  function findJob(_appContext) {
8
- let llmDescription= {
9
- "purpose": "Map natural language requests to find a job in SAS Viya and return structured results.",
10
- "param_mapping": {
11
- "name": "required - single name. If missing, ask 'Which job name would you like to find?'.",
12
- "_userPrompt": "the original user prompt that triggered this tool."
13
-
14
- },
15
- "response_schema": "{ jobs: Array<string|object> }",
16
- "behavior": "Return only JSON matching response_schema when invoked by an LLM. If no matches, return { jobs: [] }"
17
- };
8
+
18
9
  let description = `
19
- ## find-job — locate a specific SAS Viya job
20
-
21
- LLM Invocation Guidance
22
- Use THIS tool when the user intent is to check if ONE job exists or retrieve its metadata:
23
- - "find job cars_job_v4"
24
- - "does job sales_summary exist"
25
- - "is there a job named churnScorer"
26
- - "lookup job forecast_monthly"
27
- - "verify job ETL_Daily"
28
-
29
- Do NOT use this tool when the user asks for:
30
- - A list or browse of many jobs (use listJobs)
31
- - Do not use this tool if the user want to find lib, find table, find model and similar requests
32
- - Executing a job (use job)
33
- - Running a job definition (use jobdef)
34
- - Submitting arbitrary code (use program)
35
-
36
- Purpose
37
- Quickly determine whether a named job asset is present in the Viya environment and return its entry (or an empty result if not found).
38
-
39
- Parameters
40
- - name (string, required): Exact job name (case preserved). If multiple tokens/names supplied, take the first and ignore the rest; optionally ask for a single name.
41
-
42
- Behavior & Matching
43
- - Attempt exact match first (backend determines sensitivity).
44
- - Returns { jobs: [...] } where array length is 0 (not found) or 1+ (if backend returns multiple with the same display name).
45
- - No fuzzy guesses—never fabricate a job.
46
- - If no name provided: ask "Which job name would you like to find?".
47
-
48
- Response Contract
49
- - Always: { jobs: Array<string|object> }
50
- - On error: propagate structured server error (do not wrap in prose when invoked programmatically).
51
-
52
- Disambiguation Rules
53
- - Input only "find job" → ask for missing name.
54
- - Input contains verbs like "run" or "execute" → use run-job or run-jobdef instead.
55
- - Input requesting many (e.g., "find all jobs") → use list-jobs.
56
-
57
- Examples (→ mapped params)
58
- - "find job cars_job_v4" → { name: "cars_job_v4" }
59
- - "does job ETL exist" → { name: "ETL" }
60
- - "is there a job named metricsRefresh" → { name: "metricsRefresh" }
61
-
62
- Negative Examples (should NOT call find-job)
63
- - "list jobs" (list-jobs)
64
- - "run job cars_job_v4" (run-job)
65
- - "execute jobdef cars_job_v4" (run-jobdef)
66
-
67
- Clarifying Question Template
68
- - Missing name: "Which job name would you like to find?"
69
- - Multiple names: "Please provide just one job name (e.g. 'cars_job_v4')."
70
-
71
- Notes
72
- - For bulk existence checks loop over names and call find-job per name.
73
- - Combine with run-job tool if user wants to execute after confirming existence.
10
+ find-job — locate a specific SAS Viya job.
11
+
12
+ USE when: find job, does job exist, is there a job named, lookup job, verify job exists
13
+ DO NOT USE for: list jobs (use list-jobs), run job (use run-job), execute jobdef (use run-jobdef), find lib/table/model (use respective tools)
14
+
15
+ PARAMETERS
16
+ - name: string (required) job name to locate; if multiple supplied, use first
17
+
18
+ ROUTING RULES
19
+ - "find job <name>" → { name: "<name>" }
20
+ - "does job <name> exist" { name: "<name>" }
21
+ - "is there a job named <name>" { name: "<name>" }
22
+ - "lookup/verify job <name>" { name: "<name>" }
23
+ - "find job" with no name → ask "Which job name would you like to find?"
24
+ - "find all jobs / list jobs" → use list-jobs instead
25
+ - "run job <name>" use run-job instead
26
+
27
+ EXAMPLES
28
+ - "find job cars_job_v4" { name: "cars_job_v4" }
29
+ - "does job ETL exist" → { name: "ETL" }
30
+ - "is there a job named metricsRefresh" → { name: "metricsRefresh" }
31
+
32
+ NEGATIVE EXAMPLES (do not route here)
33
+ - "list jobs" (use list-jobs)
34
+ - "run job cars_job_v4" (use run-job)
35
+ - "execute jobdef cars_job_v4" (use run-jobdef)
36
+
37
+ ERRORS
38
+ Returns { jobs: [] } if not found; { jobs: [name, ...] } if found. Never hallucinate job names.
74
39
  `;
75
40
 
76
41
  let spec = {
77
42
  name: 'find-job',
78
- aliases: ['findJob','find job','find_job'],
79
43
  description: description,
80
- schema: {
81
- name: z.string(),
82
- _userPrompt: z.string()
83
- },
84
- required: ['name'],
44
+ inputSchema: z.object({
45
+ name: z.string()
46
+ }),
85
47
  handler: async (params) => {
86
48
  let r = await _listJobs(params);
87
49
  return r;
88
50
  }
89
51
  }
52
+
53
+
54
+ /* correct spec for registerTool with inputSchema */
55
+
90
56
  return spec;
91
57
  }
92
58
  export default findJob;
59
+
@@ -15,76 +15,44 @@ function findJobdef(_appContext) {
15
15
  "behavior": "Return only JSON matching response_schema when invoked by an LLM. If no matches, return { jobs: [] }"
16
16
  };
17
17
  let description = `
18
- ## find-jobdef — locate a specific SAS Viya job
18
+ find-jobdef — locate a specific SAS Viya job definition.
19
19
 
20
- LLM Invocation Guidance
21
- Use THIS tool when the user intent is to check if ONE job exists or retrieve its metadata:
22
- - "find jobdef cars_job_v4"
23
- - "does jobdef sales_summary exist"
24
- - "is there a jobdef named churnScorer"
25
- - "lookup jobdef forecast_monthly"
26
- - "verify jobdef ETL_Daily"
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)
27
22
 
28
- Do NOT use this tool when the user asks for:
29
- - find job (use find-job)
30
- - find table (use find-table
31
- - find model (use find-model)
32
- - find lib (use find-library)
33
- - Executing a job (use job)
34
- - Running a job definition (use jobdef)
35
- - Submitting arbitrary code (use program)
23
+ PARAMETERS
24
+ - name: string (required) — jobdef name to locate; if multiple supplied, use first
36
25
 
37
- Purpose
38
- Quickly determine whether a named jobdef asset is present in the Viya environment and return its entry (or an empty result if not found).
26
+ ROUTING RULES
27
+ - "find jobdef <name>" { name: "<name>" }
28
+ - "does jobdef <name> exist" → { name: "<name>" }
29
+ - "is there a jobdef named <name>" → { name: "<name>" }
30
+ - "lookup/verify jobdef <name>" → { name: "<name>" }
31
+ - "find jobdef" with no name → ask "Which jobdef name would you like to find?"
32
+ - "find all jobdefs / list jobdefs" → use list-jobdefs instead
33
+ - "run jobdef <name>" → use run-jobdef instead
39
34
 
40
- Parameters
41
- - name (string, required): Exact jobdef name (case preserved). If multiple tokens/names supplied, take the first and ignore the rest; optionally ask for a single name.
35
+ EXAMPLES
36
+ - "find jobdef cars_job_v4" { name: "cars_job_v4" }
37
+ - "does jobdef ETL exist" → { name: "ETL" }
38
+ - "is there a jobdef named metricsRefresh" → { name: "metricsRefresh" }
42
39
 
43
- Behavior & Matching
44
- - Attempt exact match first (backend determines sensitivity).
45
- - Returns { jobdefs: [...] } where array length is 0 (not found) or 1+ (if backend returns multiple with the same display name).
46
- - No fuzzy guesses—never fabricate a job.
47
- - If no name provided: ask "Which jobdef name would you like to find?".
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)
48
45
 
49
- Response Contract
50
- - Always: { jobdefs: Array<string|object> }
51
- - On error: propagate structured server error (do not wrap in prose when invoked programmatically).
52
-
53
- Disambiguation Rules
54
- - Input only "find job" → ask for missing name.
55
- - Input contains verbs like "run" or "execute" → use run-job or run-jobdef instead.
56
- - Input requesting many (e.g., "find all jobs") → use list-jobs.
57
-
58
- Examples (→ mapped params)
59
- - "find jobdef cars_job_v4" → { name: "cars_job_v4" }
60
- - "does jobdef ETL exist" → { name: "ETL" }
61
- - "is there a jobdef named metricsRefresh" → { name: "metricsRefresh" }
62
-
63
- Negative Examples (should NOT call find-jobdef)
64
- - find lib (use find-library)
65
- - find table (use find-table)
66
- - find model (use find-model)
67
- - "list job" (list-jobdefs)
68
- - "run job cars_job_v4" (run-job)
69
- - "execute jobdef cars_job_v4" (run-jobdef)
70
-
71
- Clarifying Question Template
72
- - Missing name: "Which jobdef name would you like to find?"
73
- - Multiple names: "Please provide just one jobdef name (e.g. 'cars_job_v4')."
74
-
75
- Notes
76
- - For bulk existence checks loop over names and call find-jobdef per name.
77
- - Combine with run-jobdef tool if user wants to execute after confirming existence.
46
+ ERRORS
47
+ Returns { jobdefs: [] } if not found; { jobdefs: [name, ...] } if found. Never hallucinate jobdef names.
78
48
  `;
79
49
 
80
50
  let spec = {
81
51
  name: 'find-jobdef',
82
- aliases: ['findJobdef','find jobdef','find_jobdef'],
83
52
  description: description,
84
- schema: {
85
- name: z.string()
86
- },
87
- required: ['name'],
53
+ inputSchema: z.object({
54
+ name: z.string()
55
+ }),
88
56
  handler: async (params) => {
89
57
  let r = await _listJobdefs(params);
90
58
  return r;
@@ -93,3 +61,4 @@ function findJobdef(_appContext) {
93
61
  return spec;
94
62
  }
95
63
  export default findJobdef;
64
+
@@ -1,100 +1,68 @@
1
- /*
2
- * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
- import { z } from 'zod';
6
- import _listLibrary from '../toolHelpers/_listLibrary.js';
7
- function findLibrary(_appContext) {
8
-
9
- let description = `
10
- ## find-library — locate a specific CAS or SAS library
11
-
12
- LLM Invocation Guidance
13
- Use THIS tool when the user asks any of the following (intent = existence / lookup of ONE library):
14
- - "find library Public"
15
- - "find lib Public"
16
- - "does library SASHELP exist"
17
- - "is PUBLIC library available in cas"
18
- - "lookup library sasuser in sas"
19
- - "show me library metadata for Models"
20
-
21
- Aliases for lib are: library, caslib, libref
22
-
23
- Do NOT use this tool when the user wants:
24
- - find model -> use find-model
25
- - find table -> use find-table
26
- - find job -> use find-job
27
- - find jobdef -> use find-jobdef
28
- - Columns or schema of a table (use table-info)
29
- - Creating/assigning libraries (use run-sas-program or another admin tool)
30
-
31
- Purpose
32
- Quickly verify whether a single named library exists on CAS or SAS and return its entry (or empty if not found).
33
-
34
- Parameters
35
- - name (string, required) : Exact library (caslib) name to locate. If multiple names provided (comma/space separated), use the first and ignore the rest; optionally ask for one name.
36
- - server (cas|sas, default 'cas') : Target environment. If omitted or ambiguous default to 'cas'.
37
-
38
- Behavior & Matching
39
- - Performs an exact name match (case-insensitive where backend supports it).
40
- - Returns an object: { libraries: [...] } where the array contains zero or one items (backend may still return richer metadata).
41
- - If no match: { libraries: [] } (do NOT fabricate suggestions).
42
- - If user supplies no name: ask a clarifying question: "Which library name would you like to find?".
43
- - If user clearly wants a list ("list", "show all", "enumerate") route to listLibrary instead.
44
-
45
- Response Contract
46
- - Always: { libraries: Array<string|object> }
47
- - Never include prose when invoked programmatically; only the JSON structure.
48
-
49
- Disambiguation Rules
50
- - Input only "find library" → ask for the missing name.
51
- - Input with both library name and words like "tables" → prefer listTables.
52
- - Input like "find libraries" (plural) → prefer listLibrary unless user clarifies to a single name.
53
-
54
- Examples (→ mapped params)
55
- - "find lib Public" → { name: "Public", server: "cas" }
56
- - "find library sasuser in sas" { name: "sasuser", server: "sas" }
57
- - "does library Formats exist" → { name: "Formats", server: "cas" }
58
- - "is SystemData library in cas" → { name: "SystemData", server: "cas" }
59
-
60
- Negative Examples (should NOT call find-library)
61
- - "list libs" (list-libraries)
62
- - Do not use this tool if the user want to find table, find job, find model, find job, find jobdef and similar requests
63
- - "show tables in Public" (list-tables)
64
- - "describe table cars in sashelp" (table-info)
65
-
66
- Clarifying Question Template
67
- - Missing name: "Which library name would you like to find?"
68
- - Multiple names: "Please provide just one library name to look up (e.g. 'Public')."
69
-
70
- Notes
71
- - For bulk validation of many names, call this tool repeatedly per name.
72
- - For pagination or discovery, switch to list-libraries.
73
- `;
74
-
75
- let spec = {
76
- name: 'find-library',
77
- aliases: ['findLibrary','find library','find_library'],
78
- description: description,
79
- schema: {
80
- name: z.string(),
81
- server: z.string().default('cas')
82
- },
83
- required: ['name'],
84
- handler: async (params) => {
85
- // normalize server to lowercase & default
86
- if (!params.server) params.server = 'cas';
87
- params.server = params.server.toLowerCase();
88
-
89
- // If multiple names passed (comma or space separated), take the first token (defensive)
90
- if (params.name && /[,\s]+/.test(params.name.trim())) {
91
- params.name = params.name.split(/[,\s]+/).filter(Boolean)[0];
92
- }
93
-
94
- let r = await _listLibrary(params);
95
- return r;
96
- }
97
- }
98
- return spec;
99
- }
100
- export default findLibrary;
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { z } from 'zod';
6
+ import _listLibrary from '../toolHelpers/_listLibrary.js';
7
+ function findLibrary(_appContext) {
8
+
9
+ let description = `
10
+ find-library — locate a specific CAS or SAS library.
11
+
12
+ USE when: find library, find lib, does library exist, is library available, lookup library
13
+ DO NOT USE for: list libraries (use list-libraries), find table/job/jobdef/model (use respective tools), table structure (use table-info), create library (use run-sas-program)
14
+
15
+ PARAMETERS
16
+ - name: string (required) — library/caslib name; if multiple supplied, use first
17
+ - server: 'cas' | 'sas' (default: 'cas') — target environment
18
+
19
+ ROUTING RULES
20
+ - "find lib <name>" → { name: "<name>", server: "cas" }
21
+ - "find lib <name> in cas" → { name: "<name>", server: "cas" }
22
+ - "find library <name> in sas" → { name: "<name>", server: "sas" }
23
+ - "does library <name> exist" { name: "<name>", server: "cas" }
24
+ - "find lib" with no name → ask "Which library name would you like to find?"
25
+ - "list libraries / list libs" → use list-libraries instead
26
+ - "tables in <lib>" use list-tables instead
27
+
28
+ EXAMPLES
29
+ - "find lib Public" { name: "Public", server: "cas" }
30
+ - "find library sasuser in sas" → { name: "sasuser", server: "sas" }
31
+ - "does library Formats exist" → { name: "Formats", server: "cas" }
32
+
33
+ NEGATIVE EXAMPLES (do not route here)
34
+ - "list libs" (use list-libraries)
35
+ - "show tables in Public" (use list-tables)
36
+ - "find table cars in sashelp" (use find-table)
37
+ - "find job cars_job" (use find-job)
38
+
39
+ ERRORS
40
+ Returns { libraries: [] } if not found; { libraries: [name, ...] } if found. Never hallucinate library names.
41
+ `;
42
+
43
+ let spec = {
44
+ name: 'find-library',
45
+ description: description,
46
+ inputSchema: z.object({
47
+ name: z.string(),
48
+ server: z.string().optional()
49
+ }),
50
+
51
+ handler: async (params) => {
52
+ // normalize server to lowercase & default
53
+ if (!params.server) params.server = 'cas';
54
+ params.server = params.server.toLowerCase();
55
+
56
+ // If multiple names passed (comma or space separated), take the first token (defensive)
57
+ if (params.name && /[,\s]+/.test(params.name.trim())) {
58
+ params.name = params.name.split(/[,\s]+/).filter(Boolean)[0];
59
+ }
60
+
61
+ let r = await _listLibrary(params);
62
+ return r;
63
+ }
64
+ }
65
+ return spec;
66
+ }
67
+ export default findLibrary;
68
+