@rakshasar/xsignal 0.2.0
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/LICENSE +21 -0
- package/README.md +53 -0
- package/SKILL.md +59 -0
- package/examples/watchlist-alerter.js +46 -0
- package/package.json +59 -0
- package/scripts/mcp-server.js +102 -0
- package/sdk/index.js +41 -0
- package/sdk/tools.js +64 -0
- package/sdk/tools.test.js +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xsignal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ⚡ xsignal
|
|
2
|
+
|
|
3
|
+
**Pay-per-call data ingredients for AI agents on Base**, via x402 (USDC). The flagship is the one thing no other x402
|
|
4
|
+
signal does: **`get_intent` abstains** — it returns *"no verdict"* (and says so) when it isn't confident enough, instead
|
|
5
|
+
of always answering and always charging. **3 free calls per wallet** to try, then from **$0.01**. Verify-only: never
|
|
6
|
+
signs or moves funds.
|
|
7
|
+
|
|
8
|
+
## The tools (flagship first)
|
|
9
|
+
| Tool / route | Price | What you get |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| **`get_intent`** · `GET /intent?addr=0x…&min_confidence=0.7` | $0.01 | An outcome-priced momentum verdict (`gaining`/`fading`) **only if** confidence clears your bar, else a calibrated `abstain`. Paid answers carry a keyless tamper-evidence receipt. |
|
|
12
|
+
| `get_token_brief` · `GET /brief?addr=0x…` | $0.05 | A **meal**: fuses market intel + cited social signal into a "what is happening with $TOKEN now" brief. |
|
|
13
|
+
| `get_signal` · `GET /signal?q=<topic>` | $0.01 | A scored (virality + freshness) and **cited** real-time X/social signal. |
|
|
14
|
+
| `get_token_intel` · `GET /token?addr=0x…` | $0.01 | Base token market data (liquidity/volume/price/age/flow + flags). Best as an input to the brief. |
|
|
15
|
+
| `POST /mcp` | — | MCP (streamable-http): `tools/list` discovers the 4 tools; a `tools/call` returns an x402 **payment pointer**. |
|
|
16
|
+
| `GET /health` · `/.well-known/mcp.json` · `/.well-known/agent-card.json` · `/skill.md` | free | health + agent discovery + the installable skill (no data). |
|
|
17
|
+
|
|
18
|
+
## Try it free (3 calls per wallet)
|
|
19
|
+
Add `?wallet=0xYourAddress` to any route for **3 free full results**, so you can evaluate quality before paying. After 3,
|
|
20
|
+
that wallet pays via x402.
|
|
21
|
+
```bash
|
|
22
|
+
curl "https://xsignal-production.up.railway.app/intent?addr=0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed&min_confidence=0.5&wallet=0xYourAddr"
|
|
23
|
+
```
|
|
24
|
+
Then run the example — a **watchlist alerter** that surfaces only the tokens confidently moving (and stays quiet on the rest):
|
|
25
|
+
```bash
|
|
26
|
+
WALLET=0xYourAddr node examples/watchlist-alerter.js
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Pay with x402 (beyond the free probe)
|
|
30
|
+
The first post-probe request returns **HTTP 402** with an `accepts` array (price in USDC, network `base`, `payTo`). Pay
|
|
31
|
+
with `x402-fetch`/`x402-axios` and a funded Base wallet, resubmit with the `X-PAYMENT` header, get the result. `/intent`
|
|
32
|
+
is pay-first: you pay, then get a verdict **or** an honest abstain.
|
|
33
|
+
|
|
34
|
+
## Safety
|
|
35
|
+
**We never sign or move funds.** x402 means we *receive* USDC to `payTo`; payment is **verify-only** via a facilitator.
|
|
36
|
+
Signals are scored from public X posts + public DEX data — *verify before acting; not financial advice.* Confidence is a
|
|
37
|
+
mechanical heuristic, not a calibrated probability or a prediction.
|
|
38
|
+
|
|
39
|
+
## Config (env)
|
|
40
|
+
| Var | Purpose |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `XSIGNAL_PAYTO` | address that receives USDC (public; no key). Default = the operator address. |
|
|
43
|
+
| `XSIGNAL_PRICE_USD` / `XSIGNAL_BRIEF_PRICE_USD` / `XSIGNAL_INTENT_PRICE_USD` | per-call prices (default `0.01` / `0.05` / `0.01`) |
|
|
44
|
+
| `XSIGNAL_PROBE_FREE` | free probe calls per wallet (default `3`) |
|
|
45
|
+
| `X402_NETWORK` / `FACILITATOR_URL` | `base` (mainnet CDP, needs `CDP_API_KEY_ID`/`CDP_API_KEY_SECRET`) or `base-sepolia` (keyless x402.org) |
|
|
46
|
+
| `X_BEARER_TOKEN` **or** `XAI_API_KEY` | live X/Grok source for `get_signal` (token intel + intent are keyless via DexScreener). No key → a labelled DEMO seed. |
|
|
47
|
+
|
|
48
|
+
## Run
|
|
49
|
+
```bash
|
|
50
|
+
npm start # :4520
|
|
51
|
+
npm test # full self-test suite (signal · tokenintel · brief · intent · x402 · sources · server)
|
|
52
|
+
```
|
|
53
|
+
Zero **runtime** deps for the core (plain Node >=18); `@coinbase/x402` is used only for CDP-facilitator auth on mainnet.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xsignal
|
|
3
|
+
description: Pay-per-call data ingredients for agents on Base. Flagship get_intent is the only x402 signal that ABSTAINS below your confidence bar (it refuses to answer, honestly, when it is not sure) instead of always returning a guess. Also: a fused token brief, a scored + CITED real-time X/social signal, and Base token market intel. 3 free calls per wallet to try, then micro-paid via x402 (USDC on Base) from $0.01.
|
|
4
|
+
homepage: https://xsignal-production.up.railway.app
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# xsignal — data ingredients that know when to shut up
|
|
9
|
+
|
|
10
|
+
xsignal sells **paid data ingredients for agents** on Base, priced per call in **USDC via x402**. What makes it different
|
|
11
|
+
from every other x402 data feed: the flagship tool **abstains** — it returns "no verdict" (and says so) when the signal
|
|
12
|
+
isn't strong enough, instead of always answering and always charging. **3 free calls per wallet** to try, then from
|
|
13
|
+
**$0.01**. Verify-only: it never signs or moves your funds.
|
|
14
|
+
|
|
15
|
+
## The tools (flagship first)
|
|
16
|
+
- **get_intent** ($0.01) — an **outcome-priced momentum verdict that ABSTAINS**. Post `{addr, min_confidence}` → get a
|
|
17
|
+
mechanical `gaining`/`fading` verdict **only if** the signal agreement clears your bar, else a calibrated `abstain`.
|
|
18
|
+
This is the one thing no other x402 signal does (the protocol norm is "always answer, always charge"). Paid answers
|
|
19
|
+
carry a keyless tamper-evidence receipt `{inputHash, outputHash, settlementTx}`. Confidence is a transparent heuristic,
|
|
20
|
+
**not** a prediction.
|
|
21
|
+
- **get_token_brief** ($0.05, a MEAL) — one call fuses token market intel + real-time social signal into a
|
|
22
|
+
"what is happening with $TOKEN right now" brief: market flags + top **cited** posts + a plain-language summary.
|
|
23
|
+
- **get_signal** ($0.01) — a scored (virality + freshness) and **cited** real-time X/social signal for any topic.
|
|
24
|
+
- **get_token_intel** ($0.01) — Base token market data (liquidity, volume, price, pool age, buy/sell flow, flags) from
|
|
25
|
+
public DEX pools. Market data, **not** a trust rating. Best used as an input to the brief.
|
|
26
|
+
|
|
27
|
+
## How to call it
|
|
28
|
+
|
|
29
|
+
### Try it free (3 calls per wallet)
|
|
30
|
+
Add `?wallet=0xYourAddress` to any route to get 3 free FULL results, so you can evaluate quality before paying:
|
|
31
|
+
`GET /intent?addr=0x…&min_confidence=0.7&wallet=0xYourAddr`. After 3, that wallet pays via x402.
|
|
32
|
+
|
|
33
|
+
### x402 (pay-per-call) — where the data is served
|
|
34
|
+
Every route is paid after the free probe: `GET /intent?addr=0x…&min_confidence=0.7` ($0.01) ·
|
|
35
|
+
`GET /brief?addr=0x…` ($0.05) · `GET /signal?q=<topic>` ($0.01) · `GET /token?addr=0x…` ($0.01).
|
|
36
|
+
Flow: the first (post-probe) request returns **HTTP 402** with an `accepts` array (price in USDC, network `base`, `payTo`).
|
|
37
|
+
Pay with `x402-axios`/`x402-fetch` and a funded Base wallet, resubmit with the `X-PAYMENT` header, receive the result.
|
|
38
|
+
`/intent` is pay-first: you pay, then get a verdict **or** an honest abstain.
|
|
39
|
+
|
|
40
|
+
### MCP (discovery)
|
|
41
|
+
`POST /mcp` (JSON-RPC 2.0). `tools/list` discovers the four tools; a `tools/call` returns an **x402 payment pointer**
|
|
42
|
+
(price + `accepts` + the HTTP endpoint) — MCP has no payment rail, so data is served from the paid HTTP route.
|
|
43
|
+
Discovery (free, no data): `GET /health`, `/.well-known/mcp.json`, `/.well-known/agent-card.json`, `/skill.md`.
|
|
44
|
+
|
|
45
|
+
## Cost & safety
|
|
46
|
+
- **3 free calls per wallet**, then from **$0.01 USDC** on Base. A funded agent pays in one hop.
|
|
47
|
+
- Honest by design: signals are scored from **public X posts** + **public DEX data**; all are inputs, not decisions —
|
|
48
|
+
*verify before acting; not financial advice.* Confidence is a mechanical heuristic, not a calibrated probability.
|
|
49
|
+
- Verify-only; holds no keys/funds.
|
|
50
|
+
|
|
51
|
+
## Example (buyer agent)
|
|
52
|
+
```js
|
|
53
|
+
// 3 free probe calls first (?wallet=you), then x402fetch pays automatically once the probe is used up
|
|
54
|
+
const res = await x402fetch(
|
|
55
|
+
'https://xsignal-production.up.railway.app/intent?addr=' + addr + '&min_confidence=0.7&wallet=' + myWallet
|
|
56
|
+
).then(r => r.json());
|
|
57
|
+
if (res.served) act(res.verdict, res.evidence, res.receipt); // 'gaining' | 'fading' + cited evidence + receipt
|
|
58
|
+
else skip(res.reason); // abstained: confidence below your bar
|
|
59
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* xsignal example — watchlist momentum alerter (the "reusable workflow" agents pay for)
|
|
4
|
+
* =====================================================================================
|
|
5
|
+
* Scan a watchlist of Base tokens and surface ONLY the ones that are confidently moving. This is the
|
|
6
|
+
* abstention flagship (get_intent) doing what incumbents can't: it stays QUIET on the tokens it isn't
|
|
7
|
+
* sure about, so your agent acts on signal, not noise.
|
|
8
|
+
*
|
|
9
|
+
* Runs FREE out of the box: set WALLET to your 0x address and each token spends one of your 3 free
|
|
10
|
+
* probe calls per wallet. Beyond 3, wrap fetch with x402 (x402-fetch / x402-axios + a funded Base
|
|
11
|
+
* wallet) and drop the ?wallet= param — the SAME code then pays $0.01/call automatically.
|
|
12
|
+
*
|
|
13
|
+
* WALLET=0xYourAddr node examples/watchlist-alerter.js
|
|
14
|
+
*/
|
|
15
|
+
const BASE = process.env.XSIGNAL_URL || 'https://xsignal-production.up.railway.app';
|
|
16
|
+
const WALLET = process.env.WALLET || '0x0000000000000000000000000000000000000000'; // your addr → 3 free calls, then pay via x402
|
|
17
|
+
const MIN_CONFIDENCE = Number(process.env.MIN_CONFIDENCE || 0.25); // raise it (e.g. 0.7) to watch xsignal abstain on weak movers
|
|
18
|
+
|
|
19
|
+
// A Base token watchlist (add your own 0x addresses; xsignal reads the on-chain symbol).
|
|
20
|
+
const WATCHLIST = [
|
|
21
|
+
'0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed', // DEGEN
|
|
22
|
+
// '0x...your token...',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
async function readIntent(addr) {
|
|
26
|
+
const url = `${BASE}/intent?addr=${addr}&min_confidence=${MIN_CONFIDENCE}&wallet=${WALLET}`;
|
|
27
|
+
const r = await fetch(url);
|
|
28
|
+
if (r.status === 402) return { addr, paywalled: true }; // free probe used up → pay via x402 to continue
|
|
29
|
+
if (!r.ok) return { addr, error: 'HTTP ' + r.status };
|
|
30
|
+
return { addr, ...(await r.json()) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
(async () => {
|
|
34
|
+
console.log(`xsignal watchlist alerter — surfacing only what is confidently moving (min_confidence ${MIN_CONFIDENCE})\n`);
|
|
35
|
+
const results = await Promise.all(WATCHLIST.map(readIntent));
|
|
36
|
+
for (const r of results) {
|
|
37
|
+
const sym = (r.evidence && r.evidence.market && r.evidence.market.symbol) || r.addr.slice(0, 8);
|
|
38
|
+
if (r.paywalled) console.log(`… ${r.addr} free probe used up — add x402 payment to continue`);
|
|
39
|
+
else if (r.error) console.log(`… ${r.addr} (${r.error})`);
|
|
40
|
+
else if (r.served) console.log(`${r.verdict === 'gaining' ? '🟢' : '🔴'} $${sym} ${r.verdict} (confidence ${r.confidence})`);
|
|
41
|
+
else console.log(`· $${sym} abstain — not confident enough (${r.confidence}); xsignal stays quiet`);
|
|
42
|
+
}
|
|
43
|
+
const movers = results.filter((r) => r.served);
|
|
44
|
+
console.log(`\n${movers.length}/${WATCHLIST.length} confidently moving. The rest: xsignal abstained rather than guess.`);
|
|
45
|
+
console.log('Not financial advice. Confidence is a mechanical heuristic, not a prediction.');
|
|
46
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rakshasar/xsignal",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"mcpName": "io.github.philpof102-svg/xsignal",
|
|
5
|
+
"description": "Pay-per-call data ingredients for AI agents on Base (x402/USDC). Flagship get_intent: the only x402 signal that ABSTAINS below your confidence bar instead of guessing. Also a fused token brief, cited X/social signal, and Base token market intel. 3 free calls per wallet, then from $0.01. Verify-only, never signs.",
|
|
6
|
+
"main": "sdk/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./sdk/index.js",
|
|
9
|
+
"./tools": "./sdk/tools.js"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"xsignal-mcp": "scripts/mcp-server.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"sdk/",
|
|
16
|
+
"scripts/mcp-server.js",
|
|
17
|
+
"examples/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"SKILL.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "node app.js",
|
|
24
|
+
"example": "node examples/watchlist-alerter.js",
|
|
25
|
+
"mcp": "node scripts/mcp-server.js",
|
|
26
|
+
"test": "node signal.js && node tokenintel.js && node brief.js && node intent.js && node x402.js && node sources.js && node sdk/tools.test.js && node app.js --selftest"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"model-context-protocol",
|
|
31
|
+
"x402",
|
|
32
|
+
"base",
|
|
33
|
+
"base-mainnet",
|
|
34
|
+
"ai-agents",
|
|
35
|
+
"crypto-signal",
|
|
36
|
+
"token-intel",
|
|
37
|
+
"abstention",
|
|
38
|
+
"momentum",
|
|
39
|
+
"onchain",
|
|
40
|
+
"usdc",
|
|
41
|
+
"dexscreener",
|
|
42
|
+
"agent-tools"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/philpof102-svg/xsignal"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://xsignal-production.up.railway.app",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/philpof102-svg/xsignal/issues"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@coinbase/x402": "^2.1.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* xsignal MCP server (stdio transport).
|
|
4
|
+
*
|
|
5
|
+
* Exposes xsignal's pay-per-call data ingredients as a Model Context Protocol server so any
|
|
6
|
+
* Claude Desktop / Claude Code / Cursor / agent SDK that speaks MCP can attach it as a tool.
|
|
7
|
+
* Flagship: get_intent — the only x402 signal that ABSTAINS below your confidence bar.
|
|
8
|
+
*
|
|
9
|
+
* Install (Claude Desktop / Cursor mcp config):
|
|
10
|
+
* {
|
|
11
|
+
* "mcpServers": {
|
|
12
|
+
* "xsignal": {
|
|
13
|
+
* "command": "npx",
|
|
14
|
+
* "args": ["-y", "@rakshasar/xsignal", "xsignal-mcp"],
|
|
15
|
+
* "env": { "XSIGNAL_WALLET": "0xYourAddress" }
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* Set XSIGNAL_WALLET for 3 free calls per wallet; beyond that the tools return an x402 pay pointer.
|
|
21
|
+
* No deps beyond Node 18+ (native JSON-RPC over stdio; global fetch).
|
|
22
|
+
*/
|
|
23
|
+
const ORIGIN = (process.env.XSIGNAL_ORIGIN || 'https://xsignal-production.up.railway.app').replace(/\/$/, '');
|
|
24
|
+
const WALLET = process.env.XSIGNAL_WALLET || '';
|
|
25
|
+
const SERVER_NAME = 'xsignal';
|
|
26
|
+
const SERVER_VERSION = require('../package.json').version;
|
|
27
|
+
const w = () => (WALLET ? '&wallet=' + WALLET : '');
|
|
28
|
+
|
|
29
|
+
const TOOLS = [
|
|
30
|
+
{
|
|
31
|
+
name: 'get_intent',
|
|
32
|
+
description: 'FLAGSHIP. An outcome-priced Base-token momentum verdict that ABSTAINS below your confidence bar - the only x402 signal that refuses to answer (honestly) when it is not sure, instead of always guessing. Returns "gaining"/"fading" if confidence clears min_confidence, else "abstain". Confidence is a transparent heuristic, not a prediction; not financial advice.',
|
|
33
|
+
inputSchema: { type: 'object', required: ['addr'], properties: { addr: { type: 'string', description: '0x Base token address' }, min_confidence: { type: 'number', description: '0-1, default 0.6; abstain below this' } } },
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'get_token_brief',
|
|
37
|
+
description: 'A fused MEAL: Base token market intel + real-time social signal in one "what is happening with $TOKEN now" brief (market flags + cited top posts + a non-advisory summary).',
|
|
38
|
+
inputSchema: { type: 'object', required: ['addr'], properties: { addr: { type: 'string', description: '0x Base token address' }, query: { type: 'string', description: 'optional topic/symbol for the social half' } } },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'get_signal',
|
|
42
|
+
description: 'Real-time X/social signal for a topic: scored (virality + freshness) and CITED (source urls), deduped and ranked.',
|
|
43
|
+
inputSchema: { type: 'object', required: ['query'], properties: { query: { type: 'string', description: 'topic/keywords' } } },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'get_token_intel',
|
|
47
|
+
description: 'Base token market data (liquidity, 24h volume, price + change, pool age, buy/sell flow, mechanical flags) from public DEX pools. Market data, NOT a trust rating.',
|
|
48
|
+
inputSchema: { type: 'object', required: ['addr'], properties: { addr: { type: 'string', description: '0x Base token address' } } },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'get_track_record',
|
|
52
|
+
description: 'Live transparency for the abstaining flagship: abstention rate + coverage since restart (descriptive activity, not a win-rate).',
|
|
53
|
+
inputSchema: { type: 'object', properties: {} },
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
async function callApi(path) {
|
|
58
|
+
const r = await fetch(ORIGIN + path);
|
|
59
|
+
const j = await r.json().catch(() => ({}));
|
|
60
|
+
if (r.status === 402) return { paymentRequired: true, ...j, hint: WALLET ? 'Free probe used up for this wallet - pay via x402 (x402-fetch/axios) to continue.' : 'Set XSIGNAL_WALLET env for 3 free calls per wallet, or pay via x402.' };
|
|
61
|
+
return j;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function execTool(name, a) {
|
|
65
|
+
switch (name) {
|
|
66
|
+
case 'get_intent': return callApi(`/intent?addr=${a.addr}&min_confidence=${a.min_confidence != null ? a.min_confidence : 0.6}` + w());
|
|
67
|
+
case 'get_token_brief': return callApi(`/brief?addr=${a.addr}` + (a.query ? `&q=${encodeURIComponent(a.query)}` : '') + w());
|
|
68
|
+
case 'get_signal': return callApi(`/signal?q=${encodeURIComponent(a.query || '')}` + w());
|
|
69
|
+
case 'get_token_intel': return callApi(`/token?addr=${a.addr}` + w());
|
|
70
|
+
case 'get_track_record': return callApi('/track-record');
|
|
71
|
+
default: throw new Error('unknown tool: ' + name);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function send(msg) { process.stdout.write(JSON.stringify(msg) + '\n'); }
|
|
76
|
+
|
|
77
|
+
// Minimal JSON-RPC 2.0 over stdio (MCP spec)
|
|
78
|
+
async function handle(req) {
|
|
79
|
+
const { id, method, params } = req;
|
|
80
|
+
try {
|
|
81
|
+
if (method === 'initialize') return { jsonrpc: '2.0', id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: SERVER_NAME, version: SERVER_VERSION } } };
|
|
82
|
+
if (method === 'tools/list') return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
|
|
83
|
+
if (method === 'tools/call') { const result = await execTool(params.name, params.arguments || {}); return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } }; }
|
|
84
|
+
if (method === 'notifications/initialized') return null;
|
|
85
|
+
return { jsonrpc: '2.0', id, error: { code: -32601, message: 'method not found: ' + method } };
|
|
86
|
+
} catch (e) { return { jsonrpc: '2.0', id, error: { code: -32000, message: e.message } }; }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let buffer = '';
|
|
90
|
+
process.stdin.on('data', async (chunk) => {
|
|
91
|
+
buffer += chunk.toString('utf8');
|
|
92
|
+
let idx;
|
|
93
|
+
while ((idx = buffer.indexOf('\n')) >= 0) {
|
|
94
|
+
const line = buffer.slice(0, idx).trim();
|
|
95
|
+
buffer = buffer.slice(idx + 1);
|
|
96
|
+
if (!line) continue;
|
|
97
|
+
let req; try { req = JSON.parse(line); } catch { continue; }
|
|
98
|
+
const resp = await handle(req);
|
|
99
|
+
if (resp) send(resp);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
process.stderr.write(`[xsignal MCP] ready, ${TOOLS.length} tools, origin=${ORIGIN}${WALLET ? ', probe wallet set' : ' (no XSIGNAL_WALLET - tools return pay pointers)'}\n`);
|
package/sdk/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* xsignal SDK — a tiny client for the hosted x402 data ingredients on Base.
|
|
4
|
+
*
|
|
5
|
+
* const xsignal = require('@rakshasar/xsignal');
|
|
6
|
+
* const x = xsignal.client({ wallet: '0xYourAddr' }); // wallet → 3 free calls, then x402
|
|
7
|
+
* const verdict = await x.intent('0x4ed4…', { minConfidence: 0.7 });
|
|
8
|
+
*
|
|
9
|
+
* Every method returns the parsed JSON (a served verdict, an abstain, or an x402 pay pointer with `.paymentRequired`).
|
|
10
|
+
* `fetch` and `origin` are injectable for tests. Flagship: intent() = the abstaining momentum verdict.
|
|
11
|
+
*/
|
|
12
|
+
const ORIGIN = (process.env.XSIGNAL_ORIGIN || 'https://xsignal-production.up.railway.app').replace(/\/$/, '');
|
|
13
|
+
|
|
14
|
+
function client(opts = {}) {
|
|
15
|
+
const base = (opts.origin || ORIGIN).replace(/\/$/, '');
|
|
16
|
+
const wallet = opts.wallet || process.env.XSIGNAL_WALLET || '';
|
|
17
|
+
const f = opts.fetch || (typeof fetch !== 'undefined' ? fetch : null);
|
|
18
|
+
const w = wallet ? '&wallet=' + wallet : '';
|
|
19
|
+
async function call(path) {
|
|
20
|
+
if (!f) throw new Error('no fetch available (Node 18+ or pass opts.fetch)');
|
|
21
|
+
const r = await f(base + path);
|
|
22
|
+
const j = await r.json().catch(() => ({}));
|
|
23
|
+
return r.status === 402 ? { paymentRequired: true, ...j } : j;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
/** FLAGSHIP: momentum verdict that abstains below minConfidence. */
|
|
27
|
+
intent: (addr, o = {}) => call(`/intent?addr=${addr}&min_confidence=${o.minConfidence != null ? o.minConfidence : 0.6}` + w),
|
|
28
|
+
/** fused brief (market intel + social signal). */
|
|
29
|
+
brief: (addr, o = {}) => call(`/brief?addr=${addr}` + (o.query ? `&q=${encodeURIComponent(o.query)}` : '') + w),
|
|
30
|
+
/** scored + cited real-time X/social signal for a topic. */
|
|
31
|
+
signal: (query, o = {}) => call(`/signal?q=${encodeURIComponent(query || '')}` + w),
|
|
32
|
+
/** Base token market data from public DEX pools. */
|
|
33
|
+
tokenIntel: (addr) => call(`/token?addr=${addr}` + w),
|
|
34
|
+
/** live abstention transparency (not a win-rate). */
|
|
35
|
+
trackRecord: () => call('/track-record'),
|
|
36
|
+
origin: base, wallet,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { client, ORIGIN };
|
|
41
|
+
module.exports.default = module.exports;
|
package/sdk/tools.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* xsignal SDK — LLM tool definitions. One import, plug into any framework:
|
|
4
|
+
* OpenAI / Anthropic / Vercel AI SDK / LangChain / Mastra.
|
|
5
|
+
*
|
|
6
|
+
* import { vercelAiSdk } from '@rakshasar/xsignal/tools';
|
|
7
|
+
* const result = await generateText({ model, tools: vercelAiSdk({ wallet: '0xYourAddr' }) });
|
|
8
|
+
*
|
|
9
|
+
* Each tool's execute() calls the hosted xsignal endpoint (3 free calls per wallet, then an x402 pay pointer)
|
|
10
|
+
* and returns plain JSON the LLM ingests directly. Flagship: get_intent = the abstaining momentum verdict.
|
|
11
|
+
*/
|
|
12
|
+
const { client } = require('./index.js');
|
|
13
|
+
|
|
14
|
+
const intentSpec = {
|
|
15
|
+
name: 'get_intent',
|
|
16
|
+
description: 'FLAGSHIP. An outcome-priced Base-token momentum verdict that ABSTAINS below your confidence bar - the only x402 signal that refuses to answer (honestly) when it is not sure. Returns gaining/fading if confidence clears min_confidence, else abstain. Confidence is a transparent heuristic, not a prediction; not financial advice.',
|
|
17
|
+
parameters: { type: 'object', required: ['addr'], properties: { addr: { type: 'string', description: '0x Base token address' }, min_confidence: { type: 'number', description: '0-1, default 0.6; abstain below this', minimum: 0, maximum: 1 } } },
|
|
18
|
+
execute: (a) => client().intent(a.addr, { minConfidence: a.min_confidence }),
|
|
19
|
+
};
|
|
20
|
+
const briefSpec = {
|
|
21
|
+
name: 'get_token_brief',
|
|
22
|
+
description: 'A fused MEAL: Base token market intel + real-time social signal in one "what is happening with $TOKEN now" brief (market flags + cited top posts + a non-advisory summary).',
|
|
23
|
+
parameters: { type: 'object', required: ['addr'], properties: { addr: { type: 'string', description: '0x Base token address' }, query: { type: 'string', description: 'optional topic/symbol for the social half' } } },
|
|
24
|
+
execute: (a) => client().brief(a.addr, { query: a.query }),
|
|
25
|
+
};
|
|
26
|
+
const signalSpec = {
|
|
27
|
+
name: 'get_signal',
|
|
28
|
+
description: 'Real-time X/social signal for a topic: scored (virality + freshness) and CITED (source urls), deduped and ranked.',
|
|
29
|
+
parameters: { type: 'object', required: ['query'], properties: { query: { type: 'string', description: 'topic/keywords' } } },
|
|
30
|
+
execute: (a) => client().signal(a.query),
|
|
31
|
+
};
|
|
32
|
+
const tokenIntelSpec = {
|
|
33
|
+
name: 'get_token_intel',
|
|
34
|
+
description: 'Base token market data (liquidity, 24h volume, price + change, pool age, buy/sell flow, mechanical flags) from public DEX pools. Market data, NOT a trust rating. Best as an input to get_token_brief.',
|
|
35
|
+
parameters: { type: 'object', required: ['addr'], properties: { addr: { type: 'string', description: '0x Base token address' } } },
|
|
36
|
+
execute: (a) => client().tokenIntel(a.addr),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ALL = [intentSpec, briefSpec, signalSpec, tokenIntelSpec];
|
|
40
|
+
|
|
41
|
+
// Build tool objects whose execute() is bound to a client with per-call opts ({ wallet, origin, fetch }).
|
|
42
|
+
function withOpts(opts = {}) {
|
|
43
|
+
const c = client(opts);
|
|
44
|
+
const call = { get_intent: (a) => c.intent(a.addr, { minConfidence: a.min_confidence }), get_token_brief: (a) => c.brief(a.addr, { query: a.query }), get_signal: (a) => c.signal(a.query), get_token_intel: (a) => c.tokenIntel(a.addr) };
|
|
45
|
+
return ALL.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters, execute: call[t.name] }));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** OpenAI function-calling shape. */
|
|
49
|
+
function openai() { return ALL.map((t) => ({ type: 'function', function: { name: t.name, description: t.description, parameters: t.parameters } })); }
|
|
50
|
+
/** Anthropic Claude tools shape. */
|
|
51
|
+
function anthropic() { return ALL.map((t) => ({ name: t.name, description: t.description, input_schema: t.parameters })); }
|
|
52
|
+
/** Vercel AI SDK tools object (keyed by name, with execute bound to your wallet/origin). */
|
|
53
|
+
function vercelAiSdk(opts = {}) { const out = {}; for (const t of withOpts(opts)) out[t.name] = { description: t.description, parameters: t.parameters, execute: t.execute }; return out; }
|
|
54
|
+
/** LangChain DynamicStructuredTool plain-object shape. Wrap with `new DynamicStructuredTool(...)`. */
|
|
55
|
+
function langchain(opts = {}) { return withOpts(opts).map((t) => ({ name: t.name, description: t.description, schema: t.parameters, func: t.execute })); }
|
|
56
|
+
/** Mastra createTool() compatible spec. */
|
|
57
|
+
function mastra(opts = {}) { return withOpts(opts).map((t) => ({ id: t.name, description: t.description, inputSchema: t.parameters, execute: ({ context }) => t.execute(context) })); }
|
|
58
|
+
/** Generic JSON Schema dump. */
|
|
59
|
+
function specs() { return ALL.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })); }
|
|
60
|
+
/** Direct execute by tool name (opts: { wallet, origin, fetch }). */
|
|
61
|
+
async function execute(name, args, opts = {}) { const t = withOpts(opts).find((x) => x.name === name); if (!t) throw new Error('unknown xsignal tool: ' + name); return t.execute(args || {}); }
|
|
62
|
+
|
|
63
|
+
module.exports = { openai, anthropic, vercelAiSdk, langchain, mastra, specs, execute, intentSpec, briefSpec, signalSpec, tokenIntelSpec };
|
|
64
|
+
module.exports.default = module.exports;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// xsignal SDK self-test: the framework adapters produce the right shapes + execute() hits the right hosted path
|
|
3
|
+
// (injected fetch → fully offline). Mirrors MainStreet's proven tool-adapter pattern.
|
|
4
|
+
const T = require('./tools.js');
|
|
5
|
+
const calls = [];
|
|
6
|
+
const mockFetch = async (url) => { calls.push(url); return { status: 200, json: async () => ({ ok: true, url }) }; };
|
|
7
|
+
const opts = { fetch: mockFetch, wallet: '0xabc', origin: 'https://x.test' };
|
|
8
|
+
|
|
9
|
+
(async () => {
|
|
10
|
+
const oa = T.openai(), an = T.anthropic(), specs = T.specs();
|
|
11
|
+
const vc = T.vercelAiSdk(opts), lc = T.langchain(opts), ms = T.mastra(opts);
|
|
12
|
+
const intentRes = await T.execute('get_intent', { addr: '0xTOKEN', min_confidence: 0.7 }, opts);
|
|
13
|
+
const vcIntent = await vc.get_intent.execute({ addr: '0xTOKEN', min_confidence: 0.7 });
|
|
14
|
+
|
|
15
|
+
const checks = [
|
|
16
|
+
['4 tools, flagship get_intent first', specs.length === 4 && specs[0].name === 'get_intent'],
|
|
17
|
+
['openai shape {type:function, function:{name,parameters}}', oa[0].type === 'function' && oa[0].function.name === 'get_intent' && !!oa[0].function.parameters],
|
|
18
|
+
['anthropic shape {name, input_schema}', an[0].name === 'get_intent' && !!an[0].input_schema],
|
|
19
|
+
['vercel shape: record keyed by name with execute + parameters', typeof vc.get_intent.execute === 'function' && !!vc.get_intent.parameters],
|
|
20
|
+
['langchain shape {name, schema, func}', lc[0].name === 'get_intent' && !!lc[0].schema && typeof lc[0].func === 'function'],
|
|
21
|
+
['mastra shape {id, inputSchema, execute}', ms[0].id === 'get_intent' && !!ms[0].inputSchema && typeof ms[0].execute === 'function'],
|
|
22
|
+
['execute get_intent → GET /intent with addr + min_confidence + wallet', /\/intent\?addr=0xTOKEN&min_confidence=0\.7&wallet=0xabc/.test(calls.join(' ')) && intentRes.ok === true],
|
|
23
|
+
['vercel execute also hits the hosted endpoint', vcIntent.ok === true],
|
|
24
|
+
['origin is injectable (all calls hit https://x.test)', calls.length > 0 && calls.every((u) => u.startsWith('https://x.test'))],
|
|
25
|
+
['NO fund-moving executor in the surface', !Object.keys(T).some((k) => typeof T[k] === 'function' && /^(sign|send|swap|deploy|transfer|withdraw)/i.test(k))],
|
|
26
|
+
];
|
|
27
|
+
let pass = 0; for (const [n, ok] of checks) { console.log(ok ? 'PASS' : 'FAIL', '·', n); if (ok) pass++; }
|
|
28
|
+
console.log(`\n${pass}/${checks.length} checks passed`);
|
|
29
|
+
process.exit(pass === checks.length ? 0 : 1);
|
|
30
|
+
})();
|