musa-knowledge-search 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.
@@ -0,0 +1,21 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm config:*)",
5
+ "Bash(npm publish:*)",
6
+ "Bash(gh repo:*)",
7
+ "Bash(rm -rf /tmp/airweave-push)",
8
+ "Bash(mkdir /tmp/airweave-push)",
9
+ "Read(//tmp/airweave-push/**)",
10
+ "Bash(GIT_SSL_NO_VERIFY=true git clone https://github.com/WangKangAandy/airweave.git --depth 1)",
11
+ "Bash(gh auth:*)",
12
+ "WebFetch(domain:docs.airweave.ai)",
13
+ "WebSearch",
14
+ "Bash(npm info:*)",
15
+ "Bash(npm search:*)",
16
+ "Bash(npm pack:*)",
17
+ "Bash(tar -xzf /tmp/airweave-mcp-search-*.tgz -C /tmp)",
18
+ "Read(//tmp/package/**)"
19
+ ]
20
+ }
21
+ }
package/index.js ADDED
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Airweave Search MCP Server
4
+ *
5
+ * 照搬官方 airweave-mcp-search@0.9.60 实现
6
+ * 使用 X-API-Key 认证(解决官方 Authorization: Bearer 认证 bug)
7
+ */
8
+
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import { z } from "zod";
12
+
13
+ const VERSION = "1.0.0";
14
+ const DEFAULT_BASE_URL = "https://api.airweave.ai";
15
+
16
+ // ── Filter schemas(照搬官方)──────────────────────────────────────────────
17
+ const filterConditionSchema = z.object({
18
+ field: z.string().describe(
19
+ "Field to filter on. Options: entity_id, name, created_at, updated_at, " +
20
+ "breadcrumbs.entity_id, breadcrumbs.name, breadcrumbs.entity_type, " +
21
+ "airweave_system_metadata.source_name, airweave_system_metadata.entity_type, " +
22
+ "airweave_system_metadata.original_entity_id, airweave_system_metadata.chunk_index, " +
23
+ "airweave_system_metadata.sync_id, airweave_system_metadata.sync_job_id"
24
+ ),
25
+ operator: z.enum([
26
+ "equals", "not_equals", "contains",
27
+ "greater_than", "less_than", "greater_than_or_equal", "less_than_or_equal",
28
+ "in", "not_in"
29
+ ]).describe("Comparison operator"),
30
+ value: z.union([
31
+ z.string(), z.number(), z.boolean(),
32
+ z.array(z.string()), z.array(z.number())
33
+ ]).describe("Value to compare against. Use a list for 'in'/'not_in'"),
34
+ });
35
+
36
+ const filterGroupSchema = z.object({
37
+ conditions: z.array(filterConditionSchema).min(1)
38
+ .describe("Conditions within this group (combined with AND)"),
39
+ });
40
+
41
+ // ── Configuration(照搬官方 + 保持 X-API-Key 认证)───────────────────────────
42
+ function loadConfig() {
43
+ const apiKey = process.env.AIRWEAVE_API_KEY;
44
+ const collection = process.env.AIRWEAVE_COLLECTION;
45
+ const baseUrl = process.env.AIRWEAVE_BASE_URL || DEFAULT_BASE_URL;
46
+ const organizationId = process.env.AIRWEAVE_ORGANIZATION_ID || "";
47
+
48
+ if (!apiKey) {
49
+ console.error("Error: AIRWEAVE_API_KEY environment variable is required");
50
+ process.exit(1);
51
+ }
52
+ if (!collection) {
53
+ console.error("Error: AIRWEAVE_COLLECTION environment variable is required");
54
+ process.exit(1);
55
+ }
56
+
57
+ return { apiKey, collection, baseUrl, organizationId };
58
+ }
59
+
60
+ // ── Airweave API Client(照搬官方 + 保持 X-API-Key 认证)─────────────────────
61
+ async function searchAirweave(config, tier, body) {
62
+ const url = `${config.baseUrl}/collections/${config.collection}/search/${tier}`;
63
+
64
+ const headers = {
65
+ "Content-Type": "application/json",
66
+ "X-API-Key": config.apiKey, // 保持我们的认证方式(解决官方 bug)
67
+ "X-Client-Name": "airweave-search-mcp",
68
+ "X-Client-Version": VERSION,
69
+ };
70
+
71
+ if (config.organizationId) {
72
+ headers["X-Organization-ID"] = config.organizationId;
73
+ }
74
+
75
+ console.error(`[search/${tier}] collection=${config.collection} baseUrl=${config.baseUrl} orgId=${config.organizationId || "none"}`);
76
+
77
+ const response = await fetch(url, {
78
+ method: "POST",
79
+ headers,
80
+ body: JSON.stringify(body),
81
+ });
82
+
83
+ if (!response.ok) {
84
+ const text = await response.text();
85
+ throw new Error(`Airweave API error (${response.status}): ${response.statusText}\nBody: ${text}`);
86
+ }
87
+
88
+ return await response.json();
89
+ }
90
+
91
+ // ── Format Search Response(照搬官方)────────────────────────────────────────
92
+ function formatSearchResponse(searchResponse, tier, collection) {
93
+ const results = searchResponse.results ?? [];
94
+ const formattedResults = results
95
+ .map((result, index) => {
96
+ const parts = [
97
+ `**Result ${index + 1} (Score: ${result.relevance_score?.toFixed(3) || "N/A"}):**`,
98
+ ];
99
+ // Name + source
100
+ const source = result.airweave_system_metadata?.source_name;
101
+ parts.push(source ? `${result.name || "unknown"} (${source})` : result.name || "unknown");
102
+ // Breadcrumbs
103
+ if (result.breadcrumbs?.length > 0) {
104
+ const trail = result.breadcrumbs.map((b) => b.name).join(" > ");
105
+ parts.push(`📍 ${trail}`);
106
+ }
107
+ // Content - 核心改进!
108
+ if (result.textual_representation) {
109
+ parts.push(result.textual_representation);
110
+ }
111
+ // Link
112
+ if (result.web_url) {
113
+ parts.push(`🔗 ${result.web_url}`);
114
+ }
115
+ return parts.join("\n");
116
+ })
117
+ .join("\n\n---\n\n");
118
+
119
+ const summaryText = [
120
+ `**Collection:** ${collection} | **Tier:** ${tier}`,
121
+ `**Results:** ${results.length}`,
122
+ "",
123
+ formattedResults || "No results found.",
124
+ ].join("\n");
125
+
126
+ return {
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: summaryText,
131
+ },
132
+ ],
133
+ };
134
+ }
135
+
136
+ // ── Format Error Response(照搬官方)─────────────────────────────────────────
137
+ function formatErrorResponse(error, searchRequest, collection, baseUrl) {
138
+ return {
139
+ content: [
140
+ {
141
+ type: "text",
142
+ text: [
143
+ "**Error:** Failed to search collection.",
144
+ "",
145
+ `**Details:** ${error.message}`,
146
+ "",
147
+ "**Debugging Info:**",
148
+ `- Collection: ${collection}`,
149
+ `- Base URL: ${baseUrl}`,
150
+ `- Parameters: ${JSON.stringify(searchRequest, null, 2)}`,
151
+ ].join("\n"),
152
+ },
153
+ ],
154
+ };
155
+ }
156
+
157
+ // ── Create MCP Server(照搬官方)──────────────────────────────────────────────
158
+ const config = loadConfig();
159
+ const server = new McpServer({
160
+ name: "musa-knowledge-search",
161
+ version: VERSION,
162
+ }, {
163
+ capabilities: {
164
+ tools: {},
165
+ logging: {},
166
+ },
167
+ });
168
+
169
+ const toolName = "musa_search";
170
+
171
+ // ── Search Tool(照搬官方)────────────────────────────────────────────────────
172
+ const searchSchema = {
173
+ query: z.string().min(1).max(1000)
174
+ .describe("The search query text to find relevant documents and data"),
175
+ tier: z.enum(["instant", "classic", "agentic"]).optional().default("classic")
176
+ .describe(
177
+ "Search tier: " +
178
+ "'instant' (fastest, direct vector search), " +
179
+ "'classic' (default, AI-optimized with LLM-planned strategy), " +
180
+ "'agentic' (deepest, multi-step agent with tool calling)"
181
+ ),
182
+ retrieval_strategy: z.enum(["hybrid", "semantic", "keyword"]).optional()
183
+ .describe("Only for instant tier. 'hybrid' (default, semantic + keyword), 'semantic' (dense/neural only), 'keyword' (BM25 only)"),
184
+ limit: z.number().min(1).max(1000).optional().default(100)
185
+ .describe("Maximum number of results to return"),
186
+ offset: z.number().min(0).optional().default(0)
187
+ .describe("Number of results to skip (instant and classic only)"),
188
+ thinking: z.boolean().optional()
189
+ .describe("Only for agentic tier. Enable extended thinking / chain-of-thought"),
190
+ filter: z.array(filterGroupSchema).optional()
191
+ .describe("Filter groups (combined with OR). Each group has conditions combined with AND"),
192
+ };
193
+
194
+ const searchDescription = [
195
+ "Search the MUSA knowledge base across GitHub repos, official docs, Confluence pages, Jira issues, and more.",
196
+ "",
197
+ "This tool searches an Airweave collection that indexes MUSA-related resources:",
198
+ "- **Code repos**: MUSA SDK, PyTorch-MUSA, MUSA examples and demos",
199
+ "- **Documentation**: Official MUSA docs, API references, guides",
200
+ "- **Confluence**: Internal wiki pages, design docs, meeting notes",
201
+ "- **Jira**: MUSA-related issues, bug reports, feature requests",
202
+ "",
203
+ "Supports three search tiers:",
204
+ "- `instant`: Fast vector search (~1s) — quick lookups",
205
+ "- `classic` (default): AI-optimized search (~2-5s) — best for most queries",
206
+ "- `agentic`: Multi-step agent (~10-30s) — complex analysis, summarization",
207
+ "",
208
+ "**Parameters:**",
209
+ "- `query`: Search text",
210
+ "- `tier`: instant | classic | agentic (default: classic)",
211
+ "- `limit`: Max results (default: 100)",
212
+ "- `filter`: Narrow by source, e.g. filter by source_name='github'",
213
+ "",
214
+ "**Examples:**",
215
+ "- `{'query': 'torch.musa.is_available()'}` — Find API usage examples",
216
+ "- `{'query': 'MUSA driver installation', 'tier': 'agentic'}` — Deep search guides",
217
+ "- `{'query': 'JIRA-123', 'tier': 'instant'}` — Quick issue lookup",
218
+ ].join("\n");
219
+
220
+ server.tool(
221
+ toolName,
222
+ searchDescription,
223
+ searchSchema,
224
+ async (params) => {
225
+ try {
226
+ const tier = params.tier || "classic";
227
+ const filter = params.filter;
228
+
229
+ let response;
230
+ switch (tier) {
231
+ case "instant":
232
+ response = await searchAirweave(config, "instant", {
233
+ query: params.query,
234
+ retrieval_strategy: params.retrieval_strategy,
235
+ filter,
236
+ limit: params.limit,
237
+ offset: params.offset,
238
+ });
239
+ break;
240
+ case "agentic":
241
+ response = await searchAirweave(config, "agentic", {
242
+ query: params.query,
243
+ thinking: params.thinking,
244
+ filter,
245
+ limit: params.limit,
246
+ });
247
+ break;
248
+ case "classic":
249
+ default:
250
+ response = await searchAirweave(config, "classic", {
251
+ query: params.query,
252
+ filter,
253
+ limit: params.limit,
254
+ offset: params.offset,
255
+ });
256
+ break;
257
+ }
258
+
259
+ return formatSearchResponse(response, tier, config.collection);
260
+ } catch (error) {
261
+ if (error instanceof z.ZodError) {
262
+ const errorMessages = error.errors.map((e) => `${e.path.join(".")}: ${e.message}`);
263
+ return {
264
+ content: [
265
+ {
266
+ type: "text",
267
+ text: `**Parameter Validation Errors:**\n${errorMessages.join("\n")}`,
268
+ },
269
+ ],
270
+ };
271
+ }
272
+ console.error("Error in search tool:", error);
273
+ return formatErrorResponse(error, params, config.collection, config.baseUrl);
274
+ }
275
+ }
276
+ );
277
+
278
+ // ── Config Tool(照搬官方)────────────────────────────────────────────────────
279
+ server.tool(
280
+ "get-config",
281
+ "Get the current Airweave MCP server configuration",
282
+ {},
283
+ async () => {
284
+ return {
285
+ content: [
286
+ {
287
+ type: "text",
288
+ text: [
289
+ "**Airweave MCP Server Configuration:**",
290
+ "",
291
+ `- **Collection ID:** ${config.collection}`,
292
+ `- **Base URL:** ${config.baseUrl}`,
293
+ `- **API Key:** ${config.apiKey ? "✓ Configured" : "✗ Missing"}`,
294
+ `- **Organization ID:** ${config.organizationId || "Not set"}`,
295
+ "",
296
+ "**Available Commands:**",
297
+ `- \`${toolName}\`: Search within the configured Airweave collection`,
298
+ "- `get-config`: Show this configuration information",
299
+ ].join("\n"),
300
+ },
301
+ ],
302
+ };
303
+ }
304
+ );
305
+
306
+ // ── Graceful Shutdown(照搬官方)──────────────────────────────────────────────
307
+ process.on("SIGINT", () => {
308
+ console.error("Shutting down Airweave MCP server...");
309
+ process.exit(0);
310
+ });
311
+ process.on("SIGTERM", () => {
312
+ console.error("Shutting down Airweave MCP server...");
313
+ process.exit(0);
314
+ });
315
+
316
+ // ── Main Entry(照搬官方)──────────────────────────────────────────────────────
317
+ async function main() {
318
+ const transport = new StdioServerTransport();
319
+ await server.connect(transport);
320
+ console.error("Airweave MCP Search Server started");
321
+ console.error(`Collection: ${config.collection}`);
322
+ console.error(`Base URL: ${config.baseUrl}`);
323
+ }
324
+
325
+ main().catch((error) => {
326
+ console.error("Fatal error in Airweave MCP server:", error);
327
+ process.exit(1);
328
+ });
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "musa-knowledge-search",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "musa-knowledge-search": "./index.js"
8
+ },
9
+ "description": "MCP server to search the MUSA knowledge base across GitHub, GitLab, Jira, Confluence, and docs. Supports instant/classic/agentic search tiers.",
10
+ "keywords": ["mcp", "musa", "search", "knowledge-base", "claude", "model-context-protocol"],
11
+ "license": "MIT",
12
+ "dependencies": {
13
+ "@modelcontextprotocol/sdk": "^1.0.0",
14
+ "zod": "^3.23.0"
15
+ }
16
+ }