@sassoftware/sas-score-mcp-serverjs 0.3.29-0 → 0.4.1-1
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/cli.js +118 -3
- package/openApi.yaml +121 -121
- package/package.json +4 -5
- package/skills/mcp-tool-description-optimizer/SKILL.md +129 -0
- package/skills/mcp-tool-description-optimizer/references/examples.md +123 -0
- package/skills/sas-read-and-score/SKILL.md +91 -0
- package/skills/sas-read-strategy/SKILL.md +143 -0
- package/skills/sas-score-workflow/SKILL.md +283 -0
- package/src/createMcpServer.js +11 -4
- package/src/expressMcpServer.js +1 -1
- package/src/handleGetDelete.js +1 -1
- package/src/openApi.yaml +121 -121
- package/src/toolHelpers/_jobSubmit.js +2 -0
- package/src/toolHelpers/_listLibrary.js +56 -39
- package/src/toolHelpers/readCerts.js +4 -4
- package/src/toolSet/devaScore.js +31 -44
- package/src/toolSet/findJob.js +37 -70
- package/src/toolSet/findJobdef.js +27 -58
- package/src/toolSet/findLibrary.js +29 -62
- package/src/toolSet/findModel.js +34 -57
- package/src/toolSet/findTable.js +29 -59
- package/src/toolSet/getEnv.js +26 -45
- package/src/toolSet/listJobdefs.js +30 -65
- package/src/toolSet/listJobs.js +30 -79
- package/src/toolSet/listLibraries.js +43 -55
- package/src/toolSet/listModels.js +25 -52
- package/src/toolSet/listTables.js +36 -66
- package/src/toolSet/makeTools.js +1 -0
- package/src/toolSet/modelInfo.js +21 -53
- package/src/toolSet/modelScore.js +30 -73
- package/src/toolSet/readTable.js +33 -72
- package/src/toolSet/runCasProgram.js +30 -51
- package/src/toolSet/runJob.js +24 -24
- package/src/toolSet/runJobdef.js +26 -29
- package/src/toolSet/runMacro.js +23 -24
- package/src/toolSet/runProgram.js +32 -80
- package/src/toolSet/sasQuery.js +31 -79
- package/src/toolSet/sasQueryTemplate.js +4 -5
- package/src/toolSet/sasQueryTemplate2.js +4 -5
- package/src/toolSet/scrInfo.js +4 -7
- package/src/toolSet/scrScore.js +2 -4
- package/src/toolSet/searchAssets.js +5 -6
- package/src/toolSet/setContext.js +30 -57
- package/src/toolSet/superstat.js +2 -3
- package/src/toolSet/tableInfo.js +32 -77
package/src/openApi.yaml
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
swagger: "2.0"
|
|
2
|
-
info:
|
|
3
|
-
title: SAS Viya Sample MCP Server API
|
|
4
|
-
version: "1.0.0"
|
|
5
|
-
description: API for interacting with the SAS Viya Sample MCP Server.
|
|
6
|
-
host: localhost:8080
|
|
7
|
-
basePath: /
|
|
8
|
-
schemes:
|
|
9
|
-
- http
|
|
10
|
-
- https
|
|
11
|
-
consumes:
|
|
12
|
-
- application/json
|
|
13
|
-
produces:
|
|
14
|
-
- application/json
|
|
15
|
-
paths:
|
|
16
|
-
/health:
|
|
17
|
-
get:
|
|
18
|
-
summary: Health check
|
|
19
|
-
description: Returns health and version information.
|
|
20
|
-
responses:
|
|
21
|
-
200:
|
|
22
|
-
description: Health information
|
|
23
|
-
schema:
|
|
24
|
-
type: object
|
|
25
|
-
properties:
|
|
26
|
-
name:
|
|
27
|
-
type: string
|
|
28
|
-
version:
|
|
29
|
-
type: string
|
|
30
|
-
description:
|
|
31
|
-
type: string
|
|
32
|
-
endpoints:
|
|
33
|
-
type: object
|
|
34
|
-
usage:
|
|
35
|
-
type: string
|
|
36
|
-
/apiMeta:
|
|
37
|
-
get:
|
|
38
|
-
summary: API metadata
|
|
39
|
-
description: Returns the OpenAPI specification for this server.
|
|
40
|
-
responses:
|
|
41
|
-
200:
|
|
42
|
-
description: OpenAPI document
|
|
43
|
-
schema:
|
|
44
|
-
type: object
|
|
45
|
-
/mcp:
|
|
46
|
-
options:
|
|
47
|
-
summary: CORS preflight
|
|
48
|
-
description: CORS preflight endpoint.
|
|
49
|
-
responses:
|
|
50
|
-
204:
|
|
51
|
-
description: No Content
|
|
52
|
-
post:
|
|
53
|
-
summary: MCP request
|
|
54
|
-
description: Handles MCP JSON-RPC requests.
|
|
55
|
-
parameters:
|
|
56
|
-
- name: body
|
|
57
|
-
in: body
|
|
58
|
-
required: true
|
|
59
|
-
schema:
|
|
60
|
-
type: object
|
|
61
|
-
- name: Authorization
|
|
62
|
-
in: header
|
|
63
|
-
required: false
|
|
64
|
-
type: string
|
|
65
|
-
description: Bearer token for authentication
|
|
66
|
-
- name: X-VIYA-SERVER
|
|
67
|
-
in: header
|
|
68
|
-
required: false
|
|
69
|
-
type: string
|
|
70
|
-
description: Override VIYA server
|
|
71
|
-
- name: X-REFRESH-TOKEN
|
|
72
|
-
in: header
|
|
73
|
-
required: false
|
|
74
|
-
type: string
|
|
75
|
-
description: Refresh token for authentication
|
|
76
|
-
- name: mcp-session-id
|
|
77
|
-
in: header
|
|
78
|
-
required: false
|
|
79
|
-
type: string
|
|
80
|
-
description: Session ID
|
|
81
|
-
responses:
|
|
82
|
-
200:
|
|
83
|
-
description: MCP response
|
|
84
|
-
schema:
|
|
85
|
-
type: object
|
|
86
|
-
500:
|
|
87
|
-
description: Server error
|
|
88
|
-
schema:
|
|
89
|
-
type: object
|
|
90
|
-
get:
|
|
91
|
-
summary: Get MCP session
|
|
92
|
-
description: Retrieves information for an MCP session.
|
|
93
|
-
parameters:
|
|
94
|
-
- name: mcp-session-id
|
|
95
|
-
in: header
|
|
96
|
-
required: true
|
|
97
|
-
type: string
|
|
98
|
-
description: Session ID
|
|
99
|
-
responses:
|
|
100
|
-
200:
|
|
101
|
-
description: Session information
|
|
102
|
-
schema:
|
|
103
|
-
type: object
|
|
104
|
-
400:
|
|
105
|
-
description: Invalid or missing session ID
|
|
106
|
-
delete:
|
|
107
|
-
summary: Delete MCP session
|
|
108
|
-
description: Deletes an MCP session.
|
|
109
|
-
parameters:
|
|
110
|
-
- name: mcp-session-id
|
|
111
|
-
in: header
|
|
112
|
-
required: true
|
|
113
|
-
type: string
|
|
114
|
-
description: Session ID
|
|
115
|
-
responses:
|
|
116
|
-
200:
|
|
117
|
-
description: Session deleted
|
|
118
|
-
schema:
|
|
119
|
-
type: object
|
|
120
|
-
400:
|
|
121
|
-
description: Invalid or missing session ID
|
|
1
|
+
swagger: "2.0"
|
|
2
|
+
info:
|
|
3
|
+
title: SAS Viya Sample MCP Server API
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
description: API for interacting with the SAS Viya Sample MCP Server.
|
|
6
|
+
host: localhost:8080
|
|
7
|
+
basePath: /
|
|
8
|
+
schemes:
|
|
9
|
+
- http
|
|
10
|
+
- https
|
|
11
|
+
consumes:
|
|
12
|
+
- application/json
|
|
13
|
+
produces:
|
|
14
|
+
- application/json
|
|
15
|
+
paths:
|
|
16
|
+
/health:
|
|
17
|
+
get:
|
|
18
|
+
summary: Health check
|
|
19
|
+
description: Returns health and version information.
|
|
20
|
+
responses:
|
|
21
|
+
200:
|
|
22
|
+
description: Health information
|
|
23
|
+
schema:
|
|
24
|
+
type: object
|
|
25
|
+
properties:
|
|
26
|
+
name:
|
|
27
|
+
type: string
|
|
28
|
+
version:
|
|
29
|
+
type: string
|
|
30
|
+
description:
|
|
31
|
+
type: string
|
|
32
|
+
endpoints:
|
|
33
|
+
type: object
|
|
34
|
+
usage:
|
|
35
|
+
type: string
|
|
36
|
+
/apiMeta:
|
|
37
|
+
get:
|
|
38
|
+
summary: API metadata
|
|
39
|
+
description: Returns the OpenAPI specification for this server.
|
|
40
|
+
responses:
|
|
41
|
+
200:
|
|
42
|
+
description: OpenAPI document
|
|
43
|
+
schema:
|
|
44
|
+
type: object
|
|
45
|
+
/mcp:
|
|
46
|
+
options:
|
|
47
|
+
summary: CORS preflight
|
|
48
|
+
description: CORS preflight endpoint.
|
|
49
|
+
responses:
|
|
50
|
+
204:
|
|
51
|
+
description: No Content
|
|
52
|
+
post:
|
|
53
|
+
summary: MCP request
|
|
54
|
+
description: Handles MCP JSON-RPC requests.
|
|
55
|
+
parameters:
|
|
56
|
+
- name: body
|
|
57
|
+
in: body
|
|
58
|
+
required: true
|
|
59
|
+
schema:
|
|
60
|
+
type: object
|
|
61
|
+
- name: Authorization
|
|
62
|
+
in: header
|
|
63
|
+
required: false
|
|
64
|
+
type: string
|
|
65
|
+
description: Bearer token for authentication
|
|
66
|
+
- name: X-VIYA-SERVER
|
|
67
|
+
in: header
|
|
68
|
+
required: false
|
|
69
|
+
type: string
|
|
70
|
+
description: Override VIYA server
|
|
71
|
+
- name: X-REFRESH-TOKEN
|
|
72
|
+
in: header
|
|
73
|
+
required: false
|
|
74
|
+
type: string
|
|
75
|
+
description: Refresh token for authentication
|
|
76
|
+
- name: mcp-session-id
|
|
77
|
+
in: header
|
|
78
|
+
required: false
|
|
79
|
+
type: string
|
|
80
|
+
description: Session ID
|
|
81
|
+
responses:
|
|
82
|
+
200:
|
|
83
|
+
description: MCP response
|
|
84
|
+
schema:
|
|
85
|
+
type: object
|
|
86
|
+
500:
|
|
87
|
+
description: Server error
|
|
88
|
+
schema:
|
|
89
|
+
type: object
|
|
90
|
+
get:
|
|
91
|
+
summary: Get MCP session
|
|
92
|
+
description: Retrieves information for an MCP session.
|
|
93
|
+
parameters:
|
|
94
|
+
- name: mcp-session-id
|
|
95
|
+
in: header
|
|
96
|
+
required: true
|
|
97
|
+
type: string
|
|
98
|
+
description: Session ID
|
|
99
|
+
responses:
|
|
100
|
+
200:
|
|
101
|
+
description: Session information
|
|
102
|
+
schema:
|
|
103
|
+
type: object
|
|
104
|
+
400:
|
|
105
|
+
description: Invalid or missing session ID
|
|
106
|
+
delete:
|
|
107
|
+
summary: Delete MCP session
|
|
108
|
+
description: Deletes an MCP session.
|
|
109
|
+
parameters:
|
|
110
|
+
- name: mcp-session-id
|
|
111
|
+
in: header
|
|
112
|
+
required: true
|
|
113
|
+
type: string
|
|
114
|
+
description: Session ID
|
|
115
|
+
responses:
|
|
116
|
+
200:
|
|
117
|
+
description: Session deleted
|
|
118
|
+
schema:
|
|
119
|
+
type: object
|
|
120
|
+
400:
|
|
121
|
+
description: Invalid or missing session ID
|
|
@@ -6,53 +6,70 @@
|
|
|
6
6
|
import restafedit from '@sassoftware/restafedit';
|
|
7
7
|
import deleteSession from './deleteSession.js';
|
|
8
8
|
|
|
9
|
-
async function _listLibrary(params
|
|
9
|
+
async function _listLibrary(params) {
|
|
10
10
|
|
|
11
11
|
let { server, limit, start, name, _appContext } = params;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// setup request control
|
|
22
|
-
appControl = await restafedit.setup(
|
|
23
|
-
_appContext.logonPayload,
|
|
24
|
-
config
|
|
25
|
-
,null,{},'user',{}, {}, _appContext.storeConfig
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
// query parameters
|
|
29
|
-
let payload = {
|
|
30
|
-
qs: {
|
|
31
|
-
limit: (limit != null) ? limit : 10,
|
|
32
|
-
start: start - 1
|
|
33
|
-
}
|
|
12
|
+
|
|
13
|
+
const _ilistLibrary = async (params) => {
|
|
14
|
+
let { server, limit, start, name, _appContext } = params;
|
|
15
|
+
console.error(_appContext);
|
|
16
|
+
let config = {
|
|
17
|
+
casServerName: _appContext.cas,
|
|
18
|
+
computeContext: _appContext.sas,
|
|
19
|
+
source: (server === 'sas') ? 'compute' : server,
|
|
20
|
+
table: null
|
|
34
21
|
};
|
|
22
|
+
let appControl;
|
|
23
|
+
try {
|
|
24
|
+
// setup request control
|
|
25
|
+
appControl = await restafedit.setup(
|
|
26
|
+
_appContext.logonPayload,
|
|
27
|
+
config
|
|
28
|
+
, null, {}, 'user', {}, {}, _appContext.storeConfig
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// query parameters
|
|
32
|
+
let payload = {
|
|
33
|
+
qs: {
|
|
34
|
+
limit: (limit != null) ? limit : 10,
|
|
35
|
+
start: start - 1
|
|
36
|
+
}
|
|
37
|
+
};
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
if (name != null) {
|
|
40
|
+
payload.qs = {
|
|
41
|
+
filter: `eq(name, '${name}')`
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let response = {libraries: items};
|
|
44
|
-
await deleteSession(appControl);
|
|
45
|
-
|
|
46
|
-
return { content: [{ type: 'text', text: JSON.stringify(response) }],
|
|
47
|
-
structuredContent: response
|
|
48
|
-
};
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.error(JSON.stringify(err));
|
|
51
|
-
if (appControl != null) {
|
|
44
|
+
|
|
45
|
+
let items = await restafedit.getLibraryList(appControl, payload);
|
|
46
|
+
let response = { libraries: items };
|
|
52
47
|
await deleteSession(appControl);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: 'text', text: JSON.stringify(response) }],
|
|
51
|
+
structuredContent: response
|
|
52
|
+
};
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(JSON.stringify(err));
|
|
55
|
+
if (appControl != null) {
|
|
56
|
+
await deleteSession(appControl);
|
|
57
|
+
}
|
|
58
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
53
59
|
}
|
|
54
|
-
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
55
60
|
}
|
|
61
|
+
|
|
62
|
+
let source = (server === 'all') ? ['sas', 'cas'] : [server];
|
|
63
|
+
let response = {};
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < source.length; i++) {
|
|
66
|
+
let liblist = await _ilistLibrary({ server: source[i], limit, start, name, _appContext });
|
|
67
|
+
response[source[i]] = liblist.structuredContent;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: 'text', text: JSON.stringify(response) }],
|
|
71
|
+
structuredContent: response
|
|
72
|
+
};
|
|
56
73
|
}
|
|
57
74
|
|
|
58
75
|
export default _listLibrary;
|
|
@@ -9,23 +9,23 @@ function getCerts(tlsdir) {
|
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
console.
|
|
12
|
+
console.error(`[Note] Reading certs from directory: ` + tlsdir);
|
|
13
13
|
if (fs.existsSync(tlsdir) === false) {
|
|
14
14
|
console.error("[Warning] Specified cert dir does not exist: " + tlsdir);
|
|
15
15
|
return null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
let listOfFiles = fs.readdirSync(tlsdir);
|
|
19
|
-
console.
|
|
19
|
+
console.error("[Note] TLS/SSL files found: " + listOfFiles);
|
|
20
20
|
let options = {};
|
|
21
21
|
for(let i=0; i < listOfFiles.length; i++) {
|
|
22
22
|
let fname = listOfFiles[i];
|
|
23
23
|
let name = tlsdir + '/' + listOfFiles[i];
|
|
24
24
|
let key = fname.split('.')[0];
|
|
25
|
-
console.
|
|
25
|
+
console.error('Reading TLS file: ' + name + ' as key: ' + key);
|
|
26
26
|
options[key] = fs.readFileSync(name, { encoding: 'utf8' });
|
|
27
27
|
}
|
|
28
|
-
console.
|
|
28
|
+
console.error('cert files', Object.keys(options));
|
|
29
29
|
|
|
30
30
|
return options;
|
|
31
31
|
|
package/src/toolSet/devaScore.js
CHANGED
|
@@ -2,73 +2,60 @@
|
|
|
2
2
|
* Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import {z} from 'zod';
|
|
5
|
+
import { z } from 'zod';
|
|
6
6
|
|
|
7
7
|
function devaScore(_appContext) {
|
|
8
8
|
|
|
9
9
|
let description = `
|
|
10
|
-
|
|
10
|
+
deva-score — compute a numeric score based on two input values.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
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]"
|
|
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
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
- Looking up data or metadata
|
|
15
|
+
PARAMETERS
|
|
16
|
+
- a: number (required) — first input value
|
|
17
|
+
- b: number (required) — second input value
|
|
22
18
|
|
|
23
|
-
|
|
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).
|
|
19
|
+
FORMULA: (a + b) * 42
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
- a
|
|
28
|
-
-
|
|
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)
|
|
29
26
|
|
|
30
|
-
|
|
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)
|
|
27
|
+
EXAMPLES
|
|
39
28
|
- "Calculate deva score for 5 and 10" → { a: 5, b: 10 } returns 630
|
|
40
29
|
- "Score 1 and 2" → { a: 1, b: 2 } returns 126
|
|
41
|
-
-
|
|
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)
|
|
30
|
+
- "Deva score 20 and 30" → { a: 20, b: 30 } returns 2100
|
|
46
31
|
|
|
47
|
-
|
|
48
|
-
-
|
|
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)
|
|
49
36
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`;
|
|
37
|
+
RESPONSE
|
|
38
|
+
Returns { score: (a + b) * 42 }
|
|
39
|
+
`;
|
|
53
40
|
let spec = {
|
|
54
41
|
name: 'deva-score',
|
|
55
|
-
aliases: ['devaScore','deva score','deva_score'],
|
|
56
42
|
description: description,
|
|
57
|
-
|
|
43
|
+
inputSchema: z.object({
|
|
58
44
|
a: z.number(),
|
|
59
45
|
b: z.number()
|
|
60
|
-
},
|
|
46
|
+
}),
|
|
61
47
|
handler: async ({ a, b }) => {
|
|
62
|
-
console.error(
|
|
63
|
-
let r = {score: (a + b) * 42};
|
|
48
|
+
console.error(a, b);
|
|
49
|
+
let r = { score: (a + b) * 42 };
|
|
64
50
|
console.error('deva score result', r);
|
|
65
51
|
return {
|
|
66
|
-
content: [{type: 'text', text: 'deva score result: ' + JSON.stringify(r)}],
|
|
52
|
+
content: [{ type: 'text', text: 'deva score result: ' + JSON.stringify(r) }],
|
|
67
53
|
structuredContent: r
|
|
68
|
-
|
|
69
|
-
}
|
|
54
|
+
};
|
|
70
55
|
}
|
|
71
|
-
|
|
56
|
+
}
|
|
57
|
+
|
|
72
58
|
return spec;
|
|
73
59
|
}
|
|
74
60
|
export default devaScore;
|
|
61
|
+
|
package/src/toolSet/findJob.js
CHANGED
|
@@ -5,88 +5,55 @@
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import _listJobs from '../toolHelpers/_listJobs.js';
|
|
7
7
|
function findJob(_appContext) {
|
|
8
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
44
|
+
inputSchema: z.object({
|
|
81
45
|
name: z.string(),
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
required: ['name'],
|
|
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
|
+
|