@stratabook/mcp 0.2.13 → 0.2.15

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
@@ -3,6 +3,23 @@
3
3
  All notable changes to the Strata SDKs (`@stratabook/sdk`, `@stratabook/mcp`,
4
4
  and the `strata-sdk` Rust crate) are recorded here. Versions move together.
5
5
 
6
+ ## 0.2.15
7
+
8
+ - Add explicit IntentBook prepare/submit tools in advanced mode and a one-call
9
+ post/permanent-revoke tool for locally configured Vault sessions.
10
+ - Apply the existing ask/limits/instant slider, USD budgets, capability gates,
11
+ exact retry behavior, and sponsored-fee explanation to intent controls.
12
+
13
+ ## 0.2.14
14
+
15
+ - Make `strata-mcp connect` the complete client-neutral trading setup flow:
16
+ choose limits in Strata, sign once, save the secret locally, and return to a
17
+ confirmed connected Control Center without environment variables or config
18
+ editing.
19
+ - Re-running `connect` now rotates an existing local session atomically, and
20
+ saves the new credential only after both activation and old-key revocation
21
+ are confirmed.
22
+
6
23
  ## 0.2.13
7
24
 
8
25
  - Add `strata-mcp connect`: generate the session secret locally, open the
package/README.md CHANGED
@@ -30,6 +30,11 @@ The browser and Strata receive only the public key:
30
30
  npx -y @stratabook/mcp connect
31
31
  ```
32
32
 
33
+ This opens the client-neutral Strata limit picker, registers the locally
34
+ generated key with one wallet signature, saves the credential privately, and
35
+ returns to the live Control Center. Run the same command again to replace the
36
+ old key atomically; the old key is revoked in the same signed transaction.
37
+
33
38
  There is no secret to paste into chat, no environment-variable screen, and no
34
39
  client config to edit after the read-only server is installed. Restart or
35
40
  refresh the MCP client after the browser confirms connection. Revoke the exact
@@ -99,6 +104,7 @@ Advanced mode additionally exposes the complete protocol surface, including:
99
104
  - `strata_market_making_submit_and_wait` — submit the externally signed preparation idempotently and wait for matching chain-derived state
100
105
  - `strata_market_making_strand_prepare`, `strata_market_making_strand_submit`
101
106
  - `strata_market_making_current_prepare`, `strata_market_making_current_submit`
107
+ - `strata_market_making_intent_prepare`, `strata_market_making_intent_submit` — control an existing curated IntentBook seat with a Vault session; Strata pays the fee
102
108
  - `strata_vault_status`
103
109
  - `strata_vault_setup`, `strata_vault_deposit`, `strata_vault_withdraw`, `strata_vault_delegate`, `strata_vault_policy`, `strata_vault_pause` — prepare owner actions with Strata as sponsored fee payer
104
110
  - `strata_vault_submit` — submit the owner-signed preparation; Strata pays and broadcasts
@@ -152,6 +158,12 @@ restart. Use `action: "stop"` through the same pair. The older product-specific
152
158
  tools remain available for strategies that deliberately manage every low-level
153
159
  array and safety field.
154
160
 
161
+ With a local trading session, `strata_market_making_intent_execute` posts or
162
+ permanently revokes an existing curated IntentBook seat in one call under the
163
+ same ask/limits/instant slider. The session signs only the SDK-verified packet;
164
+ the owner wallet does not sign each update. Exact submit retries return the
165
+ original confirmed signature while the packet remains live.
166
+
155
167
  For maker funding, initialize the market Vault if needed, activate the Strand
156
168
  or Current, then deposit with `strata_vault_deposit`. The market keeps that
157
169
  available collateral while a control is live and returns it after the final
@@ -17,8 +17,9 @@
17
17
  * caps (the owner's per-asset limits, tolerance, interval,
18
18
  * expiry). Hyperliquid-style.
19
19
  *
20
- * Withdraw, policy, pause, and revoke are never on this slider — they always
21
- * require the owner's wallet. The on-chain session caps are the hard ceiling;
20
+ * Vault withdrawal, policy, session rotation, and pause are never on this
21
+ * slider — they always require the owner's wallet. An IntentBook seat's own
22
+ * session-authorized revoke remains available as its permanent risk exit. The on-chain session caps are the hard ceiling;
22
23
  * this slider is a softer layer above them. An agent can *read* the level
23
24
  * (`strata_autonomy`) and *offer* to change it, but nothing an agent calls can
24
25
  * raise its own autonomy: the only knob is the human's (env / Agents page).
@@ -17,8 +17,9 @@
17
17
  * caps (the owner's per-asset limits, tolerance, interval,
18
18
  * expiry). Hyperliquid-style.
19
19
  *
20
- * Withdraw, policy, pause, and revoke are never on this slider — they always
21
- * require the owner's wallet. The on-chain session caps are the hard ceiling;
20
+ * Vault withdrawal, policy, session rotation, and pause are never on this
21
+ * slider — they always require the owner's wallet. An IntentBook seat's own
22
+ * session-authorized revoke remains available as its permanent risk exit. The on-chain session caps are the hard ceiling;
22
23
  * this slider is a softer layer above them. An agent can *read* the level
23
24
  * (`strata_autonomy`) and *offer* to change it, but nothing an agent calls can
24
25
  * raise its own autonomy: the only knob is the human's (env / Agents page).
package/dist/src/cli.js CHANGED
@@ -88,9 +88,10 @@ Options:
88
88
  --no-open Print the wallet pairing URL without opening a browser
89
89
 
90
90
  Read-only markets, books, and Sonar quotes work immediately. Run strata-mcp
91
- doctor to check them. Run strata-mcp connect once to register a local session
92
- with the owner wallet; there are no secrets to copy or environment variables
93
- to edit. Run strata-mcp disconnect to revoke it. Strata never asks for seed phrases.
91
+ doctor to check them. Run strata-mcp connect to choose limits and register a
92
+ local session with one owner signature; run it again to replace that session
93
+ atomically. There are no secrets to copy or environment variables to edit.
94
+ Run strata-mcp disconnect to revoke it. Strata never asks for seed phrases.
94
95
  `);
