burn-mcp-server 2.0.7 → 2.1.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/tsconfig.json CHANGED
@@ -1,18 +1,14 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ES2022",
5
- "moduleResolution": "node",
6
- "lib": ["ES2022"],
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
7
6
  "outDir": "./dist",
8
7
  "rootDir": "./src",
9
- "strict": false,
8
+ "strict": true,
10
9
  "esModuleInterop": true,
11
10
  "skipLibCheck": true,
12
- "resolveJsonModule": true,
13
- "noEmit": true,
14
- "allowJs": true
11
+ "resolveJsonModule": true
15
12
  },
16
- "include": ["src/lib/**/*", "src/index.ts", "src/http.ts", "src/http-dev.ts"],
17
- "exclude": ["src/setup.ts", "api/**/*", "dist/**/*", "node_modules/**/*"]
13
+ "include": ["src/**/*"]
18
14
  }
package/vercel.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
- "$schema": "https://openapi.vercel.sh/vercel.json",
3
- "buildCommand": "npm run build:vercel",
4
- "outputDirectory": "public",
2
+ "functions": {
3
+ "api/mcp.js": {
4
+ "runtime": "nodejs20.x",
5
+ "maxDuration": 30
6
+ }
7
+ },
5
8
  "rewrites": [
6
- { "source": "/mcp", "destination": "/api/mcp" },
7
- { "source": "/mcp/(.*)", "destination": "/api/mcp" }
9
+ { "source": "/mcp", "destination": "/api/mcp" }
8
10
  ]
9
11
  }
