brain-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@brain-mcpsearch my notes for project Apollo"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-brain
A Model Context Protocol (MCP) server that provides controlled access to local Brain notes.
Features
This MCP server implements the following tools:
search_notes(query) - Search note filenames and note content (case-insensitive)
list_notes(path?) - List folders and files under the configured Brain path
read_note(path) - Read one note file by relative path
Built-in safeguards:
Path traversal protection (cannot read outside the configured Brain root)
Configurable file size limit for reads/search
Configurable maximum number of search results
Related MCP server: MCP Notes Server
Prerequisites
Python 3.11 or higher
A local notes folder (default is ./brain)
Installation
Local Installation (.venv)
Create and activate a virtual environment:
python3 -m venv .venv
source .venv/bin/activateInstall the project:
python -m pip install --upgrade pip
python -m pip install -e .Optional: copy environment template:
cp .env.example .envRun the server (stdio transport):
BRAIN_PATH=./brain brain-mcpDocker
Build image
docker build -t mcp-brain .Run container
docker run -i \
-e BRAIN_PATH=/brain \
-e BRAIN_MAX_RESULTS=10 \
-e BRAIN_MAX_FILE_SIZE=1048576 \
-v "$(pwd)/brain:/brain:ro" \
mcp-brainDocker Compose
The included compose file follows the same structure as the reference project and starts a stdio-oriented container.
Optional: copy the environment template:
cp .env.example .envStart the service:
docker compose -f compose.yml up --buildRun it as a one-off interactive test:
docker compose -f compose.yml run --rm brain-mcpConfiguration
Environment Variables
Variable | Required | Description |
BRAIN_PATH | No | Root path of notes directory (default: ./brain locally, /brain in container) |
BRAIN_MAX_RESULTS | No | Maximum returned search hits (default: 10) |
BRAIN_MAX_FILE_SIZE | No | Max readable file size in bytes (default: 1048576) |
MCP Client Integration
Claude Desktop
Add a server entry to your configuration:
{
"mcpServers": {
"brain": {
"command": "/absolute/path/to/mcp-brain/.venv/bin/brain-mcp",
"env": {
"BRAIN_PATH": "/absolute/path/to/mcp-brain/brain",
"BRAIN_MAX_RESULTS": "10",
"BRAIN_MAX_FILE_SIZE": "1048576"
}
}
}
}VS Code MCP Settings
{
"mcp": {
"servers": {
"brain": {
"command": "/absolute/path/to/mcp-brain/.venv/bin/brain-mcp",
"env": {
"BRAIN_PATH": "/absolute/path/to/mcp-brain/brain",
"BRAIN_MAX_RESULTS": "10",
"BRAIN_MAX_FILE_SIZE": "1048576"
}
}
}
}
}Testing
Unit Tests
./.venv/bin/python -m pytest -qEnd-to-End MCP Smoke Test (Tool Calls)
This starts your server over stdio and calls all tools via the MCP Python client.
BRAIN_PATH=./brain ./.venv/bin/python - <<'PY'
import anyio
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(
command="brain-mcp",
env={
"BRAIN_PATH": "./brain",
"BRAIN_MAX_RESULTS": "10",
"BRAIN_MAX_FILE_SIZE": "1048576",
},
)
async with stdio_client(params) as (read_stream, write_stream):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
tools = await session.list_tools()
print("TOOLS:", [tool.name for tool in tools.tools])
print("LIST:", (await session.call_tool("list_notes", {"path": "People"})).model_dump())
print("READ:", (await session.call_tool("read_note", {"path": "People/John Smith.md"})).model_dump())
print("SEARCH:", (await session.call_tool("search_notes", {"query": "john"})).model_dump())
anyio.run(main)
PYOne-Command Quick Test Script
Use the helper script if you only want to pass a query string and get a quick response.
Local server mode:
./.venv/bin/python scripts/quick_mcp_test.py "john"With custom list path:
./.venv/bin/python scripts/quick_mcp_test.py "festival" --list-path ProjectsAgainst a running Docker container:
./.venv/bin/python scripts/quick_mcp_test.py "john" --container brain-mcpTools Reference
search_notes
Search filenames and file contents for a query.
Input:
{
"query": "john"
}Output (example):
{
"query": "john",
"files_scanned": 12,
"count": 2,
"max_results": 10,
"matches": [
{
"path": "People/John Smith.md",
"size": 321,
"line": 1,
"snippet": "John is a designer..."
}
]
}list_notes
List files and folders under the root or a subpath.
Input:
{
"path": "People"
}Output (example):
{
"path": "People",
"count": 1,
"entries": [
{
"name": "John Smith.md",
"path": "People/John Smith.md",
"type": "file",
"size": 321
}
]
}read_note
Read one note file by relative path.
Input:
{
"path": "People/John Smith.md"
}Output (example):
{
"path": "People/John Smith.md",
"size": 321,
"content": "...full note text..."
}Troubleshooting
AttributeError: 'Server' object has no attribute 'list_tools'
Cause: old MCP v1 server style with MCP v2 runtime.
Fix: this project already uses MCP v2 MCPServer APIs and depends on mcp>=2.0.0 in pyproject.toml. Reinstall:
./.venv/bin/python -m pip install -e .Brain path does not exist
Set BRAIN_PATH correctly and ensure the folder exists:
BRAIN_PATH=./brain brain-mcpPath is outside of the configured brain directory
This is expected protection against path traversal. Use a path relative to BRAIN_PATH.
Project Structure
mcp-brain/
├── src/brain_mcp/
│ ├── __init__.py
│ ├── brain.py
│ ├── config.py
│ ├── security.py
│ └── server.py
├── tests/
│ └── test_brain.py
├── brain/
├── Dockerfile
├── compose.yml
├── pyproject.toml
├── .env.example
└── README.mdLicense
MIT License - see LICENSE.
Available Tools
3 toolslist_notesA
List files and folders in the brain directory, optionally under a subpath.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
There are no annotations, so the description carries the full burden. It states the core behavior (listing files and folders) but does not disclose whether the listing is recursive, whether hidden files are included, or whether only names are returned. The read-only nature is implied by 'List' but not explicit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single 12-word sentence that front-loads the action and resource, with no wasted words. Every part earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple listing tool with one optional parameter and an output schema, the description covers the essential calling context: what is listed and where. It lacks usage guidance versus siblings, but that gap is already captured under usage_guidelines. The optional subpath and root default are communicated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The only parameter, path, is explained as an optional subpath within the brain directory, adding meaning beyond the schema which only provides a title and default null. However, it does not specify the path format or whether null explicitly means the root.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('List') and resource ('files and folders in the brain directory'), with an optional subpath scoping that clearly distinguishes it from search_notes and read_note. An agent can immediately identify this as the directory-listing tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus search_notes or read_note. The description does not mention alternatives or exclusion conditions, leaving the agent to infer that listing is different from searching or reading without explicit routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_noteA
Read a single note file from the brain directory.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden. The verb 'read' conveys a non-destructive operation, and 'from the brain directory' adds scoping context. However, it does not disclose what happens on missing paths, whether content is returned raw or parsed, or any error behavior — acceptable for a simple read but not rich.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence with zero filler. Every word contributes meaning, and the core action plus resource scope are the first things an agent sees.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read tool with an output schema, the description is minimally sufficient: it names the operation, the resource, and the directory. Still, it omits usage context relative to siblings and path format details, which matters more given the 0% schema coverage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for the undocumented 'path' parameter. It adds meaning by indicating that path refers to a note file within the brain directory, but it does not specify path format, absolute vs relative, or accepted file extensions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('read') and resource ('a single note file from the brain directory'), clearly distinguishing it from the sibling tools search_notes and list_notes. An agent can immediately tell this fetches one file's content rather than enumerating or searching notes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'a single note file' implies this tool is for fetching one specific file, contrasting with search/list siblings. However, no explicit when-to-use guidance, exclusion criteria, or alternative tool names are given, leaving the usage context implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_notesA
Search notes by filename and content using a case-insensitive query.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It usefully reveals that matching is case-insensitive and applies to both filename and content, but it does not disclose whether matching is partial, tokenized, or exact, nor does it mention result ordering, limits, or empty-result behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single sentence with the core action and scope front-loaded, followed by the case-insensitivity detail. Every word contributes meaning and there is no redundant or filler content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple: one required parameter, an output schema exists, and no nested objects are involved. The description covers the essential purpose and query semantics; however, it could be slightly more complete by explicitly addressing edge-case behavior or when to use this versus sibling tools, though those gaps are relatively minor.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for the undocumented 'query' parameter. It does so by explaining that the query drives a case-insensitive search across filename and content, which adds real meaning beyond the bare string type in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Search'), identifies the resource ('notes'), and narrows scope to 'filename and content' with a case-insensitive query. This clearly distinguishes it from the sibling tools list_notes and read_note, which are listing and retrieval operations rather than search.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'Search notes by filename and content' gives clear context that this tool is for query-based discovery, not for listing or direct reading. However, it does not explicitly state when to prefer this tool over list_notes or read_note, nor does it mention exclusions such as full-text vs substring matching.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
list_notes - First observed
read_note - First observed
search_notes
TDQS
Scored across 3 tools
Each tool maps to a distinct action on the notes collection: searching across content, listing the directory structure, and reading a specific file. There is no functional overlap between the tools.
All three tools follow a clear verb_noun pattern with snake_case: search_notes, list_notes, and read_note. The singular 'note' is semantically accurate for reading one file)Skip
The server is intentionally scoped to three essential read operations for a personal-knowledge directory. Each tool serves a unique purpose, and the small count is appropriate for a lightweight, focused MCP server.
The read-side surface is well covered: you can discover, search, and view notes. The only notable gap is the absence of any create, update, or delete operations, so if the brain is meant to be fully managed through MCP, the surface is incomplete.
Maintenance
Related MCP Connectors
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Google Keep-style notes app with an MCP server for AI agents to read/write notes.
Securely search and manage workspace context files for AI agents and teams.
Notes, files, GitHub, and Drive through one MCP connection.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceProvides secure, direct file system access to Obsidian vault files, enabling search, read, write, and discovery of notes without requiring the Obsidian app.23-
- AlicenseAqualityCmaintenanceEnables AI assistants to interact with a local folder of Markdown notes, supporting listing, reading, searching, creating, and appending to notes with strict security boundaries.5MIT
- FlicenseNot gradedqualityBmaintenanceProvides controlled, read-only or write-enabled access to a private local Yaps Markdown vault, enabling AI clients to search, read, and manage notes securely.-
- FlicenseNot gradedqualityBmaintenanceEnables saving and searching local notes, with tools to list notes and search backup files.-