patternfetch-mcp 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.
Files changed (4) hide show
  1. package/README.md +15 -0
  2. package/mcp-tools.json +79 -0
  3. package/mcp.js +109 -0
  4. package/package.json +14 -0
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # patternfetch-mcp
2
+
3
+ Local **stdio MCP bridge** for [patternfetch](https://patternfetch.com) — token-compact crypto market-state briefs (compact candles, chart patterns, support/resistance, regime, interpreted RSI/EMA) in one call, so an agent never dumps raw OHLCV into context.
4
+
5
+ ```bash
6
+ npx patternfetch-mcp
7
+ ```
8
+
9
+ Claude Desktop / Cursor / any stdio MCP client:
10
+
11
+ ```json
12
+ { "mcpServers": { "patternfetch": { "command": "npx", "args": ["-y", "patternfetch-mcp"], "env": { "PATTERNFETCH_API_KEY": "pf_..." } } } }
13
+ ```
14
+
15
+ `tools/list` works with no key; tool calls use `PATTERNFETCH_API_KEY` (or x402). Override the endpoint with `PATTERNFETCH_MCP_URL`. Proxies to the hosted MCP at `patternfetch.com/mcp`. Full client + docs: [patternfetch-client](https://github.com/MarvinRey7879/patternfetch-client). Impersonal market data, not investment advice.
package/mcp-tools.json ADDED
@@ -0,0 +1,79 @@
1
+ [
2
+ {
3
+ "name": "patternfetch_brief",
4
+ "description": "Get a token-compact market-state brief for a crypto ticker + timeframe. Returns compact candles, detected chart/candlestick patterns with geometric confidence, support/resistance levels, trend/regime, and interpreted indicators (RSI/EMA state) plus a one-line summary. WHEN: an agent needs the current technical picture of a market without dumping raw OHLCV into context (saves tokens, avoids numeric hallucination). WHEN NOT: you need order execution, portfolio advice, or non-crypto assets. Example: {\"ticker\":\"BTC/USDT\",\"timeframe\":\"4h\"}. Output is impersonal market data, NOT investment advice.",
5
+ "inputSchema": {
6
+ "type": "object",
7
+ "required": [
8
+ "ticker",
9
+ "timeframe"
10
+ ],
11
+ "properties": {
12
+ "ticker": {
13
+ "type": "string"
14
+ },
15
+ "timeframe": {
16
+ "type": "string"
17
+ },
18
+ "limit": {
19
+ "type": "integer"
20
+ }
21
+ }
22
+ }
23
+ },
24
+ {
25
+ "name": "patternfetch_delta",
26
+ "description": "Get only what CHANGED since your last brief for a ticker+timeframe (trend flips, new patterns, RSI-state changes). WHEN: an agent polls the same market repeatedly and wants minimal tokens. Returns changed=false when nothing material changed. Impersonal data, not advice.",
27
+ "inputSchema": {
28
+ "type": "object",
29
+ "required": [
30
+ "ticker",
31
+ "timeframe"
32
+ ],
33
+ "properties": {
34
+ "ticker": {
35
+ "type": "string"
36
+ },
37
+ "timeframe": {
38
+ "type": "string"
39
+ },
40
+ "limit": {
41
+ "type": "integer"
42
+ }
43
+ }
44
+ }
45
+ },
46
+ {
47
+ "name": "patternfetch_analogs",
48
+ "description": "Find historical windows whose shape resembles the current price action and return the FULL distribution of what followed (win-rate, median, min, max, n) over a fixed forward horizon. WHEN: an agent wants historical context for a setup. NOT a prediction, NOT a backtest of a strategy; past distribution does not guarantee future results. Impersonal data, not advice.",
49
+ "inputSchema": {
50
+ "type": "object",
51
+ "required": [
52
+ "ticker",
53
+ "timeframe"
54
+ ],
55
+ "properties": {
56
+ "ticker": {
57
+ "type": "string"
58
+ },
59
+ "timeframe": {
60
+ "type": "string"
61
+ },
62
+ "window": {
63
+ "type": "integer"
64
+ },
65
+ "horizon": {
66
+ "type": "integer"
67
+ }
68
+ }
69
+ }
70
+ },
71
+ {
72
+ "name": "patternfetch_capabilities",
73
+ "description": "List supported assets, timeframes, endpoints, and limits for patternfetch.",
74
+ "inputSchema": {
75
+ "type": "object",
76
+ "properties": {}
77
+ }
78
+ }
79
+ ]
package/mcp.js ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ // patternfetch — zero-dependency stdio MCP bridge.
3
+ // Proxies Model Context Protocol calls to the hosted patternfetch MCP server.
4
+ // tools/list is served from an embedded snapshot (mcp-tools.json) so introspection
5
+ // always succeeds, even before the first network round-trip; tool calls are
6
+ // forwarded to the remote endpoint.
7
+ //
8
+ // Env:
9
+ // PATTERNFETCH_MCP_URL override the remote MCP endpoint (default https://patternfetch.com/mcp)
10
+ // PATTERNFETCH_API_KEY optional bearer key for authenticated (non-x402) calls
11
+ import { stdin, stdout, env, exit } from "node:process";
12
+ import { readFileSync } from "node:fs";
13
+
14
+ const REMOTE = env.PATTERNFETCH_MCP_URL || "https://patternfetch.com/mcp";
15
+ const API_KEY = env.PATTERNFETCH_API_KEY || "";
16
+ const PROTOCOL = "2025-06-18";
17
+ const SERVER_INFO = { name: "patternfetch", version: "0.1.0" };
18
+
19
+ let STATIC_TOOLS = [];
20
+ try {
21
+ STATIC_TOOLS = JSON.parse(readFileSync(new URL("./mcp-tools.json", import.meta.url), "utf8"));
22
+ } catch { /* snapshot optional; remote tools/list still works */ }
23
+
24
+ stdout.on("error", () => {}); // ignore EPIPE if the client closes the pipe early
25
+
26
+ function send(msg) {
27
+ stdout.write(JSON.stringify(msg) + "\n");
28
+ }
29
+
30
+ // Remote may answer with plain JSON or an SSE frame ("event: message\ndata: {...}").
31
+ function parseRpc(text) {
32
+ const s = text.trim();
33
+ if (s.startsWith("{")) {
34
+ try { return JSON.parse(s); } catch { /* fall through to SSE parse */ }
35
+ }
36
+ for (const line of s.split(/\r?\n/)) {
37
+ const m = line.match(/^data:\s*(.*)$/);
38
+ if (m) {
39
+ try { return JSON.parse(m[1]); } catch { /* ignore non-JSON data lines */ }
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+
45
+ async function remoteRpc(method, params) {
46
+ const res = await fetch(REMOTE, {
47
+ method: "POST",
48
+ headers: {
49
+ "content-type": "application/json",
50
+ "accept": "application/json, text/event-stream",
51
+ ...(API_KEY ? { authorization: `Bearer ${API_KEY}` } : {}),
52
+ },
53
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
54
+ });
55
+ const j = parseRpc(await res.text());
56
+ if (!j) throw new Error("invalid response from remote MCP");
57
+ if (j.error) throw new Error(j.error.message || "remote MCP error");
58
+ return j.result;
59
+ }
60
+
61
+ async function listTools() {
62
+ try {
63
+ const r = await remoteRpc("tools/list", {});
64
+ if (r && Array.isArray(r.tools) && r.tools.length) return r.tools;
65
+ } catch { /* remote unreachable — fall back to embedded snapshot */ }
66
+ return STATIC_TOOLS;
67
+ }
68
+
69
+ async function handle(msg) {
70
+ const { id, method, params } = msg;
71
+ if (method === "initialize")
72
+ return send({ jsonrpc: "2.0", id, result: { protocolVersion: PROTOCOL, capabilities: { tools: {} }, serverInfo: SERVER_INFO } });
73
+ if (method === "ping")
74
+ return send({ jsonrpc: "2.0", id, result: {} });
75
+ if (typeof method === "string" && method.startsWith("notifications/"))
76
+ return; // notifications carry no id and need no reply
77
+ if (method === "tools/list")
78
+ return send({ jsonrpc: "2.0", id, result: { tools: await listTools() } });
79
+ if (method === "tools/call") {
80
+ try {
81
+ return send({ jsonrpc: "2.0", id, result: await remoteRpc("tools/call", params) });
82
+ } catch (e) {
83
+ return send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true } });
84
+ }
85
+ }
86
+ if (id !== undefined)
87
+ return send({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
88
+ }
89
+
90
+ let buf = "";
91
+ let pending = 0;
92
+ let ended = false;
93
+ const tryExit = () => { if (ended && pending === 0) setImmediate(() => exit(0)); };
94
+ stdin.setEncoding("utf8");
95
+ stdin.on("data", (chunk) => {
96
+ buf += chunk;
97
+ let i;
98
+ while ((i = buf.indexOf("\n")) >= 0) {
99
+ const line = buf.slice(0, i).trim();
100
+ buf = buf.slice(i + 1);
101
+ if (!line) continue;
102
+ let msg;
103
+ try { msg = JSON.parse(line); } catch { continue; }
104
+ pending++;
105
+ Promise.resolve(handle(msg)).catch(() => {}).finally(() => { pending--; tryExit(); });
106
+ }
107
+ });
108
+ // Exit only once stdin is closed AND every in-flight response has been flushed.
109
+ stdin.on("end", () => { ended = true; tryExit(); });
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "patternfetch-mcp",
3
+ "version": "0.2.0",
4
+ "description": "Local stdio MCP bridge for patternfetch — token-compact crypto market-state briefs (candles, patterns, support/resistance, regime, RSI/EMA) in one call. Proxies to the hosted patternfetch MCP server.",
5
+ "type": "module",
6
+ "bin": { "patternfetch-mcp": "mcp.js" },
7
+ "files": ["mcp.js", "mcp-tools.json", "README.md"],
8
+ "engines": { "node": ">=18" },
9
+ "keywords": ["patternfetch", "mcp", "crypto", "trading", "market-data", "technical-analysis", "ai-agent", "stdio", "x402"],
10
+ "homepage": "https://patternfetch.com",
11
+ "repository": { "type": "git", "url": "git+https://github.com/MarvinRey7879/patternfetch-client.git" },
12
+ "license": "MIT",
13
+ "author": "patternfetch"
14
+ }