Skip to content

Commit bf18e37

Browse files
authored
examples: add an MFS MCP server (code & context search) (#146)
A small MCP server that exposes MFS search/read as tools so any MCP client (Claude Code, Cursor, Codex, …) can use every MFS-indexed source as context — in the spirit of claude-context, unified over all sources. Adds the runnable example and an Integrations docs page. Verified end to end: stdio MCP client and a real Claude Code session both call the tools with correct results.
1 parent 3d557c7 commit bf18e37

5 files changed

Lines changed: 187 additions & 1 deletion

File tree

docs/integrations.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ There are two kinds of pages here:
1010
Memory.
1111
- [Open Tag](integrations/open-tag.md) — a Slack tag-in bot, an open homage to
1212
[Claude Tag](https://www.anthropic.com/news/introducing-claude-tag).
13-
- **Frameworks** — drop MFS into an existing agent / RAG stack.
13+
- **Frameworks & protocols** — drop MFS into an existing agent / RAG stack.
14+
- [MCP server](integrations/mcp.md) — expose MFS as tools to any MCP client
15+
(Claude Code, Cursor, …).
1416
- [LangChain](integrations/langchain.md) — MFS as a `Retriever` and a tool.
1517
- [LangGraph](integrations/langgraph.md) — MFS as a retrieval node in a graph.
1618

docs/integrations/mcp.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# MCP server
2+
3+
Expose MFS over the [Model Context Protocol](https://modelcontextprotocol.io) and
4+
any MCP client — Claude Code, Cursor, Codex, Windsurf — can search your indexed
5+
sources as context. It's in the spirit of
6+
[claude-context](https://github.com/zilliztech/claude-context) ("make the codebase
7+
the context for any coding agent"), but the index is MFS, so one server covers
8+
**every** source you've indexed — code, docs, issues, chat, databases — not just
9+
one codebase.
10+
11+
The runnable server is in the
12+
[example](https://github.com/zilliztech/mfs/tree/main/examples/mfs-mcp); it's about
13+
60 lines over the [Python SDK](../sdks.md).
14+
15+
## Two tools
16+
17+
```python
18+
from mcp.server.fastmcp import FastMCP
19+
import mfs_sdk
20+
21+
mcp = FastMCP("mfs-context")
22+
23+
24+
@mcp.tool()
25+
def search(query: str, scope: str = "", top_k: int = 8) -> str:
26+
"""Hybrid search across MFS-indexed sources. Empty scope = everything;
27+
or pass a path / URI prefix like "github://org/repo"."""
28+
resp = retrieval.search(q=query, path=scope or None, top_k=top_k)
29+
return "\n\n".join(f"## {h.source}\n{h.content.strip()}" for h in resp.results)
30+
31+
32+
@mcp.tool()
33+
def read(source: str, lines: str = "") -> str:
34+
"""Read a hit in full, or a line range like "40:80", by its source URI."""
35+
return browse.cat(source, range=lines or None).content
36+
37+
38+
if __name__ == "__main__":
39+
mcp.run()
40+
```
41+
42+
`search` locates, `read` pulls the exact unit into context — the same loop MFS is
43+
built around. (`retrieval` / `browse` are `mfs_sdk.RetrievalApi` / `BrowseApi`
44+
pointed at your server; see the example for the few lines of setup.)
45+
46+
## Register it
47+
48+
With Claude Code, from your project:
49+
50+
```bash
51+
claude mcp add mfs-context \
52+
--env MFS_URL=http://127.0.0.1:13619 \
53+
-- uv run --with mcp --with /abs/path/to/mfs/sdks/python python /abs/path/to/mfs/examples/mfs-mcp/server.py
54+
```
55+
56+
`claude mcp list` should report `mfs-context: ✔ Connected`, after which the agent
57+
calls the tools on its own — "where is rate limiting implemented? search our
58+
sources." Any MCP client works; point its stdio server config at the same command.

examples/mfs-mcp/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# MFS MCP server
2+
3+
A tiny [Model Context Protocol](https://modelcontextprotocol.io) server that turns
4+
MFS into searchable context for any MCP client — Claude Code, Cursor, Codex,
5+
Windsurf, and the rest.
6+
7+
It's in the spirit of [`claude-context`](https://github.com/zilliztech/claude-context)
8+
("make the codebase the context for any coding agent"), but the index is MFS, so a
9+
single server covers **every source you've indexed** — code, docs, issues, chat,
10+
databases — not just one codebase.
11+
12+
## Tools
13+
14+
[`server.py`](server.py) exposes two tools over MCP:
15+
16+
- **`search(query, scope="", top_k=8)`** — hybrid (semantic + keyword) search
17+
across MFS-indexed sources. Leave `scope` empty to search everything, or pass a
18+
path / URI prefix (e.g. `github://org/repo`) to narrow it. Returns ranked hits
19+
with a snippet and the `source` URI.
20+
- **`read(source, lines="")`** — read a hit in full, or a line range like `"40:80"`.
21+
22+
The loop an agent runs is the same one MFS is built around: `search` to locate,
23+
`read` to pull the exact unit into context.
24+
25+
## Prerequisites
26+
27+
A running MFS server with at least one indexed source (see the
28+
[docs](https://github.com/zilliztech/mfs/tree/main/docs) — a local repo is the
29+
quickest start). The server reads `MFS_URL` / `MFS_TOKEN` (defaulting to
30+
`http://127.0.0.1:13619` and `~/.mfs/server.token`).
31+
32+
## Register it
33+
34+
With Claude Code, from your project:
35+
36+
```bash
37+
claude mcp add mfs-context \
38+
--env MFS_URL=http://127.0.0.1:13619 \
39+
-- uv run --with mcp --with /abs/path/to/mfs/sdks/python python /abs/path/to/mfs/examples/mfs-mcp/server.py
40+
```
41+
42+
`claude mcp list` should report `mfs-context: ✔ Connected`. Then just ask — the
43+
agent calls `search` / `read` on its own:
44+
45+
> Where is rate limiting implemented? Search our indexed sources.
46+
47+
Any MCP client works; point its stdio server config at the same command. The
48+
server needs the [`mcp`](https://pypi.org/project/mcp/) package and the MFS Python
49+
SDK (`mfs_sdk`, under [`sdks/python`](../../sdks/python)) on its path — the
50+
`uv run --with …` invocation above pulls both in.

examples/mfs-mcp/server.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python3
2+
"""An MCP server that turns MFS into searchable context for any MCP client.
3+
4+
In the spirit of zilliztech/claude-context — "make the codebase the context for
5+
any coding agent" — but the index is MFS, so a single server covers every source
6+
you've indexed (code, docs, issues, chat, databases), not just one codebase.
7+
8+
It exposes two tools over the Model Context Protocol:
9+
10+
- ``search`` — hybrid (semantic + keyword) search across MFS-indexed sources;
11+
- ``read`` — read a hit in full (or a line range) by its source URI.
12+
13+
Point it at a running MFS server with ``MFS_URL`` / ``MFS_TOKEN`` (defaults to
14+
``http://127.0.0.1:13619`` and ``~/.mfs/server.token``). Register it with any MCP
15+
client, e.g. Claude Code:
16+
17+
claude mcp add mfs-context -- python /abs/path/to/server.py
18+
"""
19+
20+
from __future__ import annotations
21+
22+
import os
23+
from pathlib import Path
24+
25+
import mfs_sdk
26+
from mcp.server.fastmcp import FastMCP
27+
28+
mcp = FastMCP("mfs-context")
29+
30+
31+
def _token() -> str | None:
32+
if os.getenv("MFS_TOKEN"):
33+
return os.environ["MFS_TOKEN"]
34+
tok = Path.home() / ".mfs" / "server.token"
35+
return tok.read_text().strip() if tok.exists() else None
36+
37+
38+
def _api(api_cls):
39+
client = mfs_sdk.ApiClient(
40+
mfs_sdk.Configuration(host=os.getenv("MFS_URL", "http://127.0.0.1:13619"))
41+
)
42+
token = _token()
43+
if token:
44+
client.set_default_header("Authorization", f"Bearer {token}")
45+
return api_cls(client)
46+
47+
48+
@mcp.tool()
49+
def search(query: str, scope: str = "", top_k: int = 8) -> str:
50+
"""Search MFS-indexed sources (code, docs, issues, chat, databases) by meaning
51+
or keyword. Leave ``scope`` empty to search everything, or pass a path / URI
52+
prefix to narrow it (e.g. ``github://org/repo`` or a local path). Returns
53+
ranked hits, each with a snippet and the ``source`` URI to pass to ``read``.
54+
"""
55+
resp = _api(mfs_sdk.RetrievalApi).search(q=query, path=scope or None, top_k=top_k)
56+
if not resp.results:
57+
return "No matches."
58+
blocks = []
59+
for h in resp.results:
60+
score = f"{h.score:.2f}" if h.score is not None else "n/a"
61+
blocks.append(f"## {h.source} (score={score})\n{h.content.strip()}")
62+
return "\n\n".join(blocks)
63+
64+
65+
@mcp.tool()
66+
def read(source: str, lines: str = "") -> str:
67+
"""Read a source in full, or a line range like ``"40:80"``. ``source`` is the
68+
URI from a ``search`` hit. Use this to pull the exact code or text into context
69+
after ``search`` locates it."""
70+
resp = _api(mfs_sdk.BrowseApi).cat(source, range=lines or None)
71+
return resp.content
72+
73+
74+
if __name__ == "__main__":
75+
mcp.run()

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ nav:
6868
- Integrations:
6969
- Overview: integrations.md
7070
- Open Tag (Slack bot): integrations/open-tag.md
71+
- MCP server: integrations/mcp.md
7172
- LangChain: integrations/langchain.md
7273
- LangGraph: integrations/langgraph.md
7374
- Operations:

0 commit comments

Comments
 (0)