@sassoftware/sas-score-mcp-serverjs 1.0.1-26 → 1.0.1-29

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.
@@ -2,8 +2,16 @@
2
2
 
3
3
  You are a SAS Viya expert agent for GitHub Copilot.
4
4
 
5
+
5
6
  Your role: Help users complete SAS Viya tasks safely and accurately using the simplified three-step workflow.
6
7
 
8
+ **High-Level Decision Framework:**
9
+ 1. Identify the user's intent (Find, Read, Score, List)
10
+ 2. Verify resources if required
11
+ 3. Select the appropriate tool based on intent and resource type
12
+ 4. Execute and format the result
13
+
14
+
7
15
  ---
8
16
 
9
17
  ## Operating Model
@@ -51,7 +59,7 @@ Use find-resources to determine server if not specified by user.
51
59
 
52
60
  ### 3. Explicit Model Type
53
61
 
54
- If model type is ambiguous, default to MAS:
62
+ If model type is ambiguous, use MAS as a predefined fallback policy (this is an explicit exception to the 'never invent' rule):
55
63
  - `score with model X` → MAS (default)
56
64
  - `score with model X.mas` → MAS
57
65
  - `score with model X.job` → Job
@@ -2,41 +2,51 @@
2
2
  name: detail-strategy
3
3
  description: >
4
4
  Unified detail/information retrieval strategy. Handles MAS models, SCR models, and tables.
5
- Always verify resources exist using find-resources skill before retrieving details.
5
+ Always verify resources exist using find-resources skill before retrieving details, except for SCR models (which do not require pre-verification).
6
6
  ---
7
7
 
8
8
  # Detail Strategy
9
9
 
10
+
10
11
  Use this strategy when the user requests information about a resource: model details, schema, metadata, inputs/outputs, or any descriptive information.
11
12
 
12
- ## Prerequisites
13
+ If the resource type is ambiguous or cannot be determined, ask the user for clarification.
14
+ If the resource type is not MAS, SCR, or table, respond with: "Unsupported resource type. Please specify MAS model, SCR model, or table."
15
+
16
+
17
+ ## Simplified Steps
13
18
 
14
- 1. Verify the resource exists using find-resources skill
15
- 2. Determine resource type (MAS model, SCR model, or table)
19
+ 1. Determine resource type (MAS model, SCR model, or table)
20
+ 2. If MAS model or table, verify resource exists using find-resources skill
21
+ - If SCR model, skip verification and proceed to retrieval
22
+ 3. Retrieve details using the appropriate tool
16
23
 
17
24
  ---
18
25
 
26
+
19
27
  ## Step 1: Identify Resource Type
20
28
 
21
29
  Classify the resource based on naming convention or context:
22
30
 
23
- ```
24
- model X.mas → MAS model (default for "model X")
25
- model X.scr → SCR model
26
- table X in library Y → CAS or SAS table
27
- ```
31
+ | Pattern | Resource Type |
32
+ |--------------------------------|--------------|
33
+ | model X.mas or "model X" | MAS model |
34
+ | model X.scr | SCR model |
35
+ | table X in library Y | Table |
36
+
37
+ If resource type cannot be determined, ask the user for clarification.
28
38
 
29
39
  ---
30
40
 
31
- ## Step 2: Verify Resource Exists
41
+ ## Step 2: Verify Resource Exists (if required)
32
42
 
33
- Use find-resources skill to confirm resource exists before retrieval:
43
+ | Resource Type | Verification Required? | How to Verify |
44
+ |---------------|-----------------------|---------------|
45
+ | MAS model | Yes | find-model |
46
+ | SCR model | No | — |
47
+ | Table | Yes | find-table |
34
48
 
35
- ### Find MAS Model
36
- ```
37
- find-resources skill → find-model
38
- Tool: sas-score-find-model({ name: "<model>" })
39
- ```
49
+ If resource type is not supported, respond with: "Unsupported resource type. Please specify MAS model, SCR model, or table."
40
50
 
41
51
  ### Find SCR Model
