@sassoftware/sas-score-mcp-serverjs 1.0.1-8 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/.skills/.claude-plugin/plugin.json +59 -0
  2. package/.skills/agents/sas-score-mcp-serverjs-agent.md +26 -0
  3. package/.skills/copilot-instructions.md +62 -0
  4. package/.skills/skills/README.md +204 -0
  5. package/.skills/skills/detail-strategy/SKILL.md +316 -0
  6. package/.skills/skills/find-library-server/SKILL.md +62 -0
  7. package/.skills/skills/find-resources/SKILL.md +66 -0
  8. package/.skills/skills/list-library/SKILL.md +30 -0
  9. package/.skills/skills/list-mas-job-jobdef/SKILL.md +31 -0
  10. package/.skills/skills/list-tables/SKILL.md +30 -0
  11. package/.skills/skills/read-strategy/SKILL.md +87 -0
  12. package/.skills/skills/request-routing/SKILL.md +112 -0
  13. package/.skills/skills/score-cas/SKILL.md +95 -0
  14. package/.skills/skills/score-job-jobdef/SKILL.md +58 -0
  15. package/.skills/skills/score-mas-scr/SKILL.md +58 -0
  16. package/.skills/skills/score-program/SKILL.md +59 -0
  17. package/.skills/skills/score-strategy/SKILL.md +39 -0
  18. package/README.md +96 -54
  19. package/cli.js +29 -38
  20. package/openApi.yaml +121 -121
  21. package/package.json +15 -13
  22. package/scripts/docs/SCORE_SKILL_REFERENCE.md +17 -16
  23. package/scripts/docs/TOOL_DESCRIPTION_TEMPLATE.md +3 -3
  24. package/scripts/docs/TOOL_UPDATES_SUMMARY.md +65 -63
  25. package/scripts/docs/oauth-http-transport.md +2 -2
  26. package/scripts/docs/sas-mcp-tools-reference.md +43 -32
  27. package/scripts/plot_msrp_usa.py +49 -0
  28. package/scripts/refreshtoken.js +58 -0
  29. package/scripts/runListScr.mjs +16 -0
  30. package/src/createMcpServer.js +4 -1
  31. package/src/expressMcpServer.js +47 -49
  32. package/src/oauthHandlers/authorize.js +4 -1
  33. package/src/oauthHandlers/baseUrl.js +4 -0
  34. package/src/oauthHandlers/callback.js +4 -0
  35. package/src/oauthHandlers/getMetadata.js +4 -0
  36. package/src/oauthHandlers/index.js +4 -0
  37. package/src/oauthHandlers/token.js +4 -0
  38. package/src/openApi.yaml +121 -121
  39. package/src/processHeaders.js +10 -7
  40. package/src/setupSkills.js +6 -9
  41. package/src/toolHelpers/_casScore.js +32 -0
  42. package/src/toolHelpers/_desc.js +14 -0
  43. package/src/toolHelpers/_findJob.js +12 -0
  44. package/src/toolHelpers/_findJobdef.js +10 -0
  45. package/src/toolHelpers/_findLibrary.js +11 -0
  46. package/src/toolHelpers/_findMas.js +13 -0
  47. package/src/toolHelpers/_findScr.js +36 -0
  48. package/src/toolHelpers/_findTable.js +11 -0
  49. package/src/toolHelpers/_listJobdefs.js +12 -2
  50. package/src/toolHelpers/_listJobs.js +19 -8
  51. package/src/toolHelpers/{_listModels.js → _listMas.js} +4 -4
  52. package/src/toolHelpers/_listScr.js +13 -0
  53. package/src/toolHelpers/{_scrInfo.js → _scrDescribe.js} +4 -4
  54. package/src/toolHelpers/_scrScore.js +2 -2
  55. package/src/toolHelpers/_submitCasl.js +19 -17
  56. package/src/toolHelpers/{_tableInfo.js → _tableDescribe.js} +2 -2
  57. package/src/toolHelpers/getLogonPayload.js +2 -2
  58. package/src/toolSet/casModelScore.js +93 -0
  59. package/src/toolSet/casProgramScore.js +105 -0
  60. package/src/toolSet/devaScore.js +11 -6
  61. package/src/toolSet/findJob.js +74 -59
  62. package/src/toolSet/findJobdef.js +67 -64
  63. package/src/toolSet/findLibrary.js +28 -23
  64. package/src/toolSet/findMas.js +72 -0
  65. package/src/toolSet/findScr.js +69 -0
  66. package/src/toolSet/findTable.js +34 -27
  67. package/src/toolSet/getEnv.js +57 -57
  68. package/src/toolSet/jobDescribe.js +65 -0
  69. package/src/toolSet/jobScore.js +90 -0
  70. package/src/toolSet/jobdefDescribe.js +67 -0
  71. package/src/toolSet/jobdefScore.js +85 -0
  72. package/src/toolSet/listJobdefs.js +17 -8
  73. package/src/toolSet/listJobs.js +15 -8
  74. package/src/toolSet/listLibraries.js +16 -10
  75. package/src/toolSet/listMas.js +71 -0
  76. package/src/toolSet/listScr.js +62 -0
  77. package/src/toolSet/listTables.js +78 -66
  78. package/src/toolSet/{runMacro.js → macroScore.js} +86 -82
  79. package/src/toolSet/makeTools.js +39 -25
  80. package/src/toolSet/masDescribe.js +67 -0
  81. package/src/toolSet/masScore.js +95 -0
  82. package/src/toolSet/{runProgram.js → programScore.js} +96 -93
  83. package/src/toolSet/readTable.js +43 -26
  84. package/src/toolSet/sasQuery.js +24 -18
  85. package/src/toolSet/scrDescribe.js +55 -0
  86. package/src/toolSet/scrScore.js +63 -70
  87. package/src/toolSet/searchAssets.js +1 -1
  88. package/src/toolSet/setContext.js +8 -3
  89. package/src/toolSet/superstat.js +61 -61
  90. package/src/toolSet/tableDescribe.js +65 -0
  91. package/.skills/sas-find-library-smart/SKILL.md +0 -155
  92. package/.skills/sas-find-resource-strategy/SKILL.md +0 -105
  93. package/.skills/sas-list-resource-strategy/SKILL.md +0 -124
  94. package/.skills/sas-list-tables-smart/SKILL.md +0 -128
  95. package/.skills/sas-read-and-score-strategy/SKILL.md +0 -113
  96. package/.skills/sas-read-strategy/SKILL.md +0 -154
  97. package/.skills/sas-request-classifier/SKILL.md +0 -74
  98. package/.skills/sas-score-workflow-strategy/SKILL.md +0 -314
  99. package/.skills_claude/CLAUDE.md +0 -201
  100. package/.skills_claude/agents/sas-score-mcp-serverjs-agent.md +0 -58
  101. package/.skills_claude/enforce-find-resource-strategy.md +0 -35
  102. package/.skills_github/agents/sas-score-mcp-serverjs-agent.md +0 -58
  103. package/.skills_github/copilot-instructions.md +0 -201
  104. package/.skills_github/enforce-find-resource-strategy.md +0 -35
  105. package/scripts/optimize_final.py +0 -140
  106. package/scripts/optimize_tools.py +0 -99
  107. package/scripts/setup-skills.js +0 -34
  108. package/scripts/update_descriptions.py +0 -46
  109. package/src/authpkce.js +0 -219
  110. package/src/handleGetDelete.js +0 -34
  111. package/src/handleRequest.js +0 -112
  112. package/src/hapiMcpServer.js +0 -241
  113. package/src/toolSet/findModel.js +0 -60
  114. package/src/toolSet/listModels.js +0 -56
  115. package/src/toolSet/modelInfo.js +0 -55
  116. package/src/toolSet/modelScore.js +0 -89
  117. package/src/toolSet/runCasProgram.js +0 -98
  118. package/src/toolSet/runJob.js +0 -81
  119. package/src/toolSet/runJobdef.js +0 -82
  120. package/src/toolSet/scrInfo.js +0 -52
  121. package/src/toolSet/tableInfo.js +0 -58
