domain-rag-mcp-server 3.1.1 → 3.3.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 (2) hide show
  1. package/dist/index.mjs +560 -2
  2. package/package.json +47 -46
package/dist/index.mjs CHANGED
@@ -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
  }
@@ -737,6 +743,35 @@ async function getStats() {
737
743
  };
738
744
  }
739
745
 
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
+
740
775
  // src/index.ts
741
776
  var SOURCE_WEIGHTS = getSourceWeightsMap();
742
777
  var qdrant = new QdrantClient({
@@ -988,6 +1023,25 @@ var tools = [
988
1023
  properties: {}
989
1024
  }
990
1025
  },
1026
+ {
1027
+ name: "raw_sql",
1028
+ description: "Execute a raw SQL SELECT query on PostgreSQL database. ONLY SELECT queries are allowed. Use for complex queries that other tools cannot handle. Tables: persons, jira_projects, jira_issues, confluence_spaces, confluence_pages, git_commits, jira_issue_commits.",
1029
+ inputSchema: {
1030
+ type: "object",
1031
+ properties: {
1032
+ sql: {
1033
+ type: "string",
1034
+ description: "SQL SELECT query to execute. Example: SELECT issue_key, summary FROM jira_issues WHERE status = 'Done' LIMIT 10"
1035
+ },
1036
+ params: {
1037
+ type: "array",
1038
+ items: { type: "string" },
1039
+ description: "Query parameters for parameterized queries (optional). Use $1, $2, etc. in SQL."
1040
+ }
1041
+ },
1042
+ required: ["sql"]
1043
+ }
1044
+ },
991
1045
  // ============================================
992
1046
  // Vector Search Tools (Qdrant)
993
1047
  // ============================================
@@ -1190,6 +1244,52 @@ var tools = [
1190
1244
  }
1191
1245
  },
1192
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
+ // ============================================
1193
1293
  // Server Code Search Tools (LOW PRIORITY - use last)
1194
1294
  // Direct grep on server filesystem repositories
1195
1295
  // WARNING: Returns large responses that consume context window.
@@ -1238,7 +1338,42 @@ var tools = [
1238
1338
  type: "object",
1239
1339
  properties: {}
1240
1340
  }
1241
- }
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
+ }] : []
1242
1377
  ];
1243
1378
  async function handleSearchServerCode(args) {
1244
1379
  if (!config.codeSearchApi.enabled) {
@@ -1569,6 +1704,59 @@ async function handleGetDbStats() {
1569
1704
  }
1570
1705
  return response;
1571
1706
  }
1707
+ async function handleRawSql(args) {
1708
+ if (!pgAvailable) {
1709
+ return "PostgreSQL not available.";
1710
+ }
1711
+ const normalizedSql = args.sql.trim().toLowerCase();
1712
+ if (!normalizedSql.startsWith("select")) {
1713
+ return "Error: Only SELECT queries are allowed. Use SELECT to query data.";
1714
+ }
1715
+ const dangerousKeywords = ["insert", "update", "delete", "drop", "truncate", "alter", "create", "grant", "revoke"];
1716
+ for (const keyword of dangerousKeywords) {
1717
+ if (normalizedSql.includes(keyword)) {
1718
+ return `Error: Query contains forbidden keyword "${keyword}". Only SELECT queries are allowed.`;
1719
+ }
1720
+ }
1721
+ try {
1722
+ const result = await query(args.sql, args.params);
1723
+ if (result.rows.length === 0) {
1724
+ return "Query returned no results.";
1725
+ }
1726
+ const columns = Object.keys(result.rows[0]);
1727
+ let response = `## Query Results
1728
+
1729
+ `;
1730
+ response += `Rows: ${result.rows.length}
1731
+
1732
+ `;
1733
+ response += "| " + columns.join(" | ") + " |\n";
1734
+ response += "|" + columns.map(() => "---").join("|") + "|\n";
1735
+ const maxRows = Math.min(result.rows.length, 100);
1736
+ for (let i = 0; i < maxRows; i++) {
1737
+ const row = result.rows[i];
1738
+ const values = columns.map((col) => {
1739
+ const val = row[col];
1740
+ if (val === null)
1741
+ return "NULL";
1742
+ if (typeof val === "object")
1743
+ return JSON.stringify(val).substring(0, 100);
1744
+ const str = String(val);
1745
+ return str.length > 100 ? str.substring(0, 100) + "..." : str;
1746
+ });
1747
+ response += "| " + values.join(" | ") + " |\n";
1748
+ }
1749
+ if (result.rows.length > 100) {
1750
+ response += `
1751
+ *Showing first 100 of ${result.rows.length} rows*
1752
+ `;
1753
+ }
1754
+ return response;
1755
+ } catch (error) {
1756
+ const message = error instanceof Error ? error.message : "Unknown error";
1757
+ return `SQL Error: ${message}`;
1758
+ }
1759
+ }
1572
1760
  async function handleSearchAsAnalyst(args) {
1573
1761
  const results = await searchWithWeights({
1574
1762
  query: args.query,
@@ -1741,6 +1929,360 @@ async function handleGetStats() {
1741
1929
  }
1742
1930
  return response;
1743
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
+ }
1744
2286
  async function main() {
1745
2287
  await ensureCollection();
1746
2288
  await initPostgres();
@@ -1781,6 +2323,9 @@ async function main() {
1781
2323
  case "get_db_stats":
1782
2324
  result = await handleGetDbStats();
1783
2325
  break;
2326
+ case "raw_sql":
2327
+ result = await handleRawSql(args);
2328
+ break;
1784
2329
  case "search_as_analyst":
1785
2330
  result = await handleSearchAsAnalyst(args);
1786
2331
  break;
@@ -1820,6 +2365,18 @@ async function main() {
1820
2365
  case "get_index_stats":
1821
2366
  result = await handleGetStats();
1822
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;
1823
2380
  default:
1824
2381
  throw new Error(`Unknown tool: ${name}`);
1825
2382
  }
@@ -1833,6 +2390,7 @@ async function main() {
1833
2390
  await server.connect(transport);
1834
2391
  const pgStatus = pgAvailable ? "connected" : "disabled";
1835
2392
  const codeSearchStatus = config.codeSearchApi.enabled ? config.codeSearchApi.url : "disabled";
1836
- console.error(`Domain RAG MCP Server v3.1 started (embedding: ${embeddingProvider.name}, qdrant: ${config.qdrant.url}, postgres: ${pgStatus}, code-search: ${codeSearchStatus})`);
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})`);
1837
2395
  }
1838
2396
  main().catch(console.error);
package/package.json CHANGED
@@ -1,46 +1,47 @@
1
- {
2
- "name": "domain-rag-mcp-server",
3
- "version": "3.1.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
- "@types/node": "^20.10.0",
40
- "@types/pg": "^8.10.9",
41
- "@types/uuid": "^9.0.7",
42
- "esbuild": "^0.20.2",
43
- "tsx": "^4.7.0",
44
- "typescript": "^5.3.3"
45
- }
46
- }
1
+ {
2
+ "name": "domain-rag-mcp-server",
3
+ "version": "3.3.0",
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
+ "@domain-rag/shared": "file:../shared",
33
+ "@modelcontextprotocol/sdk": "^1.0.0",
34
+ "@qdrant/js-client-rest": "^1.7.0",
35
+ "dotenv": "^16.3.1",
36
+ "pg": "^8.11.3",
37
+ "uuid": "^9.0.1"
38
+ },
39
+ "devDependencies": {
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
+ }