m2m-sentinel-sdk 1.0.2 → 1.0.4

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.
Files changed (41) hide show
  1. package/README.md +44 -28
  2. package/agent_adapter.js +30 -0
  3. package/eliza_plugin.js +84 -0
  4. package/index.js +136 -19
  5. package/m2m_sentinel_sdk.js +1 -12
  6. package/mcp_server.js +172 -0
  7. package/package.json +26 -9
  8. package/typescript/index.ts +135 -35
  9. package/typescript/package.json +4 -3
  10. package/cli/m2m_cli.js +0 -25
  11. package/csharp/M2MSentinel.csproj +0 -9
  12. package/csharp/M2MSentinelClient.cs +0 -67
  13. package/go/client.go +0 -94
  14. package/go/go.mod +0 -3
  15. package/java/M2MSentinelClient.java +0 -67
  16. package/java/pom.xml +0 -20
  17. package/java/src/main/java/com/m2msentinel/M2MSentinelClient.java +0 -69
  18. package/kotlin/M2MSentinelClient.kt +0 -59
  19. package/kotlin/build.gradle.kts +0 -24
  20. package/m2m_sentinel/__init__.py +0 -3
  21. package/m2m_sentinel/client.py +0 -16
  22. package/m2m_sentinel_sdk.py +0 -9
  23. package/php/M2MSentinelClient.php +0 -52
  24. package/php/composer.json +0 -20
  25. package/python/dist/m2m_sentinel-0.1.0-py3-none-any.whl +0 -0
  26. package/python/dist/m2m_sentinel-0.1.0.tar.gz +0 -0
  27. package/python/dist/m2m_sentinel-0.1.1-py3-none-any.whl +0 -0
  28. package/python/dist/m2m_sentinel-0.1.1.tar.gz +0 -0
  29. package/python/m2m_sentinel/__pycache__/client.cpython-314.pyc +0 -0
  30. package/python/m2m_sentinel/client.py +0 -40
  31. package/python/m2m_sentinel.egg-info/PKG-INFO +0 -35
  32. package/python/m2m_sentinel.egg-info/SOURCES.txt +0 -5
  33. package/python/m2m_sentinel.egg-info/dependency_links.txt +0 -1
  34. package/python/m2m_sentinel.egg-info/top_level.txt +0 -1
  35. package/python/setup.py +0 -35
  36. package/ruby/m2m_sentinel.gemspec +0 -17
  37. package/ruby/m2m_sentinel.rb +0 -55
  38. package/rust/Cargo.toml +0 -11
  39. package/rust/src/lib.rs +0 -61
  40. package/swift/M2MSentinelClient.swift +0 -65
  41. package/swift/Package.swift +0 -23
package/README.md CHANGED
@@ -1,43 +1,59 @@
1
- # m2m-sentinel-sdk
1
+ # M2M Sentinel SDKs
2
2
 
