🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@wealthville/mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wealthville/mcp-server - npm Package Compare versions

Comparing version
0.1.2
to
0.1.3
+64
dist/http.js
#!/usr/bin/env node
/**
* Wealthville MCP server — Streamable HTTP transport for hosted/remote
* deployments (e.g. Smithery, or self-hosted at https://<host>/mcp).
*
* Stateless: a fresh McpServer + transport is created per request, so there is
* no session state to leak between callers and the process scales horizontally.
* All four tools are read-only, so statelessness costs nothing.
*
* Per-request config (optional) is read from, in order:
* - ?config=<base64 JSON> (Smithery convention: { wealthvilleApiKey, wealthvilleApiUrl })
* - ?wealthvilleApiKey= / ?wealthvilleApiUrl= (flat query params)
* - x-api-key header
* - falling back to WEALTHVILLE_API_KEY / WEALTHVILLE_API_URL env
*
* Env:
* PORT — listen port (default 8080)
*/
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
import { buildServer, VERSION } from './server.js';
const PORT = Number(process.env.PORT || 8080);
function parseConfig(req) {
const q = req.query;
let cfg = {};
if (typeof q.config === 'string') {
try {
cfg = JSON.parse(Buffer.from(q.config, 'base64').toString('utf8'));
}
catch { /* ignore malformed config */ }
}
const apiKey = cfg.wealthvilleApiKey ?? q.wealthvilleApiKey ?? req.headers['x-api-key'];
const apiUrl = cfg.wealthvilleApiUrl ?? q.wealthvilleApiUrl;
return { apiKey, apiUrl };
}
const app = express();
app.use(express.json());
// Liveness probe for hosting platforms.
app.get('/health', (_req, res) => {
res.json({ ok: true, name: 'wealthville', version: VERSION });
});
// Streamable HTTP endpoint — stateless request/response.
app.post('/mcp', async (req, res) => {
const server = buildServer(parseConfig(req));
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
res.on('close', () => { void transport.close(); void server.close(); });
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
}
catch {
if (!res.headersSent) {
res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null });
}
}
});
// Stateless server: no server-initiated streams or session teardown.
const methodNotAllowed = (_req, res) => res.status(405).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed (stateless server)' }, id: null });
app.get('/mcp', methodNotAllowed);
app.delete('/mcp', methodNotAllowed);
app.listen(PORT, () => {
// Log to stderr so it never pollutes any stdout JSON.
console.error(`Wealthville MCP (Streamable HTTP) v${VERSION} listening on :${PORT} at POST /mcp`);
});
/**
* Wealthville MCP server core — builds the McpServer with all four read-only
* tools. Shared by both transports: `index.ts` (stdio, local/npx) and
* `http.ts` (Streamable HTTP, hosted deployments like Smithery).
*
* Config resolution order for each request/process: explicit `config` arg →
* environment variables → defaults. This lets the HTTP transport pass per-request
* config while the stdio transport relies on env.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { createRequire } from 'node:module';
import { z } from 'zod';
// Single source of truth for the version: read package.json at runtime so the
// serverInfo reported over MCP always matches the published npm version. dist/
// sits one level below package.json in the tarball, and npm always includes
// package.json, so '../package.json' resolves in the installed package.
const require = createRequire(import.meta.url);
export const VERSION = require('../package.json').version;
const DEFAULT_BASE_URL = 'https://wealthville.net';
const DISCLAIMER = 'Wealthville scores are a data product, not financial advice. '
+ 'Methodology: https://www.wealthville.net/learn/wealthville-score — '
+ 'live track record (misses included): https://www.wealthville.net/track-record';
// All four tools are read-only GET wrappers over the public Wealthville API — no
// state changes and safe to retry. Surfaced as MCP annotation hints so Glama (and
// any client) can flag them non-destructive / read-only.
const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
/** Every tool returns JSON plus the methodology/disclaimer line so agents repeat it. */
function toResult(data) {
return {
content: [
{ type: 'text', text: JSON.stringify(data, null, 2) },
{ type: 'text', text: DISCLAIMER },
],
};
}
/** Build a fully-configured McpServer instance. Safe to call once per stdio process or once per HTTP request. */
export function buildServer(config = {}) {
const baseUrl = (config.apiUrl || process.env.WEALTHVILLE_API_URL || DEFAULT_BASE_URL).replace(/\/$/, '');
const apiKey = config.apiKey || process.env.WEALTHVILLE_API_KEY;
async function apiGet(path) {
const headers = { accept: 'application/json' };
if (apiKey)
headers['x-api-key'] = apiKey;
const res = await fetch(`${baseUrl}${path}`, { headers });
if (!res.ok) {
throw new Error(`Wealthville API ${res.status} for ${path}${res.status === 429 ? ' (rate limited — retry shortly or set WEALTHVILLE_API_KEY)' : ''}`);
}
return res.json();
}
const server = new McpServer({ name: 'wealthville', version: VERSION });
server.tool('get_pool_score', 'Get the Wealthville verdict (ENTER/HOLD/EXIT/AVOID) and scores (enter/hold/exit + composite '
+ 'Wealthville Score, 0-100) for one liquidity pool. Use before recommending or opening any LP position. '
+ 'Accepts a Solana pool address (base58) or an EVM 0x address / DefiLlama pool UUID.', { pool_address: z.string().min(8).describe('Pool address: Solana base58, EVM 0x, or DefiLlama UUID') }, READ_ONLY, async ({ pool_address }) => toResult(await apiGet(`/api/v1/scores/${encodeURIComponent(pool_address)}`)));
server.tool('get_top_pools', 'List liquidity pools ranked by composite Wealthville Score (0-100), freshly scored within the last '
+ '6 hours. Good for "what are the best pools right now?" questions.', {
limit: z.number().int().min(1).max(100).optional().describe('How many pools (default 25)'),
chain: z.string().optional().describe('"solana" (default), "evm" (all EVM chains), or one EVM chain e.g. "ethereum", "base"'),
}, READ_ONLY, async ({ limit, chain }) => {
const params = new URLSearchParams();
if (limit)
params.set('limit', String(limit));
if (chain)
params.set('chain', chain);
const qs = params.toString();
return toResult(await apiGet(`/api/v1/scores/top${qs ? `?${qs}` : ''}`));
});
server.tool('get_track_record', 'Get Wealthville\'s live signal track record: per-action hit rates, IL-adjusted 7-day PnL, and recent '
+ 'resolved signals — misses included (the ledger is immutable at publish time). Use when asked whether '
+ 'Wealthville scores can be trusted, or for the system\'s recent performance.', { days: z.number().int().min(7).max(90).optional().describe('Window in days (default 30)') }, READ_ONLY, async ({ days }) => toResult(await apiGet(`/api/v1/track-record${days ? `?days=${days}` : ''}`)));
server.tool('get_signals_feed', 'Get the latest published Wealthville signals (ENTER/EXIT/RISK_OFF calls with narrative and confidence). '
+ 'Use for "any new LP signals?" questions.', { limit: z.number().int().min(1).max(50).optional().describe('How many signals (default 20)') }, READ_ONLY, async ({ limit }) => toResult(await apiGet(`/api/v1/signals/feed${limit ? `?limit=${limit}` : ''}`)));
return server;
}
+11
-65
#!/usr/bin/env node
/**
* Wealthville MCP server — exposes the public Wealthville data API
* (https://wealthville.net/developers) as MCP tools so AI assistants can
* answer "should I LP into this pool?" with a scored, track-recorded answer.
* Wealthville MCP server — stdio transport (local / npx).
*
* Read-only; wraps four public HTTP endpoints. Optional env:
* Exposes the public Wealthville data API (https://wealthville.net/developers)
* as MCP tools so AI assistants can answer "should I LP into this pool?" with a
* scored, track-recorded answer. Read-only; wraps four public GET endpoints.
*
* Optional env:
* WEALTHVILLE_API_KEY — partner key (higher rate limit), sent as x-api-key
* WEALTHVILLE_API_URL — override base URL (default https://wealthville.net)
*
* For hosted/remote deployments (HTTP), see ./http.ts.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { createRequire } from 'node:module';
import { z } from 'zod';
// Single source of truth for the version: read package.json at runtime so the
// serverInfo reported over MCP always matches the published npm version. dist/
// sits one level below package.json in the tarball, and npm always includes
// package.json, so '../package.json' resolves in the installed package.
const require = createRequire(import.meta.url);
const { version: VERSION } = require('../package.json');
const BASE_URL = (process.env.WEALTHVILLE_API_URL || 'https://wealthville.net').replace(/\/$/, '');
const API_KEY = process.env.WEALTHVILLE_API_KEY;
const DISCLAIMER = 'Wealthville scores are a data product, not financial advice. '
+ 'Methodology: https://www.wealthville.net/learn/wealthville-score — '
+ 'live track record (misses included): https://www.wealthville.net/track-record';
async function apiGet(path) {
const headers = { accept: 'application/json' };
if (API_KEY)
headers['x-api-key'] = API_KEY;
const res = await fetch(`${BASE_URL}${path}`, { headers });
if (!res.ok) {
throw new Error(`Wealthville API ${res.status} for ${path}${res.status === 429 ? ' (rate limited — retry shortly or set WEALTHVILLE_API_KEY)' : ''}`);
}
return res.json();
}
/** Every tool returns JSON plus the methodology/disclaimer line so agents repeat it. */
function toResult(data) {
return {
content: [
{ type: 'text', text: JSON.stringify(data, null, 2) },
{ type: 'text', text: DISCLAIMER },
],
};
}
// All four tools are read-only GET wrappers over the public Wealthville API — no
// state changes and safe to retry. Surfaced as MCP annotation hints so Glama (and
// any client) can flag them non-destructive / read-only.
const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
const server = new McpServer({ name: 'wealthville', version: VERSION });
server.tool('get_pool_score', 'Get the Wealthville verdict (ENTER/HOLD/EXIT/AVOID) and scores (enter/hold/exit + composite '
+ 'Wealthville Score, 0-100) for one liquidity pool. Use before recommending or opening any LP position. '
+ 'Accepts a Solana pool address (base58) or an EVM 0x address / DefiLlama pool UUID.', { pool_address: z.string().min(8).describe('Pool address: Solana base58, EVM 0x, or DefiLlama UUID') }, READ_ONLY, async ({ pool_address }) => toResult(await apiGet(`/api/v1/scores/${encodeURIComponent(pool_address)}`)));
server.tool('get_top_pools', 'List liquidity pools ranked by composite Wealthville Score (0-100), freshly scored within the last '
+ '6 hours. Good for "what are the best pools right now?" questions.', {
limit: z.number().int().min(1).max(100).optional().describe('How many pools (default 25)'),
chain: z.string().optional().describe('"solana" (default), "evm" (all EVM chains), or one EVM chain e.g. "ethereum", "base"'),
}, READ_ONLY, async ({ limit, chain }) => {
const params = new URLSearchParams();
if (limit)
params.set('limit', String(limit));
if (chain)
params.set('chain', chain);
const qs = params.toString();
return toResult(await apiGet(`/api/v1/scores/top${qs ? `?${qs}` : ''}`));
});
server.tool('get_track_record', 'Get Wealthville\'s live signal track record: per-action hit rates, IL-adjusted 7-day PnL, and recent '
+ 'resolved signals — misses included (the ledger is immutable at publish time). Use when asked whether '
+ 'Wealthville scores can be trusted, or for the system\'s recent performance.', { days: z.number().int().min(7).max(90).optional().describe('Window in days (default 30)') }, READ_ONLY, async ({ days }) => toResult(await apiGet(`/api/v1/track-record${days ? `?days=${days}` : ''}`)));
server.tool('get_signals_feed', 'Get the latest published Wealthville signals (ENTER/EXIT/RISK_OFF calls with narrative and confidence). '
+ 'Use for "any new LP signals?" questions.', { limit: z.number().int().min(1).max(50).optional().describe('How many signals (default 20)') }, READ_ONLY, async ({ limit }) => toResult(await apiGet(`/api/v1/signals/feed${limit ? `?limit=${limit}` : ''}`)));
const transport = new StdioServerTransport();
await server.connect(transport);
import { buildServer } from './server.js';
const server = buildServer();
await server.connect(new StdioServerTransport());
{
"name": "@wealthville/mcp-server",
"mcpName": "io.github.amitesh-m/wealthville",
"version": "0.1.2",
"version": "0.1.3",
"description": "MCP server exposing Wealthville pool scores, verdicts, and the live track record to AI assistants",

@@ -9,3 +9,4 @@ "license": "MIT",

"bin": {
"wealthville-mcp": "dist/index.js"
"wealthville-mcp": "dist/index.js",
"wealthville-mcp-http": "dist/http.js"
},

@@ -18,4 +19,5 @@ "main": "dist/index.js",

"scripts": {
"build": "tsc -p tsconfig.json && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
"start": "node dist/index.js"
"build": "tsc -p tsconfig.json && node -e \"const fs=require('fs');['dist/index.js','dist/http.js'].forEach(f=>fs.chmodSync(f,0o755))\"",
"start": "node dist/index.js",
"start:http": "node dist/http.js"
},

@@ -33,5 +35,7 @@ "keywords": [

"@modelcontextprotocol/sdk": "^1.12.0",
"express": "^4.21.2",
"zod": "^3.23.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.11.0",

@@ -38,0 +42,0 @@ "typescript": "^5.4.0"

@@ -43,2 +43,19 @@ # Wealthville MCP Server

## Hosted / remote (HTTP)
For platforms that need an HTTPS endpoint (e.g. Smithery's "MCP Server URL"), the
same server also runs over **Streamable HTTP** at `POST /mcp` — stateless, so it
scales horizontally. Build the image and host it anywhere:
```bash
docker build -f Dockerfile.http -t wealthville-mcp-http .
docker run -p 8080:8080 wealthville-mcp-http
# health: curl -s localhost:8080/health
# then expose it over HTTPS and use https://<your-host>/mcp
```
Or without Docker: `npm run build && npm run start:http` (listens on `$PORT`, default 8080).
Per-request config is accepted via `?config=<base64 JSON>`, `?wealthvilleApiKey=…`, or an
`x-api-key` header; otherwise the `WEALTHVILLE_*` env vars below apply.
## Configuration (optional)

@@ -45,0 +62,0 @@