@battlegrid/mcp-server 31.2.7 → 31.2.9

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/README.md CHANGED
@@ -24,7 +24,7 @@ Seeing package `31.x` alongside handshake `battlegrid@33.x` — the package **be
24
24
 
25
25
  **What this changes for you:** nothing about how you call anything. Upgrading the package no longer waits on a server deploy, and a server deploy no longer strands you on a package that names the wrong contract — reconnect and the announcement follows. **Contract breaking-change notes are no longer keyed to package versions**, since a contract move is no longer a release here; the v11-and-earlier notes below are kept as history, and the live vocabulary is always discovery.
26
26
 
27
- ## Contract history — v37 → v49.5
27
+ ## Contract history — v37 → v52
28
28
 
29
29
  Eleven majors reached authors while this section stopped at v36. That gap is the mechanism, not an
30
30
  oversight: since v31 a contract move needs no release here, so nothing forced a note to be written —
@@ -32,11 +32,43 @@ and the documentation ships inside the tarball, so a note written but unpublishe
32
32
  Both halves are now closed by a rule keyed to the *served* contract rather than to a release of this
33
33
  package.
34
34
 
35
+ **50.0.0 and 51.0.0 arrived late, and the reason is worth naming.** The re-vendoring errand that
36
+ used to carry these notes is now a generated export
37
+ (`battlegrid-app/server/scripts/export-mcp-skills.mjs`), and it owns three paths — `skills/`,
38
+ `skills/EXPORT.json`, and the vendored digest. It deliberately does not touch this file. So the
39
+ digest kept arriving on time while the note stopped travelling with it, and this section sat at
40
+ v49.5 against a served contract of 51.0.0. Nothing a client could observe was wrong; what was
41
+ missing was the sentence telling them so. **A contract move still needs a human-authored entry
42
+ here, and the export lane will not remind you.**
43
+
35
44
  ### Accepted again — input that was rejected now compiles
36
45
 
37
46
  Nothing to migrate. This is the one direction that cannot break a client: a body the server used to
38
47
  refuse is now stored. Listed because a client that special-cased the refusal can delete that branch.
39
48
 
49
+ - **A signal rule patch no longer forces you to restate `allocation` and `required`** (51.0.0,
50
+ `fix-signal-rule-patch-semantics`). On `update_strategy_signal_rule`, and on the `rules` element
51
+ of `compile_strategy_plan`, both fields become optional and join `params` under ONE omission
52
+ rule: **an omitted mutable field preserves the stored value for that signal.** "Raise this
53
+ signal's weight" is now expressible.
54
+
55
+ ```jsonc
56
+ { "strategyId": "…", "expectedRevision": 7, "signalId": "volume_surge",
57
+ "allocation": 3 } // `required` and `params` keep exactly what is stored
58
+ ```
59
+
60
+ **Every existing client keeps working** — a complete payload is still a valid patch — so this is
61
+ listed for what you can now STOP sending. Before it, both fields were mandatory on every rule
62
+ surface, so a caller that had not first read the current rule had to invent a value it was never
63
+ asked about. That is not hypothetical: revision 5 of a production strategy flipped `required`
64
+ false → true unasked while moving a weight 2 → 3, turning a scoring signal into a **mandatory
65
+ gate** — which changes whether the agent takes trades at all.
66
+
67
+ One boundary on the newly legal ground, and it breaks nothing: a patch carrying **no** mutable
68
+ field is refused — *"A rule patch must change something: supply at least one of allocation,
69
+ required or params."* — rather than minting a no-op revision. Under 50.0.0 that request could not
70
+ be formed at all, so nothing that used to work is now refused.
71
+
40
72
  - **An arming trigger no longer constrains its required conditions' clock** (49.4.0,
41
73
  `restore-arming-trigger-authoring`). `compile_strategy_plan`, `apply_strategy_plan` and
42
74
  `fork_strategy` accept a strategy whose entry trigger is `ON_CANDLE_CLOSE`, `STOP_THROUGH_LEVEL`
@@ -55,6 +87,14 @@ refuse is now stored. Listed because a client that special-cased the refusal can
55
87
 
56
88
  ### Changed meaning, unchanged shape
57
89
 
