domain-rag-mcp-server 3.2.0 → 3.3.1
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/dist/index.mjs +494 -11
- package/package.json +47 -46
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// src/index.ts
|
|
4
4
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
11
11
|
import { v4 as uuidv4 } from "uuid";
|
|
12
12
|
|
|
13
|
-
// ../
|
|
13
|
+
// ../shared/src/config.ts
|
|
14
14
|
import { config as dotenvConfig } from "dotenv";
|
|
15
15
|
import { existsSync } from "fs";
|
|
16
16
|
import { join } from "path";
|
|
@@ -210,6 +210,12 @@ function buildConfig() {
|
|
|
210
210
|
codeSearchApi: {
|
|
211
211
|
enabled: getEnvBool("CODE_SEARCH_API_ENABLED", true),
|
|
212
212
|
url: getEnv("CODE_SEARCH_API_URL", "http://10.3.1.94:8085")
|
|
213
|
+
},
|
|
214
|
+
datadog: {
|
|
215
|
+
enabled: getEnvBool("DATADOG_ENABLED", false),
|
|
216
|
+
apiKey: getEnv("DATADOG_API_KEY", ""),
|
|
217
|
+
appKey: getEnv("DATADOG_APP_KEY", ""),
|
|
218
|
+
site: getEnv("DATADOG_SITE", "datadoghq.eu")
|
|
213
219
|
}
|
|
214
220
|
};
|
|
215
221
|
}
|
|
@@ -240,7 +246,7 @@ function getSourceWeightsMap() {
|
|
|
240
246
|
};
|
|
241
247
|
}
|
|
242
248
|
|
|
243
|
-
// ../
|
|
249
|
+
// ../shared/src/embeddings/types.ts
|
|
244
250
|
var BaseEmbeddingProvider = class {
|
|
245
251
|
/**
|
|
246
252
|
* Default batch implementation - calls getEmbedding sequentially
|
|
@@ -256,7 +262,7 @@ var BaseEmbeddingProvider = class {
|
|
|
256
262
|
}
|
|
257
263
|
};
|
|
258
264
|
|
|
259
|
-
// ../
|
|
265
|
+
// ../shared/src/embeddings/ollama.ts
|
|
260
266
|
var OllamaProvider = class extends BaseEmbeddingProvider {
|
|
261
267
|
name = "ollama";
|
|
262
268
|
dimensions;
|
|
@@ -320,7 +326,7 @@ var OllamaProvider = class extends BaseEmbeddingProvider {
|
|
|
320
326
|
}
|
|
321
327
|
};
|
|
322
328
|
|
|
323
|
-
// ../
|
|
329
|
+
// ../shared/src/embeddings/openai.ts
|
|
324
330
|
var OpenAIProvider = class extends BaseEmbeddingProvider {
|
|
325
331
|
name = "openai";
|
|
326
332
|
dimensions;
|
|
@@ -405,7 +411,7 @@ var OpenAIProvider = class extends BaseEmbeddingProvider {
|
|
|
405
411
|
}
|
|
406
412
|
};
|
|
407
413
|
|
|
408
|
-
// ../
|
|
414
|
+
// ../shared/src/embeddings/factory.ts
|
|
409
415
|
function createEmbeddingProvider() {
|
|
410
416
|
const providerType = config.embeddings.provider;
|
|
411
417
|
switch (providerType) {
|
|
@@ -436,7 +442,7 @@ function getEmbeddingProvider() {
|
|
|
436
442
|
return _provider;
|
|
437
443
|
}
|
|
438
444
|
|
|
439
|
-
// ../
|
|
445
|
+
// ../shared/src/postgres/client.ts
|
|
440
446
|
import pg from "pg";
|
|
441
447
|
var { Pool } = pg;
|
|
442
448
|
var pool = null;
|
|
@@ -484,7 +490,7 @@ async function checkHealth() {
|
|
|
484
490
|
}
|
|
485
491
|
}
|
|
486
492
|
|
|
487
|
-
// ../
|
|
493
|
+
// ../shared/src/postgres/search.ts
|
|
488
494
|
async function searchAll(searchQuery, options = {}) {
|
|
489
495
|
const { limit = 30, sources } = options;
|
|
490
496
|
const includeJira = !sources || sources.includes("jira");
|
|
@@ -737,7 +743,36 @@ async function getStats() {
|
|
|
737
743
|
};
|
|
738
744
|
}
|
|
739
745
|
|
|
740
|
-
// ../
|
|
746
|
+
// ../shared/src/graph-db.ts
|
|
747
|
+
async function searchGraphEntities(query_text, entityTypes, limit = 10) {
|
|
748
|
+
const params = [`%${query_text.toLowerCase()}%`];
|
|
749
|
+
let typeFilter = "";
|
|
750
|
+
if (entityTypes && entityTypes.length > 0) {
|
|
751
|
+
params.push(entityTypes);
|
|
752
|
+
typeFilter = `AND entity_type = ANY($${params.length}::text[])`;
|
|
753
|
+
}
|
|
754
|
+
params.push(limit);
|
|
755
|
+
return queryRows(
|
|
756
|
+
`SELECT * FROM graph_entities
|
|
757
|
+
WHERE (LOWER(name) LIKE $1 OR EXISTS (
|
|
758
|
+
SELECT 1 FROM unnest(aliases) a WHERE LOWER(a) LIKE $1
|
|
759
|
+
))
|
|
760
|
+
${typeFilter}
|
|
761
|
+
ORDER BY degree DESC
|
|
762
|
+
LIMIT $${params.length}`,
|
|
763
|
+
params
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
async function getGraphNeighbors(startEntityIds, maxHops = 2) {
|
|
767
|
+
if (startEntityIds.length === 0)
|
|
768
|
+
return [];
|
|
769
|
+
return queryRows(
|
|
770
|
+
`SELECT * FROM get_graph_neighbors($1::text[], $2)`,
|
|
771
|
+
[startEntityIds, maxHops]
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/index.ts
|
|
741
776
|
var SOURCE_WEIGHTS = getSourceWeightsMap();
|
|
742
777
|
var qdrant = new QdrantClient({
|
|
743
778
|
url: config.qdrant.url,
|
|
@@ -1209,6 +1244,52 @@ var tools = [
|
|
|
1209
1244
|
}
|
|
1210
1245
|
},
|
|
1211
1246
|
// ============================================
|
|
1247
|
+
// GraphRAG Tools — graph-enhanced search
|
|
1248
|
+
// ============================================
|
|
1249
|
+
{
|
|
1250
|
+
name: "graph_search",
|
|
1251
|
+
description: "Graph-enhanced hybrid search. Combines Qdrant vector search with knowledge graph traversal to return connected results with relationship context. Best for complex queries that span multiple sources (Jira + Confluence + Git + People). Use when you need to understand relationships, not just find similar text.",
|
|
1252
|
+
inputSchema: {
|
|
1253
|
+
type: "object",
|
|
1254
|
+
properties: {
|
|
1255
|
+
query: { type: "string", description: "What are you looking for?" },
|
|
1256
|
+
limit: { type: "number", description: "Max results (default: 15)", default: 15 },
|
|
1257
|
+
source_types: {
|
|
1258
|
+
type: "array",
|
|
1259
|
+
items: { type: "string", enum: ["jira", "confluence", "git_commit", "code", "decision", "domain_term"] },
|
|
1260
|
+
description: "Filter by source types (optional)"
|
|
1261
|
+
},
|
|
1262
|
+
graph_hops: { type: "number", description: "Graph traversal depth: 1 or 2 (default: 1)", default: 1 }
|
|
1263
|
+
},
|
|
1264
|
+
required: ["query"]
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
name: "entity_search",
|
|
1269
|
+
description: 'Find everything connected to a specific entity (person, service, Jira project, component). Uses knowledge graph to collect all related Jira issues, Confluence pages, Git commits, and people in one query. Use for "tell me everything about X" queries.',
|
|
1270
|
+
inputSchema: {
|
|
1271
|
+
type: "object",
|
|
1272
|
+
properties: {
|
|
1273
|
+
entity: { type: "string", description: 'Entity name or ID (e.g. "PaymentGateway", "Ivan Petrov", "PROJ-123", "AUTH")' },
|
|
1274
|
+
max_hops: { type: "number", description: "Relationship hops to traverse (default: 2)", default: 2 },
|
|
1275
|
+
limit: { type: "number", description: "Max results (default: 20)", default: 20 }
|
|
1276
|
+
},
|
|
1277
|
+
required: ["entity"]
|
|
1278
|
+
}
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
name: "impact_analysis",
|
|
1282
|
+
description: "Analyze the impact of changing a service, module, or component. Traverses the knowledge graph to find all dependent services, teams, active issues, and documentation that would be affected. Use before making significant changes.",
|
|
1283
|
+
inputSchema: {
|
|
1284
|
+
type: "object",
|
|
1285
|
+
properties: {
|
|
1286
|
+
target: { type: "string", description: 'Service, module, or component name (e.g. "auth-service", "PaymentProcessor")' },
|
|
1287
|
+
max_depth: { type: "number", description: "Traversal depth (default: 3)", default: 3 }
|
|
1288
|
+
},
|
|
1289
|
+
required: ["target"]
|
|
1290
|
+
}
|
|
1291
|
+
},
|
|
1292
|
+
// ============================================
|
|
1212
1293
|
// Server Code Search Tools (LOW PRIORITY - use last)
|
|
1213
1294
|
// Direct grep on server filesystem repositories
|
|
1214
1295
|
// WARNING: Returns large responses that consume context window.
|
|
@@ -1257,7 +1338,42 @@ var tools = [
|
|
|
1257
1338
|
type: "object",
|
|
1258
1339
|
properties: {}
|
|
1259
1340
|
}
|
|
1260
|
-
}
|
|
1341
|
+
},
|
|
1342
|
+
// ============================================
|
|
1343
|
+
// Datadog Log Search Tool
|
|
1344
|
+
// ============================================
|
|
1345
|
+
...config.datadog.enabled ? [{
|
|
1346
|
+
name: "search_datadog_logs",
|
|
1347
|
+
description: 'Search application logs in Datadog. Use this to investigate errors, debug issues, check service health, or find specific log entries. Supports Datadog query syntax (e.g. "service:api status:error", "@http.status_code:500").',
|
|
1348
|
+
inputSchema: {
|
|
1349
|
+
type: "object",
|
|
1350
|
+
properties: {
|
|
1351
|
+
query: {
|
|
1352
|
+
type: "string",
|
|
1353
|
+
description: 'Datadog log search query. Examples: "error", "service:my-api status:error", "@http.url:/api/users", "timeout OR connection refused"'
|
|
1354
|
+
},
|
|
1355
|
+
service: {
|
|
1356
|
+
type: "string",
|
|
1357
|
+
description: "Filter by service name (optional, appended to query as service:<value>)"
|
|
1358
|
+
},
|
|
1359
|
+
env: {
|
|
1360
|
+
type: "string",
|
|
1361
|
+
description: 'Filter by environment (optional, e.g. "production", "staging")'
|
|
1362
|
+
},
|
|
1363
|
+
time_range: {
|
|
1364
|
+
type: "string",
|
|
1365
|
+
description: 'Time range to search (default: "1h"). Supported: "15m", "1h", "6h", "24h", "3d", "7d"',
|
|
1366
|
+
default: "1h"
|
|
1367
|
+
},
|
|
1368
|
+
limit: {
|
|
1369
|
+
type: "number",
|
|
1370
|
+
description: "Maximum log entries to return (default: 30, max: 100)",
|
|
1371
|
+
default: 30
|
|
1372
|
+
}
|
|
1373
|
+
},
|
|
1374
|
+
required: ["query"]
|
|
1375
|
+
}
|
|
1376
|
+
}] : []
|
|
1261
1377
|
];
|
|
1262
1378
|
async function handleSearchServerCode(args) {
|
|
1263
1379
|
if (!config.codeSearchApi.enabled) {
|
|
@@ -1813,6 +1929,360 @@ async function handleGetStats() {
|
|
|
1813
1929
|
}
|
|
1814
1930
|
return response;
|
|
1815
1931
|
}
|
|
1932
|
+
async function handleGraphSearch(args) {
|
|
1933
|
+
const { query: query2, limit = 15, source_types, graph_hops = 1 } = args;
|
|
1934
|
+
const embedding = await getEmbedding(query2);
|
|
1935
|
+
const filterMust = [];
|
|
1936
|
+
if (source_types && source_types.length > 0) {
|
|
1937
|
+
filterMust.push({ key: "source_type", match: { any: source_types } });
|
|
1938
|
+
}
|
|
1939
|
+
const [chunkResults, entityResults] = await Promise.all([
|
|
1940
|
+
qdrant.search(config.qdrant.collectionName, {
|
|
1941
|
+
vector: embedding,
|
|
1942
|
+
limit: limit * 3,
|
|
1943
|
+
filter: filterMust.length > 0 ? { must: filterMust } : void 0,
|
|
1944
|
+
with_payload: true,
|
|
1945
|
+
score_threshold: 0.25
|
|
1946
|
+
}),
|
|
1947
|
+
qdrant.search("graph_entities", {
|
|
1948
|
+
vector: embedding,
|
|
1949
|
+
limit: 20,
|
|
1950
|
+
with_payload: true,
|
|
1951
|
+
score_threshold: 0.3
|
|
1952
|
+
}).catch(() => [])
|
|
1953
|
+
// graceful fallback if collection doesn't exist yet
|
|
1954
|
+
]);
|
|
1955
|
+
const topEntityIds = entityResults.slice(0, 10).map((r) => r.payload.entity_id).filter(Boolean);
|
|
1956
|
+
let expandedEntityIds = new Set(topEntityIds);
|
|
1957
|
+
if (topEntityIds.length > 0 && pgAvailable) {
|
|
1958
|
+
try {
|
|
1959
|
+
const neighbors = await getGraphNeighbors(topEntityIds, graph_hops);
|
|
1960
|
+
for (const n of neighbors)
|
|
1961
|
+
expandedEntityIds.add(n.entity_id);
|
|
1962
|
+
} catch {
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
const topEntitySet = new Set(topEntityIds.slice(0, 5));
|
|
1966
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1967
|
+
for (const r of chunkResults) {
|
|
1968
|
+
const payload = r.payload;
|
|
1969
|
+
const sourceType = payload.source_type || "";
|
|
1970
|
+
const sourceId = payload.source_id || "";
|
|
1971
|
+
const chunkEntityIds = payload.entity_ids || [];
|
|
1972
|
+
const weight = SOURCE_WEIGHTS[sourceType] || 1;
|
|
1973
|
+
const connectedCount = chunkEntityIds.filter((e) => topEntitySet.has(e) || expandedEntityIds.has(e)).length;
|
|
1974
|
+
const graphBoost = 1 + 0.3 * Math.min(connectedCount / 3, 1);
|
|
1975
|
+
const finalScore = (r.score || 0) * graphBoost * weight;
|
|
1976
|
+
const key = `${sourceType}:${sourceId}`;
|
|
1977
|
+
const existing = seen.get(key);
|
|
1978
|
+
if (!existing || finalScore > existing.score) {
|
|
1979
|
+
seen.set(key, { score: finalScore, payload });
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
const ranked = [...seen.values()].sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1983
|
+
if (ranked.length === 0) {
|
|
1984
|
+
return `No results found for graph search: "${query2}"`;
|
|
1985
|
+
}
|
|
1986
|
+
let response = `## Graph Search: "${query2}"
|
|
1987
|
+
|
|
1988
|
+
`;
|
|
1989
|
+
response += `Found ${ranked.length} results (vector + graph-enhanced):
|
|
1990
|
+
|
|
1991
|
+
`;
|
|
1992
|
+
if (expandedEntityIds.size > 0) {
|
|
1993
|
+
response += `Graph context: ${expandedEntityIds.size} connected entities found
|
|
1994
|
+
|
|
1995
|
+
`;
|
|
1996
|
+
}
|
|
1997
|
+
for (const { score, payload } of ranked) {
|
|
1998
|
+
const sourceType = payload.source_type || "";
|
|
1999
|
+
const sourceId = payload.source_id || "";
|
|
2000
|
+
const content = payload.content || "";
|
|
2001
|
+
const url = payload.source_url;
|
|
2002
|
+
const communityId = payload.community_id;
|
|
2003
|
+
response += `### [${sourceType.toUpperCase()}] ${sourceId}
|
|
2004
|
+
`;
|
|
2005
|
+
response += `Score: ${(score * 100).toFixed(1)}%`;
|
|
2006
|
+
if (communityId)
|
|
2007
|
+
response += ` | Community: ${communityId}`;
|
|
2008
|
+
response += "\n";
|
|
2009
|
+
if (url)
|
|
2010
|
+
response += `URL: ${url}
|
|
2011
|
+
`;
|
|
2012
|
+
response += `
|
|
2013
|
+
${content.substring(0, 1e3)}${content.length > 1e3 ? "..." : ""}
|
|
2014
|
+
|
|
2015
|
+
---
|
|
2016
|
+
|
|
2017
|
+
`;
|
|
2018
|
+
}
|
|
2019
|
+
return response;
|
|
2020
|
+
}
|
|
2021
|
+
async function handleEntitySearch(args) {
|
|
2022
|
+
const { entity, max_hops = 2, limit = 20 } = args;
|
|
2023
|
+
if (!pgAvailable) {
|
|
2024
|
+
return handleGraphSearch({ query: entity, limit });
|
|
2025
|
+
}
|
|
2026
|
+
let matchedEntities = [];
|
|
2027
|
+
try {
|
|
2028
|
+
matchedEntities = await searchGraphEntities(entity, void 0, 5);
|
|
2029
|
+
} catch {
|
|
2030
|
+
}
|
|
2031
|
+
if (matchedEntities.length === 0) {
|
|
2032
|
+
const embedding2 = await getEmbedding(entity);
|
|
2033
|
+
const qdrantEntities = await qdrant.search("graph_entities", {
|
|
2034
|
+
vector: embedding2,
|
|
2035
|
+
limit: 5,
|
|
2036
|
+
with_payload: true,
|
|
2037
|
+
score_threshold: 0.4
|
|
2038
|
+
}).catch(() => []);
|
|
2039
|
+
matchedEntities = qdrantEntities.map((r) => {
|
|
2040
|
+
const p = r.payload;
|
|
2041
|
+
return {
|
|
2042
|
+
entity_id: p.entity_id,
|
|
2043
|
+
entity_type: p.entity_type,
|
|
2044
|
+
name: p.name
|
|
2045
|
+
};
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
if (matchedEntities.length === 0) {
|
|
2049
|
+
return `Entity "${entity}" not found in knowledge graph. Try graph_search instead.`;
|
|
2050
|
+
}
|
|
2051
|
+
const primaryEntity = matchedEntities[0];
|
|
2052
|
+
let response = `## Entity Search: "${entity}"
|
|
2053
|
+
|
|
2054
|
+
`;
|
|
2055
|
+
response += `**Found entity:** ${primaryEntity.name} (${primaryEntity.entity_type})
|
|
2056
|
+
`;
|
|
2057
|
+
response += `**Entity ID:** ${primaryEntity.entity_id}
|
|
2058
|
+
|
|
2059
|
+
`;
|
|
2060
|
+
let neighbors = [];
|
|
2061
|
+
try {
|
|
2062
|
+
neighbors = await getGraphNeighbors([primaryEntity.entity_id], max_hops);
|
|
2063
|
+
} catch {
|
|
2064
|
+
}
|
|
2065
|
+
if (neighbors.length > 0) {
|
|
2066
|
+
response += `**Connected entities (${neighbors.length}):**
|
|
2067
|
+
|
|
2068
|
+
`;
|
|
2069
|
+
const byType = /* @__PURE__ */ new Map();
|
|
2070
|
+
for (const n of neighbors.slice(0, 50)) {
|
|
2071
|
+
const group = byType.get(n.entity_type) ?? [];
|
|
2072
|
+
group.push(n);
|
|
2073
|
+
byType.set(n.entity_type, group);
|
|
2074
|
+
}
|
|
2075
|
+
for (const [type, nodes] of byType) {
|
|
2076
|
+
response += `**${type}** (${nodes.length}):
|
|
2077
|
+
`;
|
|
2078
|
+
for (const n of nodes.slice(0, 10)) {
|
|
2079
|
+
response += ` - ${n.name} [${n.edge_type ?? "connected"}] (hop ${n.hop})
|
|
2080
|
+
`;
|
|
2081
|
+
}
|
|
2082
|
+
response += "\n";
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
const allEntityIds = [primaryEntity.entity_id, ...neighbors.slice(0, 30).map((n) => n.entity_id)];
|
|
2086
|
+
const embedding = await getEmbedding(entity);
|
|
2087
|
+
const chunks = await qdrant.search(config.qdrant.collectionName, {
|
|
2088
|
+
vector: embedding,
|
|
2089
|
+
limit,
|
|
2090
|
+
filter: {
|
|
2091
|
+
must: [{ key: "entity_ids", match: { any: allEntityIds } }]
|
|
2092
|
+
},
|
|
2093
|
+
with_payload: true,
|
|
2094
|
+
score_threshold: 0.1
|
|
2095
|
+
}).catch(() => []);
|
|
2096
|
+
if (chunks.length > 0) {
|
|
2097
|
+
response += `**Related documents (${chunks.length}):**
|
|
2098
|
+
|
|
2099
|
+
`;
|
|
2100
|
+
for (const c of chunks.slice(0, limit)) {
|
|
2101
|
+
const p = c.payload;
|
|
2102
|
+
const sourceType = p.source_type || "";
|
|
2103
|
+
const sourceId = p.source_id || "";
|
|
2104
|
+
const content = p.content || "";
|
|
2105
|
+
response += `### [${sourceType.toUpperCase()}] ${sourceId}
|
|
2106
|
+
`;
|
|
2107
|
+
if (p.source_url)
|
|
2108
|
+
response += `URL: ${p.source_url}
|
|
2109
|
+
`;
|
|
2110
|
+
response += `
|
|
2111
|
+
${content.substring(0, 600)}${content.length > 600 ? "..." : ""}
|
|
2112
|
+
|
|
2113
|
+
---
|
|
2114
|
+
|
|
2115
|
+
`;
|
|
2116
|
+
}
|
|
2117
|
+
} else {
|
|
2118
|
+
response += `*No indexed documents found for this entity. Run the graph indexer first.*
|
|
2119
|
+
`;
|
|
2120
|
+
}
|
|
2121
|
+
return response;
|
|
2122
|
+
}
|
|
2123
|
+
async function handleImpactAnalysis(args) {
|
|
2124
|
+
const { target, max_depth = 3 } = args;
|
|
2125
|
+
if (!pgAvailable) {
|
|
2126
|
+
return `PostgreSQL not available. Cannot perform graph impact analysis. Use search_as_architect instead.`;
|
|
2127
|
+
}
|
|
2128
|
+
let entities = [];
|
|
2129
|
+
try {
|
|
2130
|
+
entities = await searchGraphEntities(target, void 0, 3);
|
|
2131
|
+
} catch {
|
|
2132
|
+
}
|
|
2133
|
+
if (entities.length === 0) {
|
|
2134
|
+
return `Entity "${target}" not found in knowledge graph. Try running the graph builder first (GRAPH_ENABLED=true).`;
|
|
2135
|
+
}
|
|
2136
|
+
const primary = entities[0];
|
|
2137
|
+
let response = `## Impact Analysis: "${target}"
|
|
2138
|
+
|
|
2139
|
+
`;
|
|
2140
|
+
response += `**Analyzing:** ${primary.name} (${primary.entity_type})
|
|
2141
|
+
|
|
2142
|
+
`;
|
|
2143
|
+
let neighbors = [];
|
|
2144
|
+
try {
|
|
2145
|
+
neighbors = await getGraphNeighbors([primary.entity_id], max_depth);
|
|
2146
|
+
} catch {
|
|
2147
|
+
}
|
|
2148
|
+
if (neighbors.length === 0) {
|
|
2149
|
+
response += `No connected entities found. Graph may not be built yet.`;
|
|
2150
|
+
return response;
|
|
2151
|
+
}
|
|
2152
|
+
const byType = /* @__PURE__ */ new Map();
|
|
2153
|
+
for (const n of neighbors) {
|
|
2154
|
+
const group = byType.get(n.entity_type) ?? [];
|
|
2155
|
+
group.push(n);
|
|
2156
|
+
byType.set(n.entity_type, group);
|
|
2157
|
+
}
|
|
2158
|
+
response += `**Total affected entities: ${neighbors.length}**
|
|
2159
|
+
|
|
2160
|
+
`;
|
|
2161
|
+
response += `| Entity Type | Count | Relationship |
|
|
2162
|
+
`;
|
|
2163
|
+
response += `|-------------|-------|--------------|
|
|
2164
|
+
`;
|
|
2165
|
+
for (const [type, nodes] of byType) {
|
|
2166
|
+
const edgeTypes = [...new Set(nodes.map((n) => n.edge_type).filter(Boolean))].join(", ");
|
|
2167
|
+
response += `| ${type} | ${nodes.length} | ${edgeTypes || "various"} |
|
|
2168
|
+
`;
|
|
2169
|
+
}
|
|
2170
|
+
response += "\n";
|
|
2171
|
+
for (const [type, nodes] of byType) {
|
|
2172
|
+
response += `**${type.toUpperCase()} (${nodes.length}):**
|
|
2173
|
+
`;
|
|
2174
|
+
for (const n of nodes.slice(0, 15)) {
|
|
2175
|
+
response += ` - ${n.name} via ${n.edge_type ?? "connected"} (${n.hop} hop${n.hop > 1 ? "s" : ""})
|
|
2176
|
+
`;
|
|
2177
|
+
}
|
|
2178
|
+
if (nodes.length > 15)
|
|
2179
|
+
response += ` ... and ${nodes.length - 15} more
|
|
2180
|
+
`;
|
|
2181
|
+
response += "\n";
|
|
2182
|
+
}
|
|
2183
|
+
return response;
|
|
2184
|
+
}
|
|
2185
|
+
function parseTimeRange(timeRange) {
|
|
2186
|
+
const match = timeRange.match(/^(\d+)([mhd])$/);
|
|
2187
|
+
if (!match)
|
|
2188
|
+
return 60 * 60 * 1e3;
|
|
2189
|
+
const [, value, unit] = match;
|
|
2190
|
+
const num = parseInt(value);
|
|
2191
|
+
switch (unit) {
|
|
2192
|
+
case "m":
|
|
2193
|
+
return num * 60 * 1e3;
|
|
2194
|
+
case "h":
|
|
2195
|
+
return num * 60 * 60 * 1e3;
|
|
2196
|
+
case "d":
|
|
2197
|
+
return num * 24 * 60 * 60 * 1e3;
|
|
2198
|
+
default:
|
|
2199
|
+
return 60 * 60 * 1e3;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
async function handleSearchDatadogLogs(args) {
|
|
2203
|
+
if (!config.datadog.enabled) {
|
|
2204
|
+
return "Datadog integration is disabled. Set DATADOG_ENABLED=true with DATADOG_API_KEY and DATADOG_APP_KEY to enable.";
|
|
2205
|
+
}
|
|
2206
|
+
if (!config.datadog.apiKey || !config.datadog.appKey) {
|
|
2207
|
+
return "Datadog API keys not configured. Set DATADOG_API_KEY and DATADOG_APP_KEY environment variables.";
|
|
2208
|
+
}
|
|
2209
|
+
let ddQuery = args.query;
|
|
2210
|
+
if (args.service) {
|
|
2211
|
+
ddQuery += ` service:${args.service}`;
|
|
2212
|
+
}
|
|
2213
|
+
if (args.env) {
|
|
2214
|
+
ddQuery += ` env:${args.env}`;
|
|
2215
|
+
}
|
|
2216
|
+
const limit = Math.min(args.limit || 30, 100);
|
|
2217
|
+
const timeRangeMs = parseTimeRange(args.time_range || "1h");
|
|
2218
|
+
const now = /* @__PURE__ */ new Date();
|
|
2219
|
+
const from = new Date(now.getTime() - timeRangeMs);
|
|
2220
|
+
try {
|
|
2221
|
+
const baseUrl = `https://api.${config.datadog.site}`;
|
|
2222
|
+
const response = await fetch(`${baseUrl}/api/v2/logs/events/search`, {
|
|
2223
|
+
method: "POST",
|
|
2224
|
+
headers: {
|
|
2225
|
+
"DD-API-KEY": config.datadog.apiKey,
|
|
2226
|
+
"DD-APPLICATION-KEY": config.datadog.appKey,
|
|
2227
|
+
"Content-Type": "application/json"
|
|
2228
|
+
},
|
|
2229
|
+
body: JSON.stringify({
|
|
2230
|
+
filter: {
|
|
2231
|
+
query: ddQuery,
|
|
2232
|
+
from: from.toISOString(),
|
|
2233
|
+
to: now.toISOString()
|
|
2234
|
+
},
|
|
2235
|
+
sort: "-timestamp",
|
|
2236
|
+
page: { limit }
|
|
2237
|
+
})
|
|
2238
|
+
});
|
|
2239
|
+
if (!response.ok) {
|
|
2240
|
+
const errorText = await response.text();
|
|
2241
|
+
return `Datadog API error (${response.status}): ${errorText}`;
|
|
2242
|
+
}
|
|
2243
|
+
const data = await response.json();
|
|
2244
|
+
if (!data.data || data.data.length === 0) {
|
|
2245
|
+
return `No logs found for query: \`${ddQuery}\` (time range: ${args.time_range || "1h"})`;
|
|
2246
|
+
}
|
|
2247
|
+
let result = `## Datadog Logs
|
|
2248
|
+
|
|
2249
|
+
`;
|
|
2250
|
+
result += `**Query:** \`${ddQuery}\`
|
|
2251
|
+
`;
|
|
2252
|
+
result += `**Time range:** ${from.toISOString()} \u2192 ${now.toISOString()}
|
|
2253
|
+
`;
|
|
2254
|
+
result += `**Results:** ${data.data.length}${data.meta?.page?.after ? " (more available)" : ""}
|
|
2255
|
+
|
|
2256
|
+
`;
|
|
2257
|
+
for (const log of data.data) {
|
|
2258
|
+
const attrs = log.attributes;
|
|
2259
|
+
const ts = attrs.timestamp ? new Date(attrs.timestamp).toISOString() : "unknown";
|
|
2260
|
+
const status = (attrs.status || "info").toUpperCase();
|
|
2261
|
+
const service = attrs.service || "unknown";
|
|
2262
|
+
const host = attrs.host || "";
|
|
2263
|
+
result += `### ${status} | ${service}${host ? ` @ ${host}` : ""}
|
|
2264
|
+
`;
|
|
2265
|
+
result += `**Time:** ${ts}
|
|
2266
|
+
`;
|
|
2267
|
+
if (attrs.message) {
|
|
2268
|
+
const msg = attrs.message.length > 1e3 ? attrs.message.substring(0, 1e3) + "..." : attrs.message;
|
|
2269
|
+
result += `\`\`\`
|
|
2270
|
+
${msg}
|
|
2271
|
+
\`\`\`
|
|
2272
|
+
`;
|
|
2273
|
+
}
|
|
2274
|
+
if (attrs.tags && attrs.tags.length > 0) {
|
|
2275
|
+
result += `**Tags:** ${attrs.tags.slice(0, 10).join(", ")}
|
|
2276
|
+
`;
|
|
2277
|
+
}
|
|
2278
|
+
result += "\n---\n\n";
|
|
2279
|
+
}
|
|
2280
|
+
return result;
|
|
2281
|
+
} catch (error) {
|
|
2282
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2283
|
+
return `Failed to query Datadog: ${message}`;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
1816
2286
|
async function main() {
|
|
1817
2287
|
await ensureCollection();
|
|
1818
2288
|
await initPostgres();
|
|
@@ -1895,6 +2365,18 @@ async function main() {
|
|
|
1895
2365
|
case "get_index_stats":
|
|
1896
2366
|
result = await handleGetStats();
|
|
1897
2367
|
break;
|
|
2368
|
+
case "graph_search":
|
|
2369
|
+
result = await handleGraphSearch(args);
|
|
2370
|
+
break;
|
|
2371
|
+
case "entity_search":
|
|
2372
|
+
result = await handleEntitySearch(args);
|
|
2373
|
+
break;
|
|
2374
|
+
case "impact_analysis":
|
|
2375
|
+
result = await handleImpactAnalysis(args);
|
|
2376
|
+
break;
|
|
2377
|
+
case "search_datadog_logs":
|
|
2378
|
+
result = await handleSearchDatadogLogs(args);
|
|
2379
|
+
break;
|
|
1898
2380
|
default:
|
|
1899
2381
|
throw new Error(`Unknown tool: ${name}`);
|
|
1900
2382
|
}
|
|
@@ -1908,6 +2390,7 @@ async function main() {
|
|
|
1908
2390
|
await server.connect(transport);
|
|
1909
2391
|
const pgStatus = pgAvailable ? "connected" : "disabled";
|
|
1910
2392
|
const codeSearchStatus = config.codeSearchApi.enabled ? config.codeSearchApi.url : "disabled";
|
|
1911
|
-
|
|
2393
|
+
const datadogStatus = config.datadog.enabled ? config.datadog.site : "disabled";
|
|
2394
|
+
console.error(`Domain RAG MCP Server v3.1 started (embedding: ${embeddingProvider.name}, qdrant: ${config.qdrant.url}, postgres: ${pgStatus}, code-search: ${codeSearchStatus}, datadog: ${datadogStatus})`);
|
|
1912
2395
|
}
|
|
1913
2396
|
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,46 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "domain-rag-mcp-server",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "MCP server for domain RAG search — connects to Qdrant + PostgreSQL + Code Search API for hybrid search across Jira, Confluence, Git commits, and server-side code repositories",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.mjs",
|
|
7
|
-
"bin": {
|
|
8
|
-
"domain-rag-mcp-server": "dist/index.mjs"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist/index.mjs"
|
|
12
|
-
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "tsc",
|
|
15
|
-
"build:npm": "node build.mjs",
|
|
16
|
-
"start": "node dist/index.mjs",
|
|
17
|
-
"dev": "tsx src/index.ts"
|
|
18
|
-
},
|
|
19
|
-
"keywords": [
|
|
20
|
-
"mcp",
|
|
21
|
-
"rag",
|
|
22
|
-
"qdrant",
|
|
23
|
-
"postgresql",
|
|
24
|
-
"jira",
|
|
25
|
-
"confluence",
|
|
26
|
-
"git",
|
|
27
|
-
"semantic-search",
|
|
28
|
-
"claude",
|
|
29
|
-
"cursor"
|
|
30
|
-
],
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
33
|
-
"@qdrant/js-client-rest": "^1.7.0",
|
|
34
|
-
"dotenv": "^16.3.1",
|
|
35
|
-
"pg": "^8.11.3",
|
|
36
|
-
"uuid": "^9.0.1"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@
|
|
40
|
-
"@types/
|
|
41
|
-
"@types/
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "domain-rag-mcp-server",
|
|
3
|
+
"version": "3.3.1",
|
|
4
|
+
"description": "MCP server for domain RAG search — connects to Qdrant + PostgreSQL + Code Search API for hybrid search across Jira, Confluence, Git commits, and server-side code repositories",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"domain-rag-mcp-server": "dist/index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/index.mjs"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"build:npm": "node build.mjs",
|
|
16
|
+
"start": "node dist/index.mjs",
|
|
17
|
+
"dev": "tsx src/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"rag",
|
|
22
|
+
"qdrant",
|
|
23
|
+
"postgresql",
|
|
24
|
+
"jira",
|
|
25
|
+
"confluence",
|
|
26
|
+
"git",
|
|
27
|
+
"semantic-search",
|
|
28
|
+
"claude",
|
|
29
|
+
"cursor"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
33
|
+
"@qdrant/js-client-rest": "^1.7.0",
|
|
34
|
+
"dotenv": "^16.3.1",
|
|
35
|
+
"pg": "^8.11.3",
|
|
36
|
+
"uuid": "^9.0.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@domain-rag/shared": "file:../shared",
|
|
40
|
+
"@types/node": "^20.10.0",
|
|
41
|
+
"@types/pg": "^8.10.9",
|
|
42
|
+
"@types/uuid": "^9.0.7",
|
|
43
|
+
"esbuild": "^0.20.2",
|
|
44
|
+
"tsx": "^4.7.0",
|
|
45
|
+
"typescript": "^5.3.3"
|
|
46
|
+
}
|
|
47
|
+
}
|