@elastic.io/mcp 1.0.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/LICENSE +191 -0
- package/README.md +171 -0
- package/dist/api/client.d.ts +12 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +124 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/jsonapi.d.ts +28 -0
- package/dist/api/jsonapi.d.ts.map +1 -0
- package/dist/api/jsonapi.js +45 -0
- package/dist/api/jsonapi.js.map +1 -0
- package/dist/api/logger.d.ts +9 -0
- package/dist/api/logger.d.ts.map +1 -0
- package/dist/api/logger.js +46 -0
- package/dist/api/logger.js.map +1 -0
- package/dist/api/samples.d.ts +21 -0
- package/dist/api/samples.d.ts.map +1 -0
- package/dist/api/samples.js +465 -0
- package/dist/api/samples.js.map +1 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +49 -0
- package/dist/config.js.map +1 -0
- package/dist/embeddings/factory.d.ts +7 -0
- package/dist/embeddings/factory.d.ts.map +1 -0
- package/dist/embeddings/factory.js +25 -0
- package/dist/embeddings/factory.js.map +1 -0
- package/dist/embeddings/google.d.ts +19 -0
- package/dist/embeddings/google.d.ts.map +1 -0
- package/dist/embeddings/google.js +132 -0
- package/dist/embeddings/google.js.map +1 -0
- package/dist/embeddings/types.d.ts +27 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +2 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +226 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +10 -0
- package/dist/paths.js.map +1 -0
- package/dist/server.d.ts +29 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +361 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/confirmation.d.ts +41 -0
- package/dist/tools/confirmation.d.ts.map +1 -0
- package/dist/tools/confirmation.js +120 -0
- package/dist/tools/confirmation.js.map +1 -0
- package/dist/tools/constants.d.ts +31 -0
- package/dist/tools/constants.d.ts.map +1 -0
- package/dist/tools/constants.js +85 -0
- package/dist/tools/constants.js.map +1 -0
- package/dist/tools/definitions.d.ts +3 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +883 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/auth.d.ts +3 -0
- package/dist/tools/handlers/auth.d.ts.map +1 -0
- package/dist/tools/handlers/auth.js +540 -0
- package/dist/tools/handlers/auth.js.map +1 -0
- package/dist/tools/handlers/component.d.ts +3 -0
- package/dist/tools/handlers/component.d.ts.map +1 -0
- package/dist/tools/handlers/component.js +145 -0
- package/dist/tools/handlers/component.js.map +1 -0
- package/dist/tools/handlers/data-sample.d.ts +3 -0
- package/dist/tools/handlers/data-sample.d.ts.map +1 -0
- package/dist/tools/handlers/data-sample.js +51 -0
- package/dist/tools/handlers/data-sample.js.map +1 -0
- package/dist/tools/handlers/dynamic.d.ts +3 -0
- package/dist/tools/handlers/dynamic.d.ts.map +1 -0
- package/dist/tools/handlers/dynamic.js +110 -0
- package/dist/tools/handlers/dynamic.js.map +1 -0
- package/dist/tools/handlers/flow.d.ts +9 -0
- package/dist/tools/handlers/flow.d.ts.map +1 -0
- package/dist/tools/handlers/flow.js +899 -0
- package/dist/tools/handlers/flow.js.map +1 -0
- package/dist/tools/handlers/log.d.ts +3 -0
- package/dist/tools/handlers/log.d.ts.map +1 -0
- package/dist/tools/handlers/log.js +61 -0
- package/dist/tools/handlers/log.js.map +1 -0
- package/dist/tools/handlers/organization.d.ts +3 -0
- package/dist/tools/handlers/organization.d.ts.map +1 -0
- package/dist/tools/handlers/organization.js +39 -0
- package/dist/tools/handlers/organization.js.map +1 -0
- package/dist/tools/handlers/raw.d.ts +3 -0
- package/dist/tools/handlers/raw.d.ts.map +1 -0
- package/dist/tools/handlers/raw.js +77 -0
- package/dist/tools/handlers/raw.js.map +1 -0
- package/dist/tools/handlers/search.d.ts +3 -0
- package/dist/tools/handlers/search.d.ts.map +1 -0
- package/dist/tools/handlers/search.js +82 -0
- package/dist/tools/handlers/search.js.map +1 -0
- package/dist/tools/handlers/session.d.ts +3 -0
- package/dist/tools/handlers/session.d.ts.map +1 -0
- package/dist/tools/handlers/session.js +15 -0
- package/dist/tools/handlers/session.js.map +1 -0
- package/dist/tools/handlers.d.ts +12 -0
- package/dist/tools/handlers.d.ts.map +1 -0
- package/dist/tools/handlers.js +197 -0
- package/dist/tools/handlers.js.map +1 -0
- package/dist/tools/helpers.d.ts +45 -0
- package/dist/tools/helpers.d.ts.map +1 -0
- package/dist/tools/helpers.js +195 -0
- package/dist/tools/helpers.js.map +1 -0
- package/dist/tools/types.d.ts +32 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/validation.d.ts +24 -0
- package/dist/tools/validation.d.ts.map +1 -0
- package/dist/tools/validation.js +748 -0
- package/dist/tools/validation.js.map +1 -0
- package/dist/types/index.d.ts +47 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/vectorstore/factory.d.ts +7 -0
- package/dist/vectorstore/factory.d.ts.map +1 -0
- package/dist/vectorstore/factory.js +25 -0
- package/dist/vectorstore/factory.js.map +1 -0
- package/dist/vectorstore/qdrant.d.ts +11 -0
- package/dist/vectorstore/qdrant.d.ts.map +1 -0
- package/dist/vectorstore/qdrant.js +107 -0
- package/dist/vectorstore/qdrant.js.map +1 -0
- package/dist/vectorstore/types.d.ts +28 -0
- package/dist/vectorstore/types.d.ts.map +1 -0
- package/dist/vectorstore/types.js +2 -0
- package/dist/vectorstore/types.js.map +1 -0
- package/package.json +51 -0
- package/resources/docs/component-test-scenarios.md +2468 -0
- package/resources/docs/credential-validation-gaps.md +35 -0
- package/resources/docs/debug-issues.md +71 -0
- package/resources/docs/deployment-and-scaling.md +136 -0
- package/resources/docs/flow-building-guide.md +565 -0
- package/resources/docs/packaging-plan.md +84 -0
- package/resources/docs/standardized-components-guide.md +66 -0
- package/resources/docs/system-instructions.md +177 -0
- package/resources/docs/unique-components.md +60 -0
- package/resources/docs/verified-creds-flow-ids.md +193 -0
- package/resources/flow-examples/batch-flow-1-collect.json +51 -0
- package/resources/flow-examples/batch-flow-2-process.json +70 -0
- package/resources/flow-examples/code-component-transform.json +65 -0
- package/resources/flow-examples/code-component-trigger.json +52 -0
- package/resources/flow-examples/code-component-with-credentials-trigger.json +53 -0
- package/resources/flow-examples/code-component-with-credentials.json +67 -0
- package/resources/flow-examples/filter-step.json +63 -0
- package/resources/flow-examples/polling-sync-trigger.json +50 -0
- package/resources/flow-examples/router-branching.json +94 -0
- package/resources/flow-examples/splitter-re-assemble.json +72 -0
- package/resources/flow-examples/splitter-split.json +68 -0
- package/resources/flow-examples/timer-query-action-sync.json +68 -0
- package/resources/flow-examples/webhook-async-sync.json +49 -0
- package/resources/flow-examples/webhook-request-reply.json +63 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Credential Validation Gaps
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
When building or updating a flow, the MCP server does not validate whether credentials/secrets are actually functional before allowing them to be used. This leads to flows being created with broken credentials that fail at runtime.
|
|
5
|
+
|
|
6
|
+
## Discovered on flow `6aaa5f922fbba1001240ee87`
|
|
7
|
+
- Step 7: Azure Data Lake Storage (`makeRawRequest`)
|
|
8
|
+
- Secret used: `68655543cb48720012860817` ("My New Azure Data Lake Storage component Credential") — state `ready` but account `sparrow1test` does not exist (DNS failure)
|
|
9
|
+
- Second secret: `6842d77f5758c500127a90b1` ("QA") — state `error`, OAuth refresh_token expired since July 2025
|
|
10
|
+
|
|
11
|
+
## Fix applied (partial)
|
|
12
|
+
Changed `discoveryState.secrets` from `Set<string>` to `Map<string, string>` (id → state) so that `checkDiscoveryState()` can reject secrets in `error` state **before** submitting to the API.
|
|
13
|
+
|
|
14
|
+
### Files changed
|
|
15
|
+
- `src/tools/types.ts` — `secrets` type changed to `Map<string, string>`
|
|
16
|
+
- `src/tools/handlers.ts` — initialization updated
|
|
17
|
+
- `src/tools/handlers/auth.ts` — `list_secrets` now stores state
|
|
18
|
+
- `src/tools/validation.ts` — `checkDiscoveryState()` rejects non-ready secrets
|
|
19
|
+
- `src/tools/__tests__/handlers.test.ts` — updated to use Map
|
|
20
|
+
|
|
21
|
+
## Additional fix: verification tracking and pre-submission blocking
|
|
22
|
+
|
|
23
|
+
Added `credentialVerifications: Map<string, { ok: boolean; error?: string }>` to `DiscoveryState`.
|
|
24
|
+
|
|
25
|
+
### Changes
|
|
26
|
+
- `src/tools/types.ts` — added `credentialVerifications` field
|
|
27
|
+
- `src/tools/handlers.ts` — initialization updated
|
|
28
|
+
- `src/tools/handlers/auth.ts` — `init_verify_credentials` now polls verification result internally and stores it in `discoveryState.credentialVerifications`. Returns final result instead of pending executionId.
|
|
29
|
+
- `src/tools/validation.ts` — `checkDiscoveryState()` checks verification results for all nodes and blocks credentials/secrets that failed verification
|
|
30
|
+
- `src/tools/__tests__/handlers.test.ts` — 6 new tests + 4 updated existing tests
|
|
31
|
+
|
|
32
|
+
### What this covers
|
|
33
|
+
1. **Secret in `ready` state but actually broken** — caught if `init_verify_credentials` was called (verification result stored and checked pre-submission)
|
|
34
|
+
2. **Plain credentials** — same mechanism via `credentialVerifications` Map
|
|
35
|
+
3. **Test coverage** — secret state check + verification result check for both secrets and plain credentials
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Debug Issues — Component Testing
|
|
2
|
+
|
|
3
|
+
Issues discovered during debug_flow_step testing on flow `6aad4e79acd7d1001240041f`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Apptivo — Invalid API endpoint
|
|
8
|
+
|
|
9
|
+
- **Component**: `PS_component_testing/apptivo-component` (`689b49481890650012d6e5b9`)
|
|
10
|
+
- **Action**: `makeRawRequest`
|
|
11
|
+
- **Credential**: `QA_test` (`689b54ef1890650012d72e17`) — verifies OK
|
|
12
|
+
- **Problem**: All `makeRawRequest` calls return `500 Internal Server Error` with `{"status_code":"1503","status_reason":"Invalid API end point","status":"failure"}`
|
|
13
|
+
- **Tried URLs**:
|
|
14
|
+
- `/contacts?a=getAllContacts&countPerPage=1`
|
|
15
|
+
- `/contacts?a=getContactById&contactId=1`
|
|
16
|
+
- `contacts?a=getAllContacts&countPerPage=1` (without leading slash)
|
|
17
|
+
- **Base URL**: Component docs say base is `https://app.apptivo.com/app/dao/v6`, so the raw request URL should be relative to that. None of the tried paths worked.
|
|
18
|
+
- **Status**: Unresolved. Needs investigation of correct Apptivo API v6 URL format.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Square — URL requires no `/v2` prefix
|
|
23
|
+
|
|
24
|
+
- **Component**: `PS_component_testing/square-component` (`667be8681ed49100120a1197`)
|
|
25
|
+
- **Action**: `makeRawRequest`
|
|
26
|
+
- **Credential**: `QA` (`68babd3faa954a00120aabbf`) — verifies OK
|
|
27
|
+
- **Problem**: `/v2/locations` returns 404 (`INVALID_REQUEST_ERROR / NOT_FOUND`), but `/locations` works fine (200 with location data).
|
|
28
|
+
- **Root cause**: The component's base URL already includes `/v2`, so raw request paths should NOT include the `/v2` prefix. This is not obvious from the component descriptor.
|
|
29
|
+
- **Status**: Resolved. Use paths without `/v2` prefix (e.g. `/locations` instead of `/v2/locations`).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Phantom data sample / dynamic_metadata deletion
|
|
34
|
+
|
|
35
|
+
- **Flow**: `6aad4e79acd7d1001240041f`
|
|
36
|
+
- **Problem**: After building 18 steps with debug samples, UI showed "Failed to load the samples" for all steps. Investigation found 4 missing resources (404):
|
|
37
|
+
- `6ab260c7adc8720012aacb24` — `dynamic_metadata` on step_8 (AWS S3)
|
|
38
|
+
- `6ab2771aab80c900126621e0` — `dynamic_metadata` on step_13 (Gemini)
|
|
39
|
+
- `6ab2771badc8720012adf2dc` — `dynamic_metadata` on step_14 (Grok)
|
|
40
|
+
- `6ab28333ab80c9001267dd0a` — `selected_data_samples` on step_18 (Stripe)
|
|
41
|
+
- **UI behavior**: `src/frontend/src/modules/store/actions/data-samples.js:70-75` uses `Promise.all` to load all samples; if any returns 404, the entire load fails with "Failed to load the samples".
|
|
42
|
+
- **What dynamic_metadata actually is**: Stored in MongoDB collection `DynamicMetadata` (`src/commons/lib/models/dynamic_metadata.js`), NOT in `DataSample`. Created by `/v2/components/{id}/versions/{ver}/dynamic-metadata`. The `dynamic_metadata` field on a node references a DynamicMetadata record, not a data sample — so a 404 on these should NOT cause data sample loading to fail (unless the UI tries to load them as data samples).
|
|
43
|
+
- **No TTL/GC exists**: Neither `DataSample` nor `DynamicMetadata` models have `expireAfterSeconds` indexes. Wiper does not clean them. Both are only deleted on workspace deletion or recipe re-export.
|
|
44
|
+
- **Root cause**: Unknown. The records disappeared without any known cleanup mechanism. Possible causes:
|
|
45
|
+
- Manual deletion via API or DB
|
|
46
|
+
- Debug process resetting the draft and orphaning/deleting associated records
|
|
47
|
+
- Race condition during concurrent draft modifications (UI + API)
|
|
48
|
+
- **Fix applied**: Removed broken `dynamic_metadata` references from step_8/13/14, re-debugged Stripe to create new data sample `6ab2851cadc8720012b015e0`.
|
|
49
|
+
- **Status**: Fixed but root cause unknown. Needs investigation of what deletes data samples and dynamic metadata records outside of workspace deletion.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## SAP Business One — Debug timeout (30s)
|
|
54
|
+
|
|
55
|
+
- **Component**: `PS_component_testing/sap-business-one-component` (`676e7cb9d6b1100012f5e9be`)
|
|
56
|
+
- **Action**: `makeRawRequest`
|
|
57
|
+
- **Credential**: (`676ea7e5d6b1100012f6d05b`)
|
|
58
|
+
- **Input**: `{"method": "GET", "url": "BusinessPartners?$top=1"}`
|
|
59
|
+
- **Problem**: `debug_flow_step` times out after 30s. On-premise SAP system too slow to respond within the debug timeout window.
|
|
60
|
+
- **Status**: Unresolved. Synthetic data sample used instead. Live retrieval not possible with current 30s timeout.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## MS Dynamics CRM V2 — DNS resolution failure
|
|
65
|
+
|
|
66
|
+
- **Component**: `PS_component_testing/microsoft-dynamics-crm-v2` (`64592579991bbb00128be3e4`)
|
|
67
|
+
- **Action**: `makeRawRequest`
|
|
68
|
+
- **Secret**: (`686fc9f14109aa0012d32f07`)
|
|
69
|
+
- **Input**: `{"method": "GET", "url": "accounts?$top=1"}`
|
|
70
|
+
- **Problem**: `500 Internal Server Error` — `getaddrinfo ENOTFOUND org15005212.crm4.dynamics.com`. The Dynamics CRM instance hostname cannot be resolved (instance may be decommissioned or offline).
|
|
71
|
+
- **Status**: Unresolved. Synthetic data sample used instead. Credential/instance needs verification.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Deployment and Scaling Guide (SSE Mode)
|
|
2
|
+
|
|
3
|
+
This guide documents the architecture, horizontal scaling requirements, and security controls for deploying the elastic.io MCP server in multi-tenant cloud environments.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. The Stateful Nature of SSE Mode
|
|
8
|
+
|
|
9
|
+
When running in Server-Sent Events (SSE) mode, the server operates on a hybrid architecture:
|
|
10
|
+
1. **Persistent SSE Stream (GET /sse)**: Keeps an open, persistent HTTP response channel to push events from the server to the client.
|
|
11
|
+
2. **Client-to-Server Messages (POST /sse or POST /messages)**: Short-lived HTTP requests sent by the client to invoke tools or send notifications.
|
|
12
|
+
|
|
13
|
+
Under the hood, each user session creates a live Node.js `McpServer` object and an active REST client tied to the user's platform credentials. **This connection state and session memory exist solely in the RAM of the specific Node.js process/container that initialized it.**
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Horizontal Scaling & Clustering (Sticky Sessions)
|
|
18
|
+
|
|
19
|
+
Because the session manager is in-memory, horizontal scaling across multiple container instances (e.g. behind AWS ALB, Kubernetes NGINX Ingress, or GCP Cloud Load Balancing) requires **Sticky Sessions (Session Affinity)**.
|
|
20
|
+
|
|
21
|
+
Without Sticky Sessions:
|
|
22
|
+
* **The Issue**: A client connects to `GET /sse` and routes to *Instance A* (creating the session in memory). The client's subsequent tool call `POST /messages` gets routed by the load balancer to *Instance B*. *Instance B* does not have this session ID in its memory, returning a `404 Session not found` error.
|
|
23
|
+
* **The Solution**: The load balancer must bind a client's session to the same backend container instance for the duration of the session.
|
|
24
|
+
|
|
25
|
+
### Load Balancer Configuration Patterns
|
|
26
|
+
|
|
27
|
+
Below are configuration patterns for major cloud providers:
|
|
28
|
+
|
|
29
|
+
#### A. AWS Application Load Balancer (ALB)
|
|
30
|
+
Configure **Duration-Based Stickiness** or **Application-Controlled Stickiness** on your Target Group:
|
|
31
|
+
* **Stickiness Type**: `lb_cookie` (Load Balancer Generated Cookie)
|
|
32
|
+
* **Cookie Duration**: Match your session idle timeout (e.g., `1800 seconds` / 30 minutes).
|
|
33
|
+
* **Terraform Example**:
|
|
34
|
+
```hcl
|
|
35
|
+
resource "aws_lb_target_group" "mcp_server" {
|
|
36
|
+
name = "mcp-server-tg"
|
|
37
|
+
port = 3000
|
|
38
|
+
protocol = "HTTP"
|
|
39
|
+
vpc_id = var.vpc_id
|
|
40
|
+
|
|
41
|
+
stickiness {
|
|
42
|
+
type = "lb_cookie"
|
|
43
|
+
cookie_duration = 1800
|
|
44
|
+
enabled = true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### B. Kubernetes NGINX Ingress Controller
|
|
50
|
+
Add the session affinity annotations to your Ingress resource. NGINX will automatically inject a cookie (default: `INGRESSCOOKIE`) to route subsequent requests:
|
|
51
|
+
* **Ingress YAML Example**:
|
|
52
|
+
```yaml
|
|
53
|
+
apiVersion: networking.k8s.io/v1
|
|
54
|
+
kind: Ingress
|
|
55
|
+
metadata:
|
|
56
|
+
name: mcp-server-ingress
|
|
57
|
+
annotations:
|
|
58
|
+
nginx.ingress.kubernetes.io/affinity: "cookie"
|
|
59
|
+
nginx.ingress.kubernetes.io/session-cookie-name: "route"
|
|
60
|
+
nginx.ingress.kubernetes.io/session-cookie-hash: "sha1"
|
|
61
|
+
nginx.ingress.kubernetes.io/session-cookie-expires: "1800"
|
|
62
|
+
nginx.ingress.kubernetes.io/session-cookie-max-age: "1800"
|
|
63
|
+
spec:
|
|
64
|
+
rules:
|
|
65
|
+
- host: mcp.yourdomain.com
|
|
66
|
+
http:
|
|
67
|
+
paths:
|
|
68
|
+
- path: /
|
|
69
|
+
pathType: Prefix
|
|
70
|
+
backend:
|
|
71
|
+
service:
|
|
72
|
+
name: mcp-server-service
|
|
73
|
+
port:
|
|
74
|
+
number: 3000
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### C. Google Cloud Platform (GCP) HTTP(S) Load Balancing
|
|
78
|
+
Set the backend service's session affinity to `GENERATED_COOKIE`:
|
|
79
|
+
* **gcloud Command**:
|
|
80
|
+
```bash
|
|
81
|
+
gcloud compute backend-services update mcp-server-backend-service \
|
|
82
|
+
--session-affinity=GENERATED_COOKIE \
|
|
83
|
+
--affinity-cookie-ttl=1800 \
|
|
84
|
+
--global
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 3. Production Resource & Rate Limit Considerations
|
|
90
|
+
|
|
91
|
+
### Redis-Backed Rate Limiting
|
|
92
|
+
The default rate limiter (`sseRateLimiter` in `src/index.ts`) records IP connections in an in-memory Map.
|
|
93
|
+
* **Limitation**: In a cluster, each container rate-limits independently. An attacker could bypass rate limits by hitting different containers.
|
|
94
|
+
* **Production Fix**: Replace the custom rate limiter with `express-rate-limit` using the `rate-limit-redis` store:
|
|
95
|
+
```typescript
|
|
96
|
+
import rateLimit from 'express-rate-limit';
|
|
97
|
+
import RedisStore from 'rate-limit-redis';
|
|
98
|
+
import Redis from 'ioredis';
|
|
99
|
+
|
|
100
|
+
const redisClient = new Redis(config.redisUrl);
|
|
101
|
+
|
|
102
|
+
const limiter = rateLimit({
|
|
103
|
+
windowMs: 60 * 1000, // 1 minute
|
|
104
|
+
max: 10, // Limit each IP to 10 connections per window
|
|
105
|
+
standardHeaders: true,
|
|
106
|
+
legacyHeaders: false,
|
|
107
|
+
store: new RedisStore({
|
|
108
|
+
sendCommand: (...args: string[]) => redisClient.call(args[0], ...args.slice(1)),
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 4. Architectural Alternatives: Transitioning to Mature Solutions
|
|
116
|
+
|
|
117
|
+
The custom Node.js Express/SSE wrapper (`SessionManager`) acts as an in-memory multiplexer, which introduces maintenance overhead (writing custom reapers, session state checks, and sticky load balancer affinity).
|
|
118
|
+
|
|
119
|
+
For enterprise production setups, you are highly encouraged to move the session, transport, and process management out of the application codebase using one of two mature alternatives:
|
|
120
|
+
|
|
121
|
+
### Alternative A: Third-Party MCP Gateway (Protocol Bridging)
|
|
122
|
+
Deploy this repository strictly as a stateless `stdio` (stdin/stdout) service (which is already implemented in the code), and wrap it using a production-ready, off-the-shelf gateway:
|
|
123
|
+
* **ContextForge Gateway**: An open-source gateway designed to act as a centralized, multi-tenant proxy that handles credentials, logging, and exposes a unified SSE/REST interface.
|
|
124
|
+
* **Supergateway**: A lightweight CLI/Docker wrapper that exposes any `stdio` server over SSE with zero custom code:
|
|
125
|
+
```bash
|
|
126
|
+
supergateway --port 3000 --stdio "node dist/index.js"
|
|
127
|
+
```
|
|
128
|
+
* **Apache APISIX (`mcp-bridge` plugin)**: Bridges standard stdio processes and scales them dynamically as SSE streams, managing routing, rate-limiting, and credentials centrally.
|
|
129
|
+
|
|
130
|
+
### Alternative B: Infrastructure-Level Multi-Tenancy (Micro-Containers)
|
|
131
|
+
Achieve strict security and process-level isolation by delegating execution lifecycles to cloud infrastructure:
|
|
132
|
+
1. **Stateless Stdio Container**: Package the MCP server strictly in `stdio` mode inside a Docker image.
|
|
133
|
+
2. **On-Demand Orchestration**: Spin up isolated, single-tenant container instances (or micro-VMs) on-demand for active users (using AWS ECS, GCP Cloud Run, or Fly.io).
|
|
134
|
+
3. **Gateway Routing**: An API Gateway authenticates the user, spins up their dedicated container task, and proxies the connection.
|
|
135
|
+
4. **Benefits**: Wipes out custom session reapers, memory leak risks, and session state serialization logic from the application repository.
|
|
136
|
+
|