echelongraph-mcp 2.6.0 → 2.6.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.
- package/CHANGELOG.md +8 -0
- package/dist/http.js +8 -3
- package/dist/index.js +4 -3
- package/dist/runtime.js +19 -2
- package/package.json +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ with a provenance attestation by the release workflow of
|
|
|
6
6
|
[github.com/echelongraph/echelongraph-mcp](https://github.com/echelongraph/echelongraph-mcp),
|
|
7
7
|
from the commit tagged `v<version>`.
|
|
8
8
|
|
|
9
|
+
## 2.6.1 — 2026-10-04
|
|
10
|
+
|
|
11
|
+
- Hosted endpoint only: when the production synthetic calls `https://mcp.echelongraph.io/mcp`, the
|
|
12
|
+
endpoint marks its own API calls with a fixed `echelongraph-mcp-synthetic/1.0` token, so the
|
|
13
|
+
synthetic is never counted as hosted use. The client's own text is never passed through. The
|
|
14
|
+
`mcp_request` log line gains `client_public` (#2737).
|
|
15
|
+
- stdio behaviour is unchanged.
|
|
16
|
+
|
|
9
17
|
## 2.6.0 — 2026-10-04
|
|
10
18
|
|
|
11
19
|
- `check_sbom` checks up to 2,000 distinct purls per call, instead of refusing more than 200: it
|
package/dist/http.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// token fatal at start-up, because without it every remote user would share one API budget.
|
|
24
24
|
import http from "node:http";
|
|
25
25
|
import { createMcpHandler } from "@modelcontextprotocol/server";
|
|
26
|
-
import { forwardToken, markHttpEntrypoint, withClient } from "./runtime.js";
|
|
26
|
+
import { forwardToken, markHttpEntrypoint, SYNTHETIC_UA_FAMILY, withClient } from "./runtime.js";
|
|
27
27
|
import { BODY_TOO_LARGE_CODE, DEFAULT_RATE_LIMIT_PER_MIN, FixedWindowLimiter, ORIGIN_REFUSED_CODE, RATE_LIMITED_CODE, bodyFacts, originVerdict, resolveClient, revisionLabel, rpcError, uaFamily, } from "./httpPolicy.js";
|
|
28
28
|
// Must run before index.ts is evaluated: it is how index.ts knows not to start stdio.
|
|
29
29
|
markHttpEntrypoint();
|
|
@@ -158,7 +158,11 @@ async function serveMcp(req, res, pathname) {
|
|
|
158
158
|
body: body && body.length > 0 ? new Uint8Array(body) : undefined,
|
|
159
159
|
signal: abort.signal,
|
|
160
160
|
});
|
|
161
|
-
|
|
161
|
+
// #2737: the production synthetic names itself by its user-agent family; its API calls then
|
|
162
|
+
// carry the synthetic's fixed token (runtime.ts upstreamUserAgent), so they are never counted
|
|
163
|
+
// as hosted adoption.
|
|
164
|
+
const synthetic = uaFamily(req.headers["user-agent"]) === SYNTHETIC_UA_FAMILY;
|
|
165
|
+
const response = await withClient(client.forwardAddress, () => handler.fetch(request), { synthetic });
|
|
162
166
|
const out = { ...cors };
|
|
163
167
|
response.headers.forEach((v, k) => {
|
|
164
168
|
out[k] = v;
|
|
@@ -169,7 +173,7 @@ async function serveMcp(req, res, pathname) {
|
|
|
169
173
|
res.write(chunk);
|
|
170
174
|
}
|
|
171
175
|
res.end();
|
|
172
|
-
return { status: response.status, body };
|
|
176
|
+
return { status: response.status, body, clientPublic: client.forwardAddress !== undefined };
|
|
173
177
|
}
|
|
174
178
|
const server = http.createServer((req, res) => {
|
|
175
179
|
const start = process.hrtime.bigint();
|
|
@@ -196,6 +200,7 @@ const server = http.createServer((req, res) => {
|
|
|
196
200
|
throttled: o.throttled === true,
|
|
197
201
|
refused: o.refused ?? "",
|
|
198
202
|
ua_family: uaFamily(req.headers["user-agent"]),
|
|
203
|
+
client_public: o.clientPublic === true,
|
|
199
204
|
});
|
|
200
205
|
};
|
|
201
206
|
if (pathname === HEALTH_PATH) {
|
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ import { registerCheckAffected } from "./tools/check_affected.js";
|
|
|
52
52
|
import { registerCheckSbom } from "./tools/check_sbom.js";
|
|
53
53
|
import { registerCveIntel } from "./tools/cve_intel.js";
|
|
54
54
|
import { registerGetCwe } from "./tools/get_cwe.js";
|
|
55
|
-
import { isHttpEntrypoint, upstreamHeaders } from "./runtime.js";
|
|
55
|
+
import { isHttpEntrypoint, upstreamHeaders, upstreamUserAgent } from "./runtime.js";
|
|
56
56
|
import { registerVendorAdvisoryTools } from "./tools/vendor_advisories.js";
|
|
57
57
|
import { registerPrompts } from "./prompts.js";
|
|
58
58
|
import { registerResources } from "./resources.js";
|
|
@@ -186,8 +186,9 @@ export async function api(path, init) {
|
|
|
186
186
|
method: init?.method,
|
|
187
187
|
body: init?.body,
|
|
188
188
|
// upstreamHeaders: empty on stdio; on the hosted endpoint, the forward token and the
|
|
189
|
-
// end client's address (runtime.ts, #2316).
|
|
190
|
-
|
|
189
|
+
// end client's address (runtime.ts, #2316). upstreamUserAgent: UA, unless the hosted
|
|
190
|
+
// endpoint is answering the production synthetic (#2737).
|
|
191
|
+
headers: { ...upstreamHeaders(`${BASE}${path}`), ...(init?.headers ?? {}), "User-Agent": upstreamUserAgent(UA), Accept: "application/json" },
|
|
191
192
|
signal: ctrl.signal,
|
|
192
193
|
});
|
|
193
194
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -22,8 +22,25 @@ export function isHttpEntrypoint() {
|
|
|
22
22
|
// withClient(), and the SDK dispatches the tool handler inside that same async context, so
|
|
23
23
|
// api() can read it without the tool module passing it through every function.
|
|
24
24
|
const clientStore = new AsyncLocalStorage();
|
|
25
|
-
export function withClient(clientIp, fn) {
|
|
26
|
-
return clientStore.run({ clientIp }, fn);
|
|
25
|
+
export function withClient(clientIp, fn, opts = {}) {
|
|
26
|
+
return clientStore.run({ clientIp, synthetic: opts.synthetic === true }, fn);
|
|
27
|
+
}
|
|
28
|
+
// #2737: the production MCP synthetic probes this endpoint too, and its calls must reach the API
|
|
29
|
+
// under its own family, never as hosted MCP adoption. The synthetic names itself to this service
|
|
30
|
+
// with this family as its User-Agent's leading token; http.ts marks such a request (synthetic:
|
|
31
|
+
// true) and upstreamUserAgent() then puts this FIXED token ahead of the service's own user-agent,
|
|
32
|
+
// exactly as ECHELONGRAPH_MCP_UA does for the npm package. The client's text is never relayed: a
|
|
33
|
+
// request either carries this one constant or nothing. Anyone can claim the family, as anyone can
|
|
34
|
+
// send it to the API directly; the consequence is an undercounted adoption number, never a bypass
|
|
35
|
+
// (core-backend: user-agent classification is for counting only).
|
|
36
|
+
export const SYNTHETIC_UA_FAMILY = "echelongraph-mcp-synthetic";
|
|
37
|
+
export const SYNTHETIC_UA_TOKEN = `${SYNTHETIC_UA_FAMILY}/1.0`;
|
|
38
|
+
/** The User-Agent for one API call: `ua`, led by the synthetic's token when the request is the synthetic's. */
|
|
39
|
+
export function upstreamUserAgent(ua) {
|
|
40
|
+
if (!httpEntrypoint || clientStore.getStore()?.synthetic !== true)
|
|
41
|
+
return ua;
|
|
42
|
+
const lead = ua.split(/[/\s]/, 1)[0];
|
|
43
|
+
return lead === SYNTHETIC_UA_FAMILY ? ua : `${SYNTHETIC_UA_TOKEN} ${ua}`;
|
|
27
44
|
}
|
|
28
45
|
/** The header that carries the forward token, and the one that names the client (core-backend waf/mcpforward.go). */
|
|
29
46
|
export const FORWARD_TOKEN_HEADER = "X-EG-MCP-Forward";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echelongraph-mcp",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"mcpName": "io.echelongraph/echelongraph-mcp",
|
|
5
5
|
"description": "EchelonGraph CVE and internet-exposure data as an MCP server: CVEs with EchelonGraph multi-source scores, CISA-KEV and EPSS, plus a per-CVE internet-exposure footprint derived from Shodan data. Shodan data is owned by Shodan, which holds its copyright (© Shodan). Free and keyless, for Claude, Cursor and any MCP client.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"version": "2.6.
|
|
20
|
+
"version": "2.6.1",
|
|
21
21
|
"remotes": [
|
|
22
22
|
{
|
|
23
23
|
"type": "streamable-http",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
{
|
|
29
29
|
"registryType": "npm",
|
|
30
30
|
"identifier": "echelongraph-mcp",
|
|
31
|
-
"version": "2.6.
|
|
31
|
+
"version": "2.6.1",
|
|
32
32
|
"transport": {
|
|
33
33
|
"type": "stdio"
|
|
34
34
|
},
|