CocoSearch provides 10 Model Context Protocol (MCP) tools for semantic code search, dependency analysis, and index management. These tools enable AI agents and LLMs to search indexed codebases, trace dependencies, manage indexes, analyze search pipelines, and retrieve statistics programmatically.
Available transports: stdio, SSE, streamable HTTP
Search indexed code using natural language queries. Returns code chunks ranked by semantic similarity, with optional context expansion to enclosing function/class boundaries. Performs automatic project detection using MCP Roots when available, falling back to the index_name parameter or the working directory.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| query | string | Yes | - | Natural language search query |
| index_name | string | null | No | null | Name of the index to search. If not provided, auto-detects from current working directory. |
| limit | integer | No | 10 | Maximum results to return |
| language | string | null | No | null | Filter by language (e.g., python, typescript, hcl, dockerfile, bash). Aliases: terraform=hcl, shell/sh=bash. Comma-separated for multiple. |
| use_hybrid_search | boolean | null | No | null | Enable hybrid search (vector + keyword matching). None=auto (enabled for identifier patterns like camelCase/snake_case), True=always use hybrid, False=vector-only |
| symbol_type | string | array<string> | null | No | null | Filter by symbol type. Single: 'function', 'class', 'method', 'interface'. Array: ['function', 'method'] for OR filtering. |
| symbol_name | string | null | No | null | Filter by symbol name pattern (glob). Examples: 'get*', 'User*Service', '*Handler'. Case-insensitive matching. |
| context_before | integer | null | No | null | Number of lines to show before each match. Overrides smart context expansion when specified. |
| context_after | integer | null | No | null | Number of lines to show after each match. Overrides smart context expansion when specified. |
| smart_context | boolean | No | true | Expand context to enclosing function/class boundaries. Enabled by default. Set to False for exact line counts only. |
| include_deps | boolean | No | true | Include dependency info (imports/dependents) for each result file |
| index_names | list<string> | null | No | null | Search across multiple indexes. Results merged by relevance score. Mutually exclusive with index_name. |
Search for authentication logic in a Python project:
"Find JWT token validation functions"
This will search the auto-detected index for code chunks related to JWT token validation, automatically using hybrid search since the query contains the identifier pattern "JWT".
{
"query": "JWT token validation",
"index_name": "my-api-server",
"limit": 5,
"language": "python",
"use_hybrid_search": true,
"symbol_type": "function",
"context_before": 3,
"context_after": 3,
"smart_context": false
}[
{
"file_path": "/Users/dev/my-api/auth/jwt.py",
"start_line": 45,
"end_line": 62,
"score": 0.89,
"content": "def validate_jwt_token(token: str) -> dict:\n \"\"\"Validate JWT and return claims.\"\"\"\n try:\n payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])\n return payload\n except jwt.ExpiredSignatureError:\n raise AuthError('Token expired')\n except jwt.InvalidTokenError:\n raise AuthError('Invalid token')",
"block_type": "function",
"hierarchy": "validate_jwt_token",
"language_id": "python",
"symbol_type": "function",
"symbol_name": "validate_jwt_token",
"symbol_signature": "def validate_jwt_token(token: str) -> dict",
"match_type": "both",
"vector_score": 0.87,
"keyword_score": 0.91,
"context_before": "import jwt\nfrom .exceptions import AuthError\n\n",
"context_after": "\n\ndef refresh_token(user_id: int) -> str:\n return generate_jwt(user_id)"
}
]Note: Response may include a search_context header (when auto-detecting index) and a staleness_warning footer (when index is older than 7 days). When using index_names for cross-index search, each result includes an index_name field identifying which index it came from.
Search across multiple indexes in a single call using the index_names parameter. This is useful for searching related projects together — monorepos, shared libraries, or microservice codebases.
How it works:
- The query embedding is computed once and reused across all indexes
- Each index is searched in parallel via
ThreadPoolExecutor - Results from all indexes are merged by score and truncated to the requested
limit - Each result is tagged with its source
index_name - Partial failures are handled gracefully — if one index fails, results from other indexes are still returned
linkedIndexes auto-expansion: When cocosearch.yaml includes a linkedIndexes list, single-index searches automatically expand to include the linked indexes — no explicit index_names parameter needed. Explicitly providing index_names overrides the config. Missing linked indexes are silently skipped.
Example config:
indexName: my-api
linkedIndexes:
- shared-libs
- common-typesWith this config, searching my-api automatically includes shared-libs and common-types.
JSON Request (explicit cross-index):
{
"query": "authentication middleware",
"index_names": ["api-server", "shared-libs", "auth-service"],
"limit": 10,
"use_hybrid_search": true,
"smart_context": true
}Note: All indexes must use the same embedding provider and model. Mismatched embedding models across indexes will produce unreliable relevance scores.
Analyze the search pipeline for a query with stage-by-stage diagnostics. Runs the same pipeline as search_code but captures diagnostics at each stage: query analysis, mode selection, cache status, vector search, keyword search, RRF fusion, definition boost, filtering, and per-stage timing breakdown.
Use this to understand WHY a query returns specific results.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| query | string | Yes | - | Search query to analyze |
| index_name | string | null | No | null | Name of the index. Auto-detects if not provided. |
| limit | integer | No | 10 | Maximum results to return |
| language | string | null | No | null | Filter by language. Comma-separated for multiple. |
| use_hybrid_search | boolean | null | No | null | None=auto, True=always hybrid, False=vector-only |
| symbol_type | string | array<string> | null | No | null | Filter by symbol type |
| symbol_name | string | null | No | null | Filter by symbol name pattern (glob) |
"Why does searching for getUserById not return the right function?"
{
"query": "getUserById",
"index_name": "my-api-server"
}{
"query_analysis": {
"original_query": "getUserById",
"has_identifier": true,
"normalized_keyword_query": "getUserById get User By Id"
},
"search_mode": {
"mode": "hybrid",
"reason": "Auto-detected identifier pattern in query",
"use_hybrid_flag": null,
"has_content_text_column": true,
"has_identifier_pattern": true
},
"cache": {
"checked": false,
"hit": false,
"hit_type": "miss",
"cache_key_prefix": "a1b2c3d4e5f67890"
},
"vector_search": {
"result_count": 12,
"top_score": 0.872,
"bottom_score": 0.534
},
"keyword_search": {
"executed": true,
"normalized_query": "getUserById get User By Id",
"result_count": 8,
"top_ts_rank": 0.098
},
"fusion": {
"executed": true,
"k_constant": 60,
"vector_only_count": 8,
"keyword_only_count": 4,
"both_count": 4,
"total_fused": 16
},
"definition_boost": {
"executed": true,
"boost_multiplier": 2.0,
"boosted_count": 3,
"rank_changes": 1
},
"filtering": {
"language_filter": null,
"symbol_type_filter": null,
"symbol_name_filter": null,
"min_score": 0.0,
"pre_filter_count": 10,
"post_filter_count": 10
},
"timings": {
"query_analysis_ms": 0.1,
"cache_check_ms": 0.0,
"embedding_ms": 0.0,
"vector_search_ms": 12.3,
"keyword_search_ms": 2.1,
"rrf_fusion_ms": 0.3,
"definition_boost_ms": 0.1,
"total_ms": 15.2
},
"results": []
}Note: The results array contains full SearchResult objects (same format as search_code). Cache is always bypassed for analysis.
List all available code indexes. Returns index names and their corresponding table names.
None
Get all indexed codebases to see what's available for searching.
{}[
{
"name": "my-api-server",
"table_name": "cocosearch_my_api_server"
},
{
"name": "frontend-app",
"table_name": "cocosearch_frontend_app"
},
{
"name": "shared-utils",
"table_name": "cocosearch_shared_utils"
}
]Get statistics for code indexes. Returns file count, chunk count, storage size, language distribution, symbol counts, parse health, and staleness information.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| index_name | string | null | No | null | Name of the index (omit for all indexes) |
| include_failures | boolean | No | false | Include per-language parse failure details in response |
Check how many files and chunks are indexed in "my-api-server" and when it was last updated.
{
"index_name": "my-api-server"
}{
"name": "my-api-server",
"file_count": 342,
"chunk_count": 1523,
"storage_size": 8421376,
"storage_size_pretty": "8.0 MB",
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-02-05T14:22:00Z",
"is_stale": false,
"staleness_days": 1,
"languages": [
{
"language": "python",
"file_count": 180,
"chunk_count": 890,
"line_count": 12500
},
{
"language": "typescript",
"file_count": 120,
"chunk_count": 520,
"line_count": 8200
},
{
"language": "hcl",
"file_count": 42,
"chunk_count": 113,
"line_count": 1800
}
],
"symbols": {
"function": 450,
"class": 85,
"method": 320,
"interface": 25
},
"parse_stats": {
"parse_health_pct": 95.2,
"total_files": 342,
"total_ok": 325,
"by_language": {
"python": {
"files": 180,
"ok": 175,
"partial": 3,
"error": 2,
"no_grammar": 0
},
"typescript": {
"files": 120,
"ok": 115,
"partial": 3,
"error": 2,
"no_grammar": 0
}
}
},
"warnings": []
}When include_failures is true, the response includes a parse_failures array with file paths and error details for each failed parse:
{
"parse_failures": [
{
"file_path": "src/legacy/parser.py",
"language": "python",
"parse_status": "error",
"error_message": "tree-sitter parse failed"
}
]
}{}[
{
"name": "my-api-server",
"file_count": 342,
"chunk_count": 1523,
"storage_size": 8421376,
"storage_size_pretty": "8.0 MB",
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-02-05T14:22:00Z",
"is_stale": false,
"staleness_days": 1,
"languages": [
{
"language": "python",
"file_count": 180,
"chunk_count": 890,
"line_count": 12500
}
],
"symbols": {
"function": 450,
"class": 85
},
"parse_stats": {
"parse_health_pct": 97.8,
"total_files": 180,
"total_ok": 176,
"by_language": {}
},
"warnings": []
},
{
"name": "frontend-app",
"file_count": 215,
"chunk_count": 980,
"storage_size": 5242880,
"storage_size_pretty": "5.0 MB",
"created_at": "2026-01-20T08:15:00Z",
"updated_at": "2026-01-28T16:45:00Z",
"is_stale": true,
"staleness_days": 9,
"languages": [
{
"language": "typescript",
"file_count": 200,
"chunk_count": 920,
"line_count": 15200
}
],
"symbols": {
"function": 320,
"interface": 45
},
"parse_stats": {
"parse_health_pct": 99.0,
"total_files": 200,
"total_ok": 198,
"by_language": {}
},
"warnings": [
"Index is stale (9 days since last update)"
]
}
]Note: The line_count field is null for indexes created before v1.7 (lacking the content_text column). The symbols field is an empty object {} for pre-v1.7 indexes.
Clear (delete) a code index. Permanently deletes all indexed data for a codebase, including the associated parse results tracking table. This operation cannot be undone.
WARNING: This is a destructive operation.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| index_name | string | Yes | - | Name of the index to delete |
Delete the "old-prototype" index that's no longer needed.
{
"index_name": "old-prototype"
}{
"success": true,
"message": "Index 'old-prototype' cleared successfully"
}{
"success": false,
"error": "Index 'nonexistent' not found"
}Index a codebase directory for semantic search. Creates embeddings for all code files and stores them in the database. If the index already exists, it will be updated with any changes.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| path | string | Yes | - | Path to the codebase directory to index |
| index_name | string | null | No | null | Name for the index (auto-derived from path if not provided) |
Index the codebase at "/Users/dev/my-new-project" so it can be searched.
{
"path": "/Users/dev/my-new-project",
"index_name": "my-new-project"
}{
"success": true,
"index_name": "my-new-project",
"path": "/Users/dev/my-new-project",
"stats": {
"files_added": 150,
"files_removed": 0,
"files_updated": 0
}
}{
"success": false,
"error": "Failed to index codebase: Path does not exist: /invalid/path"
}Note: If index_name is not provided, it will be auto-derived from the path (e.g., "/Users/dev/my-api" becomes "my-api").
Get dependencies for a file (what it depends on). Returns structured dependency data with transitive traversal. With depth=1, returns direct dependencies as a flat list. With depth>1, returns a transitive dependency tree showing the full chain of dependencies.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| file | string | Yes | - | File path relative to project root |
| index_name | string | null | No | null | Index name. Auto-detects from project if not provided. |
| depth | integer | No | 1 | Traversal depth. 1=direct only, >1=transitive |
| dep_type | string | null | No | null | Filter by type: import, call, reference |
{
"file": "src/auth/jwt.py",
"depth": 2,
"dep_type": "import"
}{
"file": "src/auth/jwt.py",
"dependencies": [
{
"target_file": "src/config/settings.py",
"target_symbol": "SECRET_KEY",
"dep_type": "import",
"module": "config.settings"
},
{
"target_file": null,
"target_symbol": "jwt",
"dep_type": "import",
"module": "jwt"
}
],
"total": 2
}{
"file": "src/auth/jwt.py",
"tree": {
"file": "src/auth/jwt.py",
"symbol": null,
"dep_type": "root",
"children": [
{
"file": "src/config/settings.py",
"symbol": "SECRET_KEY",
"dep_type": "import",
"children": []
}
]
}
}Get impact analysis for a file (what would be affected if it changes). Returns a transitive reverse-dependency tree showing files that depend on the given file, useful for understanding blast radius of changes.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| file | string | Yes | - | File path relative to project root |
| index_name | string | null | No | null | Index name. Auto-detects from project if not provided. |
| depth | integer | No | 3 | Traversal depth for transitive impact analysis (max 20) |
| dep_type | string | null | No | null | Filter by type: import, call, reference |
{
"file": "src/models/user.py",
"depth": 2
}{
"file": "src/models/user.py",
"impact_tree": {
"file": "src/models/user.py",
"symbol": null,
"dep_type": "root",
"children": [
{
"file": "src/api/users.py",
"symbol": null,
"dep_type": "import",
"children": [
{
"file": "src/api/admin.py",
"symbol": null,
"dep_type": "import",
"children": []
}
]
},
{
"file": "tests/test_user.py",
"symbol": null,
"dep_type": "import",
"children": []
}
]
}
}Get dependencies for multiple files in a single batch call. More efficient than calling get_file_dependencies per file when analyzing multiple changed files (e.g., from a git diff). With depth>1, uses a shared visited set across all files to eliminate redundant traversal of overlapping dependency subgraphs.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| files | array<string> | Yes | - | File paths relative to project root |
| index_name | string | null | No | null | Index name. Auto-detects from project if not provided. |
| depth | integer | No | 1 | Traversal depth. 1=direct only, >1=transitive with shared visited set |
| dep_type | string | null | No | null | Filter by type: import, call, reference |
{
"files": ["src/auth/jwt.py", "src/auth/oauth.py"],
"depth": 2
}{
"files_requested": 2,
"depth": 1,
"results": [
{
"file": "src/auth/jwt.py",
"dependencies": [
{
"target_file": "src/config/settings.py",
"target_symbol": "SECRET_KEY",
"dep_type": "import",
"module": "config.settings"
}
],
"total": 1
},
{
"file": "src/auth/oauth.py",
"dependencies": [
{
"target_file": "src/config/settings.py",
"target_symbol": "OAUTH_CLIENT_ID",
"dep_type": "import",
"module": "config.settings"
}
],
"total": 1
}
]
}{
"files_requested": 2,
"depth": 2,
"results": [
{
"file": "src/auth/jwt.py",
"tree": {
"file": "src/auth/jwt.py",
"symbol": null,
"dep_type": "root",
"children": [
{
"file": "src/config/settings.py",
"symbol": "SECRET_KEY",
"dep_type": "import",
"children": []
}
]
}
},
{
"file": "src/auth/oauth.py",
"tree": {
"file": "src/auth/oauth.py",
"symbol": null,
"dep_type": "root",
"children": []
}
}
]
}Note: With depth>1, src/config/settings.py appears only in the first tree's children — the shared visited set prevents redundant traversal.
Get impact analysis for multiple files in a single batch call. More efficient than calling get_file_impact per file when analyzing multiple changed files (e.g., from a git diff). Uses a shared visited set across all files to eliminate redundant traversal of overlapping reverse-dependency subgraphs.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| files | array<string> | Yes | - | File paths relative to project root |
| index_name | string | null | No | null | Index name. Auto-detects from project if not provided. |
| depth | integer | No | 3 | Traversal depth for transitive impact analysis (max 20) |
| dep_type | string | null | No | null | Filter by type: import, call, reference |
{
"files": ["src/models/user.py", "src/models/order.py"],
"depth": 2
}{
"files_requested": 2,
"depth": 2,
"results": [
{
"file": "src/models/user.py",
"impact_tree": {
"file": "src/models/user.py",
"symbol": null,
"dep_type": "root",
"children": [
{
"file": "src/api/users.py",
"symbol": null,
"dep_type": "import",
"children": []
}
]
}
},
{
"file": "src/models/order.py",
"impact_tree": {
"file": "src/models/order.py",
"symbol": null,
"dep_type": "root",
"children": [
{
"file": "src/api/orders.py",
"symbol": null,
"dep_type": "import",
"children": []
}
]
}
}
]
}Note: The shared visited set means if src/api/users.py depends on both user.py and order.py, it will only appear in the first tree where it's encountered.
All four dependency tools (get_file_dependencies, get_file_impact, get_batch_dependencies, get_batch_impact) perform best-effort staleness checks and may include a "warnings" key in the response when dependency data is outdated. Warning types:
| Type | Meaning |
|---|---|
deps_not_extracted |
No dependency data exists, or extraction timestamp is missing. Run cocosearch deps extract . |
deps_outdated |
The index was re-indexed after the last dependency extraction. Run cocosearch deps extract . |
deps_branch_drift |
Git branch or commit has changed since the index was last built. Run cocosearch index . --deps |
Example response with warnings:
{
"file": "src/auth/jwt.py",
"dependencies": [],
"total": 0,
"warnings": [
{
"type": "deps_not_extracted",
"warning": "Dependencies not extracted",
"message": "No dependency data found for this index. Run `cocosearch deps extract .` to extract dependencies."
}
]
}The "warnings" key is omitted when dependency data is fresh.
All tools are implemented in src/cocosearch/mcp/server.py using the FastMCP framework.
Core search engine: src/cocosearch/search/query.py
Dependency queries: src/cocosearch/deps/query.py
Index management: src/cocosearch/management/__init__.py
Statistics: src/cocosearch/management/stats.py
Parse tracking: src/cocosearch/indexer/parse_tracking.py