42
52
  ```
@@ -2,14 +2,14 @@
2
2
  name: find-resources
3
3
  description: >
4
4
  Unified resource verification skill. Use the appropriate find tool before any execution.
5
- Determines server for tables (CAS vs SAS). Never use list tools for finding; list tools are for discovery only.
5
+ Determines server for tables (CAS vs SAS). Never use list tools for verifying or finding specific resources; list tools are for discovery and exploration only.
6
6
  ---
7
7
 
8
8
  # Unified Resource Finding Strategy
9
9
 
10
10
  Use this strategy to verify that a resource exists before executing any action.
11
11
 
12
- Do **not** use list tools for finding specific resources. List tools are for listing available resources and exploration, not verification.
12
+ Do **not** use list tools for verifying or finding specific resources. List tools are for discovery and exploration only, not for confirming the existence of a specific resource.
13
13
 
14
14
  ## Resource Types and Find Tools
15
15
 
@@ -19,12 +19,13 @@ Do **not** use list tools for finding specific resources. List tools are for lis
19
19
 
20
20
  **Tool**: `sas-score-find-library`
21
21
 
22
+
22
23
  **Logic**:
23
- - If server is specified: Use that server directly
24
- - If server is not specified:
25
- 1. Try CAS first: `sas-score-find-library({ name: "<lib>", server: "cas" })`
26
- 2. If not found, uppercase Lib and try SAS with uppercase name: `sas-score-find-library({ name: "<LIB>", server: "sas" })`
27
- 3. Report which server (or not found in either)
24
+ 1. If server is specified: Use that server directly.
25
+ 2. If server is not specified:
26
+ - Step 1: Try CAS first: `sas-score-find-library({ name: "<lib>", server: "cas" })`
27
+ - Step 2: If not found in CAS, try SAS with the library name uppercased: `sas-score-find-library({ name: "<LIB>", server: "sas" })`
28
+ - Step 3: Report which server (or not found in either)
28
29
 
29
30
  **Known default libraries**:
30
31
  - CAS: Casuser, Formats, ModelPerformanceData, Models, Public, Samples, SystemData
@@ -43,22 +44,22 @@ Do **not** use list tools for finding specific resources. List tools are for lis
43
44
  - Table name
44
45
  - Server (determined from library context or user specification)
45
46
 
47
+
46
48
  **Logic**:
47
- - If you already know that the table exists in a specific server return that result directly,
48
- otherwise follow these steps:
49
- 1. If library is a known CAS library (Casuser, Public, Samples, etc.), use cas as server
50
- 2. If library is a known SAS library (SASHELP, WORK, SASUUSER, etc.), use sas as server
51
- 3. If the server has been identified in an earlier step for this library, use that as the server
52
-
53
- 4. If server is known at this point:
54
- 1. if server is sas, uppercase library name and try: `sas-score-find-table({ lib: "<LIB>", name: "<table>", server: "sas" })`
55
- 2.find the table: `sas-score-find-table({ lib: "<library>", name: "<table>", server: "<server" });`
56
- 5. If server is not known
57
- 1. Try CAS first: `sas-score-find-table({ lib: "<library>", name: "<table>", server: "cas" })`
58
- 2. If not found, uppercase Lib and try SAS: `sas-score-find-table({ lib: "<LIBRARY>", name: "<table>", server: "sas" })`
59
- 6. If the table was found report success and server.
60
- 7. If not found, report failure.
61
-
49
+ 1. If you already know that the table exists in a specific server, return that result directly.
50
+ 2. Otherwise, follow these steps:
51
+ - Step 1: If library is a known CAS library (Casuser, Public, Samples, etc.), use CAS as server.
52
+ - Step 2: If library is a known SAS library (SASHELP, WORK, SASUSER, etc.), use SAS as server.
53
+ - Step 3: If the server has been identified in an earlier step for this library, use that as the server.
54
+ 3. If server is known at this point:
55
+ - If server is SAS, uppercase the library name and try: `sas-score-find-table({ lib: "<LIB>", name: "<table>", server: "sas" })`
56
+ - Otherwise, use the provided server: `sas-score-find-table({ lib: "<library>", name: "<table>", server: "<server>" })`
57
+ 4. If server is not known:
58
+ - Step 1: Try CAS first: `sas-score-find-table({ lib: "<library>", name: "<table>", server: "cas" })`
59
+ - Step 2: If not found in CAS, try SAS with the library name uppercased: `sas-score-find-table({ lib: "<LIBRARY>", name: "<table>", server: "sas" })`
60
+ 5. If the table was found, report success and server.
61
+ 6. If not found, report failure.
62
+
62
63
  **Output**: Table server location (CAS or SAS)
63
64
 
64
65
  ---
@@ -100,7 +101,9 @@ otherwise follow these steps:
100
101
 
101
102
  **Trigger**: "find scr model X", "does scr model X exist"
102
103
 
104
+
103
105
  **Action**: Ask user for the SCR URL/endpoint. SCR models do not have a pre-verification tool.
106
+ If the SCR URL/endpoint is invalid or missing, prompt the user to provide a valid URL.
104
107
 
105
108
 
106
109
  ---
@@ -7,8 +7,21 @@ description: >
7
7
 
8
8
  # Unified Resource Listing Strategy
9
9
 
10
+
10
11
  Use this strategy to discover and browse available resources (libraries, tables, models, jobs, jobdefs).
11
12
 
13
+ ## Resource Type to Tool Mapping
14
+
15
+ | Resource Type | List Tool |
16
+ |--------------|-----------|
17
+ | Libraries | sas-score-list-libraries |
18
+ | Tables | sas-score-list-tables |
19
+ | Models | sas-score-list-models |
20
+ | Jobs | sas-score-list-jobs |
21
+ | JobDefs | sas-score-list-jobdefs |
22
+
23
+ Use this table to select the correct tool for each resource type. Then follow the logic for parameters and pagination below.
24
+
12
25
  ## Resource Types and List Tools
13
26
 
14
27
  ### 1. List Libraries
@@ -58,8 +71,8 @@ sas-score-list-libraries({ server: "all", start: 11, limit: 10 })
58
71
 
59
72
  **Logic**:
60
73
  - If library is a known CAS library (Casuser, Public, Samples, etc.), use CAS
61
- - If library is a known SAS library (SASHELP, WORK, SASUUSER, etc.), use SAS
62
- - If ambiguous: Ask user or try both
74
+ - If library is a known SAS library (SASHELP, WORK, SASUSER, etc.), use SAS
75
+ - If ambiguous: Ask the user to clarify. If no clarification is provided, attempt both options (CAS and SAS) and return results for each, clearly labeled by server.
63
76
 
64
77
  **Parameters**:
65
78
  ```
