m2m-sentinel-sdk 1.2.5 → 1.2.7

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/agent_adapter.js CHANGED
@@ -67,7 +67,7 @@ class M2MSentinelActionProvider {
67
67
 
68
68
  const headers = {
69
69
  Accept: 'application/json',
70
- 'User-Agent': 'M2MSentinel-AgentKit/1.2.5',
70
+ 'User-Agent': 'M2MSentinel-AgentKit/1.2.7',
71
71
  ...options.headers
72
72
  };
73
73
  if (this.apiKey && !headers['x-api-key']) {
@@ -161,18 +161,16 @@ class M2MSentinelActionProvider {
161
161
  return JSON.stringify(res.body);
162
162
  }
163
163
 
164
- async getDexLiquidity(args = {}) {
165
- const pair = String(args.pair || 'WETH-USDC').trim();
166
- const res = await this._queryApi(`/v1/dex/metrics?pair=${encodeURIComponent(pair)}`);
164
+ async getDexLiquidity(_args = {}) {
165
+ const res = await this._queryApi('/v1/dex/metrics');
167
166
  if (!res.ok) {
168
167
  return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: 'Failed to retrieve DEX liquidity' });
169
168
  }
170
169
  return JSON.stringify(res.body);
171
170
  }
172
171
 
173
- async getWhaleSignals(args = {}) {
174
- const limit = Number(args.limit || 10);
175
- const res = await this._queryApi(`/v1/whales/signals?limit=${encodeURIComponent(limit)}`);
172
+ async getWhaleSignals(_args = {}) {
173
+ const res = await this._queryApi('/v1/whales/signals');
176
174
  if (!res.ok) {
177
175
  return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: 'Failed to retrieve whale signals' });
178
176
  }
@@ -198,7 +196,7 @@ class M2MSentinelActionProvider {
198
196
  },
199
197
  {
200
198
  name: 'm2m_get_gas_metrics',
201
- description: 'Get real-time Base network gas execution metrics and recommendations before submitting on-chain transactions.',
199
+ description: 'Fetch current Base gas price in wei/gwei with RPC provenance. Read-only telemetry; does not authorize or submit transactions.',
202
200
  schema: {
203
201
  type: 'object',
204
202
  properties: {}
@@ -207,7 +205,7 @@ class M2MSentinelActionProvider {
207
205
  },
208
206
  {
209
207
  name: 'm2m_get_token_price',
210
- description: 'Observe real-time Base DEX token price for slippage check and valuation.',
208
+ description: 'Fetch the median Base DEX spot price in USD across up to five deepest indexed pools for one allowlisted token symbol. No historical price series.',
211
209
  schema: {
212
210
  type: 'object',
213
211
  properties: {
@@ -222,29 +220,19 @@ class M2MSentinelActionProvider {
222
220
  },
223
221
  {
224
222
  name: 'm2m_get_dex_liquidity',
225
- description: 'Get tracked Base DEX pool reserve and liquidity metrics.',
223
+ description: 'Fetch aggregate reserve, depth, and volume metrics across tracked Base DEX liquidity pools; no individual pair filtering.',
226
224
  schema: {
227
225
  type: 'object',
228
- properties: {
229
- pair: {
230
- type: 'string',
231
- description: 'DEX pair identifier (e.g. WETH-USDC)'
232
- }
233
- }
226
+ properties: {}
234
227
  },
235
228
  invoke: (args) => this.getDexLiquidity(args)
236
229
  },
237
230
  {
238
231
  name: 'm2m_get_whale_signals',
239
- description: 'Get tracked Base whale transfer and concentration signals.',
232
+ description: 'Fetch up to 50 tracked recent high-value ERC-20 transfer signals on Base; no query parameter limit filtering.',
240
233
  schema: {
241
234
  type: 'object',
242
- properties: {
243
- limit: {
244
- type: 'number',
245
- description: 'Maximum signals to retrieve (1-50)'
246
- }
247
- }
235
+ properties: {}
248
236
  },
249
237
  invoke: (args) => this.getWhaleSignals(args)
250
238
  },
@@ -810,10 +810,59 @@ class BaseAccountPaymasterGuard {
810
810
  sendCalls(provider, request, options = {}) {
811
811
  return this.request(provider, request, options);
812
812
  }
813
+
814
+ preflightTransaction(transaction, options = {}) {
815
+ return preflightBeforeSigning({
816
+ client: this.client,
817
+ transaction,
818
+ policy: options.policy === undefined ? this.policy : options.policy,
819
+ signAndSend: options.signAndSend,
820
+ context: options.context
821
+ });
822
+ }
823
+
824
+ guardTransaction(transaction, options = {}) {
825
+ return this.preflightTransaction(transaction, options);
826
+ }
813
827
  }
814
828
 
815
829
  const BaseAccountExecutionGuard = BaseAccountPaymasterGuard;
816
830
 
831
+ /**
832
+ * Productized single-call preflight verification adapter.
833
+ *
834
+ * Preflights a caller transaction against M2M Sentinel, validates decision-grade
835
+ * evidence without invented safety claims, evaluates an affirmative caller-owned
836
+ * policy, and forwards to the caller's signing/send callback.
837
+ */
838
+ async function preflightBeforeSigning({ client, transaction, policy, signAndSend, context } = {}) {
839
+ assertClient(client);
840
+ if (typeof policy !== 'function') throw new TypeError('An explicit caller-owned policy function is required.');
841
+ if (typeof signAndSend !== 'function') throw new TypeError('A caller-owned signing/send callback is required.');
842
+
843
+ // Validate and canonicalize input transaction
844
+ const transactionSnapshot = canonicalTransaction(transaction, 'transaction');
845
+ const observation = await client.preflightTransaction(cloneJson(transactionSnapshot, 'transaction'));
846
+ const observationSnapshot = cloneJson(observation, 'observation');
847
+ assertObservationReady(observationSnapshot, transactionSnapshot);
848
+
849
+ let callerContext = { transaction: cloneJson(transactionSnapshot, 'transaction') };
850
+ if (context !== undefined) {
851
+ if (!isPlainRecord(context)) throw new TypeError('context must be a JSON object when supplied.');
852
+ callerContext = { ...cloneJson(context, 'context'), ...callerContext };
853
+ }
854
+
855
+ const policyResult = await policy(cloneJson(observationSnapshot, 'observation'), callerContext);
856
+ if (!isAffirmativePolicyResult(policyResult)) {
857
+ blocked('CALLER_POLICY_REJECTED', `Transaction rejected: ${policyRejectionReason(policyResult)}`);
858
+ }
859
+
860
+ return signAndSend(cloneJson(transactionSnapshot, 'transaction'), cloneJson(observationSnapshot, 'observation'));
861
+ }
862
+
863
+ const guardTransaction = preflightBeforeSigning;
864
+ const preflightTransactionBeforeSigning = preflightBeforeSigning;
865
+
817
866
  /** Construct the public SDK client while keeping credentials caller-owned. */
818
867
  function createPublicClient({ baseUrl, apiKey, timeoutMs, sdk } = {}) {
819
868
  if (typeof apiKey !== 'string' || apiKey.length === 0) {
@@ -861,6 +910,9 @@ module.exports = {
861
910
  preflightWalletSendCalls,
862
911
  sendCallsWithPreflight,
863
912
  executeWalletSendCalls,
913
+ preflightBeforeSigning,
914
+ guardTransaction,
915
+ preflightTransactionBeforeSigning,
864
916
  BaseAccountPaymasterGuard,
865
917
  BaseAccountExecutionGuard,
866
918
  createBaseAccountPaymasterGuard: (options) => new BaseAccountPaymasterGuard(options),
package/eliza_plugin.js CHANGED
@@ -69,7 +69,7 @@ const m2mSentinelPlugin = {
69
69
  },
70
70
  {
71
71
  name: 'GET_GAS_FEES',
72
- description: 'Fetches live Base gas fees.',
72
+ description: 'Fetches current Base gas-price telemetry with upstream provenance.',
73
73
  handler: async (runtime) => {
74
74
  try { return { text: JSON.stringify(await clientFor(runtime).getGasFees()) }; }
75
75
  catch (err) { return { text: errorText(err) }; }
package/index.d.ts CHANGED
@@ -198,6 +198,17 @@ export interface BaseAccountGuardCallOptions {
198
198
  context?: Record<string, unknown>;
199
199
  }
200
200
 
201
+ export interface PreflightBeforeSigningOptions {
202
+ client: Pick<M2MSentinelClient, 'preflightTransaction'>;
203
+ transaction: TransactionPreflightRequest;
204
+ policy: (
205
+ observation: BaseAccountPreflightObservation,
206
+ context: { transaction: TransactionPreflightRequest; [key: string]: unknown }
207
+ ) => BaseAccountCallerPolicyResult | Promise<BaseAccountCallerPolicyResult>;
208
+ signAndSend: (transaction: TransactionPreflightRequest, observation: BaseAccountPreflightObservation) => Promise<unknown>;
209
+ context?: Record<string, unknown>;
210
+ }
211
+
201
212
  export interface M2MSentinelErrorOptions {
202
213
  status?: number;
203
214
  body?: unknown;
@@ -285,10 +296,16 @@ export const preflightWalletSendCalls: typeof guardWalletSendCalls;
285
296
  export const sendCallsWithPreflight: typeof guardWalletSendCalls;
286
297
  export const executeWalletSendCalls: typeof guardWalletSendCalls;
287
298
 
299
+ export function preflightBeforeSigning(options: PreflightBeforeSigningOptions): Promise<unknown>;
300
+ export const guardTransaction: typeof preflightBeforeSigning;
301
+ export const preflightTransactionBeforeSigning: typeof preflightBeforeSigning;
302
+
288
303
  export class BaseAccountPaymasterGuard {
289
304
  constructor(options: BaseAccountPaymasterGuardOptions);
290
305
  request(provider: Eip1193Provider, request: WalletSendCallsRequest, options?: BaseAccountGuardCallOptions): Promise<unknown>;
291
306
  sendCalls(provider: Eip1193Provider, request: WalletSendCallsRequest, options?: BaseAccountGuardCallOptions): Promise<unknown>;
307
+ preflightTransaction(transaction: TransactionPreflightRequest, options?: { policy?: BaseAccountCallerPolicy; signAndSend: (tx: any, obs: any) => Promise<unknown>; context?: Record<string, unknown> }): Promise<unknown>;
308
+ guardTransaction(transaction: TransactionPreflightRequest, options?: { policy?: BaseAccountCallerPolicy; signAndSend: (tx: any, obs: any) => Promise<unknown>; context?: Record<string, unknown> }): Promise<unknown>;
292
309
  }
293
310
 
294
311
  export const BaseAccountExecutionGuard: typeof BaseAccountPaymasterGuard;
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * LangChain & LangGraph Tool Provider for M2M Sentinel.
5
+ *
6
+ * Compatible with @langchain/core/tools StructuredTool and DynamicStructuredTool.
7
+ */
8
+
9
+ const { M2MSentinelClient } = require('./index.js');
10
+
11
+ class M2MSentinelLangChainTools {
12
+ /**
13
+ * @param {Object} [options]
14
+ * @param {string} [options.apiKey] M2M Sentinel API Key
15
+ * @param {string} [options.baseUrl] M2M Sentinel Base URL
16
+ */
17
+ constructor(options = {}) {
18
+ this.client = new M2MSentinelClient(options);
19
+ }
20
+
21
+ /**
22
+ * Return a list of tool definitions compatible with LangChain agents.
23
+ * Can be passed directly to `createOpenAIToolsAgent({ tools })` or `AgentExecutor`.
24
+ */
25
+ getTools() {
26
+ return [
27
+ {
28
+ name: 'm2m_audit_contract',
29
+ description: 'Deterministic EVM bytecode capability analysis and proxy resolution for Base contracts. Run before signing or sending transactions to unverified contracts.',
30
+ schema: {
31
+ type: 'object',
32
+ properties: {
33
+ address: {
34
+ type: 'string',
35
+ description: 'The 0x-prefixed 40-hex Ethereum address on Base (Chain ID 8453) to inspect.'
36
+ }
37
+ },
38
+ required: ['address']
39
+ },
40
+ func: async ({ address }) => {
41
+ try {
42
+ const res = await this.client.auditContract(address);
43
+ return JSON.stringify(res);
44
+ } catch (err) {
45
+ return JSON.stringify({ error: err.message, status: err.status || 500 });
46
+ }
47
+ }
48
+ },
49
+ {
50
+ name: 'm2m_get_gas_metrics',
51
+ description: 'Fetch current Base gas price in wei/gwei with RPC provenance. Read-only telemetry; does not authorize or submit transactions.',
52
+ schema: {
53
+ type: 'object',
54
+ properties: {}
55
+ },
56
+ func: async () => {
57
+ try {
58
+ const res = await this.client.getGasFees();
59
+ return JSON.stringify(res);
60
+ } catch (err) {
61
+ return JSON.stringify({ error: err.message, status: err.status || 500 });
62
+ }
63
+ }
64
+ },
65
+ {
66
+ name: 'm2m_get_token_price',
67
+ description: 'Fetch sourced Base DEX spot price for one allowlisted token symbol (e.g. USDC, WETH), with pool provenance and no historical price series.',
68
+ schema: {
69
+ type: 'object',
70
+ properties: {
71
+ symbol: {
72
+ type: 'string',
73
+ description: 'The asset symbol (e.g., USDC, WETH).'
74
+ }
75
+ },
76
+ required: ['symbol']
77
+ },
78
+ func: async ({ symbol }) => {
79
+ try {
80
+ const res = await this.client.getTokenPrice(symbol);
81
+ return JSON.stringify(res);
82
+ } catch (err) {
83
+ return JSON.stringify({ error: err.message, status: err.status || 500 });
84
+ }
85
+ }
86
+ }
87
+ ];
88
+ }
89
+ }
90
+
91
+ module.exports = {
92
+ M2MSentinelLangChainTools
93
+ };
package/mcp_server.js CHANGED
@@ -5,75 +5,131 @@ const https = require('https');
5
5
  const http = require('http');
6
6
  const readline = require('readline');
7
7
 
8
+ const pkg = require('./package.json');
9
+
8
10
  let BASE_URL = process.env.M2M_SENTINEL_BASE_URL || 'https://api.m2msentinel.com';
9
11
  let API_KEY = process.env.M2M_SENTINEL_API_KEY || '';
10
12
  const TIMEOUT_MS = Number(process.env.M2M_SENTINEL_TIMEOUT_MS || 30000);
11
- const VERSION = '1.2.5';
13
+ const VERSION = pkg.version;
14
+
15
+ const SERVER_INSTRUCTIONS =
16
+ 'M2M Sentinel MCP server provides factual, read-only observations on Base Mainnet (chainId 8453).\n\n' +
17
+ '• Canonical Tools: The six m2m_* tool names (m2m_audit_contract, m2m_get_gas_metrics, m2m_get_token_price, m2m_get_dex_liquidity, m2m_get_whale_signals, m2m_get_service_status) are canonical.\n' +
18
+ '• Backwards Compatibility: Legacy tool names (audit_contract, get_capability_score, get_gas_fees, get_dex_metrics, get_token_price, get_whale_signals) are not advertised in tools/list but remain compatibility-callable.\n' +
19
+ '• Upstream Behavior: m2m_get_dex_liquidity returns aggregate tracked pool liquidity metrics without pair query filtering; m2m_get_whale_signals returns recent high-value transfer events (up to 50) without query limit filtering.\n' +
20
+ '• Access and Errors: The stdio wrapper reads M2M_SENTINEL_API_KEY when set. Protected upstream calls may return structured 401, 402, 429, or 503 responses; x402 payment metadata is surfaced, but this wrapper does not sign payments.\n' +
21
+ '• Observational Scope: All operations are factual, read-only observations. The server does not custody private keys, sign transactions, or execute on-chain state changes.\n' +
22
+ '• Limitation: Outputs reflect observed bytecode patterns and telemetry and are not safety, economic, or exploitability guarantees.';
12
23
 
13
24
  const TOOLS = [
14
25
  {
15
26
  name: 'm2m_audit_contract',
16
- description: 'Return selected static bytecode capability observations, common proxy resolution, limitations, and provenance for a Base contract. This is factual capability observation, not a safety or exploitability guarantee.',
17
- inputSchema: { type: 'object', properties: { address: { type: 'string', description: 'Base contract address (0x...)' } }, required: ['address'] }
27
+ description: 'Inspect static bytecode capabilities (e.g. mint, pause, freeze, upgradeability slots), common proxy target resolution, and coverage index for a single Base contract address. Factual capability observation only, not a safety or exploitability guarantee. Distinguishable from m2m_get_service_status (infrastructure status) and legacy score-only endpoints.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ address: {
32
+ type: 'string',
33
+ description: 'Base contract address (0x-prefixed 40-hex string, chainId 8453) to inspect.'
34
+ }
35
+ },
36
+ required: ['address']
37
+ },
38
+ annotations: {
39
+ readOnlyHint: true,
40
+ destructiveHint: false,
41
+ idempotentHint: true,
42
+ openWorldHint: true
43
+ }
18
44
  },
19
45
  {
20
46
  name: 'm2m_get_gas_metrics',
21
- description: 'Return sourced Base gas fee metrics and execution recommendations.',
22
- inputSchema: { type: 'object', properties: {} }
47
+ description: 'Fetch current Base gas price in wei/gwei with RPC provenance for the upstream observation. Read-only telemetry; does not inspect contract code, return token/DEX pricing, or authorize or submit transactions.',
48
+ inputSchema: {
49
+ type: 'object',
50
+ properties: {}
51
+ },
52
+ annotations: {
53
+ readOnlyHint: true,
54
+ destructiveHint: false,
55
+ idempotentHint: true,
56
+ openWorldHint: true
57
+ }
23
58
  },
24
59
  {
25
60
  name: 'm2m_get_token_price',
26
- description: 'Return sourced Base DEX token price observation for allowlisted assets (e.g. USDC, WETH).',
27
- inputSchema: { type: 'object', properties: { symbol: { type: 'string', description: 'Token symbol (USDC, WETH)' } }, required: ['symbol'] }
61
+ description: 'Fetch the median Base DEX spot price in USD across up to five deepest indexed pools, with contract address, decimals, and pool provenance for one allowlisted token symbol (e.g. USDC, WETH, AERO). Does not return historical price series; contrast with m2m_get_dex_liquidity, which returns aggregate pool reserve depth rather than an asset price.',
62
+ inputSchema: {
63
+ type: 'object',
64
+ properties: {
65
+ symbol: {
66
+ type: 'string',
67
+ description: 'Allowlisted token symbol on Base (e.g. USDC, WETH, AERO). Lookups are case-insensitive single symbols.'
68
+ }
69
+ },
70
+ required: ['symbol']
71
+ },
72
+ annotations: {
73
+ readOnlyHint: true,
74
+ destructiveHint: false,
75
+ idempotentHint: true,
76
+ openWorldHint: true
77
+ }
28
78
  },
29
79
  {
30
80
  name: 'm2m_get_dex_liquidity',
31
- description: 'Return tracked Base DEX pool reserve and liquidity metrics.',
32
- inputSchema: { type: 'object', properties: { pair: { type: 'string' } } }
81
+ description: 'Fetch aggregate pool reserve, depth, and volume metrics across tracked Base DEX liquidity pools. Reports tracked-pool metrics only and does not filter by individual trading pair; contrast with m2m_get_token_price, which observes token spot prices.',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {}
85
+ },
86
+ annotations: {
87
+ readOnlyHint: true,
88
+ destructiveHint: false,
89
+ idempotentHint: true,
90
+ openWorldHint: true
91
+ }
33
92
  },
34
93
  {
35
94
  name: 'm2m_get_whale_signals',
36
- description: 'Return tracked Base whale transfer signals.',
37
- inputSchema: { type: 'object', properties: { limit: { type: 'number' } } }
95
+ description: 'Fetch up to 50 tracked recent high-value ERC-20 transfer signals on Base with transaction hashes, token/sender/receiver addresses, amounts, and valuation provenance. Observes large on-chain transfer events without query parameter limits; does not inspect contract bytecode or query DEX pricing.',
96
+ inputSchema: {
97
+ type: 'object',
98
+ properties: {}
99
+ },
100
+ annotations: {
101
+ readOnlyHint: true,
102
+ destructiveHint: false,
103
+ idempotentHint: true,
104
+ openWorldHint: true
105
+ }
38
106
  },
39
107
  {
40
108
  name: 'm2m_get_service_status',
41
- description: 'Return real-time operational status of M2M Sentinel upstream RPC and persistence rails.',
42
- inputSchema: { type: 'object', properties: {} }
43
- },
44
- // Backwards compatibility aliases
45
- {
46
- name: 'audit_contract',
47
- description: 'Alias for m2m_audit_contract.',
48
- inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] }
49
- },
50
- {
51
- name: 'get_capability_score',
52
- description: 'Return the static capability coverage index and provenance for a Base contract. This is not a safety score.',
53
- inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] }
54
- },
55
- {
56
- name: 'get_gas_fees',
57
- description: 'Alias for m2m_get_gas_metrics.',
58
- inputSchema: { type: 'object', properties: {} }
59
- },
60
- {
61
- name: 'get_dex_metrics',
62
- description: 'Alias for m2m_get_dex_liquidity.',
63
- inputSchema: { type: 'object', properties: {} }
64
- },
65
- {
66
- name: 'get_token_price',
67
- description: 'Alias for m2m_get_token_price.',
68
- inputSchema: { type: 'object', properties: { symbol: { type: 'string' } }, required: ['symbol'] }
69
- },
70
- {
71
- name: 'get_whale_signals',
72
- description: 'Alias for m2m_get_whale_signals.',
73
- inputSchema: { type: 'object', properties: {} }
109
+ description: 'Fetch operational status, upstream Base RPC quorum status, and persistence availability for M2M Sentinel infrastructure. Does not return blockchain or market telemetry.',
110
+ inputSchema: {
111
+ type: 'object',
112
+ properties: {}
113
+ },
114
+ annotations: {
115
+ readOnlyHint: true,
116
+ destructiveHint: false,
117
+ idempotentHint: true,
118
+ openWorldHint: true
119
+ }
74
120
  }
75
121
  ];
76
122
 
123
+ // Backwards compatibility alias lookup
124
+ const TOOL_ALIASES = Object.freeze({
125
+ audit_contract: 'm2m_audit_contract',
126
+ get_capability_score: 'm2m_audit_contract',
127
+ get_gas_fees: 'm2m_get_gas_metrics',
128
+ get_dex_metrics: 'm2m_get_dex_liquidity',
129
+ get_token_price: 'm2m_get_token_price',
130
+ get_whale_signals: 'm2m_get_whale_signals'
131
+ });
132
+
77
133
  function parseX402Header(value) {
78
134
  if (!value) return null;
79
135
  try { return JSON.parse(value); } catch (_) {}
@@ -87,8 +143,8 @@ function parseX402Header(value) {
87
143
  }
88
144
 
89
145
  function queryApi(path, customApiKey = null, customBaseUrl = null) {
90
- const activeBaseUrl = customBaseUrl || BASE_URL;
91
- const activeApiKey = customApiKey !== null ? customApiKey : API_KEY;
146
+ const activeBaseUrl = customBaseUrl || process.env.M2M_SENTINEL_BASE_URL || BASE_URL;
147
+ const activeApiKey = customApiKey !== null ? customApiKey : (process.env.M2M_SENTINEL_API_KEY || API_KEY);
92
148
 
93
149
  return new Promise((resolve, reject) => {
94
150
  const url = new URL(path, activeBaseUrl);
@@ -129,12 +185,22 @@ function pathForTool(name, args) {
129
185
  const input = args || {};
130
186
  switch (name) {
131
187
  case 'm2m_audit_contract':
132
- case 'audit_contract':
133
- if (!input.address) throw new Error('address is required');
134
- return '/v1/audit/' + encodeURIComponent(input.address);
135
- case 'get_capability_score':
136
- if (!input.address) throw new Error('address is required');
137
- return '/v1/security/score/' + encodeURIComponent(input.address);
188
+ case 'audit_contract': {
189
+ const addr = input.address ? String(input.address).trim() : '';
190
+ if (!addr) throw new Error('address is required');
191
+ if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) {
192
+ throw new Error('A valid 40-hex 0x-prefixed Base contract address is required');
193
+ }
194
+ return '/v1/audit/' + encodeURIComponent(addr);
195
+ }
196
+ case 'get_capability_score': {
197
+ const addr = input.address ? String(input.address).trim() : '';
198
+ if (!addr) throw new Error('address is required');
199
+ if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) {
200
+ throw new Error('A valid 40-hex 0x-prefixed Base contract address is required');
201
+ }
202
+ return '/v1/security/score/' + encodeURIComponent(addr);
203
+ }
138
204
  case 'm2m_get_gas_metrics':
139
205
  case 'get_gas_fees':
140
206
  return '/v1/gas/fees';
@@ -142,9 +208,11 @@ function pathForTool(name, args) {
142
208
  case 'get_dex_metrics':
143
209
  return '/v1/dex/metrics';
144
210
  case 'm2m_get_token_price':
145
- case 'get_token_price':
146
- if (!input.symbol) throw new Error('symbol is required');
147
- return '/v1/token/price/' + encodeURIComponent(input.symbol);
211
+ case 'get_token_price': {
212
+ const sym = input.symbol ? String(input.symbol).trim().toUpperCase() : '';
213
+ if (!sym) throw new Error('symbol is required');
214
+ return '/v1/token/price/' + encodeURIComponent(sym);
215
+ }
148
216
  case 'm2m_get_whale_signals':
149
217
  case 'get_whale_signals':
150
218
  return '/v1/whales/signals';
@@ -173,24 +241,32 @@ function failure(id, code, message, data) {
173
241
  send({ jsonrpc: '2.0', id, error });
174
242
  }
175
243
 
176
- async function handleMcpMessage(request) {
244
+ async function handleMcpMessage(request, responder = null) {
245
+ const replySuccess = responder ? (id, res) => responder({ jsonrpc: '2.0', id, result: res }) : success;
246
+ const replyFailure = responder ? (id, code, msg, data) => {
247
+ const error = { code, message: msg };
248
+ if (data !== undefined) error.data = data;
249
+ responder({ jsonrpc: '2.0', id, error });
250
+ } : failure;
251
+
177
252
  if (!request || request.jsonrpc !== '2.0' || typeof request.method !== 'string') {
178
- failure(request && request.id !== undefined ? request.id : null, -32600, 'Invalid Request');
253
+ replyFailure(request && request.id !== undefined ? request.id : null, -32600, 'Invalid Request');
179
254
  return;
180
255
  }
181
256
  if (request.id === undefined || request.id === null) return;
182
257
 
183
258
  try {
184
259
  if (request.method === 'initialize') {
185
- success(request.id, {
260
+ replySuccess(request.id, {
186
261
  protocolVersion: request.params && request.params.protocolVersion ? request.params.protocolVersion : '2024-11-05',
187
262
  capabilities: { tools: {} },
188
- serverInfo: { name: 'm2m-sentinel-mcp', version: VERSION }
263
+ serverInfo: { name: 'm2m-sentinel-mcp', version: VERSION },
264
+ instructions: SERVER_INSTRUCTIONS
189
265
  });
190
266
  return;
191
267
  }
192
268
  if (request.method === 'tools/list') {
193
- success(request.id, { tools: TOOLS });
269
+ replySuccess(request.id, { tools: TOOLS });
194
270
  return;
195
271
  }
196
272
  if (request.method === 'tools/call') {
@@ -198,7 +274,7 @@ async function handleMcpMessage(request) {
198
274
  const path = pathForTool(params.name, params.arguments || {});
199
275
  const apiResult = await queryApi(path);
200
276
  const isError = !apiResult.ok;
201
- success(request.id, {
277
+ replySuccess(request.id, {
202
278
  content: [{ type: 'text', text: typeof apiResult.body === 'object' ? JSON.stringify(apiResult.body, null, 2) : String(apiResult.body) }],
203
279
  isError,
204
280
  structuredContent: apiResult.body,
@@ -212,9 +288,9 @@ async function handleMcpMessage(request) {
212
288
  });
213
289
  return;
214
290
  }
215
- failure(request.id, -32601, 'Method not found');
291
+ replyFailure(request.id, -32601, 'Method not found');
216
292
  } catch (err) {
217
- failure(request.id, -32603, err && err.message ? err.message : 'Internal error');
293
+ replyFailure(request.id, -32603, err && err.message ? err.message : 'Internal error');
218
294
  }
219
295
  }
220
296
 
@@ -250,10 +326,10 @@ USAGE:
250
326
 
251
327
  COMMANDS:
252
328
  audit <address> Disassemble bytecode, detect capabilities & resolve proxies
253
- gas Fetch real-time Base Mainnet gas metrics & advice
254
- price <symbol> Fetch DEX liquidity-weighted token price (USDC, WETH, AERO)
255
- dex [pair] Fetch tracked Base liquidity pool metrics
256
- whales [limit] Fetch large ERC-20 transfer signals on Base
329
+ gas Fetch current Base Mainnet gas-price telemetry
330
+ price <symbol> Fetch median Base DEX token price from deepest pools (USDC, WETH, AERO)
331
+ dex Fetch aggregate tracked Base liquidity pool metrics
332
+ whales Fetch large ERC-20 transfer signals on Base (up to 50)
257
333
  status Check live API, RPC quorum, and persistence health
258
334
  mcp Launch Model Context Protocol stdio JSON-RPC server
259
335
 
@@ -419,13 +495,16 @@ async function runCli(args) {
419
495
 
420
496
  } else if (commandName === 'gas' || commandName === 'fees') {
421
497
  console.log('================================================================');
422
- console.log('⛽ M2M SENTINEL — BASE GAS METRICS');
498
+ console.log('⛽ M2M SENTINEL — BASE GAS PRICE TELEMETRY');
423
499
  console.log(`Network: Base Mainnet (8453) | Sourced Latency: ${res.latencyMs}ms`);
424
500
  console.log('================================================================');
425
- console.log(`• Standard Gas Price: ${data.standard || data.gasPriceGwei || '0.005'} gwei`);
426
- if (data.fast) console.log(`• Fast Gas Price: ${data.fast} gwei`);
427
- if (data.instant) console.log(`• Instant Gas Price: ${data.instant} gwei`);
428
- if (data.recommendation) console.log(`• Execution Advice: ${data.recommendation}`);
501
+ const gasPrice = data && data.gasPrice && typeof data.gasPrice === 'object' ? data.gasPrice : data;
502
+ const gwei = gasPrice && gasPrice.gwei !== undefined
503
+ ? gasPrice.gwei
504
+ : (data && data.gasPriceGwei !== undefined ? data.gasPriceGwei : (data && data.standard));
505
+ if (gwei !== undefined) console.log(`• Gas Price: ${gwei} gwei`);
506
+ if (gasPrice && gasPrice.wei !== undefined) console.log(`• Gas Price: ${gasPrice.wei} wei`);
507
+ if (data && data.provenance) console.log(`• RPC Provenance: ${JSON.stringify(data.provenance)}`);
429
508
  console.log('================================================================\n');
430
509
 
431
510
  } else if (commandName === 'price') {
@@ -473,20 +552,33 @@ async function runCli(args) {
473
552
  // Entrypoint: Dual Mode Selection
474
553
  // ---------------------------------------------------------------------------
475
554
 
476
- const cliArgs = process.argv.slice(2);
555
+ if (require.main === module) {
556
+ const cliArgs = process.argv.slice(2);
477
557
 
478
- // If arguments are passed or run in an interactive terminal, invoke CLI
479
- if (cliArgs.length > 0 || (process.stdin.isTTY && !process.env.M2M_MCP_FORCE)) {
480
- if (cliArgs.length === 0) {
481
- printHelp();
558
+ // If arguments are passed or run in an interactive terminal, invoke CLI
559
+ if (cliArgs.length > 0 || (process.stdin.isTTY && !process.env.M2M_MCP_FORCE)) {
560
+ if (cliArgs.length === 0) {
561
+ printHelp();
562
+ } else {
563
+ runCli(cliArgs).then((code) => {
564
+ if (code !== 0 && typeof code === 'number') process.exit(code);
565
+ });
566
+ }
482
567
  } else {
483
- runCli(cliArgs).then((code) => {
484
- if (code !== 0 && typeof code === 'number') process.exit(code);
485
- });
568
+ // Piped execution without args -> start MCP stdio server
569
+ startMcpServer();
486
570
  }
487
- } else {
488
- // Piped execution without args -> start MCP stdio server
489
- startMcpServer();
490
571
  }
491
572
 
492
- module.exports = { TOOLS, pathForTool, parseX402Header, runCli, queryApi };
573
+ module.exports = {
574
+ TOOLS,
575
+ TOOL_ALIASES,
576
+ SERVER_INSTRUCTIONS,
577
+ pathForTool,
578
+ parseX402Header,
579
+ runCli,
580
+ queryApi,
581
+ handleMcpMessage,
582
+ startMcpServer,
583
+ VERSION
584
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "m2m-sentinel-sdk",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "mcpName": "io.github.M2M-Sentinel/m2m-sentinel-sdk",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -16,6 +16,9 @@
16
16
  "m2m-sentinel": "./mcp_server.js",
17
17
  "m2msentinel": "./mcp_server.js"
18
18
  },
19
+ "scripts": {
20
+ "test": "node --test"
21
+ },
19
22
  "license": "MIT",
20
23
  "homepage": "https://m2msentinel.com",
21
24
  "repository": {
@@ -54,6 +57,7 @@
54
57
  "agent_adapter.js",
55
58
  "x402_signer.js",
56
59
  "eliza_plugin.js",
60
+ "langchain_tool.js",
57
61
  "mcp_server.js",
58
62
  "smithery.yaml",
59
63
  "README.md",
package/smithery.yaml CHANGED
@@ -13,7 +13,7 @@ startCommand:
13
13
  M2M_SENTINEL_API_KEY:
14
14
  type: string
15
15
  title: M2M Sentinel API Key
16
- description: Optional API key for higher rate limits. Public free tier works without key.
16
+ description: Optional API key for prepaid access. Protected calls may return structured 401, 402, 429, or 503 responses without authorized access.
17
17
  default: ""
18
18
 
19
19
  configSchema:
@@ -22,6 +22,6 @@ configSchema:
22
22
  M2M_SENTINEL_API_KEY:
23
23
  type: string
24
24
  title: M2M Sentinel API Key
25
- description: Optional API key for higher rate limits. Public free tier works without key.
25
+ description: Optional API key for prepaid access. Protected calls may return structured 401, 402, 429, or 503 responses without authorized access.
26
26
  default: ""
27
27
 
@@ -186,6 +186,19 @@ interface ExecutionIdentityBlockedErrorInstance extends Error {
186
186
  interface BaseAccountPaymasterGuardInstance {
187
187
  request(provider: Eip1193Provider, request: WalletSendCallsRequest, options?: BaseAccountGuardCallOptions): Promise<unknown>;
188
188
  sendCalls(provider: Eip1193Provider, request: WalletSendCallsRequest, options?: BaseAccountGuardCallOptions): Promise<unknown>;
189
+ preflightTransaction(transaction: TransactionPreflightRequest, options?: { policy?: BaseAccountCallerPolicy; signAndSend: (tx: any, obs: any) => Promise<unknown>; context?: Record<string, unknown> }): Promise<unknown>;
190
+ guardTransaction(transaction: TransactionPreflightRequest, options?: { policy?: BaseAccountCallerPolicy; signAndSend: (tx: any, obs: any) => Promise<unknown>; context?: Record<string, unknown> }): Promise<unknown>;
191
+ }
192
+
193
+ export interface PreflightBeforeSigningOptions {
194
+ client: Pick<M2MSentinelClient, 'preflightTransaction'>;
195
+ transaction: TransactionPreflightRequest;
196
+ policy: (
197
+ observation: BaseAccountPreflightObservation,
198
+ context: { transaction: TransactionPreflightRequest; [key: string]: unknown }
199
+ ) => BaseAccountCallerPolicyResult | Promise<BaseAccountCallerPolicyResult>;
200
+ signAndSend: (transaction: TransactionPreflightRequest, observation: BaseAccountPreflightObservation) => Promise<unknown>;
201
+ context?: Record<string, unknown>;
189
202
  }
190
203
 
191
204
  // This source-distributed TypeScript entrypoint targets Node.js/CommonJS
@@ -243,6 +256,13 @@ export const preflightWalletSendCalls = guardWalletSendCalls;
243
256
  export const sendCallsWithPreflight = guardWalletSendCalls;
244
257
  export const executeWalletSendCalls = guardWalletSendCalls;
245
258
 
259
+ export function preflightBeforeSigning(options: PreflightBeforeSigningOptions): Promise<unknown> {
260
+ return runtime().preflightBeforeSigning(options);
261
+ }
262
+
263
+ export const guardTransaction = preflightBeforeSigning;
264
+ export const preflightTransactionBeforeSigning = preflightBeforeSigning;
265
+
246
266
  export const BaseAccountPaymasterGuard: {
247
267
  new (options: BaseAccountPaymasterGuardOptions): BaseAccountPaymasterGuardInstance;
248
268
  } = runtime().BaseAccountPaymasterGuard;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "m2m-sentinel-sdk",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "m2m-sentinel-sdk",
9
- "version": "1.2.5",
9
+ "version": "1.2.7",
10
10
  "devDependencies": {
11
11
  "@types/node": "^22.7.5",
12
12
  "typescript": "^5.0.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "m2m-sentinel-sdk",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "Source-distributed TypeScript client for M2M Sentinel Base capability intelligence API",
5
5
  "main": "index.ts",
6
6
  "types": "index.ts",
@@ -318,7 +318,7 @@ export class X402SignerClient {
318
318
 
319
319
  async fetchWithAutoPayment(path: string, options: any = {}): Promise<any> {
320
320
  const url = path.startsWith('http') ? path : `${this.baseUrl}/${path.replace(/^\/+/, '')}`;
321
- const headers = { 'Accept': 'application/json', 'User-Agent': '@m2msentinel/sdk-ts/1.2.5', ...(options.headers || {}) };
321
+ const headers = { 'Accept': 'application/json', 'User-Agent': '@m2msentinel/sdk-ts/1.2.7', ...(options.headers || {}) };
322
322
 
323
323
  let res = await fetch(url, { method: options.method || 'GET', headers, body: options.body });
324
324
  if (res.status !== 402) {
package/x402_signer.js CHANGED
@@ -271,7 +271,7 @@ class X402SignerClient {
271
271
  path: url.pathname + url.search,
272
272
  method: options.method || 'GET',
273
273
  headers: {
274
- 'User-Agent': 'M2M-Sentinel-X402Signer/1.2.5',
274
+ 'User-Agent': 'M2M-Sentinel-X402Signer/1.2.7',
275
275
  'Accept': 'application/json',
276
276
  ...(options.headers || {})
277
277
  },