Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Here’s a complete README.md you can drop next to your two files.


MCP FS Demo (Server + Host with Ollama)

This repo contains a minimal, robust Model Context Protocol (MCP) setup:

  • mcp_fs_server.py — an MCP server (Streamable HTTP) that safely exposes:

    • fs_list_here() – zero-arg directory listing (can’t be mis-called)
    • fs_read() – forgiving file reader (accepts string or object)
    • fs_summary() – one-shot list + README preview (best for tiny models)
  • host_ollama_fs.py — an MCP host using PydanticAI + Ollama (local LLM).

    • Works across older/newer pydantic-ai APIs.
    • Strong prompt to nudge tool use; resilient output printing.
    • Optional event-loop handler to silence a known AnyIO shutdown warning.

1) Prerequisites

  • Python 3.10+ (3.11 recommended)

  • Ollama installed and running locally

    • Pull at least one instruct model (e.g. llama3.2:1b to start; 3B–7B works better)
  • A folder you’re happy to expose read-only to the model (the sandbox)

⚠️ The server sandboxes access under MCP_FS_BASE. Paths outside that base are rejected.


2) File layout

your-folder/
├── mcp_fs_server.py
├── host_ollama_fs.py
└── README.md     <-- this file

3) Create & activate a virtualenv

python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

4) Install Python dependencies

pip install mcp pydantic uvicorn pydantic-ai openai httpx anyio sniffio

It’s fine if some packages are already present; this command ensures the pieces you need are installed.


5) Prepare a sandbox folder

Pick a real directory with at least one file (e.g., create a README.md):

export MCP_FS_BASE=/home/<you>/safe-demo
mkdir -p "$MCP_FS_BASE"
printf "Hello from README in %s\n" "$MCP_FS_BASE" > "$MCP_FS_BASE/README.md"

On Windows PowerShell: $env:MCP_FS_BASE="C:\Users\<you>\safe-demo"


6) Run the MCP server

python mcp_fs_server.py

You should see something like:

[SERVER] BASE_DIR = /home/<you>/safe-demo
Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

Keep it running.


7) (Optional) Verify the server by hand with curl

The Streamable HTTP transport uses a session. Open an SSE stream and post JSON-RPC with the same MCP-Session-ID.

Terminal A – open SSE (creates/binds session):

SID=$(
  curl -sS -D - -o /dev/null \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json, text/event-stream' \
    -d '{"jsonrpc":"2.0","id":"0","method":"ping","params":{}}' \
    http://127.0.0.1:8000/mcp \
  | awk -F': ' '/^mcp-session-id:/ {print $2}' | tr -d '\r'
)
echo "SID=$SID"

curl -svN \
  -H 'Accept: text/event-stream' \
  -H "MCP-Session-ID: $SID" \
  http://127.0.0.1:8000/mcp

Terminal B – send JSON-RPC commands (same session id):

# initialize (include clientInfo)
curl -sS \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H "MCP-Session-ID: $SID" \
  -d '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{
        "protocolVersion":"2024-11-05",
        "capabilities":{},
        "clientInfo":{"name":"curl","version":"1.0"}
      }}' \
  http://127.0.0.1:8000/mcp

# list tools
curl -sS \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H "MCP-Session-ID: $SID" \
  -d '{"jsonrpc":"2.0","id":"2","method":"tools/list","params":{}}' \
  http://127.0.0.1:8000/mcp

# call tools (examples)
curl -sS \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H "MCP-Session-ID: $SID" \
  -d '{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"fs_list_here","arguments":{}}}' \
  http://127.0.0.1:8000/mcp

curl -sS \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H "MCP-Session-ID: $SID" \
  -d '{"jsonrpc":"2.0","id":"4","method":"tools/call","params":{"name":"fs_read","arguments":"README.md"}}' \
  http://127.0.0.1:8000/mcp