@@ -11,8 +11,8 @@ Use this strategy when the user requests to read, fetch, or query data from a ta
11
11
  ## Prerequisites
12
12
 
13
13
  Before reading:
14
- 1. Verify the table exists using FIND-RESOURCE strategy
15
- 2. Determine the server (CAS or SAS) from Step 1
14
+ 1. Verify the table exists using find-resource strategy
15
+ 2. Determine the server (CAS or SAS) from the find-resource verification step. If the server cannot be determined, ask the user to specify. Do not proceed with a default server unless explicitly instructed by the user.
16
16
 
17
17
  ---
18
18
 
@@ -31,9 +31,9 @@ Before reading:
31
31
 
32
32
  **Parameters**:
33
33
  ```
34
- lib: "<library>" # from FIND-RESOURCE verification
35
- table: "<table>" # from FIND-RESOURCE verification
36
- server: "cas" or "sas" # from FIND-RESOURCE verification
34
+ lib: "<library>" # from find-resource verification
35
+ table: "<table>" # from find-resource verification
36
+ server: "cas" or "sas" # from find-resource verification
37
37
  start: <row number> # default 1
38
38
  limit: <max rows> # default 10, max 1000
39
39
  where: "<SQL WHERE clause>" # optional filter
@@ -83,6 +83,7 @@ sas-score-sas-query({
83
83
 
84
84
  ---
85
85
 
86
+
86
87
  ## Decision Tree
87
88
 
88
89
  ```
