socraticode
Version:
SocratiCode — MCP server for local codebase indexing, semantic search, and code dependency graphs. All private, all local via Docker.
264 lines • 14.2 kB
JavaScript
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Giancarlo Erra - Altaire Limited
import { QDRANT_COLLECTION_PREFIX, QDRANT_HOST, QDRANT_MODE, QDRANT_PORT, QDRANT_URL, SOCRATICODE_VERSION } from "../constants.js";
import { getArtifactStatusSummary } from "../services/context-artifacts.js";
import { isDockerAvailable, isQdrantImagePresent, isQdrantRunning } from "../services/docker.js";
import { getEmbeddingConfig } from "../services/embedding-config.js";
import { getEmbeddingProvider } from "../services/embedding-provider.js";
import { getGraphMetadata, getProjectMetadata, listCodebaseCollections } from "../services/qdrant.js";
/** Quick infrastructure check for embedding in codebase_about */
async function getInfraStatusSummary() {
const lines = [];
const config = getEmbeddingConfig();
try {
if (QDRANT_MODE === "external") {
const endpoint = QDRANT_URL ?? `http://${QDRANT_HOST}:${QDRANT_PORT}`;
try {
await listCodebaseCollections(); // throws if unreachable
}
catch {
lines.push(`Infrastructure: ❌ Cannot reach external Qdrant at ${endpoint}`);
lines.push(" Check QDRANT_URL / QDRANT_HOST and make sure the server is running.");
return lines;
}
try {
const provider = await getEmbeddingProvider();
const health = await provider.healthCheck();
if (health.available) {
lines.push(`Infrastructure: ✅ External Qdrant reachable (${endpoint}), ${config.embeddingProvider} embeddings ready`);
}
else {
lines.push(`Infrastructure: ⚠️ External Qdrant reachable (${endpoint}), but ${config.embeddingProvider} embeddings not ready`);
}
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
lines.push(`Infrastructure: ❌ External Qdrant reachable (${endpoint}), but ${config.embeddingProvider} provider check failed: ${message}`);
}
}
else {
const docker = await isDockerAvailable();
if (!docker) {
lines.push("Infrastructure: ❌ Docker not available");
lines.push(" Install from https://docker.com and start Docker Desktop.");
return lines;
}
const qdrant = await isQdrantRunning();
try {
const provider = await getEmbeddingProvider();
const health = await provider.healthCheck();
if (qdrant && health.available) {
lines.push(`Infrastructure: ✅ All services running (Docker, Qdrant, ${config.embeddingProvider} embeddings)`);
}
else {
const missing = [];
if (!qdrant)
missing.push("Qdrant");
if (!health.available)
missing.push(`${config.embeddingProvider} embeddings`);
lines.push(`Infrastructure: ⚠️ Docker OK, but ${missing.join(" and ")} not running`);
lines.push(" Run codebase_index or codebase_health to auto-start services.");
}
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
lines.push(`Infrastructure: ❌ Docker OK, Qdrant ${qdrant ? "running" : "not running"}; ${config.embeddingProvider} provider check failed: ${message}`);
}
}
}
catch {
lines.push("Infrastructure: ❓ Could not check status");
}
return lines;
}
export async function handleManageTool(name, _args) {
switch (name) {
case "codebase_health": {
const config = getEmbeddingConfig();
const status = {
docker: false,
ollama: false,
qdrant: false,
ollamaModel: false,
qdrantImage: false,
ollamaImage: false,
};
const icon = (ok) => (ok ? "[OK]" : "[MISSING]");
const lines = [
"SocratiCode — Infrastructure Health Check:",
"",
`Qdrant mode: ${QDRANT_MODE}`,
];
if (QDRANT_MODE === "external") {
const endpoint = QDRANT_URL ?? `http://${QDRANT_HOST}:${QDRANT_PORT}`;
lines.push(`Qdrant endpoint: ${endpoint}`);
try {
await listCodebaseCollections();
status.qdrant = true;
lines.push(`${icon(true)} External Qdrant (${endpoint}): Reachable`);
}
catch {
lines.push(`${icon(false)} External Qdrant (${endpoint}): Unreachable — check QDRANT_URL / QDRANT_HOST`);
}
}
else {
// managed mode — Docker-managed container
const docker = await isDockerAvailable();
status.docker = docker;
lines.push(`${icon(status.docker)} Docker: ${status.docker ? "Running" : "Not found — install from https://docker.com"}`);
if (docker) {
const [qdrantRunning, qdrantImage] = await Promise.all([
isQdrantRunning(),
isQdrantImagePresent(),
]);
status.qdrant = qdrantRunning;
status.qdrantImage = qdrantImage;
lines.push(`${icon(status.qdrantImage)} Qdrant image: ${status.qdrantImage ? "Pulled" : "Not pulled — will be pulled on first index"}`);
lines.push(`${icon(status.qdrant)} Qdrant container: ${status.qdrant ? "Running" : "Not running — will be started on first index"}`);
}
}
// Embedding provider health check (works for ollama, openai, google)
lines.push("");
lines.push(`Embedding provider: ${config.embeddingProvider}`);
try {
const provider = await getEmbeddingProvider();
const health = await provider.healthCheck();
lines.push(...health.statusLines);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
lines.push(`${icon(false)} Embedding provider check failed: ${message}`);
}
lines.push("");
if (QDRANT_MODE === "external") {
lines.push("External Qdrant mode — Docker is not required for the vector database.");
}
else {
lines.push(status.docker
? "Docker is available. Qdrant container will be auto-managed."
: "Docker is required for Qdrant. Install from https://docker.com");
}
return lines.join("\n");
}
case "codebase_list_projects": {
try {
const collections = await listCodebaseCollections();
if (collections.length === 0) {
return "No projects have been indexed yet. Use codebase_index to index a project.";
}
// All four references below honour QDRANT_COLLECTION_PREFIX so this
// tool keeps working on shared Qdrant deployments where SocratiCode's
// collection names carry an instance-specific prefix.
const p = QDRANT_COLLECTION_PREFIX;
const codebasePrefix = `${p}codebase_`;
const graphPrefix = `${p}codegraph_`;
const codebaseCollections = collections.filter((c) => c.startsWith(codebasePrefix));
const graphCollections = collections.filter((c) => c.startsWith(graphPrefix));
const lines = ["Indexed projects:\n"];
for (const c of codebaseCollections) {
const projectId = c.slice(codebasePrefix.length);
const hasGraph = graphCollections.includes(`${graphPrefix}${projectId}`);
const metadata = await getProjectMetadata(c);
const pathInfo = metadata?.projectPath || "(path unknown — indexed before path tracking)";
lines.push(` - ${pathInfo}`);
lines.push(` Collection: ${c}`);
if (metadata?.lastIndexedAt) {
lines.push(` Last indexed: ${metadata.lastIndexedAt}`);
}
if (metadata?.filesIndexed !== undefined) {
if (metadata.indexingStatus === "in-progress") {
lines.push(` Files: ${metadata.filesIndexed}/${metadata.filesTotal} (INCOMPLETE — run codebase_index to resume)`);
}
else {
lines.push(` Files: ${metadata.filesIndexed}`);
}
}
if (hasGraph) {
const graphMeta = await getGraphMetadata(`${graphPrefix}${projectId}`);
if (graphMeta) {
lines.push(` Code graph: ${graphMeta.nodeCount} files, ${graphMeta.edgeCount} edges (built: ${graphMeta.lastBuiltAt})`);
}
else {
lines.push(` Code graph: present`);
}
}
else {
lines.push(` Code graph: not built`);
}
// Context artifacts
try {
const pathInfo2 = metadata?.projectPath;
if (pathInfo2) {
const artifactSummary = await getArtifactStatusSummary(pathInfo2);
if (artifactSummary) {
lines.push(` ${artifactSummary.lines[0]}`);
}
}
}
catch {
// non-critical
}
}
return lines.join("\n");
}
catch {
return "Could not connect to Qdrant. Run codebase_health to check infrastructure status.";
}
}
case "codebase_about": {
const infraStatus = await getInfraStatusSummary();
return [
"SocratiCode — Codebase intelligence MCP server",
"Hybrid semantic + keyword search, dependency graphs, context artifacts.",
"",
...infraStatus,
"",
"━━━ Quick Reference ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"",
"Indexing:",
" codebase_index — Index a project (background). Poll with codebase_status.",
" codebase_update — Incremental re-index (changed files only).",
" codebase_stop — Gracefully stop in-progress indexing.",
" codebase_remove — Delete a project's index.",
" codebase_prune — Inventory stored identities; delete one only with its confirmation token.",
" codebase_watch — Start/stop/status of live file watcher.",
"",
"Search:",
" codebase_search — Hybrid semantic + BM25 search. Use after indexing.",
" codebase_status — Check index status, progress, watcher state.",
"",
"Dependency Graph:",
" codebase_graph_build — Build AST-based dependency graph (background).",
" codebase_graph_query — Imports & dependents for a file.",
" codebase_graph_stats — Graph stats: files, edges, most connected, orphans.",
" codebase_graph_circular — Find circular dependencies.",
" codebase_graph_visualize — Mermaid diagram (default) or interactive HTML explorer (mode=\"interactive\"), colour-coded by language.",
" codebase_graph_status — Poll graph build progress.",
" codebase_graph_remove — Delete a project's graph.",
"",
"Context Artifacts:",
" codebase_context — List artifacts (.socraticodecontextartifacts.json).",
" codebase_context_search — Search schemas, API specs, configs, docs.",
" codebase_context_index — Re-index artifacts (usually automatic).",
" codebase_context_remove — Delete indexed artifacts.",
"",
"Management:",
" codebase_health — Check Docker, Qdrant, embeddings status.",
" codebase_list_projects — List all indexed projects.",
"",
"━━━ Typical Workflow ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"",
"1. codebase_index → poll codebase_status until complete.",
"2. codebase_search to find code. Search before reading files.",
"3. codebase_graph_query to explore dependencies.",
"4. codebase_context_search for schemas, API specs, configs.",
"",
`v${SOCRATICODE_VERSION} · © 2026 Giancarlo Erra — Altaire Limited · AGPL-3.0`,
"https://github.com/giancarloerra/socraticode",
].join("\n");
}
default:
return `Unknown tool: ${name}`;
}
}
//# sourceMappingURL=manage-tools.js.map