@elfa-ai/mcp 4.3.0 → 4.5.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/CHANGELOG.md +13 -0
- package/README.md +2 -0
- package/dist/index.js +156 -85
- package/manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.5.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`market_chat` returns its sources.** The reply now includes `sources`: the links the research behind the answer read (X posts, news, DEX and explorer pages, prediction markets), in the order it read them, each with a `url`, a `kind` and an optional `title` such as "Searched X". Links only, never the page or post text. A source was read, not necessarily cited in `message`, and the list is empty when the answer used no linkable source. The tool asks for them with `includeSources: true` on `POST /v2/chat` (API 2.8.1); it costs nothing extra. `kind` is a plain string, so treat a value you don't recognise as `web`.
|
|
8
|
+
|
|
9
|
+
## 4.4.0
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Upstream requests carry an `elfa-mcp` User-Agent.** Every upstream call now carries `User-Agent: elfa-mcp/<version> (<transport>)`, where the transport is `stdio` or `http`, instead of the SDK's generic `@elfa-ai/sdk/<version>`. When the server knows which app is connected, it adds that too: on HTTP, the client's own `User-Agent`; on stdio, the `clientInfo` name and version from the MCP handshake, e.g. `elfa-mcp/4.4.0 (stdio; client=claude-ai/0.1.0)`. The client part is cut to printable ASCII and 100 characters, with `(`, `)`, `;` and `\` turned into spaces so the comment stays parseable. A `User-Agent` set in `ELFA_EXTRA_HEADERS`, in any casing, still wins.
|
|
14
|
+
- **The HTTP server logs each handshake.** One JSON line per successful `initialize` goes to stderr, `{"event":"initialize","client":{"name":…,"version":…}}`, so you can see which apps connect even when they never call a tool. Nothing else from the request is logged.
|
|
15
|
+
|
|
3
16
|
## 4.3.0
|
|
4
17
|
|
|
5
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -53,6 +53,8 @@ Ask *"what's trending in crypto right now?"* to confirm it works.
|
|
|
53
53
|
| `ELFA_MCP_MAX_RESPONSE_CHARS` | no | Response size ceiling, default `60000` |
|
|
54
54
|
| `ELFA_EXTRA_HEADERS` | no | JSON object of extra headers to send upstream, for proxies and non-production environments |
|
|
55
55
|
|
|
56
|
+
Upstream requests identify themselves as `User-Agent: elfa-mcp/<version> (<transport>; client=<app>)`. On stdio, `<app>` is the MCP client's name and version from the handshake; over HTTP, it is the connecting client's own `User-Agent` header. Set `User-Agent` in `ELFA_EXTRA_HEADERS` to send your own instead.
|
|
57
|
+
|
|
56
58
|
The timeout is high and retries are off on purpose. The interpretation endpoints are LLM-backed and can take over a minute, and they cost credits per attempt, so a silent retry would bill you again for a call you never saw. Raise `ELFA_RETRIES` only if you are calling the cheap measurement endpoints.
|
|
57
59
|
|
|
58
60
|
Some MCP clients apply their own timeout, often around 60 seconds. `narratives` and `market_chat` can exceed that; the request still completes and is still charged, even if the client gives up first.
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,103 @@ function describeError(error) {
|
|
|
52
52
|
return detail;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// package.json
|
|
56
|
+
var package_default = {
|
|
57
|
+
name: "@elfa-ai/mcp",
|
|
58
|
+
version: "4.5.0",
|
|
59
|
+
description: "Model Context Protocol server for the Elfa API - crypto social intelligence and the Auto condition engine",
|
|
60
|
+
license: "MIT",
|
|
61
|
+
author: "Elfa AI",
|
|
62
|
+
type: "module",
|
|
63
|
+
mcpName: "ai.elfa/mcp",
|
|
64
|
+
main: "dist/index.js",
|
|
65
|
+
types: "dist/index.d.ts",
|
|
66
|
+
bin: {
|
|
67
|
+
"elfa-mcp": "dist/index.js"
|
|
68
|
+
},
|
|
69
|
+
files: [
|
|
70
|
+
"dist/**/*",
|
|
71
|
+
"manifest.json",
|
|
72
|
+
"README.md",
|
|
73
|
+
"CHANGELOG.md",
|
|
74
|
+
"LICENSE"
|
|
75
|
+
],
|
|
76
|
+
engines: {
|
|
77
|
+
node: ">=20.0.0"
|
|
78
|
+
},
|
|
79
|
+
publishConfig: {
|
|
80
|
+
access: "public",
|
|
81
|
+
registry: "https://registry.npmjs.org"
|
|
82
|
+
},
|
|
83
|
+
scripts: {
|
|
84
|
+
build: "tsup",
|
|
85
|
+
dev: "tsx watch src/index.ts",
|
|
86
|
+
start: "node dist/index.js",
|
|
87
|
+
typecheck: "tsc --noEmit",
|
|
88
|
+
test: "vitest run",
|
|
89
|
+
"test:watch": "vitest",
|
|
90
|
+
"check:drift": "tsx scripts/check-drift.ts",
|
|
91
|
+
"update:schema": "curl -fsSL -o swagger.json https://docs.elfa.ai/swagger/swagger.json",
|
|
92
|
+
"docs:tools": "tsx scripts/gen-tool-table.ts",
|
|
93
|
+
"docs:check": "tsx scripts/gen-tool-table.ts --check",
|
|
94
|
+
verify: "npm run typecheck && npm run test && npm run check:drift && npm run docs:check",
|
|
95
|
+
prepublishOnly: "npm run build",
|
|
96
|
+
"build:bundle": "tsx scripts/build-bundle.ts"
|
|
97
|
+
},
|
|
98
|
+
keywords: [
|
|
99
|
+
"elfa",
|
|
100
|
+
"mcp",
|
|
101
|
+
"model-context-protocol",
|
|
102
|
+
"crypto",
|
|
103
|
+
"social-intelligence",
|
|
104
|
+
"trading",
|
|
105
|
+
"agent"
|
|
106
|
+
],
|
|
107
|
+
repository: {
|
|
108
|
+
type: "git",
|
|
109
|
+
url: "git+https://github.com/elfa-ai/mcp.git"
|
|
110
|
+
},
|
|
111
|
+
bugs: {
|
|
112
|
+
url: "https://github.com/elfa-ai/mcp/issues"
|
|
113
|
+
},
|
|
114
|
+
homepage: "https://docs.elfa.ai",
|
|
115
|
+
dependencies: {
|
|
116
|
+
"@elfa-ai/sdk": "^3.0.1",
|
|
117
|
+
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
118
|
+
express: "^5.1.0",
|
|
119
|
+
zod: "^3.25.0"
|
|
120
|
+
},
|
|
121
|
+
devDependencies: {
|
|
122
|
+
"@anthropic-ai/mcpb": "^2.1.2",
|
|
123
|
+
"@types/express": "^5.0.0",
|
|
124
|
+
"@types/node": "^22.10.0",
|
|
125
|
+
tsup: "^8.5.1",
|
|
126
|
+
tsx: "^4.19.0",
|
|
127
|
+
typescript: "^5.9.0",
|
|
128
|
+
vitest: "^3.0.0"
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/userAgent.ts
|
|
133
|
+
var PRODUCT = "elfa-mcp";
|
|
134
|
+
var CLIENT_MAX_LENGTH = 100;
|
|
135
|
+
function sanitizeClient(value) {
|
|
136
|
+
if (!value) return void 0;
|
|
137
|
+
const clean = value.replace(/[^\x20-\x7e]|[();\\]/g, " ").replace(/\s+/g, " ").trim().slice(0, CLIENT_MAX_LENGTH).trim();
|
|
138
|
+
return clean || void 0;
|
|
139
|
+
}
|
|
140
|
+
function userAgent(transport, client) {
|
|
141
|
+
const name = sanitizeClient(client);
|
|
142
|
+
const detail = name ? `${transport}; client=${name}` : transport;
|
|
143
|
+
return `${PRODUCT}/${package_default.version} (${detail})`;
|
|
144
|
+
}
|
|
145
|
+
function withUserAgent(headers2, value) {
|
|
146
|
+
const operatorSetsOne = Object.keys(headers2 ?? {}).some(
|
|
147
|
+
(name) => name.toLowerCase() === "user-agent"
|
|
148
|
+
);
|
|
149
|
+
return operatorSetsOne ? { ...headers2 } : { "User-Agent": value, ...headers2 };
|
|
150
|
+
}
|
|
151
|
+
|
|
55
152
|
// src/client.ts
|
|
56
153
|
var CredentialError = class extends Error {
|
|
57
154
|
};
|
|
@@ -65,13 +162,25 @@ function buildDeps(config, overrides = {}) {
|
|
|
65
162
|
timeout: config.timeout,
|
|
66
163
|
retries: config.retries,
|
|
67
164
|
...config.baseUrl ? { baseUrl: config.baseUrl } : {},
|
|
68
|
-
|
|
165
|
+
headers: withUserAgent(
|
|
166
|
+
config.extraHeaders,
|
|
167
|
+
userAgent(config.transport, overrides.client)
|
|
168
|
+
)
|
|
69
169
|
});
|
|
70
170
|
return {
|
|
71
171
|
sdk,
|
|
72
172
|
maxResponseChars: config.maxResponseChars
|
|
73
173
|
};
|
|
74
174
|
}
|
|
175
|
+
function identifyClientOnInitialize(server, deps, config) {
|
|
176
|
+
server.server.oninitialized = () => {
|
|
177
|
+
const info = server.server.getClientVersion();
|
|
178
|
+
if (!info) return;
|
|
179
|
+
deps.sdk = buildDeps(config, {
|
|
180
|
+
client: `${info.name}/${info.version}`
|
|
181
|
+
}).sdk;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
75
184
|
|
|
76
185
|
// src/config.ts
|
|
77
186
|
var DEFAULT_PORT = 3e3;
|
|
@@ -170,83 +279,6 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
170
279
|
// src/server.ts
|
|
171
280
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
172
281
|
|
|
173
|
-
// package.json
|
|
174
|
-
var package_default = {
|
|
175
|
-
name: "@elfa-ai/mcp",
|
|
176
|
-
version: "4.3.0",
|
|
177
|
-
description: "Model Context Protocol server for the Elfa API - crypto social intelligence and the Auto condition engine",
|
|
178
|
-
license: "MIT",
|
|
179
|
-
author: "Elfa AI",
|
|
180
|
-
type: "module",
|
|
181
|
-
mcpName: "ai.elfa/mcp",
|
|
182
|
-
main: "dist/index.js",
|
|
183
|
-
types: "dist/index.d.ts",
|
|
184
|
-
bin: {
|
|
185
|
-
"elfa-mcp": "dist/index.js"
|
|
186
|
-
},
|
|
187
|
-
files: [
|
|
188
|
-
"dist/**/*",
|
|
189
|
-
"manifest.json",
|
|
190
|
-
"README.md",
|
|
191
|
-
"CHANGELOG.md",
|
|
192
|
-
"LICENSE"
|
|
193
|
-
],
|
|
194
|
-
engines: {
|
|
195
|
-
node: ">=20.0.0"
|
|
196
|
-
},
|
|
197
|
-
publishConfig: {
|
|
198
|
-
access: "public",
|
|
199
|
-
registry: "https://registry.npmjs.org"
|
|
200
|
-
},
|
|
201
|
-
scripts: {
|
|
202
|
-
build: "tsup",
|
|
203
|
-
dev: "tsx watch src/index.ts",
|
|
204
|
-
start: "node dist/index.js",
|
|
205
|
-
typecheck: "tsc --noEmit",
|
|
206
|
-
test: "vitest run",
|
|
207
|
-
"test:watch": "vitest",
|
|
208
|
-
"check:drift": "tsx scripts/check-drift.ts",
|
|
209
|
-
"update:schema": "curl -fsSL -o swagger.json https://docs.elfa.ai/swagger/swagger.json",
|
|
210
|
-
"docs:tools": "tsx scripts/gen-tool-table.ts",
|
|
211
|
-
"docs:check": "tsx scripts/gen-tool-table.ts --check",
|
|
212
|
-
verify: "npm run typecheck && npm run test && npm run check:drift && npm run docs:check",
|
|
213
|
-
prepublishOnly: "npm run build",
|
|
214
|
-
"build:bundle": "tsx scripts/build-bundle.ts"
|
|
215
|
-
},
|
|
216
|
-
keywords: [
|
|
217
|
-
"elfa",
|
|
218
|
-
"mcp",
|
|
219
|
-
"model-context-protocol",
|
|
220
|
-
"crypto",
|
|
221
|
-
"social-intelligence",
|
|
222
|
-
"trading",
|
|
223
|
-
"agent"
|
|
224
|
-
],
|
|
225
|
-
repository: {
|
|
226
|
-
type: "git",
|
|
227
|
-
url: "git+https://github.com/elfa-ai/mcp.git"
|
|
228
|
-
},
|
|
229
|
-
bugs: {
|
|
230
|
-
url: "https://github.com/elfa-ai/mcp/issues"
|
|
231
|
-
},
|
|
232
|
-
homepage: "https://docs.elfa.ai",
|
|
233
|
-
dependencies: {
|
|
234
|
-
"@elfa-ai/sdk": "^3.0.1",
|
|
235
|
-
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
236
|
-
express: "^5.1.0",
|
|
237
|
-
zod: "^3.25.0"
|
|
238
|
-
},
|
|
239
|
-
devDependencies: {
|
|
240
|
-
"@anthropic-ai/mcpb": "^2.1.2",
|
|
241
|
-
"@types/express": "^5.0.0",
|
|
242
|
-
"@types/node": "^22.10.0",
|
|
243
|
-
tsup: "^8.5.1",
|
|
244
|
-
tsx: "^4.19.0",
|
|
245
|
-
typescript: "^5.9.0",
|
|
246
|
-
vitest: "^3.0.0"
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
|
|
250
282
|
// src/entitlements.ts
|
|
251
283
|
import { createHash } from "crypto";
|
|
252
284
|
|
|
@@ -853,12 +885,23 @@ function registerAutoValidate(server, deps) {
|
|
|
853
885
|
|
|
854
886
|
// src/tools/chat.ts
|
|
855
887
|
import { z as z8 } from "zod";
|
|
888
|
+
function readSources(value) {
|
|
889
|
+
if (!Array.isArray(value)) return [];
|
|
890
|
+
const sources = [];
|
|
891
|
+
for (const item of value) {
|
|
892
|
+
if (typeof item !== "object" || item === null) continue;
|
|
893
|
+
const { url, kind, title } = item;
|
|
894
|
+
if (typeof url !== "string" || typeof kind !== "string") continue;
|
|
895
|
+
sources.push(typeof title === "string" ? { url, kind, title } : { url, kind });
|
|
896
|
+
}
|
|
897
|
+
return sources;
|
|
898
|
+
}
|
|
856
899
|
function registerChat(server, deps) {
|
|
857
900
|
return server.registerTool(
|
|
858
901
|
"market_chat",
|
|
859
902
|
{
|
|
860
903
|
title: "Market chat",
|
|
861
|
-
description: "Ask Elfa for written market analysis grounded in its social data. Costs credits and varies by speed, so fast is the cheaper option. Pass sessionId from a previous reply to continue the same conversation.",
|
|
904
|
+
description: "Ask Elfa for written market analysis grounded in its social data. Costs credits and varies by speed, so fast is the cheaper option. Pass sessionId from a previous reply to continue the same conversation. The reply includes sources: links to the posts and pages the research read (links only, not their text), so you can cite or check them.",
|
|
862
905
|
inputSchema: {
|
|
863
906
|
analysisType: z8.enum([
|
|
864
907
|
"chat",
|
|
@@ -883,7 +926,16 @@ function registerChat(server, deps) {
|
|
|
883
926
|
outputSchema: {
|
|
884
927
|
message: z8.string(),
|
|
885
928
|
sessionId: z8.string().nullable(),
|
|
886
|
-
creditsConsumed: z8.number().nullable()
|
|
929
|
+
creditsConsumed: z8.number().nullable(),
|
|
930
|
+
sources: z8.array(
|
|
931
|
+
z8.object({
|
|
932
|
+
url: z8.string(),
|
|
933
|
+
kind: z8.string(),
|
|
934
|
+
title: z8.string().optional()
|
|
935
|
+
})
|
|
936
|
+
).describe(
|
|
937
|
+
"Links the research read, in order, without duplicates. kind is x_post, x_account, telegram, dex, explorer, prediction_market or web; treat any other value as web. A source was read, not necessarily cited."
|
|
938
|
+
)
|
|
887
939
|
},
|
|
888
940
|
annotations: {
|
|
889
941
|
readOnlyHint: true,
|
|
@@ -918,13 +970,16 @@ function registerChat(server, deps) {
|
|
|
918
970
|
speed: args.speed,
|
|
919
971
|
message: args.message,
|
|
920
972
|
sessionId: args.sessionId,
|
|
921
|
-
assetMetadata: Object.keys(assetMetadata).length > 0 ? assetMetadata : void 0
|
|
973
|
+
assetMetadata: Object.keys(assetMetadata).length > 0 ? assetMetadata : void 0,
|
|
974
|
+
includeSources: true
|
|
922
975
|
})
|
|
923
976
|
);
|
|
977
|
+
const data = response.data;
|
|
924
978
|
return {
|
|
925
|
-
message:
|
|
926
|
-
sessionId:
|
|
927
|
-
creditsConsumed:
|
|
979
|
+
message: data.message,
|
|
980
|
+
sessionId: data.sessionId ?? null,
|
|
981
|
+
creditsConsumed: data.creditsConsumed ?? null,
|
|
982
|
+
sources: readSources(data.sources)
|
|
928
983
|
};
|
|
929
984
|
});
|
|
930
985
|
}
|
|
@@ -1446,6 +1501,9 @@ function header(req, name) {
|
|
|
1446
1501
|
const value = req.headers[name];
|
|
1447
1502
|
return Array.isArray(value) ? value[0] : value;
|
|
1448
1503
|
}
|
|
1504
|
+
function clientField(value) {
|
|
1505
|
+
return sanitizeClient(typeof value === "string" ? value : void 0) ?? "unknown";
|
|
1506
|
+
}
|
|
1449
1507
|
function jsonRpcError(res, status, message) {
|
|
1450
1508
|
res.status(status).json({
|
|
1451
1509
|
jsonrpc: "2.0",
|
|
@@ -1466,6 +1524,8 @@ function createHttpApp(config, options = {}) {
|
|
|
1466
1524
|
const oauth = config.oauth;
|
|
1467
1525
|
const verifier = options.verifier ?? (oauth ? new TokenVerifier(oauth) : void 0);
|
|
1468
1526
|
const loadEntitlements = options.entitlements ?? fetchEntitlements;
|
|
1527
|
+
const log = options.log ?? ((line) => process.stderr.write(`${line}
|
|
1528
|
+
`));
|
|
1469
1529
|
const entitlementCache = new EntitlementCache();
|
|
1470
1530
|
app.disable("x-powered-by");
|
|
1471
1531
|
app.use(express.json({ limit: "4mb" }));
|
|
@@ -1533,7 +1593,10 @@ function createHttpApp(config, options = {}) {
|
|
|
1533
1593
|
try {
|
|
1534
1594
|
const apiKey = await resolveApiKey(req, res);
|
|
1535
1595
|
if (apiKey === void 0) return;
|
|
1536
|
-
const deps = buildDeps(config, {
|
|
1596
|
+
const deps = buildDeps(config, {
|
|
1597
|
+
apiKey: apiKey ?? void 0,
|
|
1598
|
+
client: header(req, "user-agent")
|
|
1599
|
+
});
|
|
1537
1600
|
const entitlements = apiKey ? await entitlementCache.get(apiKey, () => loadEntitlements(deps)) : void 0;
|
|
1538
1601
|
server = createServer(deps, entitlements);
|
|
1539
1602
|
transport = new StreamableHTTPServerTransport({
|
|
@@ -1541,6 +1604,13 @@ function createHttpApp(config, options = {}) {
|
|
|
1541
1604
|
allowedHosts: config.allowedHosts.length > 0 ? config.allowedHosts : defaultAllowedHosts(config),
|
|
1542
1605
|
...config.allowedOrigins.length > 0 ? { allowedOrigins: config.allowedOrigins } : {}
|
|
1543
1606
|
});
|
|
1607
|
+
res.on("finish", () => {
|
|
1608
|
+
if (res.statusCode >= 400) return;
|
|
1609
|
+
const info = server?.server.getClientVersion();
|
|
1610
|
+
if (!info) return;
|
|
1611
|
+
const client = { name: clientField(info.name), version: clientField(info.version) };
|
|
1612
|
+
log(JSON.stringify({ event: "initialize", client }));
|
|
1613
|
+
});
|
|
1544
1614
|
res.on("close", () => {
|
|
1545
1615
|
void transport?.close();
|
|
1546
1616
|
void server?.close();
|
|
@@ -1581,6 +1651,7 @@ async function main() {
|
|
|
1581
1651
|
}
|
|
1582
1652
|
const deps = buildDeps(config);
|
|
1583
1653
|
const server = createServer(deps, await fetchEntitlements(deps));
|
|
1654
|
+
identifyClientOnInitialize(server, deps, config);
|
|
1584
1655
|
await server.connect(new StdioServerTransport());
|
|
1585
1656
|
}
|
|
1586
1657
|
main().catch((error) => {
|
package/manifest.json
CHANGED
package/package.json
CHANGED