@@ -91,6 +92,8 @@ User requests data from table
91
92
  Is it an aggregation? (count, sum, avg, group by, distinct, etc.)
92
93
  ├─ YES → Use sas-score-sas-query
93
94
  └─ NO → Use sas-score-read-table
95
+
96
+ If the user's intent is ambiguous or mixes aggregation and raw reads, ask the user to clarify whether they want raw records or aggregated results before proceeding.
94
97
  ```
95
98
 
96
99
  ---
@@ -106,8 +109,8 @@ Is it an aggregation? (count, sum, avg, group by, distinct, etc.)
106
109
 
107
110
  | Error | Action |
108
111
  |---|---|
109
- | Table not found | Verify table exists with FIND-RESOURCE first |
110
- | Server mismatch | Use server from FIND-RESOURCE verification |
112
+ | Table not found | Verify table exists with find-resource first |
113
+ | Server mismatch | Use server from find-resource verification |
111
114
  | Empty result | Ask user to adjust WHERE clause or criteria |
112
115
  | Column not found | Ask user to verify column name (case sensitivity) |
113
116
 
@@ -14,7 +14,7 @@ Before executing any action, verify that the target resources exist.
14
14
 
15
15
  | Resource Type | Find Tool | Notes |
16
16
  |---|---|---|
17
- | Library | `sas-score-find-library` | Specify server (cas or sas). For tables, determine server here. |
17
+ | Library | `sas-score-find-library` | For tables, if server is not specified (cas or sas), determine server here. Other resource types do not require server specification. |
18
18
  | Table | `sas-score-find-table` | Requires library name and server. |
19
19
  | MAS Model | `sas-score-find-model` | No server selection. |
20
20
  | Job | `sas-score-find-job` | No server selection. |
@@ -50,15 +50,23 @@ Combine verification and execution results:
50
50
 
51
51
  ---
52
52
 
53
- ## Special Case: Read + Score (Combined Workflow)
54
53
 
55
- When the user requests scoring records from a table:
54
+ ## Special Case: Read + Score (Combined Workflow)
56
55
 
57
- 1. **Verify**: Find the table (determine server), find the model.
58
- 2. **Read**: Fetch rows from the table using `sas-score-read-table` or `sas-score-sas-query`.
59
- 3. **Map**: Check if table columns match model input variables. Ask user for mapping if needed.
60
- 4. **Score**: Score each row using `sas-score-mas-score` (for MAS) or `sas-score-scr-score` (for SCR).
61
- 5. **Merge**: Combine predictions with original rows.
56
+ When the user requests scoring records from a table, follow these sub-workflows in order:
57
+
58
+ 1. **Verify**
59
+ - Find the table (determine server as described above)
60
+ - Find the model
61
+ 2. **Read**
62
+ - Fetch rows from the table using `sas-score-read-table` or `sas-score-sas-query`
63
+ 3. **Map**
64
+ - Check if table columns match model input variables
65
+ - If not, ask user for mapping
66
+ 4. **Score**
67
+ - Score each row using `sas-score-mas-score` (for MAS) or `sas-score-scr-score` (for SCR)
68
+ 5. **Merge**
69
+ - Combine predictions with original rows
62
70
 
63
71
  ---
64
72
 
@@ -3,6 +3,8 @@ name: score-strategy
3
3
  description: >
4
4
  Unified scoring workflow. Handles MAS, Job, JobDef, SCR, and combined read+score scenarios.
5
5
  Always verify resources before scoring.
6
+
7
+ To reduce cognitive load, follow the stepwise flowchart below for scoring requests.
6
8
  ---
7
9
 
8
10
  # Score Strategy
@@ -17,6 +19,7 @@ Use this strategy when the user requests model scoring, predictions, or running
17
19
 
18
20
  ---
19
21
 
22
+
20
23
  ## Step 1: Parse the Score Request
21
24
 
22
25
  Identify the scoring target (model type) and input source:
@@ -28,7 +31,23 @@ score with model X.mas → MAS model
28
31
  score with model X.job → Job
29
32
  score with model X.jobdef → JobDef
30
33
  score with model X.scr → SCR model
31
- score with model X Default to MAS
34
+ score with model X (default to MAS if type is not specified) → MAS model
35
+ ```
36
+
37
+ ### Visual Flowchart
38
+
39
+ ```mermaid
40
+ flowchart TD
41
+ A[User score request] --> B{Model type specified?}
42
+ B -- .mas --> C[MAS model]
43
+ B -- .job --> D[Job]
44
+ B -- .jobdef --> E[JobDef]
45
+ B -- .scr --> F[SCR model]
46
+ B -- none --> C
47
+ C --> G[Verify MAS model]
48
+ D --> H[Verify Job]
49
+ E --> I[Verify JobDef]
50
+ F --> J[Skip verification]
32
51
  ```
