@takara-ai/miru-code 1.1.3 → 1.2.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.
@@ -6,6 +6,11 @@ const CODEX_MCP_BLOCK = `[mcp_servers.miru]
6
6
  command = "bunx"
7
7
  args = ["@takara-ai/miru-code"]
8
8
  `;
9
+ /** Same Codex block with `--benchmark` preserved across reinstall. */
10
+ const CODEX_MCP_BLOCK_BENCHMARK = `[mcp_servers.miru]
11
+ command = "bunx"
12
+ args = ["@takara-ai/miru-code", "--benchmark"]
13
+ `;
9
14
 
10
15
  /** Strip line and block comments so JSONC configs can be parsed. */
11
16
  export function stripJsonComments(text: string): string {
@@ -487,12 +492,19 @@ export async function mergeTomlBlock(path: string): Promise<InstallAction> {
487
492
  const existed = await Bun.file(path).exists();
488
493
  const existing = existed ? await Bun.file(path).text() : "";
489
494
 
490
- if (existing.includes(CODEX_MCP_BLOCK.trim())) {
495
+ const defaultBlock = CODEX_MCP_BLOCK.trim();
496
+ const benchmarkBlock = CODEX_MCP_BLOCK_BENCHMARK.trim();
497
+ if (existing.includes(defaultBlock) || existing.includes(benchmarkBlock)) {
491
498
  return "unchanged";
492
499
  }
493
500
 
501
+ const preserveBenchmark =
502
+ existing.includes(CODEX_MCP_HEADER) &&
503
+ (existing.includes('"--benchmark"') || existing.includes("'--benchmark'"));
504
+ const block = preserveBenchmark ? CODEX_MCP_BLOCK_BENCHMARK : CODEX_MCP_BLOCK;
505
+
494
506
  const base = stripTomlSection(existing, CODEX_MCP_HEADER).replace(/\n+$/, "");
495
- const next = base.length > 0 ? `${base}\n\n${CODEX_MCP_BLOCK}` : CODEX_MCP_BLOCK;
507
+ const next = base.length > 0 ? `${base}\n\n${block}` : block;
496
508
  await Bun.write(path, next.endsWith("\n") ? next : `${next}\n`);
497
509
  return existed ? "updated" : "created";
498
510
  }
@@ -3,28 +3,12 @@
3
3
  * Installed to ~/.config/opencode/plugins/miru-search-guard.ts
4
4
  */
5
5
 
6
- const REDIRECT = "Use Miru MCP search (repo = project root) instead of grep/glob/bash exploration.";
6
+ const REDIRECT =
7
+ "Use Miru MCP (repo = project root): `locate` for exact literals, `search` for meaning-based questions — not grep/glob/bash exploration.";
7
8
 
8
9
  const GREP_TOOLS = new Set(["grep", "glob", "codesearch", "codebase_search", "search"]);
9
10
  const SHELL_TOOLS = new Set(["bash", "shell", "sh"]);
10
11
 
11
- function isLiteral(pattern: string): boolean {
12
- const trimmed = pattern.trim();
13
- if (!trimmed) {
14
- return false;
15
- }
16
- if (/^[`'"][^`'"]+[`'"]$/.test(trimmed)) {
17
- return true;
18
- }
19
- if (/^[A-Z][A-Z0-9_]{2,}$/.test(trimmed)) {
20
- return true;
21
- }
22
- if (/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed) && trimmed.length <= 48) {
23
- return true;
24
- }
25
- return false;
26
- }
27
-
28
12
  function isExplorationShell(command: string): boolean {
29
13
  const cmd = command.trim();
30
14
  if (!cmd) {
@@ -43,9 +27,11 @@ function shouldBlock(tool: string, args: Record<string, unknown>): boolean {
43
27
  if (name.includes("miru")) {
44
28
  return false;
45
29
  }
30
+ if (name === "glob") {
31
+ return true;
32
+ }
46
33
  if (GREP_TOOLS.has(name)) {
47
- const pattern = String(args.pattern ?? args.query ?? args.regex ?? args.needle ?? "");
48
- return !isLiteral(pattern);
34
+ return true;
49
35
  }
50
36
  if (SHELL_TOOLS.has(name)) {
51
37
  const command = String(args.command ?? args.cmd ?? "");
@@ -2,7 +2,7 @@ import { SEARCH_GUARD_EXPAND_HINT } from "../search-policy.ts";
2
2
 
3
3
  const REDIRECT_PREFIX =
4
4
  "Use Miru MCP instead of built-in search tools (pass project root as `repo`). " +
5
- "Grep/Glob/Shell are only for exact literal confirmation or non-code tasks.";
5
+ "Prefer Miru `locate` for exact literals and `search` for meaning-based questions.";
6
6
 
7
7
  const MIRU_TOOL_RE = /miru/i;
8
8
 
@@ -121,6 +121,9 @@ export function searchGuardBlockReason(
121
121
  toolInput: Record<string, unknown>,
122
122
  ): string {
123
123
  const query = suggestedMiruQuery(toolName, toolInput);
124
+ if (GREP_TOOL_RE.test(toolName) && isLiteralGrepPattern(grepPatternFromInput(toolInput))) {
125
+ return `${REDIRECT_PREFIX} Try Miru \`locate\` with literal "${query.replace(/^['"`]|['"`]$/g, "")}". ${SEARCH_GUARD_EXPAND_HINT}`;
126
+ }
124
127
  return `${REDIRECT_PREFIX} Try Miru \`search\` with query "${query}". ${SEARCH_GUARD_EXPAND_HINT}`;
125
128
  }
126
129
 
@@ -166,10 +169,6 @@ export function evaluateSearchGuard(payload: HookPayload): GuardDecision {
166
169
  }
167
170
 
168
171
  if (GREP_TOOL_RE.test(toolName)) {
169
- const pattern = grepPatternFromInput(toolInput);
170
- if (isLiteralGrepPattern(pattern)) {
171
- return { block: false, reason: "" };
172
- }
173
172
  return { block: true, reason: searchGuardBlockReason(toolName, toolInput) };
174
173
  }
175
174
 
@@ -1,6 +1,7 @@
1
1
  import { unlink } from "node:fs/promises";
2
2
  import { loadAgentTemplate } from "../agents.ts";
3
- import { brandTitle, dim, divider, green, hint, success, writeStdout } from "../cli-ui.ts";
3
+ import { clearBenchmarkHistory } from "../benchmark/history.ts";
4
+ import { brandTitle, dim, divider, green, hint, info, success, writeStdout } from "../cli-ui.ts";
4
5
  import { ensureCredentials } from "../setup.ts";
5
6
  import {
6
7
  AGENT_TARGETS,
@@ -10,6 +11,7 @@ import {
10
11
  type InstallMode,
11
12
  isAgentDetected,
12
13
  } from "./agents.ts";
14
+ import { withPreservedBenchmarkFlag } from "./benchmark-mode.ts";
13
15
  import {
14
16
  mergeJsonMember,
15
17
  mergeTomlBlock,
@@ -17,6 +19,7 @@ import {
17
19
  removeMarked,
18
20
  removeTomlBlock,
19
21
  replaceOrAppendMarked,
22
+ stripJsonComments,
20
23
  } from "./config.ts";
21
24
  import { mergeHooks, removeHooks } from "./hooks/install.ts";
22
25
  import { promptConfirm, promptMultiSelect, requireInteractiveTerminal } from "./prompt.ts";
@@ -54,6 +57,33 @@ const ACTION_ICON: Partial<Record<InstallAction, string>> = {
54
57
  error: dim("✗"),
55
58
  };
56
59
 
60
+ async function readExistingJsonMcpEntry(
61
+ path: string,
62
+ sectionKey: string,
63
+ memberKey: string,
64
+ ): Promise<Record<string, unknown> | null> {
65
+ if (!(await Bun.file(path).exists())) {
66
+ return null;
67
+ }
68
+ try {
69
+ const parsed = JSON.parse(stripJsonComments(await Bun.file(path).text())) as unknown;
70
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
71
+ return null;
72
+ }
73
+ const section = (parsed as Record<string, unknown>)[sectionKey];
74
+ if (!section || typeof section !== "object" || Array.isArray(section)) {
75
+ return null;
76
+ }
77
+ const entry = (section as Record<string, unknown>)[memberKey];
78
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
79
+ return null;
80
+ }
81
+ return entry as Record<string, unknown>;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
57
87
  async function applyMcp(agent: AgentTarget, mode: InstallMode): Promise<WriteResult | null> {
58
88
  const mcp = agent.mcp;
59
89
  if (!mcp) {
@@ -66,10 +96,14 @@ async function applyMcp(agent: AgentTarget, mode: InstallMode): Promise<WriteRes
66
96
  return { path: mcp.path, action };
67
97
  }
68
98
 
69
- const action =
70
- mode === "install"
71
- ? await mergeJsonMember(mcp.path, mcp.key, mcp.memberKey, mcp.entry)
72
- : await removeJsonMember(mcp.path, mcp.key, mcp.memberKey);
99
+ if (mode !== "install") {
100
+ const action = await removeJsonMember(mcp.path, mcp.key, mcp.memberKey);
101
+ return { path: mcp.path, action };
102
+ }
103
+
104
+ const existing = await readExistingJsonMcpEntry(mcp.path, mcp.key, mcp.memberKey);
105
+ const entry = withPreservedBenchmarkFlag(mcp.entry, existing);
106
+ const action = await mergeJsonMember(mcp.path, mcp.key, mcp.memberKey, entry);
73
107
  return { path: mcp.path, action };
74
108
  }
75
109
 
@@ -235,6 +269,18 @@ async function apply(
235
269
  writeStdout("");
236
270
  }
237
271
 
272
+ /** Local Miru state cleaned up during uninstall (global, not per-agent). */
273
+ export async function removeUninstallLocalData(): Promise<{
274
+ benchmarkHistoryCleared: boolean;
275
+ benchmarkHistoryPath: string;
276
+ }> {
277
+ const result = await clearBenchmarkHistory();
278
+ return {
279
+ benchmarkHistoryCleared: result.cleared,
280
+ benchmarkHistoryPath: result.path,
281
+ };
282
+ }
283
+
238
284
  export async function runInstaller(mode: InstallMode): Promise<void> {
239
285
  const install = mode === "install";
240
286
  requireInteractiveTerminal(`miru ${mode}`);
@@ -297,6 +343,13 @@ export async function runInstaller(mode: InstallMode): Promise<void> {
297
343
 
298
344
  await apply(mode, chosenAgents, chosenIntegrations);
299
345
 
346
+ if (!install) {
347
+ const local = await removeUninstallLocalData();
348
+ if (local.benchmarkHistoryCleared) {
349
+ info(`Removed benchmark report ${local.benchmarkHistoryPath}`);
350
+ }
351
+ }
352
+
300
353
  success(install ? "Done. Restart agents to apply changes." : "Done. Configuration removed.");
301
354
  writeStdout("");
302
355
  }
@@ -18,7 +18,7 @@ export function buildSearchPolicyTable(native: NativeToolNames): string {
18
18
  | Same file, more context | Miru MCP \`expand\` on \`truncated: true\` | Re-search, ${native.read} whole file |
19
19
  | Similar code elsewhere | Miru MCP \`find_related\` | ${native.grep} chains |
20
20
  | Search docs or config | Miru \`search\` | ${native.grep} README paths |
21
- | Exact literal string in a file? | ${native.grep} | Miru |
21
+ | Exact literal string in a file? | Miru MCP \`locate\` | ${native.grep}, Miru \`search\` |
22
22
  | Edit a known file:line | ${native.read} (after Miru found it) | ${native.read}-before-search |`;
23
23
  }
24
24
 
@@ -29,6 +29,7 @@ ${SNIPPET_GUIDANCE}
29
29
 
30
30
  Use Miru MCP tools:
31
31
  - \`search\` — one call per question; pass project root as \`repo\`
32
+ - \`locate\` — exact substring (env var, symbol, error code); prefer \`mode=count\` or \`locations\`
32
33
  - \`expand\` — more context in the same file when \`truncated: true\` (\`file_path\` + \`anchor_line\`)
33
34
  - \`find_related\` — similar code in other files (hits may also be snippets; use \`expand\` if truncated)
34
35
 
@@ -38,7 +39,6 @@ Stop rules:
38
39
  - ${native.read} is for editing a path Miru already gave you, not for exploration
39
40
 
40
41
  Native tools are allowed ONLY when:
41
- - confirming an exact literal string (env var name, error code, quoted text)
42
42
  - reading a file you already located via Miru, to edit it
43
43
  - searching outside the indexed repo
44
44
 
@@ -49,15 +49,17 @@ export function buildMcpWorkflow(native: NativeToolNames): string {
49
49
  return `### MCP workflow
50
50
 
51
51
  1. Call \`search\` with \`repo\` set to the project root (local path or https:// git URL).
52
- 2. If a hit has \`truncated: true\`, call \`expand\` with \`file_path\` and \`anchor_line\`.
53
- 3. Use \`find_related\` to trace similar code in other files — not for more context in the same file.
54
- 4. ${native.read} via \`absolute_path\` only when editing or when \`expand\` still lacks context.`;
52
+ 2. For exact literals, call \`locate\` (prefer \`mode=count\` or \`locations\`).
53
+ 3. If a hit has \`truncated: true\`, call \`expand\` with \`file_path\` and \`anchor_line\`.
54
+ 4. Use \`find_related\` to trace similar code in other files — not for more context in the same file.
55
+ 5. ${native.read} via \`absolute_path\` only when editing or when \`expand\` still lacks context.`;
55
56
  }
56
57
 
57
58
  const CLI_FALLBACK = `### CLI fallback (no MCP in this session)
58
59
 
59
60
  \`\`\`bash
60
61
  miru search "authentication flow" .
62
+ miru locate DATABASE_URL . --mode locations
61
63
  miru expand src/auth.ts 42 .
62
64
  miru find-related src/auth.ts 42 .
63
65
  \`\`\`
@@ -65,7 +67,7 @@ miru find-related src/auth.ts 42 .
65
67
  If \`miru\` is not on \`$PATH\`, use \`bunx @takara-ai/miru-code\`.`;
66
68
 
67
69
  export function buildSubagentBody(native: NativeToolNames): string {
68
- return `When Miru MCP is available, use MCP \`search\`, \`expand\`, and \`find_related\` — not ${native.explorationDenied} for exploration.
70
+ return `When Miru MCP is available, use MCP \`search\`, \`locate\`, \`expand\`, and \`find_related\` — not ${native.explorationDenied} for exploration.
69
71
 
70
72
  ${buildSearchPolicyBody(native)}
71
73
 
@@ -108,7 +110,7 @@ alwaysApply: true
108
110
 
109
111
  ${SEARCH_POLICY_BODY}
110
112
 
111
- When Miru MCP is connected, call \`search\` once per question. On \`truncated: true\`, call \`expand\` with \`file_path\` and \`anchor_line\`. Never use Cursor SemanticSearch or Grep for codebase questions unless confirming an exact literal.`;
113
+ When Miru MCP is connected, call \`search\` once per question. On \`truncated: true\`, call \`expand\` with \`file_path\` and \`anchor_line\`. Use \`locate\` for exact literals — not Grep. Never use Cursor SemanticSearch for codebase questions.`;
112
114
 
113
115
  export const SUBAGENT_BODY = buildSubagentBody(DEFAULT_NATIVE_TOOLS);
114
116
 
@@ -119,15 +121,28 @@ export const MCP_SERVER_INSTRUCTIONS =
119
121
  "Workflow: (1) `search` once per question — do NOT re-search with paraphrases. " +
120
122
  "(2) On `truncated: true`, call `expand` with `file_path` and `anchor_line` — do NOT read the whole file. " +
121
123
  "(3) `find_related` traces similar code in other files, not more context in the same file. " +
124
+ "(4) `locate` finds exact substrings (env vars, symbols, error codes) — prefer over Grep; use mode=count or locations when possible. " +
122
125
  "Always pass the project root as `repo`. Local repos return `absolute_path` on each hit — use Read only to edit. " +
123
- "Native grep is only for confirming a known exact literal (env var name, quoted string) or files outside the indexed repo.";
126
+ "Native Grep/Glob only outside the indexed repo or for non-code tasks.";
127
+
128
+ export const MCP_BENCHMARK_SERVER_INSTRUCTIONS =
129
+ `${MCP_SERVER_INSTRUCTIONS} ` +
130
+ "BENCHMARK MODE: each `search` and `locate` includes compact `benchmark` {save_pct,miru_tok,grep_tok,saved_tok,rank1}. " +
131
+ "Call `read_benchmark` for cumulative totals across both. Do not narrate benchmark stats unless the user asks. " +
132
+ "To leave benchmark mode, tell the user to run `miru benchmark off` and restart the agent.";
124
133
 
125
134
  export const MCP_SEARCH_TOOL_DESCRIPTION =
126
135
  "Your default search for all code search queries in this indexed repo — the best, fastest, and cheapest way to find code; better than any other tool. " +
127
136
  "Returns compact snippets (~±15 lines). One call per question is usually enough. " +
128
- "Built-in grep is only when you already have an exact literal string to confirm. " +
137
+ "For exact literals (env vars, symbols, error codes), use `locate` instead. " +
129
138
  "When a hit has `truncated: true`, call `expand` with `file_path` and `anchor_line` — not re-search or Read.";
130
139
 
140
+ export const MCP_LOCATE_TOOL_DESCRIPTION =
141
+ "Exact substring locator over the Miru index. Use for known literals (env vars, symbols, error codes, quoted text) — not meaning-based questions (`search`). " +
142
+ "Returns ALL matches by default as compact {n,files,hits} — do NOT fall back to Grep/rg when n is large. " +
143
+ "Prefer mode=locations (or count for totals only); use lines when you need matching line text. " +
144
+ "Optional `limit` only if you intentionally want a sample. In benchmark mode, includes compact token savings vs agent Grep.";
145
+
131
146
  export const MCP_EXPAND_TOOL_DESCRIPTION =
132
147
  "More context in the SAME file as a search hit. Pass `file_path` + `anchor_line` from the hit; " +
133
148
  "returns adjacent indexed chunks. Use when `truncated: true` — NOT for similar code in other files (use find_related). " +
@@ -137,5 +152,9 @@ export const MCP_FIND_RELATED_TOOL_DESCRIPTION =
137
152
  "Find code similar to a file:line in OTHER parts of the codebase. Results may be snippets; use `expand` when `truncated: true`. " +
138
153
  "For more context in the same file, use `expand` instead.";
139
154
 
155
+ export const MCP_READ_BENCHMARK_TOOL_DESCRIPTION =
156
+ "Cumulative Miru vs Grep token savings from saved `search` and `locate` calls. Returns compact totals {n,saved,save_pct,miru,grep}. " +
157
+ "Do not call unless the user asks about savings.";
158
+
140
159
  export const SEARCH_GUARD_EXPAND_HINT =
141
160
  "If a hit has `truncated: true`, call `expand` with `file_path` and `anchor_line` — do not re-search or read the whole file.";
package/src/literal.ts ADDED
@@ -0,0 +1,139 @@
1
+ /** Exact literal location over already-indexed Miru chunks (no disk re-walk). */
2
+
3
+ import type { Chunk } from "./types.ts";
4
+
5
+ export type LiteralMode = "count" | "locations" | "lines";
6
+
7
+ export interface LiteralHit {
8
+ file_path: string;
9
+ line: number;
10
+ /** Present only in `lines` mode. */
11
+ text?: string;
12
+ }
13
+
14
+ export interface LiteralLocateResult {
15
+ literal: string;
16
+ mode: LiteralMode;
17
+ /** Total unique file:line matches. */
18
+ n: number;
19
+ /** Unique files with at least one match. */
20
+ files: number;
21
+ /** True only when an explicit `limit` cut the hit list short. */
22
+ truncated: boolean;
23
+ hits: LiteralHit[];
24
+ }
25
+
26
+ export interface LiteralLocateOptions {
27
+ mode?: LiteralMode;
28
+ /**
29
+ * Optional cap on returned hits (ignored for `count`).
30
+ * Omit for all matches — agents must not fall back to Grep when truncated.
31
+ */
32
+ limit?: number;
33
+ ignore_case?: boolean;
34
+ }
35
+
36
+ function resolveLimit(limit: number | undefined): number | null {
37
+ if (limit == null || !Number.isFinite(limit)) {
38
+ return null;
39
+ }
40
+ return Math.max(1, Math.floor(limit));
41
+ }
42
+
43
+ function includesLiteral(haystack: string, needle: string, ignoreCase: boolean): boolean {
44
+ if (!ignoreCase) {
45
+ return haystack.includes(needle);
46
+ }
47
+ return haystack.toLowerCase().includes(needle.toLowerCase());
48
+ }
49
+
50
+ /**
51
+ * Scan indexed chunks for an exact substring. Dedupes by file:line when chunks overlap.
52
+ * Only covers indexed content (same corpus as `search` / `expand`).
53
+ * Returns every match unless `limit` is set.
54
+ */
55
+ export function locateLiteral(
56
+ chunks: readonly Chunk[],
57
+ literal: string,
58
+ options: LiteralLocateOptions = {},
59
+ ): LiteralLocateResult {
60
+ const needle = literal;
61
+ const mode = options.mode ?? "lines";
62
+ const ignoreCase = options.ignore_case === true;
63
+ const limit = resolveLimit(options.limit);
64
+
65
+ if (!needle) {
66
+ return { literal: needle, mode, n: 0, files: 0, truncated: false, hits: [] };
67
+ }
68
+
69
+ const seen = new Set<string>();
70
+ const fileSet = new Set<string>();
71
+ const hits: LiteralHit[] = [];
72
+ let n = 0;
73
+
74
+ for (const chunk of chunks) {
75
+ if (!includesLiteral(chunk.content, needle, ignoreCase)) {
76
+ continue;
77
+ }
78
+ const lines = chunk.content.split("\n");
79
+ for (let i = 0; i < lines.length; i++) {
80
+ const lineText = lines[i] ?? "";
81
+ if (!includesLiteral(lineText, needle, ignoreCase)) {
82
+ continue;
83
+ }
84
+ const line = chunk.start_line + i;
85
+ const key = `${chunk.file_path}:${line}`;
86
+ if (seen.has(key)) {
87
+ continue;
88
+ }
89
+ seen.add(key);
90
+ fileSet.add(chunk.file_path);
91
+ n += 1;
92
+ if (mode === "count") {
93
+ continue;
94
+ }
95
+ if (limit != null && hits.length >= limit) {
96
+ continue;
97
+ }
98
+ const hit: LiteralHit = { file_path: chunk.file_path, line };
99
+ if (mode === "lines") {
100
+ hit.text = lineText;
101
+ }
102
+ hits.push(hit);
103
+ }
104
+ }
105
+
106
+ return {
107
+ literal: needle,
108
+ mode,
109
+ n,
110
+ files: fileSet.size,
111
+ truncated: mode !== "count" && limit != null && n > hits.length,
112
+ hits: mode === "count" ? [] : hits,
113
+ };
114
+ }
115
+
116
+ /** Compact agent-facing JSON (minimal keys). */
117
+ export function formatLiteralLocate(result: LiteralLocateResult): Record<string, unknown> {
118
+ const base: Record<string, unknown> = {
119
+ literal: result.literal,
120
+ mode: result.mode,
121
+ n: result.n,
122
+ files: result.files,
123
+ };
124
+
125
+ if (result.mode === "count") {
126
+ return base;
127
+ }
128
+
129
+ if (result.truncated) {
130
+ base.truncated = true;
131
+ }
132
+ base.hits = result.hits.map((hit) => {
133
+ if (hit.text !== undefined) {
134
+ return { f: hit.file_path, l: hit.line, t: hit.text };
135
+ }
136
+ return { f: hit.file_path, l: hit.line };
137
+ });
138
+ return base;
139
+ }
@@ -4,7 +4,7 @@ import { walkFiles } from "../index/file-walker.ts";
4
4
  import { getExtensions } from "../index/files.ts";
5
5
  import { normalizeRelativePath, relativePathFromRoot } from "../index/incremental.ts";
6
6
  import { MiruIndex } from "../miru-index.ts";
7
- import type { ContentType } from "../types.ts";
7
+ import { type ContentType, defaultContentTypes } from "../types.ts";
8
8
  import {
9
9
  computeSourceCacheKey,
10
10
  isAllowedRepoSource,
@@ -71,7 +71,7 @@ export class IndexCache {
71
71
  private readonly entries = new Map<string, CacheEntry>();
72
72
  readonly watchers = new Map<string, WatcherHandle>();
73
73
 
74
- constructor(content: ContentType[] = ["code"], defaultRef: string | null = null) {
74
+ constructor(content: ContentType[] = defaultContentTypes(), defaultRef: string | null = null) {
75
75
  this.content = content;
76
76
  this.defaultRef = defaultRef;
77
77
  }
package/src/mcp/serve.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ContentType } from "../types.ts";
1
+ import { type ContentType, defaultContentTypes } from "../types.ts";
2
2
  import { IndexCache } from "./index-cache.ts";
3
3
  import { createMcpServer } from "./server.ts";
4
4
  import { StdioTransport } from "./stdio.ts";
@@ -6,9 +6,10 @@ import { StdioTransport } from "./stdio.ts";
6
6
  export async function serveMcp(options: {
7
7
  ref?: string | null;
8
8
  content?: ContentType[];
9
+ benchmark?: boolean;
9
10
  }): Promise<void> {
10
- const cache = new IndexCache(options.content ?? ["code"], options.ref ?? null);
11
- const server = createMcpServer(cache);
11
+ const cache = new IndexCache(options.content ?? defaultContentTypes(), options.ref ?? null);
12
+ const server = createMcpServer(cache, { benchmark: options.benchmark ?? false });
12
13
  const transport = new StdioTransport();
13
14
  await server.connect(transport);
14
15
  }