@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.
- package/.skills/copilot-instructions.md +9 -1
- package/.skills/skills/detail-strategy/SKILL.md +26 -16
- package/.skills/skills/find-resources/SKILL.md +25 -22
- package/.skills/skills/list-resource/SKILL.md +15 -2
- package/.skills/skills/read-strategy/SKILL.md +10 -7
- package/.skills/skills/request-routing/SKILL.md +16 -8
- package/.skills/skills/score-strategy/SKILL.md +20 -1
- package/README.md +36 -21
- package/package.json +1 -1
- package/src/expressMcpServer.js +33 -54
- package/src/oauthHandlers/authorize.js +4 -1
- package/src/oauthHandlers/baseUrl.js +4 -0
- package/src/oauthHandlers/callback.js +4 -0
- package/src/oauthHandlers/getMetadata.js +4 -0
- package/src/oauthHandlers/index.js +4 -0
- package/src/oauthHandlers/token.js +4 -0
- package/src/processHeaders.js +0 -2
- package/src/toolHelpers/_listJobs.js +1 -0
- package/src/toolHelpers/getLogonPayload.js +3 -1
- package/src/authpkce.js +0 -219
- package/src/handleGetDelete.txt +0 -34
- package/src/handleRequest.txt +0 -112
- package/src/hapiMcpServer.txt +0 -241
- package/src/setupSkills.v1.js +0 -79
|
@@ -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,
|
|
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
|
-
|
|
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.
|
|
15
|
-
2.
|
|
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
|
-
|
|
25
|
-
model X.
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
1
|
|
26
|
-
2
|
|
27
|
-
3
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
1
|
|
50
|
-
2
|
|
51
|
-
3
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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,
|
|
62
|
-
- If ambiguous: Ask user
|
|
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
|
|
15
|
-
2. Determine the server (CAS or SAS) from
|
|
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
|
|
35
|
-
table: "<table>" # from
|
|
36
|
-
server: "cas" or "sas" # from
|
|
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
|
|
110
|
-
| Server mismatch | Use server from
|
|
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` |
|
|
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
|
-
|
|
54
|
+
## Special Case: Read + Score (Combined Workflow)
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
```
|
|
98
|
-
|
|
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
|
|
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
|
|
125
|
+
For bearer authflow.
|
|
117
126
|
```json
|
|
118
127
|
"sasmcp": {
|
|
119
128
|
"type": "http",
|
|
120
129
|
"url": "your remote mcp server`,
|
|
121
|
-
"
|
|
122
|
-
"
|
|
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
|
|
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
package/src/expressMcpServer.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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 (
|
|
173
|
-
//
|
|
174
|
-
console.error("[Note] Initializing new
|
|
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
|
|
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;
|
|
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
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (!sessionId
|
|
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
|
|
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
|
}
|