@sassoftware/sas-score-mcp-serverjs 0.3.18 → 0.4.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 (47) hide show
  1. package/cli.js +141 -26
  2. package/package.json +4 -3
  3. package/skills/mcp-tool-description-optimizer/SKILL.md +129 -0
  4. package/skills/mcp-tool-description-optimizer/references/examples.md +123 -0
  5. package/skills/sas-read-and-score/SKILL.md +91 -0
  6. package/skills/sas-read-strategy/SKILL.md +143 -0
  7. package/skills/sas-score-workflow/SKILL.md +282 -0
  8. package/src/createMcpServer.js +1 -0
  9. package/src/expressMcpServer.js +68 -28
  10. package/src/handleGetDelete.js +6 -3
  11. package/src/hapiMcpServer.js +30 -0
  12. package/src/openAPIJson.js +175 -175
  13. package/src/toolHelpers/_jobSubmit.js +2 -0
  14. package/src/toolHelpers/_listLibrary.js +56 -39
  15. package/src/toolHelpers/getLogonPayload.js +9 -7
  16. package/src/toolHelpers/getStoreOpts.js +1 -2
  17. package/src/toolHelpers/getToken.js +0 -1
  18. package/src/toolHelpers/refreshToken.js +48 -45
  19. package/src/toolHelpers/refreshTokenOauth.js +2 -2
  20. package/src/toolHelpers/tlogon.js +9 -0
  21. package/src/toolSet/devaScore.js +30 -38
  22. package/src/toolSet/findJob.js +23 -49
  23. package/src/toolSet/findJobdef.js +24 -54
  24. package/src/toolSet/findLibrary.js +25 -57
  25. package/src/toolSet/findModel.js +31 -53
  26. package/src/toolSet/findTable.js +25 -54
  27. package/src/toolSet/getEnv.js +20 -38
  28. package/src/toolSet/listJobdefs.js +24 -58
  29. package/src/toolSet/listJobs.js +24 -72
  30. package/src/toolSet/listLibraries.js +37 -47
  31. package/src/toolSet/listModels.js +20 -47
  32. package/src/toolSet/listTables.js +29 -58
  33. package/src/toolSet/makeTools.js +3 -0
  34. package/src/toolSet/modelInfo.js +18 -49
  35. package/src/toolSet/modelScore.js +27 -69
  36. package/src/toolSet/readTable.js +25 -62
  37. package/src/toolSet/runCasProgram.js +23 -43
  38. package/src/toolSet/runJob.js +20 -19
  39. package/src/toolSet/runJobdef.js +21 -23
  40. package/src/toolSet/runMacro.js +20 -20
  41. package/src/toolSet/runProgram.js +24 -71
  42. package/src/toolSet/sasQuery.js +23 -70
  43. package/src/toolSet/scrInfo.js +3 -4
  44. package/src/toolSet/setContext.js +22 -48
  45. package/src/toolSet/tableInfo.js +28 -71
  46. package/src/toolHelpers/getOpts.js +0 -51
  47. package/src/toolHelpers/getOptsViya.js +0 -44
@@ -126,6 +126,36 @@ async function hapiMcpServer(mcpServer, cache, baseAppEnvContext) {
126
126
  tags: ["mcp"],
127
127
  }
128
128
  },
