Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4981a79
Support dynamic index list in MultiIndexRunner
richhankins Feb 2, 2026
427a1bc
Add list_indexes tool to MCP server
richhankins Feb 2, 2026
6447099
Add delete_index tool to MCP server
richhankins Feb 2, 2026
0592040
Add delete_index Tool
richhankins Feb 2, 2026
2ea4cdf
Add index_repo tool to MCP server
richhankins Feb 2, 2026
35f06b1
Allow MCP server to start with zero indexes
richhankins Feb 2, 2026
94d91f1
Fix index_repo conditional and remove stale enum from MCP tools
richhankins Feb 6, 2026
4316cd4
Implement LayeredStore class combining local and remote indexes
richhankins Feb 6, 2026
e0fc0f7
Restore enum in Fixed Mode
richhankins Feb 6, 2026
975991f
Add --agent-managed CLI Flag
richhankins Feb 6, 2026
bbed4fc
Fix agentManaged config calculation in cmd-mcp.ts
richhankins Feb 6, 2026
b781515
Replace agent-managed with discovery mode
richhankins Feb 6, 2026
44d7569
Replace Agent-Managed with Discovery Mode
richhankins Feb 6, 2026
dcf2386
Add support for -i flags in Discovery mode with ReadOnlyLayeredStore
richhankins Feb 6, 2026
81b4dbf
Merge origin/main into dynamic-mcp-tools
richhankins Feb 6, 2026
c932344
Fix type errors from main branch merge
richhankins Feb 6, 2026
ba57527
Fix PR review comments: allow empty indexes, add error handling, fix …
richhankins Feb 6, 2026
2c37861
Fix refreshIndexList() to respect fixed mode allowlist
richhankins Feb 6, 2026
c9eed14
Make fixed mode's index list completely static
richhankins Feb 6, 2026
c9c4ea1
fix: restore clientUserAgent in DirectContext.import()
richhankins Feb 7, 2026
5c35f78
fix: prune stale clientCache entries in refreshIndexList()
richhankins Feb 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 39 additions & 23 deletions src/bin/cmd-mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FilesystemStore } from "../stores/filesystem.js";
import { runMCPServer } from "../clients/mcp-server.js";
import { parseIndexSpecs } from "../stores/index-spec.js";
import { CompositeStoreReader } from "../stores/composite.js";
import { ReadOnlyLayeredStore } from "../stores/read-only-layered-store.js";