90
+ - **`blocksScanGate` reads `true` for a class it did not** (50.0.0, `own-scan-served-set-once`), on
91
+ `preview_radar_resolution`. Blocking is now derived from the lane's **served set** rather than
92
+ switched over the reach reason, so a `FEED`-reason refusal on an operand no reader in the lane
93
+ serves BLOCKS instead of deferring. No field changes shape, and a client that already renders the
94
+ key renders the new answer — but a client that treated `blocksScanGate: false` as "this will
95
+ resolve once data arrives" now sees a deployment that will not fire. Nothing in the payload tells
96
+ you this moved.
97
+
58
98
  - **Three tools serve different values for identical input** (47.3.0, `derive-scan-fetch-from-report`).
59
99
  The radar scan leg now derives its timeframe fetch from the strategy's **report** rather than the
60
100
  on-duty agent's three perception rungs, so a required condition addressing an absolute timeframe
@@ -72,6 +112,59 @@ refuse is now stored. Listed because a client that special-cased the refusal can
72
112
 
73
113
  ### Rejected input — something you author is no longer accepted
74
114
 
115
+ - **`entry.levelSource` is refused — the level is derived, never authored** (52.0.0,
116
+ `derive-entry-level`). The strict `entry` object on `compile_strategy_plan`, `apply_strategy_plan`
117
+ and `fork_strategy` is six keys — `trigger`, `confirmTf`, `closes`, `bandAtrMultiple`,
118
+ `levelOffsetAtrMultiple`, `validForBars` — and a body carrying `levelSource` is refused naming
119
+ the key where 51.0.0 accepted it. `STOP_THROUGH_LEVEL` rests a stop past the swing channel's
120
+ CURRENT edge in the trade's direction (the 20-bar high for a long, the 20-bar low for a short);
121
+ `ON_RETEST` rests a limit in front of the edge a close most recently BROKE. Your two dials are the
122
+ unsigned distance from that edge (`levelOffsetAtrMultiple`, 0–2 ATR; a long adds, a short
123
+ subtracts) and the bar validity (`validForBars`, 1–24). A meaningful `validForBars` under
124
+ `AT_SIGNAL` or `ON_CANDLE_CLOSE` is now refused as `PARAMETER_NOT_HONOURED` like the other level
125
+ dials.
126
+
127
+ **Drop the key.** That is the whole migration for what you send; the six-key example below is
128
+ the current shape.
129
+
130
+ - **`update_strategy_signal_rule` requires `confirm: true` when the strategy has bound agents**
131
+ (51.0.0, `fix-signal-rule-patch-semantics`). The write re-materializes scoring configuration onto
132
+ every bound agent immediately — including agents holding open USDC positions — and until now
133
+ nothing on the server asked. A rule edit on a strategy with one or more bound agents is refused
134
+ without the flag, and the message names the count. An edit on a strategy with **nothing bound is
135
+ unaffected**, and so is every path through the web editor.
136
+
137
+ **Send `confirm: true`.** That is the whole migration, and `confirm` is published on the input
138
+ schema — but nothing in the schema says WHEN it becomes mandatory, because the condition is the
139
+ bound-agent count rather than the shape of your body. The refusal rides the existing
140
+ `VALIDATION_ERROR` code, so a client that omits it discovers the rule at the refusal.
141
+
142
+ Why it moved to the server: the guard existed, but only as served prose the calling model could
143
+ decline — and did, twice in production on 2026-08-24. `archive_strategy` and
144
+ `rebind_intelligence_agent` have taken a server-enforced `confirm` all along; single-rule tuning
145
+ was the outlier among its own siblings, and it is the one that writes to scoring.
146
+
147
+ - **A radar deployment is refused when its strategy reads a session-field scalar** (50.0.0,
148
+ `own-scan-served-set-once`). `upsert_radar_deployment` refuses a deployment whose slot agents'
149
+ bound strategy carries a condition reading one of five SESSION-FIELD scalars — `fieldPlayers`,
150
+ `fieldUpBias`, `fieldBiasDir`, `captConc`, `picksSpread` — with
151
+ `CONDITION_OPERAND_UNSERVED_IN_LANE`. A body accepted under 49.5.0 is refused under 50.0.0
152
+ without one byte of it changing.
153
+
154
+ **Why a refusal and not a warning.** Those five describe a game SESSION, and radar runs outside a
155
+ session at BOTH its stages — so such a condition can never resolve there. The deployment formed
156
+ no fire edge and the agent did nothing on that coin, silently, forever. The refusal converts a
157
+ permanent silence into an error at the moment you author it.
158
+
159
+ **Migrate** by moving the clause to a scalar radar reads — the Market Breadth or Reference Pairs
160
+ families, which are market-wide reads with no session dimension — or by binding the strategy to
161
+ an arena agent instead. The error carries both halves: `allowedDomain` enumerates every servable
162
+ header, and the message names the sections.
163
+
164
+ **Strategy authoring is untouched by this bump.** The same strategy is legal, and reads those
165
+ scalars correctly, on an arena agent — which is why the refusal is on the DEPLOYMENT and not on
166
+ `compile_strategy_plan` / `apply_strategy_plan`.
167
+
75
168
  - **A benchmark-bound section no longer accepts crowd metrics or rank transforms** (49.0.0,
76
169
  `fix-benchmark-legality-save-path`). On a custom section carrying a non-null `benchmarkTicker`, a