129
+ {
130
+ method: ["GET"],
131
+ path: "/ready",
132
+ options: {
133
+ handler: async (req, h) => {
134
+ let status = {status: 1};
135
+ console.error("Ready check requested, returning:", status);
136
+ return h.response(status).code(200).type('application/json');
137
+ },
138
+ auth: false,
139
+ description: "probe readiness of the server",
140
+ notes: "Help",
141
+ tags: ["mcp"],
142
+ }
143
+ },
144
+ {
145
+ method: ["GET"],
146
+ path: "/StartUp",
147
+ options: {
148
+ handler: async (req, h) => {
149
+ let status = { status: 1 };
150
+ console.error("Startup check requested, returning:", status);
151
+ return h.response(status).code(200).type('application/json');
152
+ },
153
+ auth: false,
154
+ description: "probe startup of the server",
155
+ notes: "Help",
156
+ tags: ["mcp"],
157
+ }
158
+ },
129
159
  {
130
160
  method: ["GET"],
131
161
  path: "/apiMeta",
@@ -1,176 +1,176 @@
1
- /*
2
- * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
- function openAPIJson(version) {
6
- let spec = {
7
- "swagger": "2.0",
8
- "info": {
9
- "title": "sas-score-mcp-serverjs API",
10
- "version": "1.0.0",
11
- "description": "sas-score-mcp-serverjs is a mcp server for SAS Viya"
12
- },
13
- "host": "localhost:8080",
14
- "basePath": "/",
15
- "schemes": ["http", "https"],
16
- "consumes": ["application/json"],
17
- "produces": ["application/json"],
18
- "paths": {
19
- "/health": {
20
- "get": {
21
- "summary": "Health check",
22
- "operationId": "getHealth",
23
- "description": "Returns health and version information.",
24
- "responses": {
25
- "200": {
26
- "description": "Health information",
27
- "schema": {
28
- "type": "object",
29
- "properties": {
30
- "name": { "type": "string" },
31
- "version": { "type": "string" },
32
- "description": { "type": "string" },
33
- "endpoints": { "type": "object" },
34
- "usage": { "type": "string" }
35
- }
36
- }
37
- }
38
- }
39
- }
40
- },
41
- "/apiMeta": {
42
- "get": {
43
- "summary": "API metadata using apiMeta",
44
- "operationId": "GetApiMeta",
45
- "responses": {
46
- "200": {
47
- "description": "OpenAPI document",
48
- "schema": { "type": "object" }
49
- }
50
- }
51
- }
52
- },
53
- "/openapi.json": {
54
- "get": {
55
- "summary": "API metadata using openapi.json",
56
- "operationId": "GetOpenApiJson",
57
- "description": "Returns the OpenAPI specification for this server.",
58
- "responses": {
59
- "200": {
60
- "description": "OpenAPI document",
61
- "schema": { "type": "object" }
62
- }
63
- }
64
- }
65
- },
66
- "/mcp": {
67
- "options": {
68
- "summary": "CORS preflight",
69
- "operationId": "OptionsMcp",
70
- "description": "CORS preflight endpoint.",
71
- "responses": {
72
- "204": { "description": "No Content" }
73
- }
74
- },
75
- "post": {
76
- "summary": "MCP request",
77
- "operationId": "PostMcp",
78
- "parameters": [
79
- {
80
- "name": "body",
81
- "in": "body",
82
- "required": true,
83
- "schema": { "type": "object" }
84
- },
85
- {
86
- "name": "Authorization",
87
- "in": "header",
88
- "required": false,
89
- "type": "string",
90
- "description": "Bearer token for authentication"
91
- },
92
- {
93
- "name": "X-VIYA-SERVER",
94
- "in": "header",
95
- "required": false,
96
- "type": "string",
97
- "description": "Override VIYA server"
98
- },
99
- {
100
- "name": "X-REFRESH-TOKEN",
101
- "in": "header",
102
- "required": false,
103
- "type": "string",
104
- "description": "Refresh token for authentication"
105
- },
106
- {
107
- "name": "mcp-session-id",
108
- "in": "header",
109
- "required": false,
110
- "type": "string",
111
- "description": "Session ID"
112
- }
113
- ],
114
- "responses": {
115
- "200": {
116
- "description": "MCP response",
117
- "schema": { "type": "object" }
118
- },
119
- "500": {
120
- "description": "Server error",
121
- "schema": { "type": "object" }
122
- }
123
- }
124
- },
125
- "get": {
126
- "summary": "get MCP session",
127
- "operationId": "GetMcp",
128
- "description": "Retrieves information for an MCP session.",
129
- "parameters": [
130
- {
131
- "name": "mcp-session-id",
132
- "in": "header",
133
- "required": true,
134
- "type": "string",
135
- "description": "Session ID"
136
- }
137
- ],
138
- "responses": {
139
- "200": {
140
- "description": "Session information",
141
- "schema": { "type": "object" }
142
- },
143
- "400": {
144
- "description": "Invalid or missing session ID"
145
- }
146
- }
147
- },
148
- "delete": {
149
- "summary": "Delete MCP session",
150
- "operationId": "DeleteMcp",
151
- "parameters": [
152
- {
153
- "name": "mcp-session-id",
154
- "in": "header",
155
- "required": true,
156
- "type": "string",
157
- "description": "Session ID"
158
- }
159
- ],
160
- "responses": {
161
- "200": {
162
- "description": "Session deleted",
163
- "schema": { "type": "object" }
164
- },
165
- "400": {
166
- "description": "Invalid or missing session ID"
167
- }
168
- }
169
- }
170
- }
171
- }
172
- }
173
- spec.info.version = version;
174
- return spec;
175
- };
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ function openAPIJson(version) {
6
+ let spec = {
7
+ "swagger": "2.0",
8
+ "info": {
9
+ "title": "sas-score-mcp-serverjs API",
10
+ "version": "1.0.0",
11
+ "description": "sas-score-mcp-serverjs is a mcp server for SAS Viya"
12
+ },
13
+ "host": "localhost:8080",
14
+ "basePath": "/",
15
+ "schemes": ["http", "https"],
16
+ "consumes": ["application/json"],
17
+ "produces": ["application/json"],
18
+ "paths": {
19
+ "/health": {
20
+ "get": {
21
+ "summary": "Health check",
22
+ "operationId": "getHealth",
23
+ "description": "Returns health and version information.",
24
+ "responses": {
25
+ "200": {
26
+ "description": "Health information",
27
+ "schema": {
28
+ "type": "object",
29
+ "properties": {
30
+ "name": { "type": "string" },
31
+ "version": { "type": "string" },
32
+ "description": { "type": "string" },
33
+ "endpoints": { "type": "object" },
34
+ "usage": { "type": "string" }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ },
41
+ "/apiMeta": {
42
+ "get": {
43
+ "summary": "API metadata using apiMeta",
44
+ "operationId": "GetApiMeta",
45
+ "responses": {
46
+ "200": {
47
+ "description": "OpenAPI document",
48
+ "schema": { "type": "object" }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "/openapi.json": {
54
+ "get": {
55
+ "summary": "API metadata using openapi.json",
56
+ "operationId": "GetOpenApiJson",
57
+ "description": "Returns the OpenAPI specification for this server.",
58
+ "responses": {
59
+ "200": {
60
+ "description": "OpenAPI document",
61
+ "schema": { "type": "object" }
62
+ }
63
+ }
64
+ }
65
+ },
66
+ "/mcp": {
67
+ "options": {
68
+ "summary": "CORS preflight",
69
+ "operationId": "OptionsMcp",
70
+ "description": "CORS preflight endpoint.",
71
+ "responses": {
72
+ "204": { "description": "No Content" }
73
+ }
74
+ },
75
+ "post": {
76
+ "summary": "MCP request",
77
+ "operationId": "PostMcp",
78
+ "parameters": [
79
+ {
80
+ "name": "body",
81
+ "in": "body",
82
+ "required": true,
83
+ "schema": { "type": "object" }
84
+ },
85
+ {
86
+ "name": "Authorization",
87
+ "in": "header",
88
+ "required": false,
89
+ "type": "string",
90
+ "description": "Bearer token for authentication"
91
+ },
92
+ {
93
+ "name": "X-VIYA-SERVER",
94
+ "in": "header",
95
+ "required": false,
96
+ "type": "string",
97
+ "description": "Override VIYA server"
98
+ },
99
+ {
100
+ "name": "X-REFRESH-TOKEN",
101
+ "in": "header",
102
+ "required": false,
103
+ "type": "string",
104
+ "description": "Refresh token for authentication"
105
+ },
106
+ {
107
+ "name": "mcp-session-id",
108
+ "in": "header",
109
+ "required": false,
110
+ "type": "string",
111
+ "description": "Session ID"
112
+ }
113
+ ],
114
+ "responses": {
115
+ "200": {
116
+ "description": "MCP response",
117
+ "schema": { "type": "object" }
118
+ },
119
+ "500": {
120
+ "description": "Server error",
121
+ "schema": { "type": "object" }
122
+ }
123
+ }
124
+ },
125
+ "get": {
126
+ "summary": "get MCP session",
127
+ "operationId": "GetMcp",
128
+ "description": "Retrieves information for an MCP session.",
129
+ "parameters": [
130
+ {
131
+ "name": "mcp-session-id",
132
+ "in": "header",
133
+ "required": true,
134
+ "type": "string",
135
+ "description": "Session ID"
136
+ }
137
+ ],
138
+ "responses": {
139
+ "200": {
140
+ "description": "Session information",
141
+ "schema": { "type": "object" }
142
+ },
143
+ "400": {
144
+ "description": "Invalid or missing session ID"
145
+ }
146
+ }
147
+ },
148
+ "delete": {
149
+ "summary": "Delete MCP session",
150
+ "operationId": "DeleteMcp",
151
+ "parameters": [
152
+ {
153
+ "name": "mcp-session-id",
154
+ "in": "header",
155
+ "required": true,
156
+ "type": "string",
157
+ "description": "Session ID"
158
+ }
159
+ ],
160
+ "responses": {
161
+ "200": {
162
+ "description": "Session deleted",
163
+ "schema": { "type": "object" }
164
+ },
165
+ "400": {
166
+ "description": "Invalid or missing session ID"
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ spec.info.version = version;
174
+ return spec;
175
+ };
176
176
  export default openAPIJson;
@@ -12,6 +12,8 @@ async function _jobSubmit(params) {
12
12
  // setup
13
13
  if (name === 'program') {
14
14
  let src = `
15
+ cas mycas;
16
+ caslib _all_ assign;
15
17
  proc sql;
16
18
  create table work.query_results as
17
19
  ${scenario.sql};
@@ -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
- let config = {
14
- casServerName: _appContext.cas,
15
- computeContext: _appContext.sas,
16
- source: (server === 'sas') ? 'compute' : server,
17
- table: null
18
- };
19
- let appControl;
20
- try {
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
- if (name != null) {
37
- payload.qs = {
38
- filter: `eq(name, '${name}')`
39
+ if (name != null) {
40
+ payload.qs = {
41
+ filter: `eq(name, '${name}')`
42
+ }
39
43
  }
40
- }
41
-
42
- let items = await restafedit.getLibraryList(appControl, payload);
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;
@@ -13,13 +13,14 @@ async function getLogonPayload(_appContext) {
13
13
  }
14
14
 
15
15
  async function igetLogonPayload(_appContext) {
16
-
16
+ console.error('[Info] Getting logon payload...',_appContext.AUTHFLOW);
17
17
  // Use cached logonPayload if available
18
18
  // This will cause timeouts if the token expires
19
- if (_appContext.contexts.logonPayload != null && _appContext.tokenRefresh !== true) {
19
+ /*if (_appContext.contexts.logonPayload != null && _appContext.tokenRefresh !== true) {
20
20
  console.error("[Note] Using cached logonPayload information");
21
21
  return _appContext.contexts.logonPayload;
22
22
  }
23
+ */
23
24
 
24
25
  if (_appContext.AUTHFLOW === 'code') {
25
26
  let oauthInfo = _appContext.contexts.oauthInfo;
@@ -52,17 +53,18 @@ async function igetLogonPayload(_appContext) {
52
53
  token: _appContext.bearerToken,
53
54
  tokenType: "Bearer",
54
55
  };
56
+ console.error("[Note] Bearer token in logonPayload ", _appContext.bearerToken);
55
57
  return logonPayload;
56
58
  }
57
59
 
58
60
  // Use user supplied refresh token-
59
61
  if (_appContext.AUTHFLOW === "refresh") {
60
- console.error("[Note] Using user supplied refresh token");
61
- let token = await refreshToken(_appContext,{token: _appContext.refreshToken, host: _appContext.VIYA_SERVER});
62
+ console.error("[Note] Using user supplied refresh token", _appContext.REFRESH_TOKEN);
63
+ let token = await refreshToken(_appContext,{token: _appContext.REFRESH_TOKEN, host: _appContext.VIYA_SERVER});
62
64
  let logonPayload = {
63
65
  host: _appContext.VIYA_SERVER,
64
66
  authType: "server",
65
- token: token,
67
+ token: token,
66
68
  tokenType: "Bearer",
67
69
  };
68
70
 
@@ -97,8 +99,8 @@ async function igetLogonPayload(_appContext) {
97
99
  authType: "password",
98
100
  user: _appContext.USERNAME,
99
101
  password: _appContext.PASSWORD,
100
- clientID: _appContext.CLIENTIDPW,
101
- clientSecret: _appContext.CLIENTSECRETPW,
102
+ clientID: _appContext.CLIENTID,
103
+ clientSecret: _appContext.CLIENTSECRET,
102
104
  };
103
105
 
104
106
  return logonPayload;
@@ -2,11 +2,10 @@
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 getOptsViya from './getOptsViya.js';
6
5
 
7
6
  function getStoreOpts(_appContext) {
8
7
 
9
- let opts = getOptsViya(_appContext);
8
+ let opts = _appContext.contexts.viyaCert;
10
9
  let storeOpts = {
11
10
  casProxy: true,
12
11
  httpOptions: { ...opts, rejectUnauthorized: true }
@@ -15,7 +15,6 @@ async function getToken(_appContext) {
15
15
  let sep = (os.platform() === 'win32') ? '\\' : '/';
16
16
  let credentials = homedir + sep + '.sas' + sep + 'credentials.json';
17
17
  let url = homedir + sep + '.sas' + sep + 'config.json';
18
- console.error('[Note] Using credentials file: ' + credentials);
19
18
  console.error('[Note] Using config file: ' + url);
20
19
  let profile = (_appContext.SAS_CLI_PROFILE == null || _appContext.SAS_CLI_PROFILE.toLowerCase() === 'default')
21
20
  ? 'Default' : _appContext.SAS_CLI_PROFILE;