meteora-docs-mcp 1.0.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/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/index.js +280 -0
  4. package/package.json +40 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hero
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,119 @@
1
+ # meteora-docs-mcp
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server that lets any AI assistant (Claude Code,
4
+ Claude Desktop, Cursor, …) ask questions against **Meteora's official documentation AI
5
+ assistant** at [docs.meteora.ag](https://docs.meteora.ag).
6
+
7
+ It exposes a single tool:
8
+
9
+ | Tool | Description |
10
+ |------|-------------|
11
+ | `ask_meteora_docs` | Ask a natural-language question about Meteora (DBC, DAMM v1/v2, DLMM, dynamic fees & fee sharing, Alpha Vault, fee scheduling, anti-sniper config, migration, SDK usage…). Returns the assistant's answer. |
12
+
13
+ Under the hood it uses [CycleTLS](https://github.com/Danny-Dasilva/CycleTLS) (a Go TLS stack
14
+ with a Chrome JA3 fingerprint) to pass Cloudflare bot detection — **no browser required**.
15
+
16
+ ---
17
+
18
+ ## For users — add it to your MCP client
19
+
20
+ > Requires **Node.js ≥ 18**. The first run downloads a small platform binary (CycleTLS);
21
+ > give it a few extra seconds.
22
+
23
+ ### Option A — from npm (recommended, once published)
24
+
25
+ **Claude Code (CLI):**
26
+
27
+ ```bash
28
+ claude mcp add meteora-docs -- npx -y meteora-docs-mcp
29
+ ```
30
+
31
+ **Claude Desktop** — edit `claude_desktop_config.json`
32
+ (Settings → Developer → Edit Config):
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "meteora-docs": {
38
+ "command": "npx",
39
+ "args": ["-y", "meteora-docs-mcp"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ **Cursor** — `.cursor/mcp.json` in your project (or the global one):
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "meteora-docs": {
51
+ "command": "npx",
52
+ "args": ["-y", "meteora-docs-mcp"]
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ Restart the client, then ask it something like *"Use meteora-docs to explain how DBC
59
+ migration works."*
60
+
61
+ ### Option B — from source (no npm account needed)
62
+
63
+ If you received this as a folder or `.zip`:
64
+
65
+ ```bash
66
+ cd meteora-docs-mcp
67
+ npm install
68
+ ```
69
+
70
+ Then point your client at the absolute path of `index.js`:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "meteora-docs": {
76
+ "command": "node",
77
+ "args": ["C:/full/path/to/meteora-docs-mcp/index.js"]
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ (Use forward slashes in JSON, even on Windows.)
84
+
85
+ ---
86
+
87
+ ## Quick smoke test (no MCP client)
88
+
89
+ ```bash
90
+ npm install # if you haven't
91
+ node test-client.js "What is the Dynamic Bonding Curve?"
92
+ ```
93
+
94
+ `test-client.js` spawns the server over stdio, runs the MCP handshake, and calls the tool
95
+ once. (Included in the source tree; not published to npm.)
96
+
97
+ ---
98
+
99
+ ## Troubleshooting
100
+
101
+ | Symptom | Fix |
102
+ |--------|-----|
103
+ | `Blocked by Cloudflare (HTTP 420)` | Meteora/Cloudflare changed their fingerprint detection. Update to the newest release (`npx -y meteora-docs-mcp@latest`). |
104
+ | Antivirus / Gatekeeper flags the CycleTLS binary (`index.exe` on Windows, `index` / `index-mac*` on Linux/macOS) | That's the bundled CycleTLS Go binary. It's open-source; allow it, or use a machine where you can. |
105
+ | `Could not obtain assistant token (JWT)` | Meteora changed their docs API path, or your network blocks `docs.meteora.ag`. |
106
+ | Tool never appears in the client | Make sure nothing else prints to **stdout**; check the client's MCP logs (stderr) for `[meteora-docs-mcp] … running on stdio`. |
107
+
108
+ ---
109
+
110
+ ## For the author — publish & update
111
+
112
+ See [PUBLISH.md](./PUBLISH.md) for step-by-step npm publishing and GitHub instructions.
113
+
114
+ ## License
115
+
116
+ MIT — see [LICENSE](./LICENSE).
117
+
118
+ > Unofficial. Not affiliated with or endorsed by Meteora. Answers come from Meteora's own
119
+ > public documentation assistant; use at your own discretion.
package/index.js ADDED
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * meteora-docs-mcp — Model Context Protocol server for Meteora's docs AI assistant.
4
+ *
5
+ * Exposes one tool, `ask_meteora_docs`, that forwards a natural-language question to
6
+ * Meteora's official documentation assistant (docs.meteora.ag, served by Mintlify) and
7
+ * returns the answer. Uses CycleTLS (Go TLS stack with a Chrome JA3 fingerprint) to pass
8
+ * Cloudflare bot detection — no browser required.
9
+ *
10
+ * Transport: stdio (newline-delimited JSON-RPC 2.0). Works with Claude Code, Claude
11
+ * Desktop, Cursor, and any MCP-compatible client.
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // stdout HYGIENE — must run before anything else that might log.
18
+ // The stdio transport writes JSON-RPC to process.stdout via process.stdout.write().
19
+ // CycleTLS (and other deps) call console.log(), which also targets stdout and would
20
+ // corrupt the protocol stream. Re-point every console.* method at STDERR so the only
21
+ // thing on stdout is valid JSON-RPC.
22
+ // ---------------------------------------------------------------------------
23
+ const util = require('util');
24
+ const toLine = (args) =>
25
+ args.map((a) => (typeof a === 'string' ? a : util.inspect(a, { depth: 4 }))).join(' ') + '\n';
26
+ for (const method of ['log', 'info', 'debug', 'warn', 'trace']) {
27
+ console[method] = (...args) => process.stderr.write(toLine(args));
28
+ }
29
+ console.error = (...args) => process.stderr.write(toLine(args));
30
+
31
+ const https = require('https');
32
+ const crypto = require('crypto');
33
+ const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
34
+ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
35
+ const { z } = require('zod');
36
+ const initCycleTLS = require('cycletls');
37
+
38
+ const pkg = require('./package.json');
39
+
40
+ const UA =
41
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36';
42
+ const JA3 =
43
+ '771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513-21,29-23-24,0';
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Meteora assistant plumbing (ported from scripts/meteora-ask.js)
47
+ // ---------------------------------------------------------------------------
48
+ function nanoid16() {
49
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
50
+ const bytes = crypto.randomBytes(16);
51
+ let s = '';
52
+ for (let i = 0; i < 16; i++) s += alphabet[bytes[i] % 62];
53
+ return s;
54
+ }
55
+
56
+ function fetchJwt(docPath) {
57
+ return new Promise((resolve, reject) => {
58
+ https
59
+ .get(
60
+ {
61
+ hostname: 'docs.meteora.ag',
62
+ path: '/_mintlify/assistant/siteconfig',
63
+ headers: {
64
+ accept: '*/*',
65
+ 'cache-control': 'no-cache',
66
+ referer: 'https://docs.meteora.ag/' + docPath,
67
+ 'user-agent': UA,
68
+ },
69
+ },
70
+ (res) => {
71
+ let d = '';
72
+ res.on('data', (c) => (d += c));
73
+ res.on('end', () => {
74
+ const t = d.trim().replace(/^"|"$/g, '');
75
+ t && t.startsWith('eyJ')
76
+ ? resolve(t)
77
+ : reject(new Error('Could not obtain assistant token (JWT). Meteora may have changed its docs API.'));
78
+ });
79
+ }
80
+ )
81
+ .on('error', reject)
82
+ .setTimeout(10000, function () {
83
+ this.destroy(new Error('Timeout fetching assistant token'));
84
+ });
85
+ });
86
+ }
87
+
88
+ function parseSSE(raw) {
89
+ let answer = '';
90
+ let messageId = null;
91
+ const searches = [];
92
+ let usage = null;
93
+ for (const line of String(raw).split('\n')) {
94
+ if (line.startsWith('0:')) {
95
+ try {
96
+ answer += JSON.parse(line.slice(2));
97
+ } catch {
98
+ answer += line.slice(2);
99
+ }
100
+ } else if (line.startsWith('f:')) {
101
+ try {
102
+ const f = JSON.parse(line.slice(2));
103
+ if (f.messageId) messageId = f.messageId;
104
+ } catch {}
105
+ } else if (line.startsWith('9:')) {
106
+ try {
107
+ const t = JSON.parse(line.slice(2));
108
+ if (t.args && t.args.query) searches.push(t.args.query);
109
+ } catch {}
110
+ } else if (line.startsWith('d:')) {
111
+ try {
112
+ usage = JSON.parse(line.slice(2));
113
+ } catch {}
114
+ }
115
+ }
116
+ return { answer: answer.trim(), messageId, searches, usage };
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // CycleTLS lifecycle — one shared instance (the Go process is expensive to spawn),
121
+ // lazily created and re-created if the transport dies.
122
+ // ---------------------------------------------------------------------------
123
+ let cycleTLSPromise = null;
124
+ function getCycleTLS() {
125
+ if (!cycleTLSPromise) cycleTLSPromise = initCycleTLS();
126
+ return cycleTLSPromise;
127
+ }
128
+ function resetCycleTLS() {
129
+ const p = cycleTLSPromise;
130
+ cycleTLSPromise = null;
131
+ if (p) p.then((c) => { try { c.exit(); } catch {} }).catch(() => {});
132
+ }
133
+
134
+ async function askMeteora(question, docPath) {
135
+ const path = String(docPath || 'overview').replace(/^\//, '');
136
+ const jwt = await fetchJwt(path);
137
+ const cycleTLS = await getCycleTLS();
138
+
139
+ const body = JSON.stringify({
140
+ id: 'meteora',
141
+ messages: [
142
+ {
143
+ id: nanoid16(),
144
+ createdAt: new Date().toISOString(),
145
+ role: 'user',
146
+ content: question,
147
+ parts: [{ type: 'text', text: question }],
148
+ },
149
+ ],
150
+ fp: 'meteora',
151
+ currentPath: '/' + path,
152
+ _: jwt,
153
+ });
154
+
155
+ let res;
156
+ try {
157
+ res = await cycleTLS(
158
+ 'https://leaves.mintlify.com/api/assistant/meteora/message',
159
+ {
160
+ body,
161
+ ja3: JA3,
162
+ userAgent: UA,
163
+ timeout: 60,
164
+ headers: {
165
+ accept: '*/*',
166
+ 'accept-language': 'en-US,en;q=0.8',
167
+ 'content-type': 'application/json',
168
+ origin: 'https://docs.meteora.ag',
169
+ referer: 'https://docs.meteora.ag/',
170
+ 'sec-ch-ua': '"Chromium";v="148", "Brave";v="148", "Not/A)Brand";v="99"',
171
+ 'sec-ch-ua-mobile': '?0',
172
+ 'sec-ch-ua-platform': '"Windows"',
173
+ 'sec-fetch-dest': 'empty',
174
+ 'sec-fetch-mode': 'cors',
175
+ 'sec-fetch-site': 'cross-site',
176
+ 'sec-gpc': '1',
177
+ priority: 'u=1, i',
178
+ },
179
+ },
180
+ 'post'
181
+ );
182
+ } catch (e) {
183
+ // The CycleTLS transport itself failed (process died / socket closed) — drop the
184
+ // instance so the next call respawns a fresh one.
185
+ resetCycleTLS();
186
+ throw new Error('CycleTLS request failed: ' + (e && e.message ? e.message : String(e)));
187
+ }
188
+
189
+ if (res.status === 420 || res.status === 403) {
190
+ throw new Error(
191
+ 'Blocked by Cloudflare (HTTP ' +
192
+ res.status +
193
+ '). The JA3/TLS fingerprint may need updating — check for a newer meteora-docs-mcp release.'
194
+ );
195
+ }
196
+ if (res.status >= 400) {
197
+ throw new Error('Meteora assistant returned HTTP ' + res.status + ': ' + String(res.data || '').slice(0, 300));
198
+ }
199
+
200
+ return parseSSE(String(res.data || ''));
201
+ }
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // MCP server
205
+ // ---------------------------------------------------------------------------
206
+ const server = new McpServer({ name: pkg.name, version: pkg.version });
207
+
208
+ server.registerTool(
209
+ 'ask_meteora_docs',
210
+ {
211
+ title: 'Ask Meteora Docs',
212
+ description:
213
+ "Ask a natural-language question about Meteora's Solana protocols and get an answer " +
214
+ "from Meteora's OFFICIAL documentation AI assistant (docs.meteora.ag). " +
215
+ 'Best for: Dynamic Bonding Curve (DBC), DAMM v1/v2, DLMM, dynamic fees & fee sharing, ' +
216
+ 'Alpha Vault, fee scheduling, anti-sniper config, migration, and SDK usage. ' +
217
+ 'Returns the assistant’s prose answer (plus the doc searches it ran).',
218
+ inputSchema: {
219
+ question: z
220
+ .string()
221
+ .min(3)
222
+ .describe('The question to ask, e.g. "How does the Dynamic Bonding Curve migration work?"'),
223
+ path: z
224
+ .string()
225
+ .optional()
226
+ .describe(
227
+ 'Optional docs path to scope the assistant’s context, e.g. ' +
228
+ '"overview/products/dbc/what-is-dbc". Defaults to "overview".'
229
+ ),
230
+ },
231
+ },
232
+ async ({ question, path }) => {
233
+ try {
234
+ const { answer, searches, usage } = await askMeteora(question, path);
235
+ let text = answer || '(The assistant returned an empty answer.)';
236
+ if (searches && searches.length) {
237
+ text = '_Searched: ' + searches.join(' | ') + '_\n\n' + text;
238
+ }
239
+ if (usage && usage.usage) {
240
+ const u = usage.usage;
241
+ text += '\n\n_(tokens: ' + (u.promptTokens || '?') + '+' + (u.completionTokens || '?') + ')_';
242
+ }
243
+ return { content: [{ type: 'text', text }] };
244
+ } catch (e) {
245
+ return {
246
+ content: [{ type: 'text', text: 'Error asking Meteora docs: ' + (e && e.message ? e.message : String(e)) }],
247
+ isError: true,
248
+ };
249
+ }
250
+ }
251
+ );
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // Boot + graceful shutdown
255
+ // ---------------------------------------------------------------------------
256
+ let shuttingDown = false;
257
+ function shutdown() {
258
+ if (shuttingDown) return;
259
+ shuttingDown = true;
260
+ const done = () => process.exit(0);
261
+ if (cycleTLSPromise) {
262
+ cycleTLSPromise.then((c) => { try { c.exit(); } catch {} }).then(done, done);
263
+ setTimeout(done, 2000).unref();
264
+ } else {
265
+ done();
266
+ }
267
+ }
268
+ process.on('SIGINT', shutdown);
269
+ process.on('SIGTERM', shutdown);
270
+
271
+ async function main() {
272
+ const transport = new StdioServerTransport();
273
+ await server.connect(transport);
274
+ process.stderr.write('[meteora-docs-mcp] v' + pkg.version + ' running on stdio\n');
275
+ }
276
+
277
+ main().catch((e) => {
278
+ process.stderr.write('[meteora-docs-mcp] fatal: ' + (e && e.message ? e.message : String(e)) + '\n');
279
+ process.exit(1);
280
+ });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "meteora-docs-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server that answers questions from Meteora's official documentation AI assistant (DBC, DAMM, DLMM, dynamic fees, and more).",
5
+ "type": "commonjs",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "meteora-docs-mcp": "index.js"
9
+ },
10
+ "files": [
11
+ "index.js",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "scripts": {
19
+ "start": "node index.js"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "modelcontextprotocol",
25
+ "meteora",
26
+ "solana",
27
+ "dbc",
28
+ "dlmm",
29
+ "damm",
30
+ "defi",
31
+ "documentation",
32
+ "ai-assistant"
33
+ ],
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.29.0",
37
+ "cycletls": "^2.0.5",
38
+ "zod": "^3.25.0"
39
+ }
40
+ }