33
52
 
34
53
  ### Identify Input Source
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # sas-score-mcp-serverjs
2
- A Model Context Protocol (MCP) Server for Scoring with SAS Viya
2
+ A Model Context Protocol (MCP) Server for Scoring with SAS Viya.
3
+ See [wiki](https://github.com/sassoftware/sas-score-mcp-serverjs/wiki) for the capabilities of the server.
3
4
 
4
5
  ## Major changes in release 1.0.0
5
6
 
@@ -22,6 +23,17 @@ Some examples are:
22
23
  - job Definitions
23
24
  - jobs using SAS Studio or other interfaces
24
25
 
26
+ ## Capabilities
27
+
28
+ The tools can be grouped into these categories
29
+
30
+ - Scoring with MAS models, job, jobdef and scr
31
+ - Viewing and querying tables
32
+
33
+ Supporting tools can be grouped into these categories
34
+ - Listing of MAS models, job, jobdef , tables
35
+ - Describing MAS models, job, jobdef, scr and tables
36
+
25
37
  ## Target Audience
26
38
  This MCP server was developed for two types of SAS users.
27
39
 
@@ -45,7 +57,9 @@ Typically these are set either in the .env file or as environment variables or a
45
57
  ### Required Options
46
58
 
47
59
  VIYA_SERVER=<url for Viya server>
60
+
48
61
  MCPTYPE=http|stdio
62
+
49
63
  MCPHOST=<url for the mcp server = http://localhost:8080 or some remote mcp server>
50
64
 
51
65
  >Recommended authflow is oauth - the most secure of all the options since all oauth flow occurs in the server and the actual token is never sent to the client. Bearer authflow is useful when the mcp server is remote with its own authentication process
@@ -80,28 +94,23 @@ CASSERVER=CAS server name (default: cas-shared-default)
80
94
  COMPUTECONTEXT=Compute session name or context (default: SAS Job Execution compute context)
81
95
  ```
82
96
 
83
- ## Agent
97
+ ## Agent and skills
84
98
 
85
99
  > The mcp server can be deployed as an agent in github copilot
86
100
  > The configuration files for claude can be installed locally. You have to move the files to the appriopiate place.
87
101
 
88
- Specify the following configuration values to enable agent mode
89
-
90
- ```env
91
- AGENT=TRUE
92
- MCPCLIENT=github|claude
93
- ```
94
-
95
- By default the agent information is installed in the user's home directory as .github or .claude} To install it where the mcp server is running do the following:
102
+ To download the skills to your environment issue this command:
96
103
 
97
- ```env
98
- MCPCLIENT=.github|.claude
104
+ ```sh
105
+ npx @sassoftware/sas-score-mcp-serverjs --skills github|claude
99
106
  ```
107
+ The skills and related files will be written to .github or .claude
100
108
 
101
109
 
102
110
  ## Configure the mcp client for localhost
103
111
 
104
- The mcp configuration is show below
112
+ The mcp configuration for oauth flow. For remote mcp, change the url to the
113
+ appropriate url.
105
114
 
106
115
  ```json
107
116
  "sasmcp": {
@@ -113,19 +122,17 @@ The mcp configuration is show below
113
122
  }
114
123
  ```
115
124
 
116
- For remote mcp servers:
125
+ For bearer authflow.
117
126
  ```json
118
127
  "sasmcp": {
119
128
  "type": "http",
120
129
  "url": "your remote mcp server`,
121
- "oauth": {
122
- "type": 'oauth2
123
- }
130
+ "headers" {
131
+ "Authorization": "bearer <tokenstring>"
132
+ }
124
133
  }
