@sassoftware/sas-score-mcp-serverjs 0.3.19 → 0.3.29-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.
- package/cli.js +23 -24
- package/openApi.yaml +121 -121
- package/package.json +2 -2
- package/src/createMcpServer.js +2 -1
- package/src/expressMcpServer.js +68 -28
- package/src/handleGetDelete.js +6 -3
- package/src/hapiMcpServer.js +30 -0
- package/src/openAPIJson.js +175 -175
- package/src/openApi.yaml +121 -121
- package/src/toolHelpers/getLogonPayload.js +9 -7
- package/src/toolHelpers/getStoreOpts.js +1 -2
- package/src/toolHelpers/getToken.js +0 -1
- package/src/toolHelpers/refreshToken.js +48 -46
- package/src/toolHelpers/refreshTokenOauth.js +2 -2
- package/src/toolHelpers/tlogon.js +9 -0
- package/src/toolSet/devaScore.js +10 -5
- package/src/toolHelpers/getOpts.js +0 -51
- package/src/toolHelpers/getOptsViya.js +0 -44
package/cli.js
CHANGED
|
@@ -16,14 +16,15 @@ import createMcpServer from './src/createMcpServer.js';
|
|
|
16
16
|
import fs from 'fs';
|
|
17
17
|
import { randomUUID } from 'node:crypto';
|
|
18
18
|
|
|
19
|
-
import refreshToken from './src/toolHelpers/refreshToken.js';
|
|
20
|
-
import getOptsViya from './src/toolHelpers/getOptsViya.js';
|
|
19
|
+
//import refreshToken from './src/toolHelpers/refreshToken.js';
|
|
20
|
+
//import getOptsViya from './src/toolHelpers/getOptsViya.js';
|
|
21
|
+
import readCerts from './src/toolHelpers/readCerts.js';
|
|
21
22
|
|
|
22
23
|
import { fileURLToPath } from 'url';
|
|
23
24
|
import { dirname } from 'path';
|
|
24
25
|
|
|
25
26
|
import NodeCache from 'node-cache';
|
|
26
|
-
import getOpts from './src/toolHelpers/getOpts.js';
|
|
27
|
+
//import getOpts from './src/toolHelpers/getOpts.js';
|
|
27
28
|
|
|
28
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
30
|
|
|
@@ -70,7 +71,7 @@ console.error(
|
|
|
70
71
|
// and storage provided by cloud providers
|
|
71
72
|
console.error(process.env.COMPUTECONTEXT);
|
|
72
73
|
debugger;
|
|
73
|
-
let sessionCache = new NodeCache({ stdTTL:
|
|
74
|
+
let sessionCache = new NodeCache({ stdTTL: 24 *60*60, checkperiod: 2 * 60, useClones: false });
|
|
74
75
|
|
|
75
76
|
//
|
|
76
77
|
// Load environment variables from .env file if present
|
|
@@ -167,6 +168,7 @@ const appEnvBase = {
|
|
|
167
168
|
casSession: null, /* restaf cas session object */
|
|
168
169
|
computeSession: null, /* restaf compute session object */
|
|
169
170
|
viyaCert: null, /* ssl/tsl certificates to connect to viya */
|
|
171
|
+
appCert: null,
|
|
170
172
|
logonPayload: null, /* viya logon payload to connect to viya */
|
|
171
173
|
casServerId: null,
|
|
172
174
|
computeSessonId: null,
|
|
@@ -179,10 +181,15 @@ const appEnvBase = {
|
|
|
179
181
|
process.env.APPPORT=appEnvBase.PORT;
|
|
180
182
|
|
|
181
183
|
// setup TLS options for viya calls
|
|
182
|
-
|
|
183
184
|
console.error('[Note]Viya SSL dir set to: ' + appEnvBase.VIYACERT);
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
appEnvBase.contexts.viyaCert = readCerts(appEnvBase.VIYACERT); /* appEnvBase.contexts.viyaCert is set here */
|
|
186
|
+
|
|
187
|
+
// setup TLS options for app server (expressMcpServer or hapiMcpServer)
|
|
188
|
+
|
|
189
|
+
console.error('[Note]App SSL dir set to: ' + appEnvBase.SSLCERT);
|
|
190
|
+
appEnvBase.tlsOpts = readCerts(appEnvBase.SSLCERT);
|
|
191
|
+
appEnvBase.contexts.appCert = appEnvBase.tlsOpts; /* just for completeness */
|
|
192
|
+
|
|
186
193
|
appEnvBase.contexts.storeConfig = {
|
|
187
194
|
casProxy: true,
|
|
188
195
|
options: { ns: null, proxyServer: null, httpOptions: appEnvBase.contexts.viyaCert }
|
|
@@ -205,19 +212,7 @@ if (appEnvBase.TOKENFILE != null) {
|
|
|
205
212
|
}
|
|
206
213
|
}
|
|
207
214
|
|
|
208
|
-
|
|
209
|
-
// use this for testing only.
|
|
210
|
-
if (appEnvBase.REFRESH_TOKEN != null) {
|
|
211
|
-
appEnvBase.refreshToken = appEnvBase.REFRESH_TOKEN;
|
|
212
|
-
appEnvBase.AUTHFLOW = 'refresh';
|
|
213
|
-
let t = await refreshToken(appEnvBase, { token: appEnvBase.REFRESH_TOKEN, host: appEnvBase.VIYA_SERVER });
|
|
214
|
-
appEnvBase.contexts.logonPayload = {
|
|
215
|
-
host: appEnvBase.VIYA_SERVER,
|
|
216
|
-
authType: 'server',
|
|
217
|
-
token: t,
|
|
218
|
-
tokenType: 'Bearer'
|
|
219
|
-
}
|
|
220
|
-
}
|
|
215
|
+
|
|
221
216
|
|
|
222
217
|
// if authflow is cli or code, postpone getting logonPayload until needed
|
|
223
218
|
|
|
@@ -232,16 +227,19 @@ let appEnvTemplate = Object.assign({}, appEnvBase);
|
|
|
232
227
|
|
|
233
228
|
sessionCache.set('appEnvTemplate', appEnvTemplate);
|
|
234
229
|
|
|
235
|
-
let transports = {
|
|
230
|
+
let transports = {
|
|
231
|
+
"dummy": null
|
|
232
|
+
};
|
|
236
233
|
sessionCache.set('transports', transports);
|
|
237
234
|
|
|
238
235
|
// set this for stdio transport use
|
|
239
236
|
// dummy sessionId for use in the tools
|
|
240
237
|
let useHapi = process.env.AUTHFLOW === 'code' ? true : false;
|
|
238
|
+
console.error('[Note] appEnvBase is', JSON.stringify(appEnvBase, null,2));
|
|
239
|
+
// creat a dummy sessionId for stdio since there is only one session and transport in that case, and tools need a sessionId to access the appEnvBase and contexts
|
|
240
|
+
let sessionId = randomUUID();
|
|
241
|
+
sessionCache.set(sessionId, appEnvBase);
|
|
241
242
|
if (mcpType === 'stdio') {
|
|
242
|
-
let sessionId = randomUUID();
|
|
243
|
-
sessionCache.set('currentId', sessionId);
|
|
244
|
-
sessionCache.set(sessionId, appEnvBase);
|
|
245
243
|
console.error('[Note] Setting up stdio transport with sessionId:', sessionId);
|
|
246
244
|
console.error('[Note] Used in setting up tools and some persistence(not all).');
|
|
247
245
|
await coreSSE(mcpServer);
|
|
@@ -249,6 +247,7 @@ if (mcpType === 'stdio') {
|
|
|
249
247
|
} else {
|
|
250
248
|
console.error('[Note] Starting HTTP MCP server...');
|
|
251
249
|
if (useHapi === true) {
|
|
250
|
+
process.env.AUTHTYPE = null;
|
|
252
251
|
await hapiMcpServer(mcpServer, sessionCache, appEnvBase);
|
|
253
252
|
console.error('[Note] Using HAPI HTTP server...')
|
|
254
253
|
} else {
|
package/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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sassoftware/sas-score-mcp-serverjs",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.29-0",
|
|
4
4
|
"description": "A mcp server for SAS Viya",
|
|
5
5
|
"author": "Deva Kumar <deva.kumar@sas.com>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"scripts": {
|
|
13
|
-
"start": "node cli.js",
|
|
13
|
+
"start": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 node cli.js",
|
|
14
14
|
"testi": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 npx @modelcontextprotocol/inspector",
|
|
15
15
|
"test": "cd test && node",
|
|
16
16
|
"debug": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 node --inspect-brk cli.js",
|
package/src/createMcpServer.js
CHANGED
|
@@ -75,8 +75,9 @@ async function createMcpServer(cache, _appContext) {
|
|
|
75
75
|
let toolName = _appContext.brand + '-' + tool.name;
|
|
76
76
|
// console.error(`\n[Note] Registering tool ${i + 1} : ${toolName}`);
|
|
77
77
|
let toolHandler = wrapf(cache, tool.handler);
|
|
78
|
-
|
|
78
|
+
tool.name = toolName; // update tool name with brand prefix for registration
|
|
79
79
|
mcpServer.tool(toolName, tool.description, tool.schema, toolHandler);
|
|
80
|
+
|
|
80
81
|
toolNames.push(toolName);
|
|
81
82
|
});
|
|
82
83
|
console.error(`[Note] Registered ${toolSet.length} tools: ${toolNames}`);
|
package/src/expressMcpServer.js
CHANGED
|
@@ -6,17 +6,14 @@ import express from "express";
|
|
|
6
6
|
|
|
7
7
|
import https from "https";
|
|
8
8
|
import cors from "cors";
|
|
9
|
-
//import rateLimit from "express-rate-limit";
|
|
10
|
-
//import helmet from "helmet";
|
|
11
9
|
import bodyParser from "body-parser";
|
|
12
|
-
|
|
13
10
|
import selfsigned from "selfsigned";
|
|
14
11
|
import openAPIJson from "./openAPIJson.js";
|
|
15
|
-
import fs from "fs";
|
|
16
12
|
|
|
17
13
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
18
14
|
import { randomUUID } from "node:crypto";
|
|
19
15
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
+
import tlogon from "./toolHelpers/tlogon.js";
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
// setup express server
|
|
@@ -98,10 +95,9 @@ app.get("/openapi.json", (req, res) => {
|
|
|
98
95
|
|
|
99
96
|
// handle processing of information in header.
|
|
100
97
|
function requireBearer(req, res, next) {
|
|
101
|
-
|
|
102
|
-
|
|
103
98
|
// process any new header information
|
|
104
|
-
|
|
99
|
+
console.error("=======================================================");
|
|
100
|
+
console.error("Processing headers for incoming request to /mcp endpoint");
|
|
105
101
|
// Allow different VIYA server per sessionid(user)
|
|
106
102
|
let headerCache = {};
|
|
107
103
|
if (req.header("X-VIYA-SERVER") != null) {
|
|
@@ -115,29 +111,44 @@ function requireBearer(req, res, next) {
|
|
|
115
111
|
if (hdr != null) {
|
|
116
112
|
headerCache.bearerToken = hdr.slice(7);
|
|
117
113
|
headerCache.AUTHFLOW = "bearer";
|
|
114
|
+
console.error("[Note] Using user supplied bearer token for authorization");
|
|
115
|
+
console.error("[Debug] Bearer token starts with:", headerCache.bearerToken);
|
|
116
|
+
} else {
|
|
117
|
+
console.error("[Note] No bearer token supplied in Authorization header");
|
|
118
|
+
headerCache.bearerToken = null;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
// faking out api key since Viya does not support
|
|
121
122
|
// not ideal for production
|
|
122
123
|
const hdr2 = req.header("X-REFRESH-TOKEN");
|
|
123
124
|
if (hdr2 != null) {
|
|
124
|
-
headerCache.
|
|
125
|
+
headerCache.REFRESH_TOKEN = hdr2;
|
|
125
126
|
headerCache.AUTHFLOW = "refresh";
|
|
127
|
+
console.error("[Note] Using user supplied refresh token for authorization");
|
|
126
128
|
}
|
|
127
129
|
cache.set("headerCache", headerCache);
|
|
128
130
|
next();
|
|
131
|
+
console.error("Finished processing headers for /mcp request");
|
|
132
|
+
console.log("=======================================================");
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
// process mcp endpoint requests
|
|
132
136
|
const handleRequest = async (req, res) => {
|
|
133
|
-
let transport;
|
|
137
|
+
let transport = null;
|
|
134
138
|
let transports = cache.get("transports");
|
|
139
|
+
console.error("=========================================================");
|
|
140
|
+
console.error("Processing POST /mcp request");
|
|
141
|
+
if (transports == null) {
|
|
142
|
+
console.error("[Error] ***** transports cache is null. This is an error");
|
|
143
|
+
transports = {};
|
|
144
|
+
cache.set("transports", transports);
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
console.error("current transports in cache:", Object.keys(transports));
|
|
136
148
|
try {
|
|
137
149
|
|
|
138
150
|
let sessionId = req.headers["mcp-session-id"];
|
|
139
|
-
console.error("
|
|
140
|
-
console.error("post /mcp called with session ID:", sessionId);
|
|
151
|
+
console.error("[Note]Incoming session ID:", sessionId);
|
|
141
152
|
let body = (req.body == null) ? 'no body' : JSON.stringify(req.body);
|
|
142
153
|
console.error('[Note] Payload is ', body);
|
|
143
154
|
if (/*!sessionId &&*/ isInitializeRequest(req.body)) {
|
|
@@ -157,7 +168,7 @@ const handleRequest = async (req, res) => {
|
|
|
157
168
|
});
|
|
158
169
|
// Clean up transport when closed
|
|
159
170
|
transport.onclose = () => {
|
|
160
|
-
if (transport.sessionId) {
|
|
171
|
+
if (transport.sessionId && transports[transport.sessionId]) {
|
|
161
172
|
delete transports[transport.sessionId];
|
|
162
173
|
}
|
|
163
174
|
};
|
|
@@ -165,9 +176,11 @@ const handleRequest = async (req, res) => {
|
|
|
165
176
|
await mcpServer.connect(transport);
|
|
166
177
|
|
|
167
178
|
// Save transport data and app context for use in tools
|
|
168
|
-
console.error('
|
|
179
|
+
console.error('[Note] Connected to mcpServer');
|
|
169
180
|
cache.set("transports", transports);
|
|
181
|
+
console.error("=======================================================");
|
|
170
182
|
return await transport.handleRequest(req, res, req.body);
|
|
183
|
+
|
|
171
184
|
// cache transport
|
|
172
185
|
|
|
173
186
|
} else if (sessionId != null) {
|
|
@@ -176,8 +189,8 @@ const handleRequest = async (req, res) => {
|
|
|
176
189
|
console.error("[Note] Found transport:", transport != null);
|
|
177
190
|
if (transport == null) {
|
|
178
191
|
// this can happen if client is holding on to old session id
|
|
179
|
-
console.error("[Error] No transport found for session ID:", sessionId, "Returning a
|
|
180
|
-
res.status(
|
|
192
|
+
console.error("[Error] No transport found for session ID:", sessionId, "Returning a 404 error with instructions for the user");
|
|
193
|
+
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.`);
|
|
181
194
|
return;
|
|
182
195
|
}
|
|
183
196
|
|
|
@@ -194,9 +207,15 @@ const handleRequest = async (req, res) => {
|
|
|
194
207
|
let headerCache = cache.get("headerCache");
|
|
195
208
|
_appContext = Object.assign({}, appEnvTemplate, headerCache);
|
|
196
209
|
cache.set(sessionId, _appContext);
|
|
210
|
+
} else {
|
|
211
|
+
let headerCache = cache.get("headerCache");
|
|
212
|
+
console.error('compare tokens', headerCache.bearerToken === _appContext.bearerToken);
|
|
213
|
+
_appContext = Object.assign(_appContext, headerCache);
|
|
214
|
+
console.error('New bearerToken:', _appContext.bearerToken);
|
|
215
|
+
cache.set(sessionId, _appContext);
|
|
197
216
|
}
|
|
198
217
|
console.error("[Note] Using existing transport for session ID:", sessionId);
|
|
199
|
-
|
|
218
|
+
console.error("==========================================================");
|
|
200
219
|
await transport.handleRequest(req, res, req.body);
|
|
201
220
|
return;
|
|
202
221
|
}
|
|
@@ -220,20 +239,27 @@ const handleRequest = async (req, res) => {
|
|
|
220
239
|
}
|
|
221
240
|
};
|
|
222
241
|
const handleGetDelete = async (req, res) => {
|
|
223
|
-
console.error(
|
|
242
|
+
console.error("=========================================================");
|
|
243
|
+
console.error(`[Note] ${req.method} /mcp called`);
|
|
224
244
|
const sessionId = req.headers["mcp-session-id"];
|
|
225
|
-
console.error("
|
|
245
|
+
console.error("[Note] SessionId:", sessionId);
|
|
246
|
+
|
|
226
247
|
let transports = cache.get("transports");
|
|
227
248
|
let transport = (sessionId == null) ? null : transports[sessionId];
|
|
249
|
+
console.error("[Note] Transport found:", transport != null);
|
|
228
250
|
if (!sessionId || transport == null) {
|
|
229
|
-
res.status(
|
|
251
|
+
res.status(404).send(`[Error] In ${req.method}: Invalid or missing session ID ${sessionId}`);
|
|
230
252
|
return;
|
|
231
253
|
}
|
|
232
|
-
|
|
254
|
+
if (req.method === "GET") {
|
|
255
|
+
await transport.handleRequest(req, res);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
233
258
|
if (req.method === "DELETE" && sessionId != null) {
|
|
234
|
-
console.error("Deleting transport and cache for session ID:", sessionId);
|
|
259
|
+
console.error("[Note] Deleting transport and cache for session ID:", sessionId);
|
|
235
260
|
delete transports[sessionId];
|
|
236
261
|
cache.del(sessionId);
|
|
262
|
+
res.status(201).send(`[Info] Deleted session ${sessionId}`);
|
|
237
263
|
}
|
|
238
264
|
}
|
|
239
265
|
|
|
@@ -241,26 +267,40 @@ app.options("/mcp", (_, res) => res.sendStatus(204));
|
|
|
241
267
|
app.post("/mcp", requireBearer, handleRequest);
|
|
242
268
|
app.get("/mcp", handleGetDelete);
|
|
243
269
|
app.delete("/mcp", handleGetDelete);
|
|
244
|
-
app.get("/
|
|
245
|
-
console.error("
|
|
270
|
+
app.get("/StartUp", (_req, res) => {
|
|
271
|
+
console.error("===================================================================")
|
|
272
|
+
console.error("Received request for Startup endpoint. Current app status:", appStatus);
|
|
273
|
+
console.error("===================================================================");
|
|
246
274
|
if (appStatus === false) {
|
|
247
|
-
return res.status(
|
|
275
|
+
return res.status(503).json({ status: "starting" });
|
|
248
276
|
}
|
|
249
277
|
return res.status(200).json({ status: "started" });
|
|
250
278
|
});
|
|
251
|
-
|
|
279
|
+
app.get("/tlogon", async (_req, res) => {
|
|
280
|
+
console.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Testing logon");
|
|
281
|
+
if (appStatus === false) {
|
|
282
|
+
return res.status(503).json({ status: "not ready" });
|
|
283
|
+
}
|
|
284
|
+
let r = await tlogon(baseAppEnvContext);
|
|
285
|
+
console.error(r);
|
|
286
|
+
return res.status(200).json(r);
|
|
287
|
+
});
|
|
252
288
|
app.get("/status", (_req, res) => {
|
|
289
|
+
console.error("===================================================================")
|
|
253
290
|
console.error("Received request for status endpoint. Current app status:", appStatus);
|
|
291
|
+
console.error("===================================================================");
|
|
254
292
|
if (appStatus === false) {
|
|
255
|
-
return res.status(
|
|
293
|
+
return res.status(503).json({ status: "not ready" });
|
|
256
294
|
}
|
|
257
295
|
return res.status(200).json({ status: "ready" });
|
|
258
296
|
});
|
|
259
297
|
|
|
260
298
|
app.get("/ready", (_req, res) => {
|
|
299
|
+
console.error("===================================================================")
|
|
261
300
|
console.error("Received request for ready endpoint. Current app status:", appStatus);
|
|
301
|
+
console.error("===================================================================")
|
|
262
302
|
if (appStatus === false) {
|
|
263
|
-
return res.status(
|
|
303
|
+
return res.status(503).json({ status: "not ready" });
|
|
264
304
|
}
|
|
265
305
|
return res.status(200).json({ status: "ready" });
|
|
266
306
|
});
|
|
@@ -274,12 +314,12 @@ let appServer;
|
|
|
274
314
|
|
|
275
315
|
// get TLS options
|
|
276
316
|
if (appEnvBase.HTTPS === 'TRUE') {
|
|
277
|
-
//appEnvBase.tlsOpts = getOpts(appEnvBase);
|
|
278
317
|
if (appEnvBase.tlsOpts == null) {
|
|
279
318
|
appEnvBase.tlsOpts = await getTls(appEnvBase);
|
|
280
319
|
console.error(Object.keys(appEnvBase.tlsOpts));
|
|
281
320
|
appEnvBase.tlsOpts.requestCert = false;
|
|
282
321
|
appEnvBase.tlsOpts.rejectUnauthorized = false;
|
|
322
|
+
appEnvBase.contexts.appCert = appEnvBase.tlsOpts; /* just for completeness */
|
|
283
323
|
}
|
|
284
324
|
|
|
285
325
|
cache.set("appEnvBase", appEnvBase);
|
package/src/handleGetDelete.js
CHANGED
|
@@ -5,22 +5,25 @@
|
|
|
5
5
|
|
|
6
6
|
async function handleGetDelete(mcpServer, cache, req, h) {
|
|
7
7
|
const sessionId = req.headers["mcp-session-id"];
|
|
8
|
-
console.error(
|
|
8
|
+
console.error("=======================================================");
|
|
9
|
+
console.error(`[Note] Handling ${req.method} for session ID:`, sessionId);
|
|
9
10
|
let transports = cache.get("transports");
|
|
10
11
|
let transport = transports[sessionId];
|
|
11
12
|
if (!sessionId || transport == null) {
|
|
12
13
|
console.error('[Note] Looks like a fresh start - no session id or transport found');
|
|
13
|
-
h.abandon;
|
|
14
|
+
return h.abandon;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
if (req.method === "GET") {
|
|
17
18
|
// You can customize the response as needed
|
|
19
|
+
console.error("[Note] Payload:", req.payload);
|
|
20
|
+
console.log("======================================================");
|
|
18
21
|
await transport.handleRequest(req.raw.req, req.raw.res, req.payload);
|
|
19
22
|
return h.abandon;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
if (req.method === "DELETE") {
|
|
23
|
-
console.error("Deleting transport and cache for session ID:", sessionId);
|
|
26
|
+
console.error("[Note] Deleting transport and cache for session ID:", sessionId);
|
|
24
27
|
delete transports[sessionId];
|
|
25
28
|
cache.del(sessionId);
|
|
26
29
|
return h.response(`[Info] In DELETE: Session ID ${sessionId} deleted`).code(201);
|
package/src/hapiMcpServer.js
CHANGED
|
@@ -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",
|