95
96
  }
96
97
  async function runDoctor(options, sessionEnv) {
@@ -49,7 +49,7 @@ export declare const STRATA_AGENT_HARNESS: {
49
49
  readonly instruction: "When an owner requests a Vault pause or resume and vault.pause is live, prepare the exact transaction with the official SDK, verify that the wallet and requested state are unchanged, then have the owner-configured signer sign and broadcast it externally. Preparation alone does not change state.";
50
50
  }, {
51
51
  readonly id: "onboard_vault";
52
- readonly instruction: "Onboarding is one owner signature, once: register the external session key with vault.setup (only the wallet and the session key are required; one session then trades every market) or simply name the session key on the first vault.deposit, which registers it in the same transaction. Policy fields — expiry, cadence, tolerance, per-asset limits — are optional. Verify every echoed field and the prepared transaction before external owner signing and broadcast; retain the session key only in the owner's signer.";
52
+ readonly instruction: "Onboarding is one owner signature: register the external session key with vault.setup (only the wallet and the session key are required; one session then trades every market) or simply name the session key on the first vault.deposit, which registers it in the same transaction. Policy fields — expiry, cadence, tolerance, per-asset limits — are optional. Supplying replace_session_public_key atomically revokes the old key while registering the new one. Verify every echoed field and the prepared transaction before external owner signing and broadcast; retain the session key only in the owner's signer.";
53
53
  }, {
54
54
  readonly id: "fund_vault";
55
55
  readonly instruction: "When vault.deposit is live, select an asset from the discovered market and use an exact positive atomic amount. Verify the echoed owner, market, asset, and amount plus the prepared transaction before external owner signing and broadcast.";
@@ -64,7 +64,7 @@ export declare const STRATA_AGENT_HARNESS: {
64
64
  readonly instruction: "When vault.policy.manage is live, use blocked mode with no allowed wallets to freeze withdrawals or restricted mode with one to eight exact destination-owner wallets. Verify the echoed mode and complete wallet list before external owner signing and broadcast.";
65
65
  }, {
66
66
  readonly id: "reconcile_maker_status";
67
- readonly instruction: "When mm.status.read is live, read the maker's products by wallet address (public, no signature) before and after any maker action: resting firm orders, the intent budget, live signed quotes, each Strand and Current with its remaining exposure and expiry, oracle health, and armed dead-man guards. Reconcile against what the agent believes it posted; treat missing, expired, or disabled products as not quoting.";
67
+ readonly instruction: "When mm.status.read is live, read the maker's products by wallet address (public, no signature) before and after any maker action: resting firm orders, live signed quotes, each Strand and Current with its remaining exposure and expiry, oracle health, and armed dead-man guards. Reconcile against what the agent believes it posted; treat missing, expired, or disabled products as not quoting.";
68
68
  }, {
69
69
  readonly id: "stream_maker_fills";
70
70
  readonly instruction: "When mm.fills.stream is live, keep one maker stream open per market through the official SDK by wallet address (public, no signature): start from the maker snapshot, apply only contiguous maker_fill and maker_status events, and recover any gap or reconnect from a fresh snapshot. Reconcile every fill and exposure change against the maker's own state before quoting further.";
@@ -551,4 +551,4 @@ export declare const STRATA_ACTION_GRAPH: {
551
551
  }];
552
552
  };
553
553
  export declare const STRATA_ACTION_GRAPH_URI = "strata://action-graph/v1";
554
- export declare const STRATA_AGENT_HARNESS_INSTRUCTIONS = "Strata read-only tools work immediately with no wallet, approval, session key, or environment setup. Call the requested tool directly: use strata_quote for a quote, strata_markets only when the market is unknown, and strata_portfolio for a public account. Do not begin with capability, graph, status, or market discovery unless the requested tool is unavailable, the objective is advanced or ambiguous, or the user asks what Strata supports. Simple MCP tools accept exact human amounts such as 0.1 SOL, 20 USDC, or $20; advanced and SDK interfaces also accept token atoms. Never request or accept a session secret in chat. A session is needed only when the user asks to sign a trading write; if it is missing, strata_trade returns a read-only quote and the single setup link https://stratabook.app/agents. The user can run npx -y @stratabook/mcp connect to generate and store the secret locally while the Agents page receives only its public key. Revoke, withdraw, pause, and policy changes are owner-wallet actions and never use the session signer. Strata Agent Harness 1.0. The action-graph resources are optional references for advanced integrations. The external agent owner controls permission and signer authority. Strata accepts public keys, detached signatures, and signed transactions, never private keys or seed phrases. Check quote bindings, labelled fees, minimum output, price impact, tolerance, and expiry. When portfolio.read is live, read the owner's live Vault portfolio before sizing a write and treat null USD totals as an incomplete valuation, never as zero. When mm.status.read is live, reconcile the owner's maker products and exposure before and after maker actions. For normal Strand or Current operation, prefer strata_market_making_prepare, sign only its prepared transaction externally, then pass its unchanged preparationToken with the signed transaction to strata_market_making_submit_and_wait. Advanced order, TWAP, and execution integrations may use explicit challenge, prepare, verify, sign, and submit flows with idempotency. Stop on ambiguity, sequence gaps, unavailable capabilities, paused markets, inconsistent bindings, expiry, or missing signer authority.";
554
+ export declare const STRATA_AGENT_HARNESS_INSTRUCTIONS = "Strata read-only tools work immediately with no wallet, approval, session key, or environment setup. Call the requested tool directly: use strata_quote for a quote, strata_markets only when the market is unknown, and strata_portfolio for a public account. Do not begin with capability, graph, status, or market discovery unless the requested tool is unavailable, the objective is advanced or ambiguous, or the user asks what Strata supports. Simple MCP tools accept exact human amounts such as 0.1 SOL, 20 USDC, or $20; advanced and SDK interfaces also accept token atoms. Never request or accept a session secret in chat. A session is needed only when the user asks to sign a trading write; if it is missing, strata_trade returns a read-only quote and the single setup link https://stratabook.app/agents. The user can run npx -y @stratabook/mcp connect to generate the secret locally, choose on-chain limits, and register with one owner signature while the Agents page receives only the public key. Rerunning the command atomically replaces the old session. Revoke, withdraw, pause, and policy changes are owner-wallet actions and never use the session signer. Strata Agent Harness 1.0. The action-graph resources are optional references for advanced integrations. The external agent owner controls permission and signer authority. Strata accepts public keys, detached signatures, and signed transactions, never private keys or seed phrases. Check quote bindings, labelled fees, minimum output, price impact, tolerance, and expiry. When portfolio.read is live, read the owner's live Vault portfolio before sizing a write and treat null USD totals as an incomplete valuation, never as zero. When mm.status.read is live, reconcile the owner's maker products and exposure before and after maker actions. For normal Strand or Current operation, prefer strata_market_making_prepare, sign only its prepared transaction externally, then pass its unchanged preparationToken with the signed transaction to strata_market_making_submit_and_wait. Advanced order, TWAP, and execution integrations may use explicit challenge, prepare, verify, sign, and submit flows with idempotency. Stop on ambiguity, sequence gaps, unavailable capabilities, paused markets, inconsistent bindings, expiry, or missing signer authority.";
@@ -141,7 +141,7 @@ export const STRATA_AGENT_HARNESS = {
141
141
  },
142
142
  {
143
143
  "id": "onboard_vault",
144
- "instruction": "Onboarding is one owner signature, once: register the external session key with vault.setup (only the wallet and the session key are required; one session then trades every market) or simply name the session key on the first vault.deposit, which registers it in the same transaction. Policy fields — expiry, cadence, tolerance, per-asset limits — are optional. Verify every echoed field and the prepared transaction before external owner signing and broadcast; retain the session key only in the owner's signer."
144
+ "instruction": "Onboarding is one owner signature: register the external session key with vault.setup (only the wallet and the session key are required; one session then trades every market) or simply name the session key on the first vault.deposit, which registers it in the same transaction. Policy fields — expiry, cadence, tolerance, per-asset limits — are optional. Supplying replace_session_public_key atomically revokes the old key while registering the new one. Verify every echoed field and the prepared transaction before external owner signing and broadcast; retain the session key only in the owner's signer."
145
145
  },
146
146
  {
147
147
  "id": "fund_vault",
@@ -161,7 +161,7 @@ export const STRATA_AGENT_HARNESS = {
161
161
  },
162
162
  {
163
163
  "id": "reconcile_maker_status",
164
- "instruction": "When mm.status.read is live, read the maker's products by wallet address (public, no signature) before and after any maker action: resting firm orders, the intent budget, live signed quotes, each Strand and Current with its remaining exposure and expiry, oracle health, and armed dead-man guards. Reconcile against what the agent believes it posted; treat missing, expired, or disabled products as not quoting."
164
+ "instruction": "When mm.status.read is live, read the maker's products by wallet address (public, no signature) before and after any maker action: resting firm orders, live signed quotes, each Strand and Current with its remaining exposure and expiry, oracle health, and armed dead-man guards. Reconcile against what the agent believes it posted; treat missing, expired, or disabled products as not quoting."
165
165
  },
166
166
  {
167
167
  "id": "stream_maker_fills",
@@ -789,4 +789,4 @@ export const STRATA_ACTION_GRAPH = {
789
789
  ]
790
790
  };
791
791
  export const STRATA_ACTION_GRAPH_URI = "strata://action-graph/v1";
792
- export const STRATA_AGENT_HARNESS_INSTRUCTIONS = "Strata read-only tools work immediately with no wallet, approval, session key, or environment setup. Call the requested tool directly: use strata_quote for a quote, strata_markets only when the market is unknown, and strata_portfolio for a public account. Do not begin with capability, graph, status, or market discovery unless the requested tool is unavailable, the objective is advanced or ambiguous, or the user asks what Strata supports. Simple MCP tools accept exact human amounts such as 0.1 SOL, 20 USDC, or $20; advanced and SDK interfaces also accept token atoms. Never request or accept a session secret in chat. A session is needed only when the user asks to sign a trading write; if it is missing, strata_trade returns a read-only quote and the single setup link https://stratabook.app/agents. The user can run npx -y @stratabook/mcp connect to generate and store the secret locally while the Agents page receives only its public key. Revoke, withdraw, pause, and policy changes are owner-wallet actions and never use the session signer. Strata Agent Harness 1.0. The action-graph resources are optional references for advanced integrations. The external agent owner controls permission and signer authority. Strata accepts public keys, detached signatures, and signed transactions, never private keys or seed phrases. Check quote bindings, labelled fees, minimum output, price impact, tolerance, and expiry. When portfolio.read is live, read the owner's live Vault portfolio before sizing a write and treat null USD totals as an incomplete valuation, never as zero. When mm.status.read is live, reconcile the owner's maker products and exposure before and after maker actions. For normal Strand or Current operation, prefer strata_market_making_prepare, sign only its prepared transaction externally, then pass its unchanged preparationToken with the signed transaction to strata_market_making_submit_and_wait. Advanced order, TWAP, and execution integrations may use explicit challenge, prepare, verify, sign, and submit flows with idempotency. Stop on ambiguity, sequence gaps, unavailable capabilities, paused markets, inconsistent bindings, expiry, or missing signer authority.";
792
+ export const STRATA_AGENT_HARNESS_INSTRUCTIONS = "Strata read-only tools work immediately with no wallet, approval, session key, or environment setup. Call the requested tool directly: use strata_quote for a quote, strata_markets only when the market is unknown, and strata_portfolio for a public account. Do not begin with capability, graph, status, or market discovery unless the requested tool is unavailable, the objective is advanced or ambiguous, or the user asks what Strata supports. Simple MCP tools accept exact human amounts such as 0.1 SOL, 20 USDC, or $20; advanced and SDK interfaces also accept token atoms. Never request or accept a session secret in chat. A session is needed only when the user asks to sign a trading write; if it is missing, strata_trade returns a read-only quote and the single setup link https://stratabook.app/agents. The user can run npx -y @stratabook/mcp connect to generate the secret locally, choose on-chain limits, and register with one owner signature while the Agents page receives only the public key. Rerunning the command atomically replaces the old session. Revoke, withdraw, pause, and policy changes are owner-wallet actions and never use the session signer. Strata Agent Harness 1.0. The action-graph resources are optional references for advanced integrations. The external agent owner controls permission and signer authority. Strata accepts public keys, detached signatures, and signed transactions, never private keys or seed phrases. Check quote bindings, labelled fees, minimum output, price impact, tolerance, and expiry. When portfolio.read is live, read the owner's live Vault portfolio before sizing a write and treat null USD totals as an incomplete valuation, never as zero. When mm.status.read is live, reconcile the owner's maker products and exposure before and after maker actions. For normal Strand or Current operation, prefer strata_market_making_prepare, sign only its prepared transaction externally, then pass its unchanged preparationToken with the signed transaction to strata_market_making_submit_and_wait. Advanced order, TWAP, and execution integrations may use explicit challenge, prepare, verify, sign, and submit flows with idempotency. Stop on ambiguity, sequence gaps, unavailable capabilities, paused markets, inconsistent bindings, expiry, or missing signer authority.";
@@ -23,4 +23,5 @@ export declare function writeTradingConnection(connection: StoredTradingConnecti
23
23
  export declare function removeTradingConnection(path?: string): Promise<void>;
24
24
  /** Explicit environment variables win; otherwise load the private local file. */
25
25
  export declare function loadTradingEnvironment(env?: Readonly<Record<string, string | undefined>>): Promise<Record<string, string | undefined>>;
26
+ export declare function pairingPageUrl(webBase: string, action: PairingAction, sessionPublicKey: string, callbackUrl: string, existing: Pick<StoredTradingConnection, "owner_wallet" | "session_public_key"> | null): string;
26
27
  export declare function runLocalPairing(options: PairingOptions): Promise<void>;
@@ -114,41 +114,50 @@ function launchBrowser(url) {
114
114
  child.on("error", () => undefined);
115
115
  child.unref();
116
116
  }
117
- function successHtml(action, wallet) {
118
- const connected = action === "connect";
119
- return `<!doctype html>
120
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
121
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'">
122
- <title>Strata agent ${connected ? "connected" : "disconnected"}</title>
123
- <style>html{color-scheme:dark}body{margin:0;min-height:100vh;display:grid;place-items:center;background:#080b11;color:#e2e8f0;font:15px Inter,system-ui,sans-serif}.card{width:min(520px,calc(100% - 48px));padding:28px;border:1px solid transparent;border-radius:16px;background:linear-gradient(#10141c,#090c12) padding-box,linear-gradient(120deg,#22d3ee,#8b5cf6) border-box;box-shadow:0 22px 80px #0008}h1{margin:0 0 10px;color:white;font-size:24px}p{line-height:1.55;color:#94a3b8}.ok{color:#34d399;font-weight:700}.mono{font-family:ui-monospace,monospace;color:#cbd5e1}</style>
124
- </head><body><main class="card"><div class="ok">${connected ? "CONNECTED" : "REVOKED"}</div>
125
- <h1>${connected ? "Your agent can trade" : "Agent access removed"}</h1>
126
- <p>${connected ? "The session secret was saved only on this computer. Restart or refresh your MCP client, then trade normally." : "The local trading credential has been removed. Read-only Strata tools still work."}</p>
127
- <p class="mono">Wallet ${wallet.slice(0, 6)}…${wallet.slice(-6)}</p></main></body></html>`;
117
+ export function pairingPageUrl(webBase, action, sessionPublicKey, callbackUrl, existing) {
118
+ const agentUrl = new URL("/agents", pairingWebBase(webBase));
119
+ agentUrl.searchParams.set("pair", action);
120
+ agentUrl.searchParams.set("session_public_key", sessionPublicKey);
121
+ if (existing)
122
+ agentUrl.searchParams.set("owner_wallet", existing.owner_wallet);
123
+ if (action === "connect" && existing) {
124
+ agentUrl.searchParams.set("replace_session_public_key", existing.session_public_key);
125
+ }
126
+ agentUrl.searchParams.set("callback", callbackUrl);
127
+ return agentUrl.toString();
128
128
  }
129
- async function waitForOnChainSession(apiBase, action, ownerWallet, sessionPublicKey) {
129
+ async function waitForOnChainSession(apiBase, action, ownerWallet, sessionPublicKey, replaceSessionPublicKey) {
130
130
  const deadline = Date.now() + 60_000;
131
131
  let lastState = "unavailable";
132
+ const stateFor = async (key) => {
133
+ const query = new URLSearchParams({
134
+ wallet_address: ownerWallet,
135
+ session_public_key: key,
136
+ });
137
+ const response = await fetch(`${apiBase.replace(/\/$/, "")}/v2/vault/status?${query.toString()}`, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(10_000) });
138
+ if (!response.ok) {
139
+ lastState = `http_${response.status}`;
140
+ return null;
141
+ }
142
+ const body = await response.json();
143
+ if (body.wallet_address !== ownerWallet)
144
+ return null;
145
+ if (!body.session)
146
+ return "absent";
147
+ if (body.session.session_public_key !== key || typeof body.session.state !== "string")
148
+ return null;
149
+ return body.session.state;
150
+ };
132
151
  while (Date.now() < deadline) {
133
152
  try {
134
- const query = new URLSearchParams({
135
- wallet_address: ownerWallet,
136
- session_public_key: sessionPublicKey,
137
- });
138
- const response = await fetch(`${apiBase.replace(/\/$/, "")}/v2/vault/status?${query.toString()}`, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(10_000) });
139
- if (response.ok) {
140
- const body = await response.json();
141
- if (body.wallet_address === ownerWallet) {
142
- lastState = typeof body.session?.state === "string" ? body.session.state : "absent";
143
- const exactSession = body.session?.session_public_key === sessionPublicKey;
144
- if (action === "connect" && exactSession && body.session?.state === "active")
145
- return;
146
- if (action === "disconnect" && (!body.session || (exactSession && body.session.state === "absent")))
147
- return;
148
- }
149
- }
150
- else {
151
- lastState = `http_${response.status}`;
153
+ const state = await stateFor(sessionPublicKey);
154
+ lastState = state ?? lastState;
155
+ if (action === "disconnect" && state === "absent")
156
+ return;
157
+ if (action === "connect" && state === "active") {
158
+ if (!replaceSessionPublicKey || await stateFor(replaceSessionPublicKey) === "absent")
159
+ return;
160
+ lastState = "old_session_still_active";
152
161
  }
153
162
  }
154
163
  catch (error) {
@@ -159,7 +168,7 @@ async function waitForOnChainSession(apiBase, action, ownerWallet, sessionPublic
159
168
  throw new Error(`Strata could not confirm the ${action === "connect" ? "active" : "revoked"} session on-chain `
160
169
  + `(last state: ${lastState}). The local credential was not changed.`);
161
170
  }
162
- async function waitForPairingCallback(action, sessionPublicKey, state, onComplete) {
171
+ async function waitForPairingCallback(sessionPublicKey, state, returnUrl, onComplete) {
163
172
  let settle;
164
173
  let reject;
165
174
  const completion = new Promise((resolvePromise, rejectPromise) => {
@@ -189,13 +198,13 @@ async function waitForPairingCallback(action, sessionPublicKey, state, onComplet
189
198
  finished = true;
190
199
  try {
191
200
  await onComplete(ownerWallet);
192
- response.writeHead(200, {
193
- "content-type": "text/html; charset=utf-8",
201
+ response.writeHead(303, {
202
+ location: returnUrl,
194
203
  "cache-control": "no-store",
195
204
  "x-content-type-options": "nosniff",
196
205
  "x-frame-options": "DENY",
197
206
  });
198
- response.end(successHtml(action, ownerWallet));
207
+ response.end();
199
208
  settle(ownerWallet);
200
209
  }
201
210
  catch (error) {
@@ -238,16 +247,12 @@ export async function runLocalPairing(options) {
238
247
  const env = options.env ?? process.env;
239
248
  const path = options.credentialsFile ?? tradingCredentialsPath(env);
240
249
  const existing = await readTradingConnection(path);
241
- if (options.action === "connect" && existing) {
242
- throw new Error(`Trading is already connected for wallet ${existing.owner_wallet}. `
243
- + "Run strata-mcp disconnect before replacing this session.");
244
- }
245
250
  if (options.action === "disconnect" && !existing) {
246
251
  process.stdout.write("Strata trading is not connected. Read-only tools remain ready.\n");
247
252
  return;
248
253
  }
249
254
  let generated;
250
- if (existing) {
255
+ if (options.action === "disconnect" && existing) {
251
256
  generated = existing;
252
257
  }
253
258
  else {
@@ -262,8 +267,14 @@ export async function runLocalPairing(options) {
262
267
  };
263
268
  }
264
269
  const state = randomBytes(24).toString("hex");
265
- const callback = await waitForPairingCallback(options.action, generated.session_public_key, state, async (ownerWallet) => {
266
- await waitForOnChainSession(options.apiBase ?? DEFAULT_API_BASE, options.action, ownerWallet, generated.session_public_key);
270
+ const webBase = pairingWebBase(options.webBase ?? DEFAULT_PAIRING_WEB_BASE);
271
+ const returnUrl = new URL("/agents", webBase);
272
+ returnUrl.searchParams.set("paired", options.action === "connect" ? "connected" : "revoked");
273
+ const replaceSessionPublicKey = options.action === "connect"
274
+ ? existing?.session_public_key ?? null
275
+ : null;
276
+ const callback = await waitForPairingCallback(generated.session_public_key, state, returnUrl.toString(), async (ownerWallet) => {
277
+ await waitForOnChainSession(options.apiBase ?? DEFAULT_API_BASE, options.action, ownerWallet, generated.session_public_key, replaceSessionPublicKey);
267
278
  if (options.action === "disconnect") {
268
279
  if (existing?.owner_wallet !== ownerWallet) {
269
280
  throw new Error("the revoking wallet does not own this local Strata connection");
@@ -277,14 +288,10 @@ export async function runLocalPairing(options) {
277
288
  connected_at_ms: (options.nowMs ?? Date.now)(),
278
289
  }, path);
279
290
  });
280
- const webBase = pairingWebBase(options.webBase ?? DEFAULT_PAIRING_WEB_BASE);
281
- const agentUrl = new URL("/agents", webBase);
282
- agentUrl.searchParams.set("pair", options.action);
283
- agentUrl.searchParams.set("session_public_key", generated.session_public_key);
284
- agentUrl.searchParams.set("callback", callback.callbackUrl);
285
- process.stdout.write(`${options.action === "connect" ? "Connect" : "Revoke"} Strata agent access in your browser:\n${agentUrl.toString()}\n\n`);
291
+ const agentUrl = pairingPageUrl(webBase, options.action, generated.session_public_key, callback.callbackUrl, existing);
292
+ process.stdout.write(`${options.action === "disconnect" ? "Revoke" : replaceSessionPublicKey ? "Replace" : "Connect"} Strata agent access in your browser:\n${agentUrl}\n\n`);
286
293
  if (options.openBrowser !== false)
287
- launchBrowser(agentUrl.toString());
294
+ launchBrowser(agentUrl);
288
295
  process.stdout.write("Waiting for the owner-wallet signature…\n");
289
296
  const ownerWallet = await callback.completion;
290
297
  process.stdout.write(options.action === "connect"
@@ -173,6 +173,9 @@ const LEGACY_TOOL_CAPABILITIES = {
173
173
  strata_market_making_strand_submit: { ids: ["mm.strand.manage"] },
174
174
  strata_market_making_current_prepare: { ids: ["mm.current.manage"] },
175
175
  strata_market_making_current_submit: { ids: ["mm.current.manage"] },
176
+ strata_market_making_intent_prepare: { ids: ["mm.intent.manage"] },
177
+ strata_market_making_intent_submit: { ids: ["mm.intent.manage"] },
178
+ strata_market_making_intent_execute: { ids: ["mm.intent.manage"] },
176
179
  strata_market_making_prepare: { ids: ["mm.strand.manage", "mm.current.manage"], match: "any" },
177
180
  strata_market_making_submit_and_wait: { ids: ["mm.strand.manage", "mm.current.manage"], match: "any" },
178
181
  strata_execute_quote: { ids: ["trade.submit"] },
@@ -216,6 +219,9 @@ const PLATFORM_TOOL_CAPABILITIES = {
216
219
  strata_market_making_strand_submit: { ids: ["mm.strand.manage"] },
217
220
  strata_market_making_current_prepare: { ids: ["mm.current.manage"] },
218
221
  strata_market_making_current_submit: { ids: ["mm.current.manage"] },
222
+ strata_market_making_intent_prepare: { ids: ["mm.intent.manage"] },
223
+ strata_market_making_intent_submit: { ids: ["mm.intent.manage"] },
224
+ strata_market_making_intent_execute: { ids: ["mm.intent.manage"] },
219
225
  strata_market_making_prepare: { ids: ["mm.strand.manage", "mm.current.manage"], match: "any" },
220
226
  strata_market_making_submit_and_wait: { ids: ["mm.strand.manage", "mm.current.manage"], match: "any" },
221
227
  strata_rewards: { ids: ["rewards.read"] },
@@ -524,7 +530,7 @@ export async function createStrataMcpServer(options = {}) {
524
530
  },
525
531
  }, async ({ marketId, walletAddress }) => {
526
532
  const response = await platformClient.marketMaking.status(marketId, walletAddress);
527
- return toolResult(response, `${response.active_products} active maker products; reconcile intent, Strand, Current, signed-quote, and dead-man state before changing exposure.`);
533
+ return toolResult(response, `${response.active_products} active maker products; reconcile Strand, Current, signed-quote, and dead-man state before changing exposure.`);
528
534
  });
529
535
  registerTool("strata_market_making_reputation", {
530
536
  title: "Read Strata maker reputation",
@@ -2059,6 +2065,76 @@ export async function createStrataMcpServer(options = {}) {
2059
2065
  });
2060
2066
  return toolResult(response, `Submitted ${response.action} as ${response.signature}.`);
2061
2067
  }));
2068
+ const makerIntentPrepare = registerTool("strata_market_making_intent_prepare", {
2069
+ title: "Prepare Strata IntentBook control",
2070
+ description: "Prepare one sponsored Vault-session transaction for an existing curated IntentBook seat. "
2071
+ + "Post updates its side, price band, and maximum fill. Revoke permanently closes the seat; "
2072
+ + "it cannot be posted again. The owner wallet does not sign each update.",
2073
+ inputSchema: {
2074
+ marketId: makerMarketIdSchema,
2075
+ action: z.enum(["post", "revoke"]),
2076
+ ownerWallet: makerPublicKeySchema,
2077
+ sessionPublicKey: makerPublicKeySchema,
2078
+ side: z.enum(["buy", "sell", "both"]).optional(),
2079
+ minPriceAtoms: makerPositiveAtomicSchema.optional(),
2080
+ maxPriceAtoms: makerPositiveAtomicSchema.optional(),
2081
+ maxFillSizeAtoms: makerPositiveAtomicSchema.optional(),
2082
+ },
2083
+ annotations: {
2084
+ readOnlyHint: false,
2085
+ destructiveHint: true,
2086
+ idempotentHint: false,
2087
+ openWorldHint: true,
2088
+ },
2089
+ }, async (args) => guardedTool(client, "mm.intent.manage", async () => {
2090
+ let request;
2091
+ if (args.action === "revoke") {
2092
+ request = {
2093
+ action: "revoke",
2094
+ ownerWallet: args.ownerWallet,
2095
+ sessionPublicKey: args.sessionPublicKey,
2096
+ };
2097
+ }
2098
+ else {
2099
+ if (args.side === undefined
2100
+ || args.minPriceAtoms === undefined
2101
+ || args.maxPriceAtoms === undefined
2102
+ || args.maxFillSizeAtoms === undefined) {
2103
+ return toolError("invalid_request", "Intent post requires side, minPriceAtoms, maxPriceAtoms, and maxFillSizeAtoms.", false);
2104
+ }
2105
+ request = {
2106
+ action: "post",
2107
+ ownerWallet: args.ownerWallet,
2108
+ sessionPublicKey: args.sessionPublicKey,
2109
+ side: args.side,
2110
+ minPriceAtoms: args.minPriceAtoms,
2111
+ maxPriceAtoms: args.maxPriceAtoms,
2112
+ maxFillSizeAtoms: args.maxFillSizeAtoms,
2113
+ };
2114
+ }
2115
+ const response = await platformClient.marketMaking.intent.prepare(args.marketId, request);
2116
+ return toolResult(response, `Prepared IntentBook ${response.action}; verify and add only the session signature before ${response.expires_at_ms}. Strata pays the network fee.`);
2117
+ }));
2118
+ const makerIntentSubmit = registerTool("strata_market_making_intent_submit", {
2119
+ title: "Submit Strata IntentBook control",
2120
+ description: "Submit the exact session-signed IntentBook packet. Exact retries during the packet's live "
2121
+ + "blockhash window return the original confirmed signature.",
2122
+ inputSchema: {
2123
+ marketId: makerMarketIdSchema,
2124
+ signedTransactionBase64: makerBase64Schema,
2125
+ },
2126
+ annotations: {
2127
+ readOnlyHint: false,
2128
+ destructiveHint: true,
2129
+ idempotentHint: true,
2130
+ openWorldHint: true,
2131
+ },
2132
+ }, async ({ marketId, signedTransactionBase64 }) => guardedTool(client, "mm.intent.manage", async () => {
2133
+ const response = await platformClient.marketMaking.intent.submit(marketId, {
2134
+ signedTransactionBase64,
2135
+ });
2136
+ return toolResult(response, `Submitted IntentBook control as ${response.signature}.`);
2137
+ }));
2062
2138
  registerAutonomyTools(registerTool, client, platformClient, options.sessionAutonomy, () => typeof Date !== "undefined" ? Date.now() : 0);
2063
2139
  void [
2064
2140
  markets,
@@ -2077,6 +2153,8 @@ export async function createStrataMcpServer(options = {}) {
2077
2153
  makerStrandSubmit,
2078
2154
  makerCurrentPrepare,
2079
2155
  makerCurrentSubmit,
2156
+ makerIntentPrepare,
2157
+ makerIntentSubmit,
2080
2158
  ];
2081
2159
  applyToolAvailability(registeredTools, initialCatalog, initialPlatformCatalog, toolMode);
2082
2160
  let closed = false;
@@ -2175,6 +2253,65 @@ function registerAutonomyTools(registerTool, client, platformClient, autonomy, n
2175
2253
  };
2176
2254
  };
2177
2255
  const refuse = (reason, prepared, summary) => toolResult({ executed: false, reason, prepared }, summary);
2256
+ // ── one-shot existing IntentBook seat control ──────────────────────────────
2257
+ registerTool("strata_market_making_intent_execute", {
2258
+ title: "Execute Strata IntentBook control",
2259
+ description: "Post or permanently revoke an existing curated IntentBook seat in one call. The configured "
2260
+ + "Vault session verifies and signs; Strata pays the network fee. Under ask, or above a limits "
2261
+ + "ceiling, this prepares the exact packet but does not sign it.",
2262
+ inputSchema: {
2263
+ marketId: makerMarketIdSchema,
2264
+ action: z.enum(["post", "revoke"]),
2265
+ side: z.enum(["buy", "sell", "both"]).optional(),
2266
+ minPriceAtoms: makerPositiveAtomicSchema.optional(),
2267
+ maxPriceAtoms: makerPositiveAtomicSchema.optional(),
2268
+ maxFillSizeAtoms: makerPositiveAtomicSchema.optional(),
2269
+ },
2270
+ annotations: {
2271
+ readOnlyHint: false,
2272
+ destructiveHint: true,
2273
+ idempotentHint: false,
2274
+ openWorldHint: true,
2275
+ },
2276
+ }, async (args) => guardedTool(client, "mm.intent.manage", async () => {
2277
+ let operation;
2278
+ if (args.action === "revoke") {
2279
+ operation = { action: "revoke", ownerWallet: autonomy.ownerWallet };
2280
+ }
2281
+ else {
2282
+ if (args.side === undefined
2283
+ || args.minPriceAtoms === undefined
2284
+ || args.maxPriceAtoms === undefined
2285
+ || args.maxFillSizeAtoms === undefined) {
2286
+ return toolError("invalid_request", "Intent post requires side, minPriceAtoms, maxPriceAtoms, and maxFillSizeAtoms.", false);
2287
+ }
2288
+ operation = {
2289
+ action: "post",
2290
+ ownerWallet: autonomy.ownerWallet,
2291
+ side: args.side,
2292
+ minPriceAtoms: args.minPriceAtoms,
2293
+ maxPriceAtoms: args.maxPriceAtoms,
2294
+ maxFillSizeAtoms: args.maxFillSizeAtoms,
2295
+ };
2296
+ }
2297
+ const baseAtoms = operation.action === "post" ? BigInt(operation.maxFillSizeAtoms) : 0n;
2298
+ const notional = await estimateBaseNotionalUsd(resolver, markFor, args.marketId, baseAtoms);
2299
+ const decision = decideAutonomy(autonomy, args.marketId, notional, nowMs());
2300
+ if (!decision.allow) {
2301
+ const prepared = await platformClient.marketMaking.intent.prepare(args.marketId, {
2302
+ ...operation,
2303
+ sessionPublicKey: autonomy.signer.publicKey,
2304
+ });
2305
+ return refuse(decision.reason, prepared, decision.reason);
2306
+ }
2307
+ const receipt = await platformClient.marketMaking.intent.execute(args.marketId, {
2308
+ operation,
2309
+ signer: autonomy.signer,
2310
+ });
2311
+ if (notional !== null)
2312
+ autonomy.dailyBudget.record(notional, nowMs());
2313
+ return toolResult({ executed: true, receipt, notional_usd: notional }, `Executed IntentBook ${operation.action} as ${receipt.signature}.`);
2314
+ }));
2178
2315
  // ── one-shot immediate execution from a fresh Sonar quote ─────────────────
2179
2316
  registerTool("strata_execute_quote", {
2180
2317
  title: "Execute a Strata quote",
@@ -14,6 +14,7 @@ export const SIMPLE_TOOL_NAMES = new Set([
14
14
  "strata_market_making_status",
15
15
  "strata_market_making_prepare",
16
16
  "strata_market_making_submit_and_wait",
17
+ "strata_market_making_intent_execute",
17
18
  "strata_autonomy",
18
19
  "strata_trade",
19
20
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratabook/mcp",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "description": "Connect AI agents to Strata markets and Sonar quotes with MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT OR Apache-2.0",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@modelcontextprotocol/sdk": "1.30.0",
48
- "@stratabook/sdk": "0.2.13",
48
+ "@stratabook/sdk": "0.2.15",
49
49
  "zod": "^3.25.76"
50
50
  },
51
51
  "devDependencies": {