@@ -1,4 +1,4 @@
1
- # SAS MCP Server Tools Reference
1
+ # SAS MCP Server Tools Reference
2
2
 
3
3
  ## Overview
4
4
 
@@ -11,8 +11,10 @@ All tools are registered as sas-score-<toolname>
11
11
  - [Model Management & Scoring](#model-management--scoring)
12
12
  - [Library Management](#library-management)
13
13
  - [Table Operations](#table-operations)
14
- - [Job Management](#job-management)
15
- - [Program Execution](#program-execution)
14
+ - [Jobs Model](#jobs-model)
15
+ - [Jobdef Models](#jobdef-models)
16
+ - [Program Models](#program-models)
17
+ - [Macro Models](#macro-models)
16
18
  - [Context & Configuration](#context--configuration)
17
19
  - [Utility Tools](#utility-tools)
18
20
 
@@ -20,7 +22,7 @@ All tools are registered as sas-score-<toolname>
20
22
 
21
23
  ## Model Management & Scoring
22
24
 
23
- ### list-models
25
+ ### list-mas
24
26
 
25
27
  Enumerate models published to MAS (Model Aggregation Service).
26
28
 
@@ -42,7 +44,7 @@ list 25 models
42
44
 
43
45
  ---
44
46
 
45
- ### find-model
47
+ ### find-mas
46
48
 
47
49
  Locate a specific model deployed to MAS.
48
50
 
@@ -61,7 +63,7 @@ find model myModel
61
63
 
62
64
  ---
63
65
 
64
- ### model-info
66
+ ### mas-describe
65
67
 
66
68
  Retrieve detailed metadata for a deployed model including input/output variables, data types, and constraints.
67
69
 
@@ -80,12 +82,12 @@ Retrieve detailed metadata for a deployed model including input/output variables
80
82
 
81
83
  **Example:**
82
84
  ```
83
- model-info model=churnRisk
85
+ mas-describe model=churnRisk
84
86
  ```
85
87
 
86
88
  ---
87
89
 
88
- ### model-score
90
+ ### mas-score
89
91
 
90
92
  Score user-supplied scenario data using a MAS-published model.
91
93
 
@@ -105,13 +107,13 @@ Score user-supplied scenario data using a MAS-published model.
105
107
 
106
108
  **Example:**
107
109
  ```
108
- model-score model=mycoolmodel scenario={x:1,y:2}
109
- model-score model=cancer1 scenario="age=45, sex=M, tumor=stage2"
110
+ mas-score model=mycoolmodel scenario={x:1,y:2}
111
+ mas-score model=cancer1 scenario="age=45, sex=M, tumor=stage2"
110
112
  ```
111
113
 
112
114
  ---
113
115
 
114
- ### scr-info
116
+ ### scr-describe
115
117
 
116
118
  Return input/output schema and metadata for an SCR (Score Code Runtime) model.
117
119
 
@@ -124,7 +126,7 @@ Return input/output schema and metadata for an SCR (Score Code Runtime) model.
124
126
 
125
127
  **Example:**
126
128
  ```
127
- scr-info name="https://scr-host/models/loan"
129
+ scr-describe name="https://scr-host/models/loan"
128
130
  ```
129
131
 
130
132
  ---
@@ -138,7 +140,7 @@ Score a scenario using an SCR container model.
138
140
  - `scenario` (string | object | array, optional): Input values
139
141
 
140
142
  **Usage:**
141
- - Run scrInfo first to inspect expected inputs
143
+ - Run scrDescribe first to inspect expected inputs
142
144
  - Omit scenario to get model metadata
143
145
 
144
146
  **Example:**
@@ -243,7 +245,7 @@ find table cars in sashelp in sas
243
245
 
244
246
  ---
245
247
 
246
- ### table-info
248
+ ### table-describe
247
249
 
248
250
  Return metadata about a table including columns, types, and statistics.
249
251
 
@@ -262,7 +264,7 @@ Return metadata about a table including columns, types, and statistics.
262
264
 
263
265
  **Example:**
264
266
  ```
265
- table-info table=cars lib=Public
267
+ table-describe table=cars lib=Public
266
268
  describe table air in lib sashelp on sas server
267
269
  ```
268
270
 
@@ -320,7 +322,7 @@ sasquery table=mylib.students query="How many students in each year as percentag
320
322
 
321
323
  ---
322
324
 
323
- ## Job Management
325
+ ## Jobs Model
324
326
 
325
327
  ### list-jobs
326
328
 
@@ -363,7 +365,7 @@ find job cars_job_v4
363
365
 
364
366
  ---
365
367
 
366
- ### run-job
368
+ ### job-score
367
369
 
368
370
  Execute a job on a SAS Viya server.
369
371
 
@@ -377,12 +379,14 @@ Execute a job on a SAS Viya server.
377
379
  **Example:**
378
380
  ```
379
381
  run job xyz param1=10,param2=val2
380
- run-job myjob scenario a=10,b=20
382
+ job-score myjob scenario a=10,b=20
381
383
  job myjob scenario a=10,b=20
382
384
  ```
383
385
 
384
386
  ---
385
387
 
388
+ ## Jobdef Models
389
+
386
390
  ### list-jobdefs
387
391
 
388
392
  Enumerate SAS Viya job definition assets.
@@ -422,7 +426,7 @@ find jobdef metricsRefresh
422
426
 
423
427
  ---
424
428
 
425
- ### run-jobdef
429
+ ### jobdef-score
426
430
 
427
431
  Execute a job definition on a SAS Viya server.
428
432
 
@@ -435,15 +439,15 @@ Execute a job definition on a SAS Viya server.
435
439
 
436
440
  **Example:**
437
441
  ```
438
- run-jobdef xyz param1=10,param2=val2
442
+ jobdef-score xyz param1=10,param2=val2
439
443
  jobdef myjobdef scenario a=10,b=20
440
444
  ```
441
445
 
442
446
  ---
443
447
 
444
- ## Program Execution
448
+ ## Program Models
445
449
 
446
- ### run-program
450
+ ### program-score
447
451
 
448
452
  Execute arbitrary SAS code or stored programs on a SAS Viya server.
449
453
 
@@ -469,7 +473,9 @@ program sample folder=/Public/models scenario="name='John', age=45" output=a
469
473
 
470
474
  ---
471
475
 
472
- ### run-macro
476
+ ## Macro Models
477
+
478
+ ### macro-score
473
479
 
474
480
  Submit and execute a SAS macro on a SAS Viya server.
475
481
 
@@ -478,8 +484,8 @@ Submit and execute a SAS macro on a SAS Viya server.
478
484
  - `scenario` (string, optional): Parameters or SAS setup code
479
485
 
480
486
  **Scenario formats:**
481
- - Comma-separated: `"x=1, y=abc"` converted to %let statements
482
- - Raw SAS: `"%let x=1; %let y=abc;"` passed through unchanged
487
+ - Comma-separated: `"x=1, y=abc"` → converted to %let statements
488
+ - Raw SAS: `"%let x=1; %let y=abc;"` → passed through unchanged
483
489
 
484
490
  **Example:**
485
491
  ```
@@ -520,14 +526,14 @@ Use this to verify that the mcp server is up and running.
520
526
 
521
527
  ### deva-score
522
528
 
523
- Compute a numeric score based on two input values using the formula: (a + b) × 42
529
+ Compute a numeric score based on two input values using the formula: (a + b) × 42
524
530
 
525
531
  **Parameters:**
526
532
  - `a` (number, required): First numeric input
527
533
  - `b` (number, required): Second numeric input
528
534
 
529
535
  **Returns:**
530
- - Numeric result: (a + b) × 42
536
+ - Numeric result: (a + b) × 42
531
537
 
532
538
  **Usage:**
533
539
  - "Calculate deva score for 5 and 10"
@@ -545,11 +551,13 @@ deva-score a=1 b=2 // returns 126
545
551
 
546
552
  | Category | Tool Count | Tools |
547
553
  |----------|-----------|-------|
548
- | **Model Management** | 6 | list-models, find-model, model-info, model-score, scr-info, scr-score |
554
+ | **Model Management** | 6 | list-mas, find-mas, mas-describe, mas-score, scr-describe, scr-score |
549
555
  | **Library Management** | 2 | list-libraries, find-library |
550
- | **Table Operations** | 5 | list-tables, find-table, table-info, read-table, sas-query |
551
- | **Job Management** | 6 | list-jobs, find-job, job, list-jobdefs, find-jobdef, job-def |
552
- | **Program Execution** | 2 | run-program, run-macro |
556
+ | **Table Operations** | 5 | list-tables, find-table, table-describe, read-table, sas-query |
557
+ | **Jobs Model** | 3 | list-jobs, find-job, job-score |
558
+ | **Jobdef Models** | 3 | list-jobdefs, find-jobdef, jobdef-score |
559
+ | **Program Models** | 1 | program-score |
560
+ | **Macro Models** | 1 | macro-score |
553
561
  | **Context & Config** | 1 | set-context |
554
562
  | **Utilities** | 1 | deva-score |
555
563
  | **Total** | **24** | |
@@ -558,7 +566,7 @@ deva-score a=1 b=2 // returns 126
558
566
 
559
567
  ## Common Patterns
560
568
 
561
- ### Discovery Inspection Action
569
+ ### Discovery → Inspection → Action
562
570
 
563
571
  1. **List** tools to discover available resources
564
572
  2. **Find** tools to locate specific items
@@ -598,3 +606,6 @@ Tools accepting scenarios support multiple formats:
598
606
 
599
607
  *Document generated for @sassoftware/mcp-serverjs*
600
608
  *Last updated: December 2024*
609
+
610
+
611
+
@@ -0,0 +1,49 @@
1
+ import matplotlib
2
+ matplotlib.use('Agg')
3
+ import matplotlib.pyplot as plt
4
+ from pathlib import Path
5
+
6
+ data = [
7
+ ("Cadillac", 50474.375),
8
+ ("Hummer", 49995.0),
9
+ ("Lincoln", 42875.555556),
10
+ ("Buick", 30537.777778),
11
+ ("GMC", 29560.5),
12
+ ("Mercury", 27972.777778),
13
+ ("Chrysler", 27252.0),
14
+ ("Chevrolet", 26587.037037),
15
+ ("Dodge", 26253.846154),
16
+ ("Jeep", 24518.333333),
17
+ ("Pontiac", 24156.363636),
18
+ ("Ford", 24015.869565),
19
+ ("Oldsmobile", 23763.333333),
20
+ ("Saturn", 17234.375),
21
+ ]
22
+
23
+ # Sort by value descending
24
+ data.sort(key=lambda x: x[1], reverse=True)
25
+ makes = [d[0] for d in data]
26
+ values = [d[1] for d in data]
27
+
28
+ out_dir = Path('outputs')
29
+ out_dir.mkdir(parents=True, exist_ok=True)
30
+ out_file = out_dir / 'make_avg_msrp_usa.png'
31
+
32
+ plt.figure(figsize=(12, 6))
33
+ bars = plt.bar(makes, values, color='tab:blue')
34
+ plt.title('Average MSRP by Make (Origin = USA)')
35
+ plt.ylabel('Average MSRP')
36
+ plt.xticks(rotation=45, ha='right')
37
+ plt.tight_layout()
38
+
39
+ # Annotate bars
40
+ for bar in bars:
41
+ height = bar.get_height()
42
+ plt.annotate(f'{height:,.0f}',
43
+ xy=(bar.get_x() + bar.get_width() / 2, height),
44
+ xytext=(0, 3),
45
+ textcoords='offset points',
46
+ ha='center', va='bottom', fontsize=8)
47
+
48
+ plt.savefig(out_file)
49
+ print(f'Plot saved to {out_file}')
@@ -0,0 +1,58 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { Agent, fetch } from 'undici';
6
+ import fs from "fs";
7
+
8
+ refreshToken()
9
+ .then (token => {
10
+ console.log(token);
11
+ fs.writeFileSync('token.txt', `"AUTHORIZATION": "Bearer ${token}"`, 'utf8');
12
+ })
13
+ .catch (err => {
14
+ console.error('[Error] Failed to refresh token: ', err);
15
+ });
16
+ async function refreshToken(){
17
+ let host = process.env.VIYA_SERVER;
18
+ let token = process.env.REFRESH_TOKEN;
19
+ let url = `${host}/SASLogon/oauth/token`;
20
+
21
+ let aconnect = {
22
+ rejectUnauthorized: false // or false, if you really want to bypass checks
23
+ }
24
+
25
+ const agent = new Agent(aconnect);
26
+
27
+ console.error('[Info] Refreshing token...', token);
28
+ const ibody = {
29
+ grant_type: 'refresh_token',
30
+ refresh_token: token,
31
+ client_id: 'sas.cli'
32
+ };
33
+
34
+ let body = new URLSearchParams(ibody);
35
+ try {
36
+ const response = await fetch(url, {
37
+ method: 'POST',
38
+ headers: {
39
+ 'Accept': 'application/json',
40
+ 'Content-Type': 'application/x-www-form-urlencoded',
41
+ dispatcher: agent
42
+ },
43
+ body: body.toString()
44
+ });
45
+
46
+ if (!response.ok) {
47
+ const error = await response.text();
48
+ console.error('[Error] Failed to refresh token: ', error);
49
+ throw new Error(error);
50
+ }
51
+
52
+ const data = await response.json();
53
+ return data.access_token;
54
+ } catch (err) {
55
+ console.error('[Error] Failed to refresh token: ', err);
56
+ throw err;
57
+ }
58
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import listScr from '../src/toolSet/listScr.js';
3
+
4
+ async function run() {
5
+ const appContext = { brand: 'sas-score' };
6
+ const spec = listScr(appContext);
7
+ try {
8
+ const res = await spec.handler({});
9
+ console.log(JSON.stringify(res, null, 2));
10
+ } catch (err) {
11
+ console.error('Error running listScr:', err);
12
+ process.exit(1);
13
+ }
14
+ }
15
+
16
+ run();
@@ -78,6 +78,7 @@ async function createMcpServer(cache, _appContext) {
78
78
  // Register the tools with brand prefix
79
79
  console.error(`[Note] Brand: ${_appContext.brand}`);
80
80
  let toolNames = [];
81
+ let totalDescription = 0;
81
82
  toolSet.forEach((tool, i) => {
82
83
  let toolName = _appContext.brand + '-' + tool.name;
83
84
  //tool.inputSchema.additionalProperties = false; // disallow extra properties
@@ -85,12 +86,14 @@ async function createMcpServer(cache, _appContext) {
85
86
  description: tool.description,
86
87
  inputSchema: tool.inputSchema
87
88
  }
89
+ totalDescription += tool.description.length;``
88
90
  let toolHandler = wrapf(cache, tool.handler);
89
- // console.error(`[Note] Registering tool ${toolName} with config: ${JSON.stringify(config)}`);
90
91
  let r = mcpServer.registerTool(toolName, config, toolHandler);
91
92
  toolNames.push(toolName);
92
93
  });
94
+ console.error(`[Note] Agent Mode: ${_appContext.agent}`);
93
95
  console.error(`[Note] Registered ${toolSet.length} tools: ${toolNames}`);
96
+ console.error(`[Note] Total description length: ${totalDescription} characters`);
94
97
  cache.set("mcpServer", mcpServer);
95
98
  return mcpServer;
96
99
  }
@@ -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(mcpServer, cache, baseAppEnvContext) {
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
- if (transports == null) {
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
- if (/*!sessionId &&*/ isInitializeRequest(req.body)) {
173
- // create transport
174
- console.error("[Note] Initializing new transport for MCP session...");
175
-
176
- transport = new StreamableHTTPServerTransport({
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
- 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;
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 && transports[transport.sessionId]) {
190
- delete transports[transport.sessionId];
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
- 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;
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 curren session - used to pass _appContext to tools
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
- 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) {
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" && sessionId != null) {
270
+ if (req.method === "DELETE") {
275
271
  console.error("[Note] Deleting transport and cache for session ID:", sessionId);
276
- delete transports[sessionId];
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,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";
@@ -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");