@sassoftware/sas-score-mcp-serverjs 1.0.1-3 → 1.0.1-31
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/.skills/agents/sas-score-mcp-serverjs-agent.md +190 -0
- package/.skills/copilot-instructions.md +241 -0
- package/.skills/skills/README.md +125 -0
- package/.skills/skills/detail-strategy/SKILL.md +272 -0
- package/.skills/skills/find-resources/SKILL.md +155 -0
- package/.skills/skills/list-resource/SKILL.md +258 -0
- package/.skills/skills/read-strategy/SKILL.md +137 -0
- package/.skills/skills/request-routing/SKILL.md +107 -0
- package/.skills/skills/score-strategy/SKILL.md +231 -0
- package/README.md +96 -54
- package/cli.js +37 -27
- package/openApi.yaml +121 -121
- package/package.json +14 -14
- package/scripts/docs/oauth-http-transport.md +2 -2
- package/scripts/refreshtoken.js +58 -0
- package/src/createMcpServer.js +0 -1
- package/src/expressMcpServer.js +47 -49
- package/src/oauthHandlers/authorize.js +4 -1
- package/src/oauthHandlers/baseUrl.js +4 -0
- package/src/oauthHandlers/callback.js +5 -1
- package/src/oauthHandlers/getMetadata.js +4 -0
- package/src/oauthHandlers/index.js +4 -0
- package/src/oauthHandlers/token.js +4 -0
- package/src/openApi.yaml +121 -121
- package/src/processHeaders.js +10 -7
- package/src/setupSkills.js +12 -7
- package/src/toolHelpers/_findJob.js +12 -0
- package/src/toolHelpers/_findJobdef.js +10 -0
- package/src/toolHelpers/_findLibrary.js +10 -0
- package/src/toolHelpers/_findModel.js +12 -0
- package/src/toolHelpers/_findTable.js +10 -0
- package/src/toolHelpers/_listJobs.js +2 -1
- package/src/toolHelpers/_listLibrary.js +1 -1
- package/src/toolHelpers/_listTables.js +1 -1
- package/src/toolHelpers/getLogonPayload.js +2 -2
- package/src/toolSet/devaScore.js +61 -61
- package/src/toolSet/findJob.js +2 -1
- package/src/toolSet/findJobdef.js +7 -7
- package/src/toolSet/findLibrary.js +68 -68
- package/src/toolSet/findModel.js +2 -2
- package/src/toolSet/findTable.js +2 -2
- package/src/toolSet/jobInfo.js +59 -0
- package/src/toolSet/jobdefInfo.js +59 -0
- package/src/toolSet/listJobdefs.js +61 -61
- package/src/toolSet/listJobs.js +61 -61
- package/src/toolSet/listLibraries.js +78 -78
- package/src/toolSet/listModels.js +56 -56
- package/src/toolSet/listTables.js +66 -66
- package/src/toolSet/makeTools.js +3 -0
- package/src/toolSet/modelInfo.js +1 -1
- package/src/toolSet/modelScore.js +23 -25
- package/src/toolSet/readTable.js +63 -63
- package/src/toolSet/runCasProgram.js +21 -10
- package/src/toolSet/runJob.js +15 -19
- package/src/toolSet/runJobdef.js +15 -19
- package/src/toolSet/runMacro.js +82 -82
- package/src/toolSet/sasQuery.js +77 -77
- package/src/toolSet/scrScore.js +60 -69
- package/src/toolSet/setContext.js +65 -65
- package/src/toolSet/superstat.js +61 -61
- package/src/toolSet/tableInfo.js +58 -58
- package/.skills_claude/README.md +0 -303
- package/.skills_claude/TESTING_GUIDE.md +0 -252
- package/.skills_claude/agents/sas-viya-scoring-expert.md +0 -58
- package/.skills_claude/claude-desktop-config.json +0 -16
- package/.skills_claude/claude-desktop-system-prompt.md +0 -127
- package/.skills_claude/copilot-instructions.md +0 -155
- package/.skills_claude/instructions.md +0 -184
- package/.skills_claude/skills/sas-find-library-smart/SKILL.md +0 -157
- package/.skills_claude/skills/sas-find-resource-strategy/SKILL.md +0 -105
- package/.skills_claude/skills/sas-list-resource-strategy/SKILL.md +0 -124
- package/.skills_claude/skills/sas-list-tables-smart/SKILL.md +0 -126
- package/.skills_claude/skills/sas-read-and-score/SKILL.md +0 -112
- package/.skills_claude/skills/sas-read-strategy/SKILL.md +0 -154
- package/.skills_claude/skills/sas-request-classifier/SKILL.md +0 -69
- package/.skills_claude/skills/sas-score-workflow/SKILL.md +0 -200
- package/.skills_claude/skills-index.md +0 -345
- package/.skills_github/agents/sas-viya-scoring-expert.md +0 -58
- package/.skills_github/copilot-instructions.md +0 -177
- package/.skills_github/skills/sas-find-library-smart/SKILL.md +0 -155
- package/.skills_github/skills/sas-find-resource-strategy/SKILL.md +0 -105
- package/.skills_github/skills/sas-list-resource-strategy/SKILL.md +0 -124
- package/.skills_github/skills/sas-list-tables-smart/SKILL.md +0 -128
- package/.skills_github/skills/sas-read-and-score/SKILL.md +0 -113
- package/.skills_github/skills/sas-read-strategy/SKILL.md +0 -154
- package/.skills_github/skills/sas-request-classifier/SKILL.md +0 -74
- package/.skills_github/skills/sas-score-workflow/SKILL.md +0 -314
- package/scripts/optimize_final.py +0 -140
- package/scripts/optimize_tools.py +0 -99
- package/scripts/setup-skills.js +0 -34
- package/scripts/update_descriptions.py +0 -46
- package/src/authpkce.js +0 -219
- package/src/handleGetDelete.js +0 -34
- package/src/handleRequest.js +0 -112
- package/src/hapiMcpServer.js +0 -241
- package/src/toolSet/.claude/settings.local.json +0 -13
package/src/expressMcpServer.js
CHANGED
|
@@ -9,6 +9,7 @@ import cors from "cors";
|
|
|
9
9
|
import bodyParser from "body-parser";
|
|
10
10
|
import selfsigned from "selfsigned";
|
|
11
11
|
import openAPIJson from "./openAPIJson.js";
|
|
12
|
+
import createMcpServer from "./createMcpServer.js";
|
|
12
13
|
|
|
13
14
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
14
15
|
import { randomUUID } from "node:crypto";
|
|
@@ -21,7 +22,7 @@ import processHeaders from "./processHeaders.js";
|
|
|
21
22
|
|
|
22
23
|
// setup express server
|
|
23
24
|
|
|
24
|
-
async function expressMcpServer(
|
|
25
|
+
async function expressMcpServer(_mcpServer, cache, baseAppEnvContext) {
|
|
25
26
|
// setup for change to persistence session
|
|
26
27
|
cache.del("headerCache");
|
|
27
28
|
const app = express();
|
|
@@ -50,6 +51,10 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
|
|
|
50
51
|
const pkceStore = new Map(); // ourState -> { codeVerifier, clientRedirectUri, clientState }
|
|
51
52
|
const codeStore = new Map(); // ourCode -> { access_token, refresh_token, expires_in }
|
|
52
53
|
|
|
54
|
+
// Per-session transports — each initialize creates its own transport
|
|
55
|
+
const transports = new Map(); // sessionId -> transport
|
|
56
|
+
cache.set("transports", transports);
|
|
57
|
+
|
|
53
58
|
app.get('/.well-known/oauth-protected-resource', (req, res) => {
|
|
54
59
|
let payload = {
|
|
55
60
|
resource: `${baseAppEnvContext.mcpHost}/mcp`,
|
|
@@ -152,67 +157,54 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
|
|
|
152
157
|
|
|
153
158
|
// process mcp endpoint requests
|
|
154
159
|
const handleRequest = async (req, res) => {
|
|
155
|
-
let transport = null;
|
|
156
|
-
let transports = cache.get("transports");
|
|
157
160
|
console.error("=========================================================");
|
|
158
161
|
console.error("Processing POST /mcp request");
|
|
159
|
-
|
|
160
|
-
console.error("[Error] ***** transports cache is null. This is an error");
|
|
161
|
-
transports = {};
|
|
162
|
-
cache.set("transports", transports);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
console.error("current transports in cache:", Object.keys(transports));
|
|
162
|
+
console.error("current active sessions:", transports.size);
|
|
166
163
|
try {
|
|
167
164
|
|
|
168
165
|
let sessionId = req.headers["mcp-session-id"];
|
|
169
166
|
console.error("[Note]Incoming session ID:", sessionId);
|
|
170
167
|
let body = (req.body == null) ? 'no body' : JSON.stringify(req.body);
|
|
171
168
|
console.error('[Note] Payload is ', body);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
console.error("[Note]
|
|
175
|
-
|
|
176
|
-
|
|
169
|
+
|
|
170
|
+
if (isInitializeRequest(req.body)) {
|
|
171
|
+
console.error("[Note]>>>>>>>>>>>>>>>>>>>>>>>>> Creating new MCP server");
|
|
172
|
+
let mcpServer = await createMcpServer(cache, baseAppEnvContext);
|
|
173
|
+
// New session — create a dedicated transport
|
|
174
|
+
console.error("[Note] Initializing new session with fresh transport...");
|
|
175
|
+
const isInitRequest = req.body?.method === 'initialize';
|
|
176
|
+
|
|
177
|
+
const transport = new StreamableHTTPServerTransport({
|
|
177
178
|
sessionIdGenerator: () => randomUUID(),
|
|
178
179
|
enableJsonResponse: true,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
console.error("[Note]
|
|
184
|
-
transports[sessionId] = transport;
|
|
180
|
+
onsessioninitialized: (newSessionId) => {
|
|
181
|
+
console.error("[Note] Session initialized with ID:", newSessionId);
|
|
182
|
+
transports.set(newSessionId, transport);
|
|
183
|
+
cache.set("transports", transports);
|
|
184
|
+
console.error("[Note] Total active sessions:", Object.keys(transports));
|
|
185
185
|
},
|
|
186
186
|
});
|
|
187
|
-
// Clean up transport when closed
|
|
188
187
|
transport.onclose = () => {
|
|
189
|
-
if (transport.sessionId
|
|
190
|
-
|
|
188
|
+
if (transport.sessionId) {
|
|
189
|
+
console.error("[Note] Session closed, removing transport:", transport.sessionId);
|
|
190
|
+
transports.delete(transport.sessionId);
|
|
191
|
+
cache.del(transport.sessionId);
|
|
191
192
|
}
|
|
192
193
|
};
|
|
193
|
-
console.error("[Note] Connecting mcpServer to new transport...");
|
|
194
194
|
await mcpServer.connect(transport);
|
|
195
|
-
|
|
196
|
-
// Save transport data and app context for use in tools
|
|
197
|
-
console.error('[Note] Connected to mcpServer');
|
|
198
|
-
cache.set("transports", transports);
|
|
199
195
|
console.error("=======================================================");
|
|
200
196
|
return await transport.handleRequest(req, res, req.body);
|
|
201
197
|
|
|
202
|
-
// cache transport
|
|
203
|
-
|
|
204
198
|
} else if (sessionId != null) {
|
|
205
|
-
|
|
206
|
-
transport
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// this can happen if client is holding on to old session id
|
|
210
|
-
console.error("[Error] No transport found for session ID:", sessionId, "Returning a 404 error with instructions for the user");
|
|
211
|
-
res.status(404).send(`Invalid or missing session ID ${sessionId}. Please ensure your MCP client is configured to use the correct session ID returned in the 'mcp-session-id' header of the response from the /mcp endpoint.`);
|
|
212
|
-
return;
|
|
199
|
+
const transport = transports.get(sessionId);
|
|
200
|
+
if (!transport) {
|
|
201
|
+
console.error("[Note] Unknown session ID:", sessionId);
|
|
202
|
+
return res.status(404).json({ jsonrpc: "2.0", error: { code: -32000, message: "Session not found. Please re-initialize." }, id: null });
|
|
213
203
|
}
|
|
204
|
+
console.error('[Note] Incoming session ID:', sessionId);
|
|
205
|
+
console.error("[Note] Using transport for session ID:", sessionId);
|
|
214
206
|
|
|
215
|
-
// post the
|
|
207
|
+
// post the current session - used to pass _appContext to tools
|
|
216
208
|
cache.set("currentId", sessionId);
|
|
217
209
|
|
|
218
210
|
// get app context for session
|
|
@@ -230,7 +222,6 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
|
|
|
230
222
|
_appContext = Object.assign(_appContext, headerCache);
|
|
231
223
|
cache.set(sessionId, _appContext);
|
|
232
224
|
}
|
|
233
|
-
console.error("[Note] Using existing transport for session ID:", sessionId);
|
|
234
225
|
console.error("==========================================================");
|
|
235
226
|
await transport.handleRequest(req, res, req.body);
|
|
236
227
|
return;
|
|
@@ -260,21 +251,28 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
|
|
|
260
251
|
const sessionId = req.headers["mcp-session-id"];
|
|
261
252
|
console.error("[Note] SessionId:", sessionId);
|
|
262
253
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (!sessionId || transport == null) {
|
|
267
|
-
res.status(404).send(`[Error] In ${req.method}: Invalid or missing session ID ${sessionId}`);
|
|
268
|
-
return;
|
|
254
|
+
if (!sessionId) {
|
|
255
|
+
console.error("[Note] No session ID on /DELETE — rejecting");
|
|
256
|
+
return res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: Mcp-Session-Id header is required" }, id: null });
|
|
269
257
|
}
|
|
258
|
+
|
|
259
|
+
const transport = transports.get(sessionId);
|
|
260
|
+
if (!transport) {
|
|
261
|
+
console.error("[Note] Unknown session ID:", sessionId);
|
|
262
|
+
return res.status(404).json({ jsonrpc: "2.0", error: { code: -32000, message: "Session not found. Please re-initialize." }, id: null });
|
|
263
|
+
}
|
|
264
|
+
|
|
270
265
|
if (req.method === "GET") {
|
|
266
|
+
console.error("[Note] calling transport.handleRequest for GET /mcp");
|
|
271
267
|
await transport.handleRequest(req, res);
|
|
272
268
|
return;
|
|
273
269
|
}
|
|
274
|
-
if (req.method === "DELETE"
|
|
270
|
+
if (req.method === "DELETE") {
|
|
275
271
|
console.error("[Note] Deleting transport and cache for session ID:", sessionId);
|
|
276
|
-
delete
|
|
272
|
+
transports.delete(sessionId);
|
|
277
273
|
cache.del(sessionId);
|
|
274
|
+
console.error("[Note] Deleted session ID:", sessionId);
|
|
275
|
+
console.error("[Note] Total active sessions:", Object.keys(transports));
|
|
278
276
|
res.status(201).send(`[Info] Deleted session ${sessionId}`);
|
|
279
277
|
}
|
|
280
278
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
1
5
|
import baseUrl from "./baseUrl.js";
|
|
2
6
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
3
7
|
import { randomUUID } from "node:crypto";
|
|
@@ -75,7 +79,7 @@ async function callback(req, res, pkceStore, codeStore, appContext) {
|
|
|
75
79
|
// which was part of the payload from the client to /oauth/authorize
|
|
76
80
|
// we trust since it was associated with the valid PKCE state
|
|
77
81
|
console.error("[Note] OAuth callback complete, redirecting to MCP client");
|
|
78
|
-
console.
|
|
82
|
+
console.error(pending.clientRedirectUri.toString())
|
|
79
83
|
return res.redirect(`${pending.clientRedirectUri}?${redirectParams}`);
|
|
80
84
|
} catch (err) {
|
|
81
85
|
console.error("[Error] OAuth callback handler error:", err);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
1
5
|
function token(req, res, appContext, codeStore, cache) {
|
|
2
6
|
console.error("===============================================================");
|
|
3
7
|
console.error("[Note] at /token endpoint");
|
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
|
package/src/processHeaders.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { start } from "node:repl";
|
|
2
|
-
|
|
3
1
|
/*
|
|
4
2
|
* Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
5
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -32,20 +30,25 @@ function processHeaders(req, res, next, cache, appContext) {
|
|
|
32
30
|
const hdr = req.header("Authorization");
|
|
33
31
|
//for now, ignore Authorization if authflow is not bearer
|
|
34
32
|
let token = (hdr != null) ? hdr.slice(7) : null;
|
|
35
|
-
//console.error("[Note] Authorization token", token);
|
|
36
33
|
debugger;
|
|
37
|
-
console.
|
|
34
|
+
console.error('[Note} AUTHFLOW=', appContext.AUTHFLOW);
|
|
35
|
+
console.error("[Note] External authorization :", appContext.AUTHEXTERNAL);
|
|
38
36
|
if (appContext.AUTHFLOW === 'bearer') {
|
|
39
37
|
debugger;
|
|
40
38
|
let startAuth = false;
|
|
41
|
-
|
|
39
|
+
|
|
42
40
|
if (appContext.AUTHEXTERNAL === true) {
|
|
43
41
|
console.error("[Note] Expecting external authorization");
|
|
44
42
|
if (token != null) {
|
|
45
43
|
console.error("[Note] Using user supplied token for authorization");
|
|
46
44
|
headerCache.bearerToken = token;
|
|
47
45
|
} else {
|
|
48
|
-
|
|
46
|
+
console.error("[Note] No Authorization token provided in header for external authorization.");
|
|
47
|
+
console.error("[Note] Returning 404 since we are configured for external token and no token provided in header.");
|
|
48
|
+
return res.status(404).json({
|
|
49
|
+
error: "unauthorized",
|
|
50
|
+
error_description: "[Error] Missing token for external authorization."
|
|
51
|
+
});
|
|
49
52
|
}
|
|
50
53
|
} else if (token == null) {
|
|
51
54
|
console.error("[Note] No Authorization token provided in header.");
|
|
@@ -55,7 +58,7 @@ function processHeaders(req, res, next, cache, appContext) {
|
|
|
55
58
|
let tokenlist = cache.get("tokenlist");
|
|
56
59
|
let tokenData = tokenlist[token];
|
|
57
60
|
if (tokenData == null) {
|
|
58
|
-
return res.status(
|
|
61
|
+
return res.status(401).json({
|
|
59
62
|
error: "unauthorized",
|
|
60
63
|
error_description: "[Error] Expired token. Clear token and try again."
|
|
61
64
|
});
|
package/src/setupSkills.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
|
|
7
|
-
function setupSkills(clientName) {
|
|
7
|
+
function setupSkills(clientName,agentFolder) {
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
// Paths
|
|
10
10
|
let destination;
|
|
@@ -13,10 +13,13 @@ function setupSkills(clientName) {
|
|
|
13
13
|
destination = path.join(process.cwd(), clientName);
|
|
14
14
|
clientName = clientName.slice(1);
|
|
15
15
|
} else {
|
|
16
|
-
destination = path.join(os.homedir(), '.' + clientName);
|
|
16
|
+
destination = path.join(os.homedir(), '.' + clientName );
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
if (agentFolder) {
|
|
20
|
+
destination = path.join(destination, agentFolder);
|
|
21
|
+
}
|
|
22
|
+
const source = path.join(__dirname, `../.skills`);
|
|
20
23
|
console.error("==================================================================");
|
|
21
24
|
console.error(` Copying ${source} to ${destination}...`);
|
|
22
25
|
|
|
@@ -25,22 +28,24 @@ function setupSkills(clientName) {
|
|
|
25
28
|
if (!fs.existsSync(to)) fs.mkdirSync(to, { recursive: true });;
|
|
26
29
|
fs.readdirSync(from).forEach(element => {
|
|
27
30
|
const fromPath = path.join(from, element);
|
|
28
|
-
|
|
31
|
+
let toPath = path.join(to, element);
|
|
32
|
+
if (clientName === 'claude' && element === 'copilot-instructions.md') {
|
|
33
|
+
toPath = path.join(to, 'CLAUDE.md');
|
|
34
|
+
}
|
|
29
35
|
if (fs.lstatSync(fromPath).isFile()) {
|
|
30
36
|
console.error(` 📄 Copying file: ${element}`);
|
|
31
37
|
fs.copyFileSync(fromPath, toPath);
|
|
32
38
|
} else if (fs.lstatSync(fromPath).isDirectory()) {
|
|
33
39
|
console.error(`📂 Copying folder: ${element}`);
|
|
34
|
-
copyFolderSync(fromPath, toPath)
|
|
40
|
+
copyFolderSync(fromPath, toPath);
|
|
35
41
|
}
|
|
36
42
|
});
|
|
37
43
|
}
|
|
38
|
-
|
|
39
44
|
function listExpandedFolder(dir, indent = "") {
|
|
40
45
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
41
46
|
|
|
42
47
|
for (const entry of entries) {
|
|
43
|
-
console.
|
|
48
|
+
console.error(indent + entry.name);
|
|
44
49
|
|
|
45
50
|
if (entry.isDirectory()) {
|
|
46
51
|
listExpandedFolder(path.join(dir, entry.name), indent + " ");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import _listJobs from './_listJobs.js';
|
|
7
|
+
async function _findJob(params) {
|
|
8
|
+
let r = await _listJobs(params);
|
|
9
|
+
console.log ("findJob result:" , r);
|
|
10
|
+
return r;
|
|
11
|
+
}
|
|
12
|
+
export default _findJob;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import _listJobdefs from './_listJobdefs.js';
|
|
7
|
+
async function _findJobdef(params) {
|
|
8
|
+
return await _listJobdefs(params);
|
|
9
|
+
}
|
|
10
|
+
export default _findJobdef;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import _listLibrary from './_listLibrary.js';
|
|
7
|
+
async function _findLibrary(params) {
|
|
8
|
+
return await _listLibrary(params);
|
|
9
|
+
}
|
|
10
|
+
export default _findLibrary;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import _listModels from './_listModels.js';
|
|
7
|
+
async function _findModel(params) {
|
|
8
|
+
let r = await _listModels(params);
|
|
9
|
+
console.log ("findModel result:" , r);
|
|
10
|
+
return r;
|
|
11
|
+
}
|
|
12
|
+
export default _findModel;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import _listTables from './_listTables.js';
|
|
7
|
+
async function _findTable(params) {
|
|
8
|
+
return await _listTables(params);
|
|
9
|
+
}
|
|
10
|
+
export default _findTable;
|
|
@@ -42,8 +42,9 @@ async function _listJobs(params) {
|
|
|
42
42
|
names[jname] = {parameters: (value == null) ? {} : value.toJS() };
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
|
-
|
|
45
|
+
console.error('parameters', JSON.stringify(names, null, 2));
|
|
46
46
|
let response = {jobs: Object.keys(names)};
|
|
47
|
+
console.error('response', JSON.stringify(response, null, 2));
|
|
47
48
|
|
|
48
49
|
if (name != null) {
|
|
49
50
|
response = { name: name, parameters: names[name].parameters };
|
|
@@ -54,7 +54,7 @@ async function _listLibrary(params) {
|
|
|
54
54
|
if (appControl != null) {
|
|
55
55
|
await deleteSession(appControl);
|
|
56
56
|
}
|
|
57
|
-
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
57
|
+
return { isError: true, content: [{ type: 'text', text: (typeof err === 'string') ? err : JSON.stringify(err) }] };
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|