filebread-mcp 0.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/index.js +274 -0
- package/package.json +18 -0
package/index.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const readline = require("readline");
|
|
4
|
+
const https = require("https");
|
|
5
|
+
const http = require("http");
|
|
6
|
+
|
|
7
|
+
const API_KEY = process.env.FILEBREAD_API_KEY;
|
|
8
|
+
const API_URL = process.env.FILEBREAD_API_URL || "https://filebread.com";
|
|
9
|
+
|
|
10
|
+
if (!API_KEY) {
|
|
11
|
+
process.stderr.write(
|
|
12
|
+
"[filebread-mcp] Error: FILEBREAD_API_KEY is required.\n" +
|
|
13
|
+
"Get your API key at https://filebread.com/settings/api-keys\n"
|
|
14
|
+
);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const TOOLS = [
|
|
19
|
+
{
|
|
20
|
+
name: "get_context",
|
|
21
|
+
description:
|
|
22
|
+
"Get optimized context from the project knowledge base. Returns the most relevant documentation, code snippets, and project knowledge for a given query. Use this to understand how things work in the project before writing code.",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
query: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description:
|
|
29
|
+
"What you want to know about the project. Be specific - e.g. 'how does authentication work?' or 'what are the coding conventions?'",
|
|
30
|
+
},
|
|
31
|
+
token_budget: {
|
|
32
|
+
type: "number",
|
|
33
|
+
description:
|
|
34
|
+
"Maximum tokens to return (default: 4000, range: 500-16000).",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ["query"],
|
|
38
|
+
},
|
|
39
|
+
annotations: { readOnlyHint: true },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "search_knowledge",
|
|
43
|
+
description:
|
|
44
|
+
"Search the knowledge base for specific information. Returns matching chunks with similarity scores.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
query: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "Search query - can be a question, keyword, or code snippet",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
required: ["query"],
|
|
54
|
+
},
|
|
55
|
+
annotations: { readOnlyHint: true },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "list_sources",
|
|
59
|
+
description:
|
|
60
|
+
"List all indexed knowledge sources (files and documents) available in the project.",
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {},
|
|
64
|
+
},
|
|
65
|
+
annotations: { readOnlyHint: true },
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// --- HTTP client (zero dependencies) ---
|
|
70
|
+
|
|
71
|
+
function apiRequest(method, path, body) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const url = new URL(path, API_URL);
|
|
74
|
+
const mod = url.protocol === "https:" ? https : http;
|
|
75
|
+
|
|
76
|
+
const options = {
|
|
77
|
+
hostname: url.hostname,
|
|
78
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
79
|
+
path: url.pathname,
|
|
80
|
+
method,
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"X-API-Key": API_KEY,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const req = mod.request(options, (res) => {
|
|
88
|
+
let data = "";
|
|
89
|
+
res.on("data", (chunk) => (data += chunk));
|
|
90
|
+
res.on("end", () => {
|
|
91
|
+
try {
|
|
92
|
+
resolve({ status: res.statusCode, body: JSON.parse(data) });
|
|
93
|
+
} catch {
|
|
94
|
+
resolve({ status: res.statusCode, body: data });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
req.on("error", reject);
|
|
100
|
+
req.setTimeout(30000, () => {
|
|
101
|
+
req.destroy();
|
|
102
|
+
reject(new Error("Request timeout"));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (body) req.write(JSON.stringify(body));
|
|
106
|
+
req.end();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- Tool handlers ---
|
|
111
|
+
|
|
112
|
+
async function handleGetContext(args) {
|
|
113
|
+
const payload = { query: args.query };
|
|
114
|
+
if (args.token_budget) payload.token_budget = args.token_budget;
|
|
115
|
+
|
|
116
|
+
const res = await apiRequest("POST", "/api/v1/context", payload);
|
|
117
|
+
|
|
118
|
+
if (res.status !== 200) {
|
|
119
|
+
return { text: `Error (${res.status}): ${JSON.stringify(res.body)}`, isError: true };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const { context, metadata } = res.body;
|
|
123
|
+
|
|
124
|
+
let text = `## Context for: ${metadata.query}\n`;
|
|
125
|
+
text += `Intent: ${metadata.intent} | Tokens: ${metadata.tokens_used}/${metadata.token_budget} | `;
|
|
126
|
+
text += `Score: ${metadata.evaluation_score} | Sources: ${metadata.chunks_returned}\n\n`;
|
|
127
|
+
|
|
128
|
+
if (!context || context.length === 0) {
|
|
129
|
+
text += "No relevant context found for this query.";
|
|
130
|
+
} else {
|
|
131
|
+
text += context
|
|
132
|
+
.map((item) => {
|
|
133
|
+
const source = item.source?.name ? `**Source: ${item.source.name}** (relevance: ${item.relevance_score})\n\n` : "";
|
|
134
|
+
return source + item.content;
|
|
135
|
+
})
|
|
136
|
+
.join("\n---\n\n");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { text };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function handleSearchKnowledge(args) {
|
|
143
|
+
const res = await apiRequest("POST", "/api/search", { query: args.query });
|
|
144
|
+
|
|
145
|
+
if (res.status !== 200) {
|
|
146
|
+
return { text: `Error (${res.status}): ${JSON.stringify(res.body)}`, isError: true };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const results = res.body.results || res.body;
|
|
150
|
+
|
|
151
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
152
|
+
return { text: "No results found." };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const text = results
|
|
156
|
+
.map((r) => {
|
|
157
|
+
const name = r.source_name || r.file_name || "unknown";
|
|
158
|
+
const score = r.similarity || r.score || 0;
|
|
159
|
+
return `**${name}** (score: ${Number(score).toFixed(2)})\n${r.content || r.text || ""}`;
|
|
160
|
+
})
|
|
161
|
+
.join("\n---\n\n");
|
|
162
|
+
|
|
163
|
+
return { text };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function handleListSources() {
|
|
167
|
+
const res = await apiRequest("GET", "/api/files", null);
|
|
168
|
+
|
|
169
|
+
if (res.status !== 200) {
|
|
170
|
+
return { text: `Error (${res.status}): ${JSON.stringify(res.body)}`, isError: true };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const files = res.body.files || [];
|
|
174
|
+
|
|
175
|
+
if (files.length === 0) {
|
|
176
|
+
return { text: "No indexed files found. Upload files or connect a source at filebread.com" };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let text = `## Indexed Sources (${files.length} files)\n\n`;
|
|
180
|
+
text += files
|
|
181
|
+
.map((f) => `- **${f.original_name}** (${f.content_type})`)
|
|
182
|
+
.join("\n");
|
|
183
|
+
|
|
184
|
+
return { text };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// --- MCP Protocol ---
|
|
188
|
+
|
|
189
|
+
function handleMessage(msg) {
|
|
190
|
+
const { method, id, params = {} } = msg;
|
|
191
|
+
|
|
192
|
+
if (method === "initialize") {
|
|
193
|
+
return respond(id, {
|
|
194
|
+
protocolVersion: "2025-03-26",
|
|
195
|
+
capabilities: { tools: { listChanged: false } },
|
|
196
|
+
serverInfo: { name: "filebread", version: "0.1.0" },
|
|
197
|
+
instructions:
|
|
198
|
+
"FileBread provides optimized project context for AI coding agents. Use get_context to retrieve relevant documentation and knowledge before writing code.",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (method === "notifications/initialized") return;
|
|
203
|
+
|
|
204
|
+
if (method === "tools/list") {
|
|
205
|
+
return respond(id, { tools: TOOLS });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (method === "tools/call") {
|
|
209
|
+
return handleToolCall(id, params.name, params.arguments || {});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (method === "ping") {
|
|
213
|
+
return respond(id, {});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return respondError(id, -32601, `Method not found: ${method}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function handleToolCall(id, name, args) {
|
|
220
|
+
try {
|
|
221
|
+
let result;
|
|
222
|
+
|
|
223
|
+
switch (name) {
|
|
224
|
+
case "get_context":
|
|
225
|
+
if (!args.query) return respondError(id, -32602, "Missing required parameter: query");
|
|
226
|
+
result = await handleGetContext(args);
|
|
227
|
+
break;
|
|
228
|
+
case "search_knowledge":
|
|
229
|
+
if (!args.query) return respondError(id, -32602, "Missing required parameter: query");
|
|
230
|
+
result = await handleSearchKnowledge(args);
|
|
231
|
+
break;
|
|
232
|
+
case "list_sources":
|
|
233
|
+
result = await handleListSources();
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
return respondError(id, -32602, `Unknown tool: ${name}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
respond(id, {
|
|
240
|
+
content: [{ type: "text", text: result.text }],
|
|
241
|
+
...(result.isError && { isError: true }),
|
|
242
|
+
});
|
|
243
|
+
} catch (err) {
|
|
244
|
+
respond(id, {
|
|
245
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
246
|
+
isError: true,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function respond(id, result) {
|
|
252
|
+
const msg = JSON.stringify({ jsonrpc: "2.0", id, result });
|
|
253
|
+
process.stdout.write(msg + "\n");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function respondError(id, code, message) {
|
|
257
|
+
const msg = JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
|
|
258
|
+
process.stdout.write(msg + "\n");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// --- Main loop ---
|
|
262
|
+
|
|
263
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
264
|
+
|
|
265
|
+
rl.on("line", (line) => {
|
|
266
|
+
try {
|
|
267
|
+
const msg = JSON.parse(line.trim());
|
|
268
|
+
handleMessage(msg);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
process.stderr.write(`[filebread-mcp] Parse error: ${err.message}\n`);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
rl.on("close", () => process.exit(0));
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "filebread-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for FileBread - give AI agents context about your project",
|
|
5
|
+
"bin": {
|
|
6
|
+
"filebread-mcp": "./index.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": ["mcp", "context", "ai", "claude", "cursor", "rag"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/FileBread/file_bread"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"files": ["index.js"]
|
|
18
|
+
}
|