Build and return a configured FastMCP server backed by the given cache.
(cache: _IndexCache)
| 43 | |
| 44 | |
| 45 | def create_server(cache: _IndexCache) -> FastMCP: |
| 46 | """Build and return a configured FastMCP server backed by the given cache.""" |
| 47 | server = FastMCP( |
| 48 | "semble", |
| 49 | instructions=( |
| 50 | "Instant code search for any local or remote git repository. " |
| 51 | "Call `search` once with a focused query, it returns the file path and exact line. " |
| 52 | "Navigate directly to that file at the given line; do not grep for the same content. " |
| 53 | "Use `find_related` to discover similar code elsewhere in the same repo. " |
| 54 | "When working in a local project, pass the project root as `repo`. " |
| 55 | "For remote repos, pass an explicit https:// URL. Never guess or infer URLs." |
| 56 | ), |
| 57 | ) |
| 58 | |
| 59 | @server.tool() |
| 60 | async def search( |
| 61 | query: Annotated[str, Field(description="Natural language or code query.")], |
| 62 | repo: Annotated[str, Field(description=_REPO_DESCRIPTION)], |
| 63 | top_k: Annotated[int, Field(description="Number of results to return.", ge=1)] = 5, |
| 64 | max_snippet_lines: Annotated[ |
| 65 | int | None, |
| 66 | Field( |
| 67 | description=( |
| 68 | "Lines of source to include per result. " |
| 69 | "Default (10): function/class signature + first body lines, enough to confirm the location. " |
| 70 | "0: file path and line range only. None: full chunk (~10-20 lines). " |
| 71 | "If the snippet does not contain enough context to confirm you have the right location, " |
| 72 | "call again with max_snippet_lines=None." |
| 73 | ), |
| 74 | ge=0, |
| 75 | ), |
| 76 | ] = 10, |
| 77 | ) -> str: |
| 78 | """Search once with a focused query describing what the code does or its name. |
| 79 | |
| 80 | Write queries using function/class names or behavior descriptions, not error messages. |
| 81 | Returns file paths and line numbers — navigate directly there, do not repeat the search. |
| 82 | Pass a git URL or local path as `repo`; indexes are cached for the session. |
| 83 | """ |
| 84 | try: |
| 85 | index = await _get_index(repo, cache) |
| 86 | except ValueError as exc: |
| 87 | return str(exc) |
| 88 | results = index.search(query, top_k=top_k, max_snippet_lines=max_snippet_lines) |
| 89 | if not results: |
| 90 | return json.dumps({"error": "No results found."}) |
| 91 | return json.dumps(format_results(query, results, max_snippet_lines)) |
| 92 | |
| 93 | @server.tool() |
| 94 | async def find_related( |
| 95 | file_path: Annotated[ |
| 96 | str, |
| 97 | Field(description="Path to the file as stored in the index (use file_path from a search result)."), |
| 98 | ], |
| 99 | line: Annotated[int, Field(description="Line number (1-indexed).")], |
| 100 | repo: Annotated[str, Field(description=_REPO_DESCRIPTION)], |
| 101 | top_k: Annotated[int, Field(description="Number of similar chunks to return.", ge=1)] = 5, |
| 102 | max_snippet_lines: Annotated[ |
no outgoing calls