77
170
  column whose metric is enrichment-stage (the `CROWD_*` family, `FLOW_ALIGN`, `SMART_RETAIL`,
@@ -123,11 +216,11 @@ refuse is now stored. Listed because a client that special-cased the refusal can
123
216
  that clause into its own LIVE condition and `conditionRef` it. `exit: true` is legal only under
124
217
  `clock: "CLOSE"` — an exit fired on a forming bar is an intrabar exit.
125
218
 
126
- - **A strategy requires a seven-key `entry` object** (44.0.0 for four keys, 47.0.0 for three more —
127
- `add-entry-on-close`, `add-level-trigger-execution`). Required on every CREATE, on
128
- `compile_strategy_plan`, `apply_strategy_plan` and `update_intelligence_agent`. A client sending
129
- 44.0.0's four-key object is refused under 47 with `entry.levelSource: Required` without one byte of
130
- it changing.
219
+ - **A strategy requires a six-key `entry` object** (44.0.0 for four keys, 47.0.0 for three more —
220
+ `add-entry-on-close`, `add-level-trigger-execution` — and 52.0.0 removed `levelSource`,
221
+ `derive-entry-level`). Required on every CREATE, on `compile_strategy_plan`, `apply_strategy_plan`
222
+ and `update_intelligence_agent`. A client sending 44.0.0's four-key object is refused with
223
+ `entry.levelOffsetAtrMultiple: Required` without one byte of it changing.
131
224
 
132
225
  ```jsonc
133
226
  "entry": {
@@ -135,14 +228,13 @@ refuse is now stored. Listed because a client that special-cased the refusal can
135
228
  "confirmTf": "4h", // the strategy timeframe or the rung below it — nothing else
136
229
  "closes": 1, // 1–5; must be 1 unless ON_CANDLE_CLOSE
137
230
  "bandAtrMultiple": 1.0, // > 0, and <= the platform's entry-deviation gate
138
- "levelSource": "SWING_HIGH", // | SWING_LOW | BOLLINGER_UPPER | BOLLINGER_LOWER
139
- "levelOffsetAtrMultiple": 0, // 0–2, UNSIGNED; must be 0 unless a level trigger
140
- "validForBars": 4 // 1–24 of the strategy's own bars
231
+ "levelOffsetAtrMultiple": 0, // 0–2, UNSIGNED distance from the derived edge; 0 unless a level trigger
232
+ "validForBars": 4 // 1–24 of the strategy's own bars; 4 unless a level trigger
141
233
  }
142
234
  ```
143
235
 
144
236
  `AT_SIGNAL` with those values is byte-identical to pre-44 behaviour. The legality matrix runs one
145
- way: all seven keys are always present, so a MEANINGFUL value under a trigger that ignores it is
237
+ way: all six keys are always present, so a MEANINGFUL value under a trigger that ignores it is
146
238
  refused rather than accepted-and-dropped — a dial never silently does nothing.
147
239
 
148
240
  - **A `strategyTimeframe` the platform does not ingest is refused** (39.0.0,
@@ -179,6 +271,31 @@ refuse is now stored. Listed because a client that special-cased the refusal can
179
271
 
180
272
  ### Reshaped output — the same call returns a different shape
181
273
 
274
+ - **The stored entry discipline no longer names a level source** (52.0.0, `derive-entry-level`).
275
+ `StrategyDTO.entry` (`get_strategy`, `list_strategies`, the `fork_strategy` / `archive_strategy` /
276
+ `restore_strategy` envelopes) and the apply envelope's `postState.entry` lose `levelSource`.
277
+ `TradeOutcomeDTO.entryDiscipline` (`get_trade_outcome_by_decision`, `list_trade_outcomes`) loses
278
+ it and gains `levelOffsetAtrMultiple: number | null` — the offset in force at the fire, `null` on
279
+ rows written before its column existed. A reader that rendered the level source renders the
280
+ geometry from `trigger` and the outcome's `direction` instead: which edge and which order shape
281
+ are a pure function of those two fields, so no stored copy is served.
282
+
283
+ - **`update_strategy_signal_rule` gains its own response envelope** (51.0.0,
284
+ `fix-signal-rule-patch-semantics`). It no longer shares `{ strategy }` with its siblings. The
285
+ response is `{ strategy, ruleChanges }`, where `ruleChanges` is the server's own before/after
286
+ pair for the edited signal — `[{ signalId, before, after }]`, each side a full rule object. It is
287
+ `null` when the mutation changed no rule, **never `[]`**.
288
+
289
+ **Report the change from that pair, not from memory.** The planner always computed the diff and
290
+ the tool discarded it, so a caller narrating what it just did had only its own recollection of
291
+ the before-value. One production edit shipped a wrong receipt on top of a wrong write that way,
292
+ and the write was unreconstructable from the audit trail afterwards.
293
+
294
+ Additive, but published on a `.strict()` shape — a decoder pinned to the old two-key object
295
+ rejects the new key. `fork_strategy`, `archive_strategy` and `restore_strategy` keep the shared
296
+ `StrategyResponseSchema` and publish exactly what they did; it was deliberately NOT widened for
297
+ them, so this reshape reaches one tool only.
298
+
182
299
  - **An entry void now names the gate that refused it** (49.5.0,
183
300
  `fix-arming-trigger-clock-authority`), on `get_radar_activity_summary`. In the cause rollup, the
184
301
  `ENTRY_VOID` group's `gateCode` widens from always-`null` to `QualificationGateCode | null`: a
@@ -223,6 +340,13 @@ refuse is now stored. Listed because a client that special-cased the refusal can
223
340
 
224
341
  ### Widened enum — new members your own copy rejects
225
342
 
343
+ - **`TradeExecutionFailureReason` gains `LEVEL_NOT_RESTABLE`** (52.0.0, `derive-entry-level`): a
344
+ level entry refused at placement because its resting price sat on the wrong side of the exchange
345
+ mid — a buy stop at or below it, a buy limit at or above it, and the mirror for a sell. It appears
346
+ on the `list_signal_logs` failure-reason filter input and on the signal-pipeline execution
347
+ summary's `failureReason`, with origin `CLIENT_GATE`. There is no distance limit: a level far from
348
+ the mark rests until its bar validity expires.
349
+
226
350
  - **The authorable metric vocabulary widens by 29 keys** (46.1.0, `add-indicator-catalog-coverage`):
227
351
  Keltner (`KC_UPPER`/`KC_MID`/`KC_LOWER`), Supertrend (`ST_LINE`/`ST_DIR`), Hull (`HMA20`),
228
352
  WaveTrend (`WT1`/`WT2`), QQE (`QQE_RSI_MA`/`QQE_STOP`), Parabolic SAR (`PSAR`), Ichimoku
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ import { type Implementation, type Prompt, type Resource } from '@modelcontextpr
49
49
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
50
50
  * correction. Never move it to track the server.
51
51
  */
52
- export declare const PACKAGE_VERSION = "31.2.7";
52
+ export declare const PACKAGE_VERSION = "31.2.9";
53
53
  export declare const DEFAULT_URL = "https://mcp.battlegrid.trade/mcp";
54
54
  export interface EnvConfig {
55
55
  apiKeys: string[];
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema
52
52
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
53
53
  * correction. Never move it to track the server.
54
54
  */
55
- export const PACKAGE_VERSION = '31.2.7';
55
+ export const PACKAGE_VERSION = '31.2.9';
56
56
  export const DEFAULT_URL = 'https://mcp.battlegrid.trade/mcp';
57
57
  const MAX_RETRIES = 3;
58
58
  const RETRY_DELAYS_MS = [2000, 4000, 8000];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@battlegrid/mcp-server",
3
- "version": "31.2.7",
3
+ "version": "31.2.9",
4
4
  "description": "BattleGrid MCP server — play crypto prediction games from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",