Skip to content

Commit d3ef073

Browse files
committed
mcp server++
1 parent d686576 commit d3ef073

6 files changed

Lines changed: 441 additions & 135 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ target/
1313

1414
node_modules/
1515
index-output/
16+
reposerver/.reposerver-state/

public/output.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2959,6 +2959,9 @@
29592959
.text-yellow-500 {
29602960
color: var(--color-yellow-500);
29612961
}
2962+
.lowercase {
2963+
text-transform: lowercase;
2964+
}
29622965
.uppercase {
29632966
text-transform: uppercase;
29642967
}

src/mcp/server.rs

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -81,49 +81,51 @@ async fn mcp_info() -> impl IntoResponse {
8181
async fn mcp_docs() -> impl IntoResponse {
8282
let payload = ApiResponse::success(json!({
8383
"dsl": {
84-
"semantics": "AND-first",
85-
"or_support": "For OR semantics, issue multiple search calls and merge/deduplicate client-side.",
86-
"wildcards": "Plain search terms do not support wildcard matching. Use regex mode for pattern matching.",
87-
"regex": "Use regex:<pattern> to enable regex matching.",
84+
"semantics": "Structured search payload",
85+
"or_support": "Use structured any_terms:[...] for OR semantics. all_terms are ANDed.",
86+
"wildcards": "Use path/file as glob-like filters. Plain terms are literal term matches.",
87+
"regex": "Use regex field to enable regex content matching.",
8888
"path_search_behavior": "path_search requires a non-empty query and is for fuzzy path matching only.",
8989
"file_list_behavior": "file_list enumerates directories and files with optional recursive depth and limit.",
9090
"recency_workflow": "For recent or older change questions: repositories -> repo_branches -> search by branch and compare indexed_at or is_live.",
91-
"filters": [
92-
"repo:<name>",
93-
"branch:<name>",
94-
"lang:<language>",
95-
"path:<glob>",
96-
"file:<glob>",
97-
"regex:<pattern>",
98-
"case:yes|no|auto",
99-
"historical:yes|no"
91+
"search_fields": [
92+
"repo: string",
93+
"branch: string",
94+
"lang: string",
95+
"path: glob-like string",
96+
"file: glob-like string",
97+
"regex: string",
98+
"case: yes|no|auto",
99+
"historical: boolean",
100+
"all_terms: string[] (AND)",
101+
"any_terms: string[] (OR)"
100102
],
101103
"troubleshooting": [
102104
"No results with repo filter: call repositories and use exact repo key.",
103105
"No branch results: call repo_branches and use exact branch name.",
104-
"Need OR behavior: run multiple search calls and merge client-side.",
106+
"Need OR behavior: place alternatives in any_terms:[\"termA\",\"termB\"].",
105107
"Need regex matching: use regex:<pattern> instead of wildcard plain terms.",
106108
"Need directory listing: use file_list instead of path_search with empty query."
107109
]
108110
},
109111
"cookbook": [
110112
"1) repositories(limit=20) to discover repo keys",
111113
"2) repo_branches(repo) to discover branch names and freshness",
112-
"3) search(query='repo:<repo> branch:<branch> <term>') for scoped search",
113-
"4) search with historical:yes for older snapshots",
114-
"5) search with regex:<pattern> for pattern matching",
114+
"3) search({repo, branch, all_terms:[\"term\"]}) for scoped search",
115+
"4) search({repo, branch, historical:true, all_terms:[\"term\"]}) for older snapshots",
116+
"5) search({repo, regex:\"pattern\"}) for regex matching",
115117
"6) file_list(repo, branch, path, depth, limit) for enumeration",
116118
"7) path_search(repo, branch, query) for fuzzy path lookup",
117119
"8) file_content(repo, branch, path) for raw source text",
118120
"9) symbol_insights(params) for definitions and references",
119-
"10) OR behavior: send multiple search calls or one batch search with queries[]",
121+
"10) OR behavior: search({repo, any_terms:[\"termA\",\"termB\"], dedupe:\"repo_path_line\"})",
120122
"11) For no results, broaden filters and retry per branch"
121123
]
122124
}));
123125
(StatusCode::OK, Json(payload))
124126
}
125127

126-
#[derive(Debug, Deserialize)]
128+
#[derive(Debug, Deserialize, Serialize)]
127129
struct JsonRpcRequest {
128130
jsonrpc: Option<String>,
129131
id: Option<Value>,
@@ -155,6 +157,17 @@ struct ToolCallParams {
155157
}
156158

157159
async fn mcp_rpc(Json(req): Json<JsonRpcRequest>) -> Response {
160+
let raw_request =
161+
serde_json::to_string(&req).unwrap_or_else(|_| "<serialize_error>".to_string());
162+
tracing::trace!(
163+
target: "pointer::mcp_rpc",
164+
method = %req.method,
165+
id = ?req.id,
166+
jsonrpc = ?req.jsonrpc,
167+
raw = %raw_request,
168+
"mcp rpc request"
169+
);
170+
158171
if req.jsonrpc.as_deref() != Some("2.0") {
159172
return jsonrpc_error(req.id, -32600, "jsonrpc must be \"2.0\"");
160173
}
@@ -175,7 +188,7 @@ async fn mcp_rpc(Json(req): Json<JsonRpcRequest>) -> Response {
175188
"name": "pointer-mcp",
176189
"version": env!("CARGO_PKG_VERSION"),
177190
},
178-
"instructions": "Use tools to query indexed code and symbol information. Operational flow: repositories -> repo_branches -> file_list/path_search -> file_content/search/symbol_insights. For recency/version questions like 'recent change', call repo_branches first, then run search repeatedly with explicit branch filters (e.g. branch:main, branch:release/*), compare branch-level results and indexed_at/is_live metadata, and add historical:yes when historical snapshots should be included. DSL behavior is AND-first; do not rely on OR in a single query. For OR intent, issue multiple search calls (one query per alternative) and merge/deduplicate client-side. Plain terms do not support wildcard matching; use regex:\"...\" for pattern matching. path_search requires a non-empty query and is not a directory listing endpoint; use file_list for enumeration. search supports batch mode with queries:[...] and optional dedupe mode.",
191+
"instructions": "Use tools to query indexed code and symbol information. Operational flow: repositories -> repo_branches -> file_list/path_search -> file_content/search/symbol_insights. Use structured search fields: all_terms are AND semantics and any_terms are OR semantics (fanout + dedupe). For recency/version questions like 'recent change', call repo_branches first, then run search with explicit branch values and compare indexed_at/is_live metadata; add historical:true when historical snapshots should be included. Plain terms do not support wildcard matching; use regex for pattern matching. path_search requires a non-empty query and is not a directory listing endpoint; use file_list for enumeration.",
179192
});
180193
jsonrpc_result(req.id, result)
181194
}
@@ -307,36 +320,39 @@ fn mcp_tools() -> Vec<Value> {
307320
vec![
308321
json!({
309322
"name": "search",
310-
"description": "Search indexed source code (from Pointer index, not local filesystem) using Pointer DSL query syntax. Supports branch-scoped and historical queries (e.g. 'repo:linux branch:main symbol' or 'repo:linux branch:main historical:yes symbol'). Plain terms use substring matching and do NOT support wildcard syntax; use regex:\"...\" for pattern queries. Treat query terms/filters as AND semantics. For OR semantics, run multiple search calls and merge/deduplicate results client-side. Supports optional batch mode with queries:[...] plus dedupe controls.",
323+
"description": "Search indexed source code using structured fields (not a free-form DSL string). Use all_terms for AND semantics (all terms must match). Use any_terms for OR semantics (server executes one query per term, then merges/deduplicates using dedupe). Include repo/branch filters for version-aware questions, and set historical:true for older snapshots. path/file are glob-like filters, regex is a content regex filter, and case controls case sensitivity (yes|no|auto). At least one of all_terms, any_terms, or regex is required.",
311324
"inputSchema": {
312325
"type": "object",
313326
"properties": {
314-
"query": { "type": "string", "description": "Single-query mode. Provide either query or queries." },
315-
"queries": {
316-
"type": "array",
317-
"items": { "type": "string" },
318-
"minItems": 1,
319-
"maxItems": 8,
320-
"description": "Batch mode. Each query is executed separately and merged."
321-
},
322-
"page": { "type": "integer", "minimum": 1, "description": "Single-query page number." },
327+
"repo": { "type": "string" },
328+
"branch": { "type": "string" },
329+
"lang": { "type": "string" },
330+
"path": { "type": "string", "description": "Glob-like path filter" },
331+
"file": { "type": "string", "description": "Glob-like file filter" },
332+
"regex": { "type": "string", "description": "Regex content filter" },
333+
"case": { "type": "string", "enum": ["yes", "no", "auto"] },
334+
"historical": { "type": "boolean" },
335+
"all_terms": { "type": "array", "items": { "type": "string" } },
336+
"any_terms": { "type": "array", "items": { "type": "string" }, "maxItems": 8 },
337+
"page": { "type": "integer", "minimum": 1 },
323338
"dedupe": {
324339
"type": "string",
325340
"enum": ["repo_path_line", "repo_path", "none"],
326-
"description": "Batch dedupe strategy."
341+
"description": "Used when any_terms fanout is active."
327342
},
328343
"max_results_per_query": { "type": "integer", "minimum": 1, "maximum": 100 }
329344
},
330345
"anyOf": [
331-
{ "required": ["query"] },
332-
{ "required": ["queries"] }
346+
{ "required": ["all_terms"] },
347+
{ "required": ["any_terms"] },
348+
{ "required": ["regex"] }
333349
],
334350
"additionalProperties": false
335351
}
336352
}),
337353
json!({
338354
"name": "repositories",
339-
"description": "List indexed repositories available for search. Use this first to discover exact repository keys before applying repo:<name> filters. Results include index_freshness metadata.",
355+
"description": "List indexed repositories available for search. Call this first to discover exact repository keys to pass in search.repo. Results include index_freshness metadata.",
340356
"inputSchema": {
341357
"type": "object",
342358
"properties": {
@@ -347,7 +363,7 @@ fn mcp_tools() -> Vec<Value> {
347363
}),
348364
json!({
349365
"name": "repo_branches",
350-
"description": "List indexed branches/heads for a repository, including commit_sha, indexed_at, and is_live. Use this before branch-by-branch comparisons for 'newer/older/recent change' questions and then run search with explicit branch:<name> filters. Results include per-branch freshness ages.",
366+
"description": "List indexed branches/heads for a repository, including commit_sha, indexed_at, and is_live. For recency/version questions, call this before search and then run branch-by-branch comparisons by setting search.branch explicitly. Includes per-branch freshness ages.",
351367
"inputSchema": {
352368
"type": "object",
353369
"properties": { "repo": { "type": "string" } },
@@ -357,7 +373,7 @@ fn mcp_tools() -> Vec<Value> {
357373
}),
358374
json!({
359375
"name": "file_content",
360-
"description": "Read raw indexed file content (no syntax highlighting) for an exact repo/branch/path from the index. Use this after file_list/path_search or when you already know the exact file path. Includes branch freshness metadata.",
376+
"description": "Read raw indexed file content (no syntax highlighting) for an exact repo/branch/path from the index. Use this after file_list/path_search to inspect implementation details. Includes branch freshness metadata.",
361377
"inputSchema": {
362378
"type": "object",
363379
"properties": {
@@ -371,7 +387,7 @@ fn mcp_tools() -> Vec<Value> {
371387
}),
372388
json!({
373389
"name": "file_list",
374-
"description": "Enumerate files/directories under a path for a repository+branch from the index. Supports bounded recursive listing with depth and limit. Use this for directory listing workflows. Response includes truncated flag, branch freshness, and stable paths.",
390+
"description": "Enumerate files/directories under a path for a repository+branch from the index. Supports bounded recursive traversal with depth and limit. Use this for directory listing workflows and then call file_content on specific files. Response includes truncated flag, branch freshness, and stable paths.",
375391
"inputSchema": {
376392
"type": "object",
377393
"properties": {
@@ -387,7 +403,7 @@ fn mcp_tools() -> Vec<Value> {
387403
}),
388404
json!({
389405
"name": "path_search",
390-
"description": "Search file and directory paths within a repository and branch using a non-empty query. This matches paths only (fuzzy path lookup) and does not enumerate full directory contents; use file_list for enumeration. Includes freshness metadata.",
406+
"description": "Search file and directory paths within a repository and branch using a non-empty query (fuzzy path lookup). This is path-only matching and does not enumerate full directory contents; use file_list for enumeration and file_content for file bodies. Includes freshness metadata.",
391407
"inputSchema": {
392408
"type": "object",
393409
"properties": {
@@ -402,7 +418,7 @@ fn mcp_tools() -> Vec<Value> {
402418
}),
403419
json!({
404420
"name": "symbol_insights",
405-
"description": "Find symbol definitions and references with snippets in indexed code. For scoped analysis, set params.scope (repository/directory/file/custom) and optional include_paths/excluded_paths. Includes freshness metadata for the selected branch.",
421+
"description": "Find symbol definitions and references with snippets in indexed code. For scoped analysis, set params.scope (repository/directory/file/custom) and optional include_paths/excluded_paths. Use this for 'where is symbol defined/used' workflows. Includes freshness metadata for the selected branch.",
406422
"inputSchema": {
407423
"type": "object",
408424
"properties": {

0 commit comments

Comments
 (0)