If you prefer cookies instead of headers, open the SSE with -c mcp.jar -b mcp.jar and reuse that cookie jar on POSTs.


8) Run Ollama (local LLM)

In another terminal:

ollama serve
# pull a model if needed:
# ollama pull llama3.2:1b
# (tool-following improves with larger models, e.g., llama3.2:3b / qwen2.5:3b)

9) Run the host (PydanticAI + Ollama)

Open a new terminal (venv active) and run:

python host_ollama_fs.py

Expected behavior:

  • The server logs will show lines like:

    [SERVER] fs_summary on /home/<you>/safe-demo → N files; README = README.md
    

    or

    [SERVER] fs_list_here called → N items
    [SERVER] fs_read args: README.md
    
  • The host prints a short human summary (no code).

If the 1B model ignores tools, switch the prompt in host_ollama_fs.py to the one-shot fs_summary (already the default), or try a slightly larger local model.


10) Troubleshooting

“model X not found, try pulling it first”

  • Ollama error. Run ollama list to see installed tags and update OLLAMA_MODEL in host_ollama_fs.py to match exactly.
  • If you just pulled, restart Ollama: pkill ollama; ollama serve.

The host prints code instead of calling tools

  • Small 1B models often do this. The host prompt already forbids code; still, prefer fs_summary (single call).
  • Try a 3B–7B model for better tool compliance.

Server shows “Processing CallToolRequest” but not your [SERVER] ... prints

  • The call failed before entering the tool (name/args validation).

  • The provided tools avoid this:

    • fs_list_here() has no args
    • fs_read() accepts a string or object
  • Make sure your prompt refers to the exact tool names reported by tools/list.

“Bad Request: Missing session ID” (curl)

  • Use the two-terminal sequence. Send MCP-Session-ID: $SID on both the SSE GET and all POSTs.
  • Or use cookies with the same jar for SSE and POSTs.

“Invalid request parameters” / “Received request before initialization was complete”

  • You posted tools/list before initialize completed for the same session. Wait for the SSE message of the initialize response, then call tools/list.

Noisy shutdown error in the host like:

RuntimeError: Attempted to exit cancel scope in a different task than it was entered in
  • This is a known async teardown quirk when exceptions bubble out during transport cleanup.

  • It does not mean your tools failed (check server logs).

  • Fixes:

    • Upgrade: pip install -U anyio mcp pydantic-ai httpx sniffio
    • Keep the loop-level exception handler included in host_ollama_fs.py (swallows only that known message).

11) Tips

  • Keep MCP_FS_BASE pointed to a folder with at least one file (e.g., README.md) to demo fs_read.
  • For best results with tool use, try a slightly larger model: llama3.2:3b, qwen2.5:3b, etc.
  • You can always validate the server independently via the curl sequence in §7.

12) Commands reference

# venv
python -m venv venv
source venv/bin/activate

# install deps
pip install mcp pydantic uvicorn pydantic-ai openai httpx anyio sniffio

# set base dir for server
export MCP_FS_BASE=/home/<you>/safe-demo

# run server
python mcp_fs_server.py

# run ollama
ollama serve
ollama list
# ollama pull llama3.2:3b  # (optional, better tool following)

# run host
python host_ollama_fs.py

13) What’s exposed (server tool contracts)

  • fs_list_here()no args Returns:

    { "ok": true, "base": "...", "path": ".", "items": [ { "name": "file.txt", "kind": "file", "size": 123, "mtime": 172... }, ... ] }
  • fs_read(args)args can be string (e.g., "README.md") or object:

    { "path": "README.md" }           // or
    { "dir": ".", "name": "README.md" }

    Returns:

    { "ok": true, "path": "README.md", "encoding": "utf-8", "content": "..." }
  • fs_summary({ "path": "." }) → list + README preview in one call.


That’s it! You now have a minimal but sturdy MCP server + host you can run locally and iterate on.

About

A Sandbox to familiarize ourselves with MCP

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages