@elfa-ai/mcp 1.0.0 → 1.0.2

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 CHANGED
@@ -1,6 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## 1.0.0
3
+ ## 1.0.2
4
+
5
+ Fixes found by running every tool against a live API.
6
+
7
+ - `elfa_status` returned the raw key-status payload, which includes the API key itself, the account email and internal identifiers. It now reports only tier, limits, usage, scopes and expiry, and surfaces the reason when the key lookup fails
8
+ - Default timeout raised to 120s. The LLM-backed endpoints regularly take longer than 30s, so they timed out before returning
9
+ - Retries now default to 0. A timed-out request to a credit-charging endpoint was retried three times and billed each time, so a failed call could cost four times its price
10
+ - `elfa_narratives` reports how many mentions matched, and says so when the summariser returns nothing, instead of an unexplained empty list
11
+
12
+ ## 1.0.1
4
13
 
5
14
  First release of the TypeScript server, published as `@elfa-ai/mcp`.
6
15
 
package/README.md CHANGED
@@ -40,9 +40,14 @@ Ask *"what's trending in crypto right now?"* to confirm it works.
40
40
  | --- | --- | --- |
41
41
  | `ELFA_API_KEY` | yes | Authenticates every request |
42
42
  | `ELFA_HMAC_SECRET` | no | Signs Auto requests. Needed only to connect an exchange, or to run a query whose action places an order |
43
- | `ELFA_TIMEOUT` | no | Request timeout in ms, default `30000` |
43
+ | `ELFA_TIMEOUT` | no | Request timeout in ms, default `120000` |
44
+ | `ELFA_RETRIES` | no | Retries on failure, default `0` |
44
45
  | `ELFA_MCP_MAX_RESPONSE_CHARS` | no | Response size ceiling, default `60000` |
45
46
 
47
+ 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.
48
+
49
+ Some MCP clients apply their own timeout, often around 60 seconds. `elfa_narratives` and `elfa_chat` can exceed that; the request still completes and is still charged, even if the client gives up first.
50
+
46
51
  Without `ELFA_HMAC_SECRET` everything still works except exchange linking and order-placing queries — those return a message telling you what to set.
47
52
 
48
53
  ## Tools