3
- > Official Client SDK for M2M Sentinel: Autonomous Machine-to-Machine Web3 Security & AI Intelligence Engine.
3
+ Base URL: `https://m2msentinel.vercel.app`.
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/m2m-sentinel-sdk.svg)](https://www.npmjs.com/package/m2m-sentinel-sdk)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ ## Support status
6
+
7
+ Supported in this repository:
8
+ - JavaScript/Node: `public/sdk/index.js` (`m2m_sentinel_sdk.js` re-exports it)
9
+ - TypeScript source: `public/sdk/typescript/index.ts`
10
+ - Python source: `public/sdk/m2m_sentinel/` and `public/sdk/python/m2m_sentinel/`
11
+ - MCP stdio server: `public/sdk/mcp_server.js`
12
+
13
+ Community preview only: Go, Rust, Java, Kotlin, Swift, C#, PHP, Ruby, CLI, Eliza plugin, and agent adapter. They include current route/header examples but are not covered by the supported-SDK guarantee; verify against `/openapi.json` before production use.
7
14
 
8
15
  ## Installation
9
16
 
17
+ Install from source (recommended for current `v1.0.4` build):
18
+
10
19
  ```bash
11
- npm install m2m-sentinel-sdk
20
+ # JavaScript / Node.js (local path)
21
+ node -e "const { M2MSentinelClient } = require('./public/sdk')"
22
+
23
+ # Python (local path)
24
+ python -m pip install ./public/sdk/python
12
25
  ```
13
26
 
14
- ## Quickstart
27
+ *Note on Registry Packages (`v1.0.4`)*:
28
+ Once published to npm and PyPI (via `npm publish` and `twine upload`), install directly via the package manager:
29
+ - **npm registry**: `m2m-sentinel-sdk`
30
+ - **PyPI registry**: `m2m-sentinel`
15
31
 
16
- ```javascript
17
- const { M2MSentinelClient } = require('m2m-sentinel-sdk');
32
+ ## Authentication
18
33
 
19
- const client = new M2MSentinelClient();
34
+ API keys must be sent only in headers:
20
35
 
21
- async function run() {
22
- // Audit a Smart Contract
23
- const audit = await client.auditContract('0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
24
- console.log('Contract Risk Audit:', audit);
36
+ ```text
37
+ x-api-key: <key>
38
+ ```
25
39
 
26
- // Fetch Real-Time Whale Transfer Alerts
27
- const whales = await client.getWhaleSignals();
28
- console.log('Whale Signals:', whales);
29
- }
40
+ `Authorization: Bearer <key>` is accepted by the API, but the supported SDKs use `x-api-key`. Never place credentials in URLs or query strings; the server rejects them with HTTP 401.
30
41
 
31
- run();
32
- ```
42
+ ## x402 / 402 behavior
43
+
44
+ Paid endpoints accept either `x-api-key` or a settled x402 v2 payment. Without credentials they return HTTP 402 and a `PAYMENT-REQUIRED` header containing a JSON challenge. Supported SDKs raise/return a payment-required error that exposes the parsed challenge. After settlement, retry with `PAYMENT-SIGNATURE`; successful paid responses may include `PAYMENT-RESPONSE` and include `provenance` in the JSON body.
45
+
46
+ ## Free-tier signup
47
+
48
+ 1. `POST /v1/subscribe/free/challenge` with `{ "userWallet": "0x..." }`.
49
+ 2. Sign `intent.messageToSign` with that wallet.
50
+ 3. `POST /v1/subscribe/free/claim` with `{ "intentId": "...", "signature": "..." }`.
51
+ 4. Store the returned `apiKey`; it cannot be recovered later.
52
+
53
+ ## Current routes
54
+
55
+ Public: `GET /v1/status`, `GET /v1/plans`, free signup, and subscription intent/claim routes.
33
56
 
34
- ## Features
35
- - **Smart Contract Risk Auditor** (`/v1/audit/:address`)
36
- - **Real-Time Whale Signals** (`/v1/whales/signals`)
37
- - **DEX Liquidity Depth & Spreads** (`/v1/dex/metrics`)
38
- - **AI Web Search Intelligence** (`/v1/search`)
57
+ Protected/paid: `GET /v1/audit/:address`, `GET /v1/security/score/:address`, `GET /v1/gas/fees`, `GET /v1/dex/metrics`, `GET /v1/token/price/:symbol`, `GET /v1/whales/signals`, `GET /v1/keys/self`, `POST /v1/keys/revoke`.
39
58
 
40
- ## Documentation & Portals
41
- - **Live Portal**: [https://m2msentinel.vercel.app](https://m2msentinel.vercel.app)
42
- - **API Docs**: [https://m2msentinel.vercel.app/docs.html](https://m2msentinel.vercel.app/docs.html)
43
- - **OpenAPI Spec**: [https://m2msentinel.vercel.app/openapi.json](https://m2msentinel.vercel.app/openapi.json)
59
+ SDKs preserve response bodies, including `provenance`. They surface 401, 402, 405, 429 (`Retry-After`), and 503 `DATA_SOURCE_UNAVAILABLE` without inventing fallback values.
@@ -0,0 +1,30 @@
1
+ // UNSUPPORTED / COMMUNITY PREVIEW — not covered by the supported-SDK guarantee; verify against /openapi.json before production use.
2
+
3
+ const { M2MSentinelClient } = require('./index.js');
4
+
5
+ class M2MSentinelAgentTool {
6
+ constructor(options = {}) {
7
+ this.name = 'm2m_sentinel_security_audit';
8
+ this.description = 'Audits EVM smart contracts on Base for security risks, proxies, and bytecode findings.';
9
+ this.client = new M2MSentinelClient({
10
+ apiKey: options.apiKey || process.env.M2M_SENTINEL_API_KEY,
11
+ baseUrl: options.baseUrl
12
+ });
13
+ }
14
+
15
+ async _call(address) {
16
+ try {
17
+ return await this.client.auditContract(address);
18
+ } catch (err) {
19
+ return {
20
+ error: err.name || 'M2MSentinelError',
21
+ message: err.message,
22
+ status: err.status,
23
+ paymentRequired: err.paymentRequired || null,
24
+ retryAfter: err.retryAfter || null
25
+ };
26
+ }
27
+ }
28
+ }
29
+
30
+ module.exports = { M2MSentinelAgentTool };
@@ -0,0 +1,84 @@
1
+ // UNSUPPORTED / COMMUNITY PREVIEW — not covered by the supported-SDK guarantee; verify against /openapi.json before production use.
2
+
3
+ const { M2MSentinelClient } = require('./index.js');
4
+
5
+ function clientFor(runtime) {
6
+ const apiKey = runtime?.getSetting?.('M2M_SENTINEL_API_KEY') || process.env.M2M_SENTINEL_API_KEY;
7
+ return new M2MSentinelClient({ apiKey });
8
+ }
9
+
10
+ function errorText(err) {
11
+ if (err.status === 402) return 'M2M Sentinel payment required. Configure M2M_SENTINEL_API_KEY or settle the x402 challenge.';
12
+ if (err.status === 503 && err.body?.error === 'DATA_SOURCE_UNAVAILABLE') return 'M2M Sentinel data source unavailable; no estimated fallback value is returned.';
13
+ if (err.status === 429) return 'M2M Sentinel rate limited. Retry-After: ' + (err.retryAfter || 'not provided');
14
+ return 'M2M Sentinel API request failed: ' + err.message;
15
+ }
16
+
17
+ const m2mSentinelPlugin = {
18
+ name: '@elizaos/plugin-m2m-sentinel',
19
+ description: 'Preview ElizaOS actions for M2M Sentinel protected Web3 API routes.',
20
+ actions: [
21
+ {
22
+ name: 'AUDIT_CONTRACT',
23
+ description: 'Audits an EVM contract on Base Mainnet.',
24
+ handler: async (runtime, message) => {
25
+ const address = message.content.text.match(/0x[a-fA-F0-9]{40}/)?.[0];
26
+ if (!address) return { text: 'Please provide a valid 42-character EVM contract address.' };
27
+ try {
28
+ const data = await clientFor(runtime).auditContract(address);
29
+ const audit = data.audit || {};
30
+ const proxy = audit.proxyResolution || {};
31
+ return { text: `M2M Sentinel audit for ${address}: ${audit.securityRating || 'UNKNOWN'}; proxy=${proxy.isProxy ? proxy.proxyType : 'NO'}; provenance=${JSON.stringify(audit.provenance || data.provenance || null)}` };
32
+ } catch (err) { return { text: errorText(err) }; }
33
+ }
34
+ },
35
+ {
36
+ name: 'GET_SECURITY_SCORE',
37
+ description: 'Returns the security score for a Base contract address.',
38
+ handler: async (runtime, message) => {
39
+ const address = message.content.text.match(/0x[a-fA-F0-9]{40}/)?.[0];
40
+ if (!address) return { text: 'Please provide a valid EVM contract address.' };
41
+ try {
42
+ const data = await clientFor(runtime).getSecurityScore(address);
43
+ const score = data.securityScore === null ? 'not decision-grade' : `${data.securityScore}/100`;
44
+ return { text: `M2M Sentinel security score for ${address}: ${score}. Provenance: ${JSON.stringify(data.provenance || null)}` };
45
+ } catch (err) { return { text: errorText(err) }; }
46
+ }
47
+ },
48
+ {
49
+ name: 'GET_DEX_METRICS',
50
+ description: 'Fetches Base DEX metrics.',
51
+ handler: async (runtime) => {
52
+ try { return { text: JSON.stringify(await clientFor(runtime).getDexMetrics()) }; }
53
+ catch (err) { return { text: errorText(err) }; }
54
+ }
55
+ },
56
+ {
57
+ name: 'GET_TOKEN_PRICE',
58
+ description: 'Queries a tracked Base token price.',
59
+ handler: async (runtime, message) => {
60
+ const symbol = (message.content.text.match(/price of ([a-zA-Z0-9]+)/i)?.[1] || 'USDC').toUpperCase();
61
+ try { return { text: JSON.stringify(await clientFor(runtime).getTokenPrice(symbol)) }; }
62
+ catch (err) { return { text: errorText(err) }; }
63
+ }
64
+ },
65
+ {
66
+ name: 'GET_GAS_FEES',
67
+ description: 'Fetches live Base gas fees.',
68
+ handler: async (runtime) => {
69
+ try { return { text: JSON.stringify(await clientFor(runtime).getGasFees()) }; }
70
+ catch (err) { return { text: errorText(err) }; }
71
+ }
72
+ },
73
+ {
74
+ name: 'GET_WHALE_SIGNALS',
75
+ description: 'Fetches whale signals.',
76
+ handler: async (runtime) => {
77
+ try { return { text: JSON.stringify(await clientFor(runtime).getWhaleSignals()) }; }
78
+ catch (err) { return { text: errorText(err) }; }
79
+ }
80
+ }
81
+ ]
82
+ };
83
+
84
+ module.exports = { m2mSentinelPlugin };
package/index.js CHANGED
@@ -1,26 +1,143 @@
1
- const axios = require('axios');
1
+ const DEFAULT_BASE_URL = 'https://m2msentinel.vercel.app';
2
+ const DEFAULT_TIMEOUT_MS = 30000;
3
+
4
+ class M2MSentinelError extends Error {
5
+ constructor(message, options = {}) {
6
+ super(message);
7
+ this.name = this.constructor.name;
8
+ this.status = options.status;
9
+ this.body = options.body;
10
+ this.retryAfter = options.retryAfter || null;
11
+ this.paymentRequired = options.paymentRequired || null;
12
+ this.paymentResponse = options.paymentResponse || null;
13
+ }
14
+ }
15
+
16
+ class PaymentRequiredError extends M2MSentinelError {}
17
+ class RateLimitedError extends M2MSentinelError {}
18
+ class DataSourceUnavailableError extends M2MSentinelError {}
19
+
20
+ function parseHeaderJson(value) {
21
+ if (!value) return null;
22
+ try { return JSON.parse(value); } catch (_) { return null; }
23
+ }
24
+
25
+ function normalizeOptions(optionsOrApiKey, baseUrl) {
26
+ if (optionsOrApiKey && typeof optionsOrApiKey === 'object') return { ...optionsOrApiKey };
27
+ return { apiKey: optionsOrApiKey || undefined, baseUrl: baseUrl || DEFAULT_BASE_URL };
28
+ }
2
29
 
3
30
  class M2MSentinelClient {
4
- constructor(apiKey, baseUrl = 'https://m2msentinel.vercel.app') {
5
- this.apiKey = apiKey;
6
- this.baseUrl = baseUrl.replace(/\/+$/, '');
7
- this.client = axios.create({
8
- baseURL: this.baseUrl,
9
- headers: {
10
- 'Authorization': `Bearer ${this.apiKey}`,
11
- 'Content-Type': 'application/json'
12
- }
13
- });
31
+ constructor(optionsOrApiKey, baseUrl) {
32
+ const options = normalizeOptions(optionsOrApiKey, baseUrl);
33
+ this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
34
+ this.apiKey = options.apiKey || undefined;
35
+ this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
36
+ this.paymentSignature = options.paymentSignature || undefined;
37
+ }
38
+
39
+ async request(method, path, body, options = {}) {
40
+ const headers = {
41
+ 'Accept': 'application/json'
42
+ };
43
+ if (body !== undefined) headers['Content-Type'] = 'application/json';
44
+ const apiKey = options.apiKey || this.apiKey;
45
+ if (apiKey) headers['x-api-key'] = apiKey;
46
+ const paymentSignature = options.paymentSignature || this.paymentSignature;
47
+ if (paymentSignature) headers['PAYMENT-SIGNATURE'] = paymentSignature;
48
+
49
+ const controller = new AbortController();
50
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs || this.timeoutMs);
51
+ let response;
52
+ try {
53
+ response = await fetch(this.baseUrl + path, {
54
+ method,
55
+ headers,
56
+ body: body === undefined ? undefined : JSON.stringify(body),
57
+ signal: controller.signal
58
+ });
59
+ } catch (err) {
60
+ if (err && err.name === 'AbortError') throw new M2MSentinelError('M2M Sentinel request timed out', { status: 0 });
61
+ throw err;
62
+ } finally {
63
+ clearTimeout(timeout);
14
64
  }
15
65
 
16
- async getStatus() {
17
- try {
18
- const response = await this.client.get('/v1/status');
19
- return response.data;
20
- } catch (error) {
21
- throw error;
22
- }
66
+ const text = await response.text();
67
+ let data = null;
68
+ if (text) {
69
+ try { data = JSON.parse(text); } catch (_) { data = { raw: text }; }
70
+ }
71
+
72
+ const paymentRequired = parseHeaderJson(response.headers.get('PAYMENT-REQUIRED'));
73
+ const paymentResponse = parseHeaderJson(response.headers.get('PAYMENT-RESPONSE')) || response.headers.get('PAYMENT-RESPONSE');
74
+ if (response.ok) return data;
75
+
76
+ const details = {
77
+ status: response.status,
78
+ body: data,
79
+ retryAfter: response.headers.get('Retry-After'),
80
+ paymentRequired,
81
+ paymentResponse
82
+ };
83
+ const message = data && data.message ? data.message : 'M2M Sentinel HTTP ' + response.status;
84
+ if (response.status === 402) throw new PaymentRequiredError(message, details);
85
+ if (response.status === 429) throw new RateLimitedError(message, details);
86
+ if (response.status === 503 && data && data.error === 'DATA_SOURCE_UNAVAILABLE') throw new DataSourceUnavailableError(message, details);
87
+ throw new M2MSentinelError(message, details);
88
+ }
89
+
90
+ getStatus() { return this.request('GET', '/v1/status'); }
91
+ getPlans() { return this.request('GET', '/v1/plans'); }
92
+ createFreeChallenge(userWallet) { return this.request('POST', '/v1/subscribe/free/challenge', { userWallet }); }
93
+ claimFreeTier(intentId, signature) { return this.request('POST', '/v1/subscribe/free/claim', { intentId, signature }); }
94
+ createSubscriptionIntent(tier, userWallet) { return this.request('POST', '/v1/subscribe/intents', { tier, userWallet }); }
95
+ claimSubscription(intentId, signature, txHash) { return this.request('POST', '/v1/subscribe/crypto', { intentId, signature, txHash }); }
96
+ auditContract(address, options) { return this.request('GET', '/v1/audit/' + encodeURIComponent(address), undefined, options); }
97
+ getSecurityScore(address, options) { return this.request('GET', '/v1/security/score/' + encodeURIComponent(address), undefined, options); }
98
+ getGasFees(options) { return this.request('GET', '/v1/gas/fees', undefined, options); }
99
+ getDexMetrics(options) { return this.request('GET', '/v1/dex/metrics', undefined, options); }
100
+ getTokenPrice(symbol, options) { return this.request('GET', '/v1/token/price/' + encodeURIComponent(symbol), undefined, options); }
101
+ getWhaleSignals(options) { return this.request('GET', '/v1/whales/signals', undefined, options); }
102
+ getKeySelf() { return this.request('GET', '/v1/keys/self'); }
103
+ revokeKey(confirm = true) { return this.request('POST', '/v1/keys/revoke', { confirm }); }
104
+ }
105
+
106
+ function createEthersSentinelMiddleware(apiKey, baseUrl) {
107
+ const client = new M2MSentinelClient({ apiKey, baseUrl });
108
+ return {
109
+ client,
110
+ verifyContractBeforeTx: async (targetAddress) => {
111
+ const audit = await client.auditContract(targetAddress);
112
+ const verdict = audit && audit.audit && audit.audit.verdict;
113
+ if (verdict && verdict.securityRating === 'HIGH_RISK') {
114
+ throw new Error('[M2M Sentinel] Transaction blocked: ' + targetAddress + ' flagged HIGH_RISK.');
115
+ }
116
+ return audit;
117
+ }
118
+ };
119
+ }
120
+
121
+ function createViemSentinelInterceptor(apiKey, baseUrl) {
122
+ const client = new M2MSentinelClient({ apiKey, baseUrl });
123
+ return {
124
+ client,
125
+ inspectSwapTarget: async (address) => {
126
+ const score = await client.getSecurityScore(address);
127
+ if (score && score.decisionGrade && typeof score.securityScore === 'number' && score.securityScore < 50) {
128
+ throw new Error('[M2M Sentinel] Security score too low (' + score.securityScore + '/100) for ' + address);
129
+ }
130
+ return score;
23
131
  }
132
+ };
24
133
  }
25
134
 
26
- module.exports = { M2MSentinelClient };
135
+ module.exports = {
136
+ M2MSentinelClient,
137
+ M2MSentinelError,
138
+ PaymentRequiredError,
139
+ RateLimitedError,
140
+ DataSourceUnavailableError,
141
+ createEthersSentinelMiddleware,
142
+ createViemSentinelInterceptor
143
+ };
@@ -1,12 +1 @@
1
- class M2MSentinelSDK {
2
- constructor(apiKey) {
3
- this.apiKey = apiKey;
4
- this.baseUrl = 'https://m2msentinel.vercel.app/v1';
5
- }
6
-
7
- async getTokenPrice() {
8
- const response = await fetch(`${this.baseUrl}/token/price`);
9
- return response.json();
10
- }
11
- }
12
- module.exports = M2MSentinelSDK;
1
+ module.exports = require('./index.js');
package/mcp_server.js ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const https = require('https');
5
+ const readline = require('readline');
6
+
7
+ const BASE_URL = process.env.M2M_SENTINEL_BASE_URL || 'https://m2msentinel.vercel.app';
8
+ const API_KEY = process.env.M2M_SENTINEL_API_KEY || '';
9
+ const TIMEOUT_MS = Number(process.env.M2M_SENTINEL_TIMEOUT_MS || 30000);
10
+
11
+ const TOOLS = [
12
+ {
13
+ name: 'audit_contract',
14
+ description: 'Audit an EVM smart contract address on Base and return bytecode findings, verdict, score, and provenance.',
15
+ inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] }
16
+ },
17
+ {
18
+ name: 'get_security_score',
19
+ description: 'Return the M2M Sentinel security score and provenance for a Base contract address.',
20
+ inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] }
21
+ },
22
+ {
23
+ name: 'get_gas_fees',
24
+ description: 'Return current Base gas fee data and provenance.',
25
+ inputSchema: { type: 'object', properties: {} }
26
+ },
27
+ {
28
+ name: 'get_dex_metrics',
29
+ description: 'Return tracked Base DEX metrics and provenance.',
30
+ inputSchema: { type: 'object', properties: {} }
31
+ },
32
+ {
33
+ name: 'get_token_price',
34
+ description: 'Return tracked Base token price data and provenance for a symbol such as USDC or WETH.',
35
+ inputSchema: { type: 'object', properties: { symbol: { type: 'string' } }, required: ['symbol'] }
36
+ },
37
+ {
38
+ name: 'get_whale_signals',
39
+ description: 'Return tracked Base whale signals and provenance.',
40
+ inputSchema: { type: 'object', properties: {} }
41
+ }
42
+ ];
43
+
44
+ function parseHeaderJson(value) {
45
+ if (!value) return null;
46
+ try { return JSON.parse(value); } catch (_) { return null; }
47
+ }
48
+
49
+ function queryApi(path) {
50
+ return new Promise((resolve, reject) => {
51
+ const url = new URL(path, BASE_URL);
52
+ const headers = { Accept: 'application/json', 'User-Agent': 'M2MSentinel-MCP/1.0.4' };
53
+ if (API_KEY) headers['x-api-key'] = API_KEY;
54
+
55
+ const req = https.request(url, { method: 'GET', headers, timeout: TIMEOUT_MS }, (res) => {
56
+ let data = '';
57
+ res.setEncoding('utf8');
58
+ res.on('data', (chunk) => { data += chunk; });
59
+ res.on('end', () => {
60
+ let body = null;
61
+ if (data) {
62
+ try { body = JSON.parse(data); } catch (_) { body = { raw: data }; }
63
+ }
64
+ resolve({
65
+ status: res.statusCode,
66
+ ok: res.statusCode >= 200 && res.statusCode < 300,
67
+ body,
68
+ paymentRequired: parseHeaderJson(res.headers['payment-required']),
69
+ paymentResponse: parseHeaderJson(res.headers['payment-response']) || res.headers['payment-response'] || null,
70
+ retryAfter: res.headers['retry-after'] || null
71
+ });
72
+ });
73
+ });
74
+ req.on('timeout', () => req.destroy(new Error('M2M Sentinel request timed out')));
75
+ req.on('error', reject);
76
+ req.end();
77
+ });
78
+ }
79
+
80
+ function pathForTool(name, args) {
81
+ const input = args || {};
82
+ switch (name) {
83
+ case 'audit_contract':
84
+ if (!input.address) throw new Error('address is required');
85
+ return '/v1/audit/' + encodeURIComponent(input.address);
86
+ case 'get_security_score':
87
+ if (!input.address) throw new Error('address is required');
88
+ return '/v1/security/score/' + encodeURIComponent(input.address);
89
+ case 'get_gas_fees':
90
+ return '/v1/gas/fees';
91
+ case 'get_dex_metrics':
92
+ return '/v1/dex/metrics';
93
+ case 'get_token_price':
94
+ if (!input.symbol) throw new Error('symbol is required');
95
+ return '/v1/token/price/' + encodeURIComponent(input.symbol);
96
+ case 'get_whale_signals':
97
+ return '/v1/whales/signals';
98
+ default:
99
+ throw new Error('Unknown tool: ' + name);
100
+ }
101
+ }
102
+
103
+ function send(message) {
104
+ process.stdout.write(JSON.stringify(message) + '\n');
105
+ }
106
+
107
+ function success(id, result) {
108
+ send({ jsonrpc: '2.0', id, result });
109
+ }
110
+
111
+ function failure(id, code, message, data) {
112
+ const error = { code, message };
113
+ if (data !== undefined) error.data = data;
114
+ send({ jsonrpc: '2.0', id, error });
115
+ }
116
+
117
+ async function handle(request) {
118
+ if (!request || request.jsonrpc !== '2.0' || typeof request.method !== 'string') {
119
+ failure(request && request.id !== undefined ? request.id : null, -32600, 'Invalid Request');
120
+ return;
121
+ }
122
+ if (request.id === undefined || request.id === null) return;
123
+
124
+ try {
125
+ if (request.method === 'initialize') {
126
+ success(request.id, {
127
+ protocolVersion: request.params && request.params.protocolVersion ? request.params.protocolVersion : '2024-11-05',
128
+ capabilities: { tools: {} },
129
+ serverInfo: { name: 'm2m-sentinel-mcp', version: '1.0.4' }
130
+ });
131
+ return;
132
+ }
133
+ if (request.method === 'tools/list') {
134
+ success(request.id, { tools: TOOLS });
135
+ return;
136
+ }
137
+ if (request.method === 'tools/call') {
138
+ const params = request.params || {};
139
+ const path = pathForTool(params.name, params.arguments || {});
140
+ const apiResult = await queryApi(path);
141
+ const isError = !apiResult.ok;
142
+ success(request.id, {
143
+ content: [{ type: 'text', text: JSON.stringify(apiResult.body, null, 2) }],
144
+ isError,
145
+ structuredContent: apiResult.body,
146
+ _meta: {
147
+ httpStatus: apiResult.status,
148
+ paymentRequired: apiResult.paymentRequired,
149
+ paymentResponse: apiResult.paymentResponse,
150
+ retryAfter: apiResult.retryAfter
151
+ }
152
+ });
153
+ return;
154
+ }
155
+ failure(request.id, -32601, 'Method not found');
156
+ } catch (err) {
157
+ failure(request.id, -32603, err && err.message ? err.message : 'Internal error');
158
+ }
159
+ }
160
+
161
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
162
+ rl.on('line', (line) => {
163
+ if (!line.trim()) return;
164
+ let request;
165
+ try {
166
+ request = JSON.parse(line);
167
+ } catch (err) {
168
+ failure(null, -32700, 'Parse error');
169
+ return;
170
+ }
171
+ handle(request);
172
+ });
package/package.json CHANGED
@@ -1,19 +1,36 @@
1
1
  {
2
2
  "name": "m2m-sentinel-sdk",
3
- "version": "1.0.2",
4
- "description": "Official Client SDK for M2M Sentinel Web3 Security Risk Auditor & AI Intelligence Engine",
3
+ "version": "1.0.4",
4
+ "description": "JavaScript/Node.js client SDK for M2M Sentinel Web3 Pre-Transaction Risk Decision API",
5
5
  "main": "index.js",
6
- "scripts": {
7
- "test": "node index.js"
6
+ "types": "typescript/index.d.ts",
7
+ "license": "MIT",
8
+ "homepage": "https://m2msentinel.vercel.app",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/m2msentinel/m2m-sentinel"
12
+ },
13
+ "bugs": {
14
+ "url": "https://m2msentinel.vercel.app/docs.html"
8
15
  },
9
16
  "keywords": [
10
17
  "web3",
11
- "ai-agent",
18
+ "x402",
19
+ "m2m-sentinel",
20
+ "base",
21
+ "risk-api",
12
22
  "smart-contract-audit",
13
- "crypto-api",
14
- "m2m",
15
- "sentinel"
23
+ "agents"
16
24
  ],
17
25
  "author": "M2M Sentinel",
18
- "license": "MIT"
26
+ "files": [
27
+ "index.js",
28
+ "m2m_sentinel_sdk.js",
29
+ "agent_adapter.js",
30
+ "eliza_plugin.js",
31
+ "mcp_server.js",
32
+ "README.md",
33
+ "typescript/"
34
+ ]
19
35
  }
36
+