125
134
  ```
126
135
 
127
- For transport protocol stdio. For claude drop the type
128
-
129
136
  ```json
130
137
  "sas-mcp-server": {
131
138
  "type: "stdio"
@@ -144,13 +151,21 @@ For transport protocol stdio. For claude drop the type
144
151
  #### Step 2: Start the mcp server
145
152
 
146
153
  If using stdio transport, most of the mcp clients will start the server automatically.
147
- But this step is necessary of using http transport.
148
-
154
+ But for http transport, the mcp server must be started.
149
155
 
156
+ If running locally
150
157
  ```sh
151
158
  npx @sassoftware/sas-score-mcp-serverjs@latest
152
159
  ```
153
160
 
161
+ The mcp is also available as a docker image. Add or remove the env variables as needed.
162
+
163
+ ```sh
164
+ docker run -p 8080:8080 --name sasscore -e VIYA_SERVER=<yourviayserver> -e AUTHFLOW=oauth ghcr.io/sassoftware/sas-score-mcp-serverjs:latest
165
+ ```
166
+
167
+ If you want to run it in docker then use docker run:
168
+
154
169
  Make sure that the .env file is in the current working directory or specify the options in the command line
155
170
 
156
171
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sassoftware/sas-score-mcp-serverjs",
3
- "version": "1.0.1-26",
3
+ "version": "1.0.1-29",
4
4
  "description": "A mcp server for SAS Viya",
5
5
  "author": "Deva Kumar <deva.kumar@sas.com>",
6
6
  "license": "Apache-2.0",
@@ -50,6 +50,25 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
50
50
  const pkceStore = new Map(); // ourState -> { codeVerifier, clientRedirectUri, clientState }
51
51
  const codeStore = new Map(); // ourCode -> { access_token, refresh_token, expires_in }
52
52
 
53
+ // Create ONE shared transport for all sessions/users
54
+ const sharedTransport = new StreamableHTTPServerTransport({
55
+ sessionIdGenerator: () => randomUUID(),
56
+ enableJsonResponse: true,
57
+ enableDnsRebindingProtection: true,
58
+ onsessioninitialized: (sessionId) => {
59
+ console.error("[Note] Session initialized with ID:", sessionId);
60
+ },
61
+ });
62
+
63
+ // Connect mcpServer to the shared transport ONCE
64
+ await mcpServer.connect(sharedTransport);
65
+ console.error("[Note] MCP Server connected to shared transport");
66
+
67
+ // Store the shared transport for use in request handlers
68
+ cache.set("sharedTransport", sharedTransport);
69
+ const transports = new Map(); // Track active session transports for cleanup
70
+ cache.set("transports", transports);
71
+
53
72
  app.get('/.well-known/oauth-protected-resource', (req, res) => {
54
73
  let payload = {
55
74
  resource: `${baseAppEnvContext.mcpHost}/mcp`,
@@ -152,67 +171,28 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
152
171
 
153
172
  // process mcp endpoint requests
154
173
  const handleRequest = async (req, res) => {
155
- let transport = null;
156
- let transports = cache.get("transports");
174
+ let transport = cache.get("sharedTransport");
157
175
  console.error("=========================================================");
158
176
  console.error("Processing POST /mcp request");
159
- if (transports == null) {
160
- console.error("[Error] ***** transports cache is null. This is an error");
161
- transports = {};
162
- cache.set("transports", transports);
163
- }
164
177
 
165
- console.error("current transports in cache:", Object.keys(transports));
178
+ console.error("current active sessions:", cache.get("transports").size);
166
179
  try {
167
180
 
168
181
  let sessionId = req.headers["mcp-session-id"];
169
182
  console.error("[Note]Incoming session ID:", sessionId);
170
183
  let body = (req.body == null) ? 'no body' : JSON.stringify(req.body);
171
184
  console.error('[Note] Payload is ', body);
172
- if (/*!sessionId &&*/ isInitializeRequest(req.body)) {
173
- // create transport
174
- console.error("[Note] Initializing new transport for MCP session...");
175
-
176
- transport = new StreamableHTTPServerTransport({
177
- sessionIdGenerator: () => randomUUID(),
178
- enableJsonResponse: true,
179
- enableDnsRebindingProtection: true,
180
- onsessioninitialized: (sessionId) => {
181
- // Store the transport by session ID
182
- console.error('Session initialized');
183
- console.error("[Note] Transport initialized with ID:", sessionId);
184
- transports[sessionId] = transport;
185
- },
186
- });
187
- // Clean up transport when closed
188
- transport.onclose = () => {
189
- if (transport.sessionId && transports[transport.sessionId]) {
190
- delete transports[transport.sessionId];
191
- }
192
- };
193
- console.error("[Note] Connecting mcpServer to new transport...");
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);
185
+ if (!sessionId && isInitializeRequest(req.body)) {
186
+ // Use the shared transport for new initialization request
187
+ console.error("[Note] Initializing new session with shared transport...");
199
188
  console.error("=======================================================");
200
189
  return await transport.handleRequest(req, res, req.body);
201
190
 
202
- // cache transport
203
-
204
191
  } else if (sessionId != null) {
205
192
  console.error('[Note] Incoming session ID:', sessionId);
206
- transport = transports[sessionId];
207
- console.error("[Note] Found transport:", transport != null);
208
- if (transport == null) {
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;
213
- }
214
-
215
- // post the curren session - used to pass _appContext to tools
193
+ console.error("[Note] Using shared transport for session ID:", sessionId);
194
+
195
+ // post the current session - used to pass _appContext to tools
216
196
  cache.set("currentId", sessionId);
217
197
 
218
198
  // get app context for session
@@ -230,7 +210,6 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
230
210
  _appContext = Object.assign(_appContext, headerCache);
231
211
  cache.set(sessionId, _appContext);
232
212
  }
233
- console.error("[Note] Using existing transport for session ID:", sessionId);
234
213
  console.error("==========================================================");
235
214
  await transport.handleRequest(req, res, req.body);
236
215
  return;
@@ -260,20 +239,20 @@ async function expressMcpServer(mcpServer, cache, baseAppEnvContext) {
260
239
  const sessionId = req.headers["mcp-session-id"];
261
240
  console.error("[Note] SessionId:", sessionId);
262
241
 
263
- let transports = cache.get("transports");
264
- let transport = (sessionId == null) ? null : transports[sessionId];
265
- console.error("[Note] Transport found:", transport != null);
266
- if (!sessionId || transport == null) {
242
+ let transport = cache.get("sharedTransport");
243
+ console.error("[Note] Using shared transport");
244
+ /*
245
+ if (!sessionId) {
267
246
  res.status(404).send(`[Error] In ${req.method}: Invalid or missing session ID ${sessionId}`);
268
247
  return;
269
248
  }
249
+ */
270
250
  if (req.method === "GET") {
271
251
  await transport.handleRequest(req, res);
272
252
  return;
273
253
  }
274
254
  if (req.method === "DELETE" && sessionId != null) {
275
- console.error("[Note] Deleting transport and cache for session ID:", sessionId);
276
- delete transports[sessionId];
255
+ console.error("[Note] Deleting cache for session ID:", sessionId);
277
256
  cache.del(sessionId);
278
257
  res.status(201).send(`[Info] Deleted session ${sessionId}`);
279
258
  }
@@ -1,4 +1,7 @@
1
- //authorize
1
+ /*
2
+ * Copyright © 2026, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
2
5
  import { randomBytes, createHash } from "node:crypto";
3
6
  import baseUrl from "./baseUrl.js";
4
7
 
@@ -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 baseUrl(appContext) {
2
6
  const protocol = appContext.HTTPS === "TRUE" ? "https" : "http";
3
7
  //const host = "localhost";
@@ -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";
@@ -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
  function getMetadata(req, res, appEnvContext) {
3
7
  let base = '';
@@ -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 authorize from "./authorize.js";
2
6
  import callback from "./callback.js";
3
7
  import token from "./token.js";