package/dist/index.js CHANGED
@@ -67,6 +67,7 @@ function buildDeps(config, overrides = {}) {
67
67
  const sdk = new ElfaSDK({
68
68
  elfaApiKey: apiKey,
69
69
  timeout: config.timeout,
70
+ retries: config.retries,
70
71
  ...hmacSecret ? { hmacSecret } : {},
71
72
  ...config.baseUrl ? { baseUrl: config.baseUrl } : {}
72
73
  });
@@ -80,13 +81,19 @@ function buildDeps(config, overrides = {}) {
80
81
  // src/config.ts
81
82
  var DEFAULT_PORT = 3e3;
82
83
  var DEFAULT_HOST = "127.0.0.1";
83
- var DEFAULT_TIMEOUT = 3e4;
84
+ var DEFAULT_TIMEOUT = 12e4;
85
+ var DEFAULT_RETRIES = 0;
84
86
  var DEFAULT_MAX_RESPONSE_CHARS = 6e4;
85
87
  function num(value, fallback) {
86
88
  if (!value) return fallback;
87
89
  const parsed = Number(value);
88
90
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
89
91
  }
92
+ function count(value, fallback) {
93
+ if (!value) return fallback;
94
+ const parsed = Number(value);
95
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
96
+ }
90
97
  function list(value) {
91
98
  if (!value) return [];
92
99
  return value.split(",").map((entry) => entry.trim()).filter(Boolean);
@@ -102,6 +109,7 @@ function loadConfig(env = process.env) {
102
109
  hmacSecret: env.ELFA_HMAC_SECRET || void 0,
103
110
  baseUrl: env.ELFA_BASE_URL || void 0,
104
111
  timeout: num(env.ELFA_TIMEOUT, DEFAULT_TIMEOUT),
112
+ retries: count(env.ELFA_RETRIES, DEFAULT_RETRIES),
105
113
  maxResponseChars: num(
106
114
  env.ELFA_MCP_MAX_RESPONSE_CHARS,
107
115
  DEFAULT_MAX_RESPONSE_CHARS
@@ -119,7 +127,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
119
127
  // package.json
120
128
  var package_default = {
121
129
  name: "@elfa-ai/mcp",
122
- version: "1.0.0",
130
+ version: "1.0.2",
123
131
  description: "Model Context Protocol server for the Elfa API - crypto social intelligence and the Auto condition engine",
124
132
  license: "MIT",
125
133
  author: "Elfa Team",
@@ -1043,7 +1051,7 @@ function registerNarratives(server, deps) {
1043
1051
  "elfa_narratives",
1044
1052
  {
1045
1053
  title: "Narratives",
1046
- description: "Written narrative analysis with source links, for when counts are not enough. 5 credits per call, so prefer elfa_trending or elfa_mentions when metrics will do. scope=market extracts the narratives moving the market. scope=keywords summarises events for keywords you supply.",
1054
+ description: "Written narrative analysis with source links, for when counts are not enough. 5 credits per call, so prefer elfa_trending or elfa_mentions when metrics will do. scope=market extracts the narratives moving the market. scope=keywords summarises events for keywords you supply, and can take over a minute to return.",
1047
1055
  inputSchema: {
1048
1056
  scope: z11.enum(["market", "keywords"]).default("market").describe("market is a broad sweep. keywords needs the keywords argument."),
1049
1057
  keywords: z11.string().optional().describe("Up to 5 comma separated keywords for scope=keywords."),
@@ -1058,7 +1066,9 @@ function registerNarratives(server, deps) {
1058
1066
  outputSchema: {
1059
1067
  scope: z11.string(),
1060
1068
  notice: z11.string(),
1061
- items: z11.array(z11.record(z11.unknown()))
1069
+ items: z11.array(z11.record(z11.unknown())),
1070
+ matched: z11.number().nullable(),
1071
+ note: z11.string().nullable()
1062
1072
  },
1063
1073
  annotations: {
1064
1074
  readOnlyHint: true,
@@ -1090,15 +1100,19 @@ function registerNarratives(server, deps) {
1090
1100
  searchType: args.searchType
1091
1101
  })
1092
1102
  );
1103
+ const items2 = asArray(
1104
+ response2.data
1105
+ ).map((entry) => ({
1106
+ summary: entry.summary,
1107
+ sources: asArray(entry.sourceLinks)
1108
+ }));
1109
+ const matched = response2.metadata?.total ?? null;
1093
1110
  return {
1094
1111
  scope: args.scope,
1095
1112
  notice: UNTRUSTED_NOTICE,
1096
- items: asArray(
1097
- response2.data
1098
- ).map((entry) => ({
1099
- summary: entry.summary,
1100
- sources: asArray(entry.sourceLinks)
1101
- }))
1113
+ items: items2,
1114
+ matched,
1115
+ note: items2.length === 0 && matched ? `Matched ${matched} mentions but produced no summary. Try a narrower keyword set or a different time window.` : null
1102
1116
  };
1103
1117
  }
1104
1118
  const response = await deps.sdk.getTrendingNarratives(
@@ -1108,15 +1122,18 @@ function registerNarratives(server, deps) {
1108
1122
  maxTweetsPerNarrative: args.maxTweetsPerNarrative
1109
1123
  })
1110
1124
  );
1125
+ const items = asArray(
1126
+ response.data?.trending_narratives
1127
+ ).map((entry) => ({
1128
+ narrative: entry.narrative,
1129
+ sources: asArray(entry.source_links)
1130
+ }));
1111
1131
  return {
1112
1132
  scope: args.scope,
1113
1133
  notice: UNTRUSTED_NOTICE,
1114
- items: asArray(
1115
- response.data?.trending_narratives
1116
- ).map((entry) => ({
1117
- narrative: entry.narrative,
1118
- sources: asArray(entry.source_links)
1119
- }))
1134
+ items,
1135
+ matched: response.data?.metadata?.total_tweets ?? null,
1136
+ note: items.length === 0 ? "No narratives were produced for this window. Try timeFrame=week." : null
1120
1137
  };
1121
1138
  });
1122
1139
  }
@@ -1125,6 +1142,33 @@ function registerNarratives(server, deps) {
1125
1142
 
1126
1143
  // src/tools/status.ts
1127
1144
  import { z as z12 } from "zod";
1145
+ var REPORTED = [
1146
+ "tier",
1147
+ "status",
1148
+ "isExpired",
1149
+ "expiresAt",
1150
+ "billingMode",
1151
+ "usage",
1152
+ "limits",
1153
+ "remainingRequests",
1154
+ "dailyRequestLimit",
1155
+ "monthlyRequestLimit",
1156
+ "requestsPerMinute",
1157
+ "allowOverage",
1158
+ "bonusCredits",
1159
+ "depositCredits",
1160
+ "spendCapCredits",
1161
+ "athenaEnabled",
1162
+ "hmacEnabled",
1163
+ "scopes"
1164
+ ];
1165
+ function summarise(data) {
1166
+ const out = {};
1167
+ for (const field of REPORTED) {
1168
+ if (data[field] !== void 0) out[field] = data[field];
1169
+ }
1170
+ return out;
1171
+ }
1128
1172
  function registerStatus(server, deps) {
1129
1173
  server.registerTool(
1130
1174
  "elfa_status",
@@ -1134,7 +1178,8 @@ function registerStatus(server, deps) {
1134
1178
  inputSchema: {},
1135
1179
  outputSchema: {
1136
1180
  reachable: z12.boolean(),
1137
- key: z12.record(z12.unknown()).nullable()
1181
+ key: z12.record(z12.unknown()).nullable(),
1182
+ problem: z12.string().nullable()
1138
1183
  },
1139
1184
  annotations: {
1140
1185
  readOnlyHint: true,
@@ -1150,7 +1195,8 @@ function registerStatus(server, deps) {
1150
1195
  ]);
1151
1196
  return {
1152
1197
  reachable: ping.status === "fulfilled",
1153
- key: status.status === "fulfilled" ? status.value.data : null
1198
+ key: status.status === "fulfilled" ? summarise(status.value.data) : null,
1199
+ problem: status.status === "rejected" ? describeError(status.reason) : null
1154
1200
  };
1155
1201
  })
1156
1202
  );
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "server": {
3
3
  "name": "elfa",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "docs": "https://docs.elfa.ai",
6
6
  "spec": "https://docs.elfa.ai/swagger/swagger.json",
7
7
  "specScope": "/v2"
@@ -18,7 +18,10 @@
18
18
  "idempotentHint": true,
19
19
  "openWorldHint": false
20
20
  },
21
- "operations": ["ping-v2", "getApiKeyStatus-v2"]
21
+ "operations": [
22
+ "ping-v2",
23
+ "getApiKeyStatus-v2"
24
+ ]
22
25
  },
23
26
  {
24
27
  "name": "elfa_mentions",
@@ -65,7 +68,10 @@
65
68
  "idempotentHint": true,
66
69
  "openWorldHint": false
67
70
  },
68
- "operations": ["getTrendingNarratives-v2", "getEventSummary-v2"]
71
+ "operations": [
72
+ "getTrendingNarratives-v2",
73
+ "getEventSummary-v2"
74
+ ]
69
75
  },
70
76
  {
71
77
  "name": "elfa_account_stats",
@@ -78,7 +84,9 @@
78
84
  "idempotentHint": true,
79
85
  "openWorldHint": false
80
86
  },
81
- "operations": ["getAccountSmartStats-v2"]
87
+ "operations": [
88
+ "getAccountSmartStats-v2"
89
+ ]
82
90
  },
83
91
  {
84
92
  "name": "elfa_chat",
@@ -91,7 +99,9 @@
91
99
  "idempotentHint": false,
92
100
  "openWorldHint": false
93
101
  },
94
- "operations": ["chat-v2"]
102
+ "operations": [
103
+ "chat-v2"
104
+ ]
95
105
  },
96
106
  {
97
107
  "name": "elfa_auto_build",
@@ -104,7 +114,9 @@
104
114
  "idempotentHint": false,
105
115
  "openWorldHint": false
106
116
  },
107
- "operations": ["auto-chat-v2"]
117
+ "operations": [
118
+ "auto-chat-v2"
119
+ ]
108
120
  },
109
121
  {
110
122
  "name": "elfa_auto_validate",
@@ -117,7 +129,10 @@
117
129
  "idempotentHint": true,
118
130
  "openWorldHint": false
119
131
  },
120
- "operations": ["auto-validate-query-v2", "auto-validate-query-draft-v2"]
132
+ "operations": [
133
+ "auto-validate-query-v2",
134
+ "auto-validate-query-draft-v2"
135
+ ]
121
136
  },
122
137
  {
123
138
  "name": "elfa_auto_query",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elfa-ai/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Model Context Protocol server for the Elfa API - crypto social intelligence and the Auto condition engine",
5
5
  "license": "MIT",
6
6
  "author": "Elfa Team",