// stdio subcommand (stdio-based MCP server for local clients like Claude Desktop)
const stdioCommand = new Command("stdio")
Expand All @@ -15,36 +16,44 @@ const stdioCommand = new Command("stdio")
"-i, --index <specs...>",
"Index spec(s): name, path:/path, or s3://bucket/key"
)
.option("--discovery", "Enable discovery mode (read-only, manage indexes via CLI)")
.option("--search-only", "Disable list_files/read_file tools (search only)")
.action(async (options) => {
try {
const indexSpecs: string[] | undefined = options.index;
const discoveryFlag = options.discovery;

let store;
let indexNames: string[];
let indexNames: string[] | undefined;
let discovery: boolean;

if (indexSpecs && indexSpecs.length > 0) {
// Parse index specs and create composite store
if (discoveryFlag && indexSpecs && indexSpecs.length > 0) {
// Discovery mode WITH remote indexes: merge local + remote
const specs = parseIndexSpecs(indexSpecs);
const remoteStore = await CompositeStoreReader.fromSpecs(specs);
const localStore = new FilesystemStore();
store = new ReadOnlyLayeredStore(localStore, remoteStore);
indexNames = undefined; // Discovery mode: no fixed list
discovery = true;
} else if (indexSpecs && indexSpecs.length > 0) {
// Fixed mode: use read-only CompositeStoreReader
const specs = parseIndexSpecs(indexSpecs);
store = await CompositeStoreReader.fromSpecs(specs);
indexNames = specs.map((s) => s.displayName);
store = await CompositeStoreReader.fromSpecs(specs);
discovery = false;
} else {
// No --index: use default store, list all indexes
// Discovery mode only: use FilesystemStore
store = new FilesystemStore();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch sets up discovery mode without requiring any indexes, but MultiIndexRunner.create() still throws when the store has zero valid indexes, so ctxc mcp ... can still fail to start on a fresh install. If discovery mode is intended to allow “start empty and index later”, consider aligning runner/server startup behavior with that expectation.

Severity: high

Other Locations
  • src/bin/cmd-mcp.ts:106

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

indexNames = await store.list();
if (indexNames.length === 0) {
console.error("Error: No indexes found.");
console.error("The MCP server requires at least one index to operate.");
console.error("Run 'ctxc index --help' to see how to create an index.");
process.exit(1);
}
indexNames = undefined;
discovery = true;
}

// Start MCP server (writes to stdout, reads from stdin)
await runMCPServer({
store,
indexNames,
searchOnly: options.searchOnly,
discovery,
});
} catch (error) {
// Write errors to stderr (stdout is for MCP protocol)
Expand All @@ -60,6 +69,7 @@ const httpCommand = new Command("http")
"-i, --index <specs...>",
"Index spec(s): name, path:/path, or s3://bucket/key"
)
.option("--discovery", "Enable discovery mode (read-only, manage indexes via CLI)")
.option("--port <number>", "Port to listen on", "3000")
.option("--host <host>", "Host to bind to", "localhost")
.option("--cors <origins>", "CORS origins (comma-separated, or '*' for any)")
Expand All @@ -72,26 +82,31 @@ const httpCommand = new Command("http")
.action(async (options) => {
try {
const indexSpecs: string[] | undefined = options.index;
const discoveryFlag = options.discovery;

let store;
let indexNames: string[] | undefined;
let discovery: boolean;

if (indexSpecs && indexSpecs.length > 0) {
// Parse index specs and create composite store
if (discoveryFlag && indexSpecs && indexSpecs.length > 0) {
// Discovery mode WITH remote indexes: merge local + remote
const specs = parseIndexSpecs(indexSpecs);
const remoteStore = await CompositeStoreReader.fromSpecs(specs);
const localStore = new FilesystemStore();
store = new ReadOnlyLayeredStore(localStore, remoteStore);
indexNames = undefined; // Discovery mode: no fixed list
discovery = true;
} else if (indexSpecs && indexSpecs.length > 0) {
// Fixed mode: use read-only CompositeStoreReader
const specs = parseIndexSpecs(indexSpecs);
store = await CompositeStoreReader.fromSpecs(specs);
indexNames = specs.map((s) => s.displayName);
store = await CompositeStoreReader.fromSpecs(specs);
discovery = false;
} else {
// No --index: use default store, serve all
// Discovery mode only: use FilesystemStore
store = new FilesystemStore();
const availableIndexes = await store.list();
if (availableIndexes.length === 0) {
console.error("Error: No indexes found.");
console.error("The MCP server requires at least one index to operate.");
console.error("Run 'ctxc index --help' to see how to create an index.");
process.exit(1);
}
indexNames = undefined;
discovery = true;
}

// Parse CORS option
Expand All @@ -112,6 +127,7 @@ const httpCommand = new Command("http")
store,
indexNames,
searchOnly: options.searchOnly,
discovery,
port: parseInt(options.port, 10),
host: options.host,
cors,
Expand Down
1 change: 0 additions & 1 deletion src/clients/cli-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ async function loadModel(
return new AugmentLanguageModel(modelName, {
apiKey: credentials.apiKey,
apiUrl: credentials.apiUrl,
clientUserAgent,
}) as unknown as LanguageModel;
}
default:
Expand Down
77 changes: 67 additions & 10 deletions src/clients/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,23 @@ import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import type { IndexStoreReader } from "../stores/types.js";
import type { IndexStoreReader, IndexStore } from "../stores/types.js";
import type { Source } from "../sources/types.js";
import { MultiIndexRunner } from "./multi-index-runner.js";
import { buildClientUserAgent, type MCPClientInfo } from "../core/utils.js";
import {
SEARCH_DESCRIPTION,
LIST_FILES_DESCRIPTION,
READ_FILE_DESCRIPTION,
withListIndexesReference,
withIndexList,
} from "./tool-descriptions.js";
/**
* Configuration for the MCP server.
*/
export interface MCPServerConfig {
/** Store to load indexes from */
store: IndexStoreReader;
/** Store to load indexes from (accepts both reader-only and full store) */
store: IndexStoreReader | IndexStore;
/**
* Index names to expose. If undefined, all indexes in the store are exposed.
*/
Expand All @@ -71,6 +73,13 @@ export interface MCPServerConfig {
* @default "0.1.0"
*/
version?: string;
/**
* Discovery mode flag.
* When true: use withListIndexesReference (no enum in schemas), dynamic index list
* When false/undefined: use withIndexList (include enum in schemas), static index list
* @default false
*/
discovery?: boolean;
}
/**
* Create an MCP server instance.
Expand Down Expand Up @@ -104,6 +113,7 @@ export async function createMCPServer(
searchOnly: config.searchOnly,
clientUserAgent,
});

const { indexNames, indexes } = runner;
const searchOnly = !runner.hasFileOperations();
// Format index list for tool descriptions
Expand Down Expand Up @@ -146,13 +156,35 @@ export async function createMCPServer(
required?: string[];
};
};
// Tool descriptions with available indexes (from shared module)
const searchDescription = withIndexList(SEARCH_DESCRIPTION, indexListStr);
const listFilesDescription = withIndexList(LIST_FILES_DESCRIPTION, indexListStr);
const readFileDescription = withIndexList(READ_FILE_DESCRIPTION, indexListStr);

// Tool descriptions: use enum in fixed mode, reference in discovery mode
let searchDescription: string;
let listFilesDescription: string;
let readFileDescription: string;

if (config.discovery) {
// Discovery mode: use reference to list_indexes (no enum)
searchDescription = withListIndexesReference(SEARCH_DESCRIPTION);
listFilesDescription = withListIndexesReference(LIST_FILES_DESCRIPTION);
readFileDescription = withListIndexesReference(READ_FILE_DESCRIPTION);
} else {
// Fixed mode: include enum with index list
searchDescription = withIndexList(SEARCH_DESCRIPTION, indexListStr);
listFilesDescription = withIndexList(LIST_FILES_DESCRIPTION, indexListStr);
readFileDescription = withIndexList(READ_FILE_DESCRIPTION, indexListStr);
}
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools: Tool[] = [
{
name: "list_indexes",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new list_indexes tool + the discovery-vs-fixed behavior (no enum vs enum for index_name) doesn’t appear to be covered by existing MCP server tests; a focused unit test here could help prevent regressions.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

description: "List all available indexes with their metadata. Call this to discover what indexes are available before using search, list_files, or read_file tools.",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "search",
description: searchDescription,
Expand All @@ -162,7 +194,7 @@ export async function createMCPServer(
index_name: {
type: "string",
description: "Name of the index to search.",
enum: indexNames,
...(config.discovery ? {} : { enum: runner.indexes.map(i => i.name) }),
},
query: {
type: "string",
Expand All @@ -189,7 +221,7 @@ export async function createMCPServer(
index_name: {
type: "string",
description: "Name of the index.",
enum: indexNames,
...(config.discovery ? {} : { enum: runner.indexes.map(i => i.name) }),
},
directory: {
type: "string",
Expand Down Expand Up @@ -220,7 +252,7 @@ export async function createMCPServer(
index_name: {
type: "string",
description: "Name of the index.",
enum: indexNames,
...(config.discovery ? {} : { enum: runner.indexes.map(i => i.name) }),
},
path: {
type: "string",
Expand Down Expand Up @@ -261,6 +293,31 @@ export async function createMCPServer(
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;

// Handle list_indexes separately (no index_name required)
if (name === "list_indexes") {
try {
await runner.refreshIndexList();
const { indexes } = runner;
if (indexes.length === 0) {
return {
content: [{ type: "text", text: "No indexes available. Use `ctxc index` CLI to create one." }],
};
}
const lines = indexes.map((i) =>
`- ${i.name} (${i.type}://${i.identifier}) - synced ${i.syncedAt}`
);
return {
content: [{ type: "text", text: `Available indexes:\n${lines.join("\n")}` }],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error listing indexes: ${error}` }],
isError: true,
};
}
}

try {
const indexName = args?.index_name as string;
const client = await runner.getClient(indexName);
Expand Down
Loading