package/api/health.mjs DELETED
@@ -1,7 +0,0 @@
1
- export const config = { runtime: 'edge' }
2
- export default async function handler(req) {
3
- return new Response(JSON.stringify({ ok: true, url: req.url, method: req.method, runtime: 'edge' }), {
4
- status: 200,
5
- headers: { 'content-type': 'application/json' },
6
- })
7
- }
package/public/index.html DELETED
@@ -1,47 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <title>Burn MCP Server</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <style>
8
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 640px; margin: 80px auto; padding: 0 20px; line-height: 1.6; color: #1a1a1a; }
9
- h1 { font-size: 28px; margin-bottom: 8px; }
10
- .tag { display: inline-block; background: #ff6a00; color: white; padding: 2px 10px; border-radius: 10px; font-size: 12px; margin-right: 6px; }
11
- pre { background: #f5f5f5; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; }
12
- a { color: #ff6a00; text-decoration: none; }
13
- a:hover { text-decoration: underline; }
14
- code { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; font-size: 13px; }
15
- </style>
16
- </head>
17
- <body>
18
- <h1>Burn MCP Server</h1>
19
- <p><span class="tag">MCP</span> <span class="tag">Streamable HTTP</span> Read less, absorb more.</p>
20
- <p>This is the remote HTTP endpoint for <a href="https://burn451.cloud">Burn</a>'s Model Context Protocol server. 26 tools to let Claude / Cursor / Windsurf search, triage, and analyze your saved reading.</p>
21
-
22
- <h2>Connect</h2>
23
- <p>Paste this into your MCP client (Claude Desktop, Cursor, Windsurf, etc.):</p>
24
- <pre>{
25
- "mcpServers": {
26
- "burn": {
27
- "url": "https://mcp.burn451.cloud/mcp",
28
- "headers": {
29
- "Authorization": "Bearer &lt;your-token&gt;"
30
- }
31
- }
32
- }
33
- }</pre>
34
-
35
- <p>Get your token: <a href="https://burn451.cloud">burn451.cloud</a> → Settings → MCP Server → Copy Access Token</p>
36
-
37
- <h2>Links</h2>
38
- <ul>
39
- <li><a href="https://github.com/Fisher521/burn-mcp-server">Source code on GitHub</a></li>
40
- <li><a href="https://www.npmjs.com/package/burn-mcp-server">npm package (stdio mode)</a></li>
41
- <li><a href="https://burn451.cloud">Burn web app</a></li>
42
- <li><a href="https://x.com/hawking520">Built by @hawking520</a></li>
43
- </ul>
44
-
45
- <p style="color: #888; font-size: 13px; margin-top: 40px;">MIT licensed. Part of an open ecosystem of AI-era reading tools.</p>
46
- </body>
47
- </html>
package/src/http-dev.ts DELETED
@@ -1,56 +0,0 @@
1
- // Burn MCP — standalone Node HTTP dev server for local testing.
2
- // For production deploy, see api/mcp.ts (Vercel Edge) or src/http.ts (the handler).
3
-
4
- import { createServer } from 'node:http'
5
- import { handleMcpRequest } from './http.js'
6
-
7
- const PORT = Number(process.env.PORT) || 3001
8
-
9
- const server = createServer(async (nodeReq, nodeRes) => {
10
- try {
11
- const url = new URL(nodeReq.url || '/', `http://${nodeReq.headers.host || 'localhost'}`)
12
- const headers = new Headers()
13
- for (const [k, v] of Object.entries(nodeReq.headers)) {
14
- if (typeof v === 'string') headers.set(k, v)
15
- }
16
- const body = ['GET', 'HEAD'].includes(nodeReq.method || 'GET')
17
- ? null
18
- : await new Promise<Buffer>((resolve, reject) => {
19
- const chunks: Buffer[] = []
20
- nodeReq.on('data', c => chunks.push(c))
21
- nodeReq.on('end', () => resolve(Buffer.concat(chunks)))
22
- nodeReq.on('error', reject)
23
- })
24
-
25
- const webReq = new Request(url, {
26
- method: nodeReq.method,
27
- headers,
28
- body: body && body.length > 0 ? body : null,
29
- })
30
-
31
- const webRes = await handleMcpRequest(webReq)
32
-
33
- nodeRes.statusCode = webRes.status
34
- webRes.headers.forEach((v, k) => nodeRes.setHeader(k, v))
35
- if (webRes.body) {
36
- const reader = webRes.body.getReader()
37
- while (true) {
38
- const { done, value } = await reader.read()
39
- if (done) break
40
- nodeRes.write(value)
41
- }
42
- }
43
- nodeRes.end()
44
- } catch (e: any) {
45
- nodeRes.statusCode = 500
46
- nodeRes.setHeader('content-type', 'application/json')
47
- nodeRes.end(JSON.stringify({ error: 'Internal error', detail: String(e?.message || e) }))
48
- }
49
- })
50
-
51
- server.listen(PORT, () => {
52
- console.log(`Burn MCP HTTP server listening on http://localhost:${PORT}`)
53
- console.log(`Test: curl -H "Authorization: Bearer <TOKEN>" -X POST http://localhost:${PORT}/mcp \\`)
54
- console.log(` -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \\`)
55
- console.log(` -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'`)
56
- })
package/src/http.ts DELETED
@@ -1,62 +0,0 @@
1
- // Burn MCP — HTTP transport entry (Web Standard fetch API)
2
- //
3
- // Works on Vercel Edge / Node runtime, Cloudflare Workers, Deno, Bun.
4
- // Users connect by adding ONE URL to Claude/Cursor/Windsurf MCP settings instead of installing npx.
5
- //
6
- // Auth: each request carries `Authorization: Bearer <BURN_MCP_TOKEN>` header.
7
- // Stateless: fresh Supabase client per request (short in-memory session cache for performance).
8
-
9
- import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'
10
- import { createClient } from '@supabase/supabase-js'
11
- import { getOrExchangeSession, applySession } from './lib/auth.js'
12
- import { createBurnServer } from './setup.js'
13
-
14
- const SUPABASE_URL = process.env.BURN_SUPABASE_URL || 'https://juqtxylquemiuvvmgbej.supabase.co'
15
- const SUPABASE_ANON_KEY = process.env.BURN_SUPABASE_ANON_KEY || 'sb_publishable_reVgmmCC6ndIo6jFRMM2LQ_wujj5FrO'
16
-
17
- /** Main handler — turn an incoming HTTP Request into a JSON-RPC MCP Response. */
18
- export async function handleMcpRequest(req: Request): Promise<Response> {
19
- // --- Auth: require Bearer token ---
20
- const authHeader = req.headers.get('authorization') || req.headers.get('Authorization') || ''
21
- const match = authHeader.match(/^Bearer\s+(.+)$/i)
22
- const token = match?.[1]?.trim()
23
-
24
- if (!token) {
25
- return new Response(JSON.stringify({
26
- error: 'Missing Authorization header',
27
- hint: 'Add `Authorization: Bearer <BURN_MCP_TOKEN>` header. Get your token at https://burn451.cloud → Settings → MCP Server.',
28
- }), { status: 401, headers: { 'content-type': 'application/json' } })
29
- }
30
-
31
- // --- Exchange token → Supabase session (cached in memory 5 min per token) ---
32
- let session
33
- try {
34
- session = await getOrExchangeSession(token)
35
- } catch (e: any) {
36
- return new Response(JSON.stringify({
37
- error: 'Invalid or expired Burn MCP token',
38
- detail: String(e?.message || e),
39
- }), { status: 401, headers: { 'content-type': 'application/json' } })
40
- }
41
-
42
- // --- Build per-request Supabase client with JWT in headers (faster than setSession, Edge-safe) ---
43
- const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
44
- auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },
45
- global: {
46
- headers: { Authorization: `Bearer ${session.access_token}` },
47
- },
48
- })
49
-
50
- const server = createBurnServer(supabase, { rateLimitPerMin: 60 })
51
-
52
- // --- Streamable HTTP transport (stateless mode — no session reuse across requests) ---
53
- const transport = new WebStandardStreamableHTTPServerTransport({
54
- sessionIdGenerator: undefined, // stateless mode — MCP SDK treats each request as a new conversation
55
- enableJsonResponse: true,
56
- })
57
-
58
- await server.connect(transport)
59
- return transport.handleRequest(req)
60
- }
61
-
62
- // Standalone Node dev server is in src/http-dev.ts (npm run dev:http)
@@ -1,26 +0,0 @@
1
- // stdio-only: local session cache on disk (~/.burn/mcp-session.json)
2
- // NEVER import this from http.ts — it pulls in node:fs/os/path which crashes Edge runtime.
3
-
4
- import { homedir } from 'node:os'
5
- import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
6
- import { join } from 'node:path'
7
- import type { Session } from './auth.js'
8
-
9
- const CACHE_DIR = join(homedir(), '.burn')
10
- const CACHE_FILE = join(CACHE_DIR, 'mcp-session.json')
11
-
12
- export function loadCachedSession(): Session | null {
13
- try {
14
- const raw = readFileSync(CACHE_FILE, 'utf-8')
15
- const data = JSON.parse(raw)
16
- if (data.access_token && data.refresh_token) return data
17
- } catch { /* no cache or invalid */ }
18
- return null
19
- }
20
-
21
- export function saveCachedSession(session: Session): void {
22
- try {
23
- mkdirSync(CACHE_DIR, { recursive: true })
24
- writeFileSync(CACHE_FILE, JSON.stringify(session), { mode: 0o600 })
25
- } catch { /* non-fatal */ }
26
- }
package/src/lib/auth.ts DELETED
@@ -1,71 +0,0 @@
1
- // Burn MCP auth — Edge/runtime-safe (no node: imports here)
2
- //
3
- // stdio-only session cache (fs-based) is in ./auth-stdio.ts — only imported by src/index.ts.
4
- // HTTP mode uses in-memory TTL cache declared here (safe in Edge).
5
-
6
- import { SupabaseClient } from '@supabase/supabase-js'
7
-
8
- const DEFAULT_EXCHANGE_URL = 'https://api.burn451.cloud/api/mcp-exchange'
9
-
10
- export interface Session {
11
- access_token: string
12
- refresh_token: string
13
- }
14
-
15
- /** Exchange a long-lived MCP token for a Supabase session (fresh from API). */
16
- export async function exchangeToken(
17
- mcpToken: string,
18
- exchangeUrl: string = process.env.BURN_MCP_EXCHANGE_URL || DEFAULT_EXCHANGE_URL,
19
- ): Promise<Session> {
20
- const resp = await fetch(exchangeUrl, {
21
- method: 'POST',
22
- headers: { 'Content-Type': 'application/json' },
23
- body: JSON.stringify({ token: mcpToken }),
24
- })
25
- if (!resp.ok) {
26
- let detail = ''
27
- try { detail = JSON.stringify(await resp.json()) } catch {}
28
- throw new Error(`Token exchange failed (${resp.status}): ${detail}`)
29
- }
30
- const data = await resp.json() as any
31
- if (!data.access_token || !data.refresh_token) {
32
- throw new Error('Token exchange succeeded but returned no session tokens')
33
- }
34
- return { access_token: data.access_token, refresh_token: data.refresh_token }
35
- }
36
-
37
- /** Apply a session to a supabase client, with optional auto-cache on token refresh (stdio mode only). */
38
- export async function applySession(
39
- supabase: SupabaseClient,
40
- session: Session,
41
- onRefresh?: (s: Session) => void,
42
- ): Promise<void> {
43
- const { error } = await supabase.auth.setSession(session)
44
- if (error) throw new Error(`Failed to set session: ${error.message}`)
45
- if (onRefresh) {
46
- supabase.auth.onAuthStateChange((_event, s) => {
47
- if (s?.access_token && s?.refresh_token) {
48
- onRefresh({ access_token: s.access_token, refresh_token: s.refresh_token })
49
- }
50
- })
51
- }
52
- }
53
-
54
- // ---------------------------------------------------------------------------
55
- // HTTP mode: per-token in-memory session cache (TTL-based, safe in Edge)
56
- // ---------------------------------------------------------------------------
57
-
58
- interface CacheEntry { session: Session; expiresAt: number }
59
- const httpSessionCache = new Map<string, CacheEntry>()
60
- const HTTP_CACHE_TTL_MS = 5 * 60_000 // 5 min
61
-
62
- /** Get or refresh a session for an HTTP request. */
63
- export async function getOrExchangeSession(mcpToken: string): Promise<Session> {
64
- const now = Date.now()
65
- const cached = httpSessionCache.get(mcpToken)
66
- if (cached && cached.expiresAt > now) return cached.session
67
-
68
- const session = await exchangeToken(mcpToken)
69
- httpSessionCache.set(mcpToken, { session, expiresAt: now + HTTP_CACHE_TTL_MS })
70
- return session
71
- }