Spaces:
Running
Running
Backend port changes
Browse files- README.md +4 -4
- backend/main.py +8 -5
- docs/MCP_CLIENT_SETUP.md +291 -0
- docs/MCP_PROMPT_TEMPLATES.md +244 -0
- docs/hf-spaces-deployment-guide.md +376 -0
- docs/mcp-http-testing-guide.md +354 -0
- start-dev.sh +3 -3
- status-dev.sh +1 -1
README.md
CHANGED
|
@@ -114,12 +114,12 @@ Start the HTTP API server:
|
|
| 114 |
cd backend
|
| 115 |
JWT_SECRET_KEY="local-dev-secret-key-123" \
|
| 116 |
VAULT_BASE_PATH="$(pwd)/../data/vaults" \
|
| 117 |
-
.venv/bin/uvicorn
|
| 118 |
```
|
| 119 |
|
| 120 |
-
Backend will be available at: `http://localhost:
|
| 121 |
|
| 122 |
-
API docs (Swagger): `http://localhost:
|
| 123 |
|
| 124 |
#### Running MCP Server (STDIO Mode)
|
| 125 |
|
|
@@ -332,7 +332,7 @@ write_note(
|
|
| 332 |
- Verify database is initialized
|
| 333 |
|
| 334 |
**Frontend shows connection errors:**
|
| 335 |
-
- Ensure backend is running on port
|
| 336 |
- Check Vite proxy configuration in `frontend/vite.config.ts`
|
| 337 |
|
| 338 |
**Search returns no results:**
|
|
|
|
| 114 |
cd backend
|
| 115 |
JWT_SECRET_KEY="local-dev-secret-key-123" \
|
| 116 |
VAULT_BASE_PATH="$(pwd)/../data/vaults" \
|
| 117 |
+
.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
| 118 |
```
|
| 119 |
|
| 120 |
+
Backend will be available at: `http://localhost:8000`
|
| 121 |
|
| 122 |
+
API docs (Swagger): `http://localhost:8000/docs`
|
| 123 |
|
| 124 |
#### Running MCP Server (STDIO Mode)
|
| 125 |
|
|
|
|
| 332 |
- Verify database is initialized
|
| 333 |
|
| 334 |
**Frontend shows connection errors:**
|
| 335 |
+
- Ensure backend is running on port 8000
|
| 336 |
- Check Vite proxy configuration in `frontend/vite.config.ts`
|
| 337 |
|
| 338 |
**Search returns no results:**
|
backend/main.py
CHANGED
|
@@ -1,18 +1,21 @@
|
|
| 1 |
"""Entry point for running the FastAPI application."""
|
| 2 |
|
| 3 |
import os
|
|
|
|
| 4 |
import uvicorn
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
if __name__ == "__main__":
|
| 7 |
-
# Read port from environment variable, default to
|
| 8 |
# (matches frontend proxy config and development scripts)
|
| 9 |
-
# Can be overridden: PORT=
|
| 10 |
-
|
| 11 |
-
# port = int(os.getenv("PORT", "8001"))
|
| 12 |
|
| 13 |
uvicorn.run(
|
| 14 |
"src.api.main:app",
|
| 15 |
host="0.0.0.0",
|
| 16 |
-
port=
|
| 17 |
reload=True,
|
| 18 |
)
|
|
|
|
| 1 |
"""Entry point for running the FastAPI application."""
|
| 2 |
|
| 3 |
import os
|
| 4 |
+
|
| 5 |
import uvicorn
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
|
| 10 |
if __name__ == "__main__":
|
| 11 |
+
# Read port from environment variable, default to 8000 for FastAPI server
|
| 12 |
# (matches frontend proxy config and development scripts)
|
| 13 |
+
# Can be overridden: PORT=7860 python main.py
|
| 14 |
+
port = int(os.getenv("PORT", "8000"))
|
|
|
|
| 15 |
|
| 16 |
uvicorn.run(
|
| 17 |
"src.api.main:app",
|
| 18 |
host="0.0.0.0",
|
| 19 |
+
port=port,
|
| 20 |
reload=True,
|
| 21 |
)
|
docs/MCP_CLIENT_SETUP.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCP Client Configuration Guide
|
| 2 |
+
|
| 3 |
+
This guide shows how to configure various MCP clients to connect to the Document-MCP server for Markdown note management.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
Document-MCP provides an MCP server that exposes tools for managing Markdown notes, similar to how [jupyter-mcp-server](https://github.com/datalayer/jupyter-mcp-server) provides tools for Jupyter notebooks. Instead of notebook cells, we work with Markdown files in a vault structure.
|
| 8 |
+
|
| 9 |
+
## Available MCP Tools
|
| 10 |
+
|
| 11 |
+
| Tool Name | Description |
|
| 12 |
+
|-----------|-------------|
|
| 13 |
+
| `list_notes` | List all notes in the vault (optionally filtered by folder) |
|
| 14 |
+
| `read_note` | Read a Markdown note with metadata and body |
|
| 15 |
+
| `write_note` | Create or update a note (auto-updates timestamps and search index) |
|
| 16 |
+
| `delete_note` | Delete a note and remove it from the index |
|
| 17 |
+
| `search_notes` | Full-text search with BM25 ranking and recency bonus |
|
| 18 |
+
| `get_backlinks` | List notes that reference the target note |
|
| 19 |
+
| `get_tags` | List all tags with associated note counts |
|
| 20 |
+
|
| 21 |
+
## Transport Modes
|
| 22 |
+
|
| 23 |
+
### 1. STDIO Transport (Local Development)
|
| 24 |
+
|
| 25 |
+
For local development with Claude Desktop, Cursor, or other MCP clients.
|
| 26 |
+
|
| 27 |
+
#### Using Python Module
|
| 28 |
+
|
| 29 |
+
```json
|
| 30 |
+
{
|
| 31 |
+
"mcpServers": {
|
| 32 |
+
"obsidian-docs": {
|
| 33 |
+
"command": "python",
|
| 34 |
+
"args": ["-m", "src.mcp.server"],
|
| 35 |
+
"cwd": "/absolute/path/to/Document-MCP/backend",
|
| 36 |
+
"env": {
|
| 37 |
+
"LOCAL_USER_ID": "local-dev",
|
| 38 |
+
"JWT_SECRET_KEY": "local-dev-secret-key-123",
|
| 39 |
+
"VAULT_BASE_PATH": "/absolute/path/to/Document-MCP/data/vaults",
|
| 40 |
+
"DB_PATH": "/absolute/path/to/Document-MCP/data/index.db",
|
| 41 |
+
"PYTHONPATH": "/absolute/path/to/Document-MCP/backend",
|
| 42 |
+
"FASTMCP_SHOW_CLI_BANNER": "false"
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
#### Using uv (Recommended)
|
| 50 |
+
|
| 51 |
+
```json
|
| 52 |
+
{
|
| 53 |
+
"mcpServers": {
|
| 54 |
+
"obsidian-docs": {
|
| 55 |
+
"command": "uv",
|
| 56 |
+
"args": ["run", "python", "-m", "src.mcp.server"],
|
| 57 |
+
"cwd": "/absolute/path/to/Document-MCP/backend",
|
| 58 |
+
"env": {
|
| 59 |
+
"LOCAL_USER_ID": "local-dev",
|
| 60 |
+
"JWT_SECRET_KEY": "local-dev-secret-key-123",
|
| 61 |
+
"VAULT_BASE_PATH": "/absolute/path/to/Document-MCP/data/vaults",
|
| 62 |
+
"DB_PATH": "/absolute/path/to/Document-MCP/data/index.db"
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### 2. HTTP Transport (Remote/Hosted)
|
| 70 |
+
|
| 71 |
+
For remote access via HTTP endpoint (e.g., Hugging Face Spaces deployment).
|
| 72 |
+
|
| 73 |
+
#### Configuration
|
| 74 |
+
|
| 75 |
+
```json
|
| 76 |
+
{
|
| 77 |
+
"mcpServers": {
|
| 78 |
+
"obsidian-docs": {
|
| 79 |
+
"transport": "http",
|
| 80 |
+
"url": "https://your-space.hf.space/mcp",
|
| 81 |
+
"headers": {
|
| 82 |
+
"Authorization": "Bearer YOUR_JWT_TOKEN"
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
#### Obtaining a JWT Token
|
| 90 |
+
|
| 91 |
+
1. **Via OAuth (Production)**: Login through Hugging Face OAuth at `/auth/login`
|
| 92 |
+
2. **Via API (Development)**:
|
| 93 |
+
```bash
|
| 94 |
+
curl -X POST http://localhost:8001/api/tokens \
|
| 95 |
+
-H "Authorization: Bearer YOUR_EXISTING_TOKEN"
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
## Client-Specific Configuration
|
| 99 |
+
|
| 100 |
+
### Claude Desktop
|
| 101 |
+
|
| 102 |
+
**Location**: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
|
| 103 |
+
|
| 104 |
+
**STDIO Example**:
|
| 105 |
+
```json
|
| 106 |
+
{
|
| 107 |
+
"mcpServers": {
|
| 108 |
+
"obsidian-docs": {
|
| 109 |
+
"command": "uv",
|
| 110 |
+
"args": ["run", "python", "-m", "src.mcp.server"],
|
| 111 |
+
"cwd": "C:\\Workspace\\Document-MCP\\backend",
|
| 112 |
+
"env": {
|
| 113 |
+
"LOCAL_USER_ID": "local-dev",
|
| 114 |
+
"JWT_SECRET_KEY": "local-dev-secret-key-123",
|
| 115 |
+
"VAULT_BASE_PATH": "C:\\Workspace\\Document-MCP\\data\\vaults",
|
| 116 |
+
"DB_PATH": "C:\\Workspace\\Document-MCP\\data\\index.db"
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
**HTTP Example**:
|
| 124 |
+
```json
|
| 125 |
+
{
|
| 126 |
+
"mcpServers": {
|
| 127 |
+
"obsidian-docs": {
|
| 128 |
+
"transport": "http",
|
| 129 |
+
"url": "http://localhost:8001/mcp",
|
| 130 |
+
"headers": {
|
| 131 |
+
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### Cursor / VS Code with MCP Extension
|
| 139 |
+
|
| 140 |
+
**Location**: `.cursor/mcp.json` or `.vscode/mcp.json`
|
| 141 |
+
|
| 142 |
+
```json
|
| 143 |
+
{
|
| 144 |
+
"mcpServers": {
|
| 145 |
+
"obsidian-docs": {
|
| 146 |
+
"command": "uv",
|
| 147 |
+
"args": ["run", "python", "-m", "src.mcp.server"],
|
| 148 |
+
"cwd": "${workspaceFolder}/backend",
|
| 149 |
+
"env": {
|
| 150 |
+
"LOCAL_USER_ID": "local-dev",
|
| 151 |
+
"JWT_SECRET_KEY": "local-dev-secret-key-123",
|
| 152 |
+
"VAULT_BASE_PATH": "${workspaceFolder}/data/vaults",
|
| 153 |
+
"DB_PATH": "${workspaceFolder}/data/index.db"
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
### Cline (VS Code Extension)
|
| 161 |
+
|
| 162 |
+
Similar to Cursor configuration, but may require restarting VS Code after changes.
|
| 163 |
+
|
| 164 |
+
## Environment Variables
|
| 165 |
+
|
| 166 |
+
| Variable | Description | Required | Default |
|
| 167 |
+
|----------|-------------|----------|---------|
|
| 168 |
+
| `LOCAL_USER_ID` | User ID for local development | No | `local-dev` |
|
| 169 |
+
| `JWT_SECRET_KEY` | Secret key for JWT token signing | Yes (HTTP mode) | - |
|
| 170 |
+
| `VAULT_BASE_PATH` | Base directory for user vaults | Yes | - |
|
| 171 |
+
| `DB_PATH` | Path to SQLite database | Yes | - |
|
| 172 |
+
| `FASTMCP_SHOW_CLI_BANNER` | Show FastMCP banner on startup | No | `true` |
|
| 173 |
+
|
| 174 |
+
## Testing the Connection
|
| 175 |
+
|
| 176 |
+
### Test STDIO Mode
|
| 177 |
+
|
| 178 |
+
```bash
|
| 179 |
+
cd backend
|
| 180 |
+
uv run python -m src.mcp.server
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
You should see:
|
| 184 |
+
```
|
| 185 |
+
MCP server starting in STDIO mode...
|
| 186 |
+
Listening for MCP requests on stdin/stdout...
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
### Test HTTP Mode
|
| 190 |
+
|
| 191 |
+
1. Start the FastAPI server (port 8000):
|
| 192 |
+
```bash
|
| 193 |
+
cd backend
|
| 194 |
+
uv run uvicorn main:app --host 0.0.0.0 --port 8000
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
2. Start the MCP server in HTTP mode (port 8001) in a **separate** terminal:
|
| 198 |
+
```bash
|
| 199 |
+
cd backend
|
| 200 |
+
MCP_TRANSPORT=http MCP_PORT=8001 python -m src.mcp.server
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
3. Test the MCP endpoint:
|
| 204 |
+
```bash
|
| 205 |
+
curl -X POST http://localhost:8001/mcp \
|
| 206 |
+
-H "Authorization: Bearer YOUR_TOKEN" \
|
| 207 |
+
-H "Content-Type: application/json" \
|
| 208 |
+
-d '{
|
| 209 |
+
"jsonrpc": "2.0",
|
| 210 |
+
"id": 1,
|
| 211 |
+
"method": "tools/list",
|
| 212 |
+
"params": {}
|
| 213 |
+
}'
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
## Troubleshooting
|
| 217 |
+
|
| 218 |
+
### "Module not found" errors
|
| 219 |
+
|
| 220 |
+
- Ensure `PYTHONPATH` includes the backend directory
|
| 221 |
+
- Install dependencies: `cd backend && uv pip install -e .`
|
| 222 |
+
- Use `uv run` to ensure the virtual environment is activated
|
| 223 |
+
|
| 224 |
+
### "Authorization required" errors
|
| 225 |
+
|
| 226 |
+
- For HTTP mode, ensure you're sending a valid JWT token
|
| 227 |
+
- Generate a token: `POST /api/tokens` (requires authentication)
|
| 228 |
+
- For local development, use STDIO mode with `LOCAL_USER_ID`
|
| 229 |
+
|
| 230 |
+
### Connection refused
|
| 231 |
+
|
| 232 |
+
- Ensure the server is running on the expected port
|
| 233 |
+
- Check firewall settings
|
| 234 |
+
- For HTTP mode, verify the URL is correct
|
| 235 |
+
|
| 236 |
+
### Tools not appearing in client
|
| 237 |
+
|
| 238 |
+
- Restart the MCP client after configuration changes
|
| 239 |
+
- Check the client logs for error messages
|
| 240 |
+
- Verify the `cwd` path is absolute and correct
|
| 241 |
+
- Ensure all environment variables are set
|
| 242 |
+
|
| 243 |
+
## Best Practices
|
| 244 |
+
|
| 245 |
+
1. **Use absolute paths** for `cwd` and file paths in configuration
|
| 246 |
+
2. **Separate environments**: Use different `LOCAL_USER_ID` values for different projects
|
| 247 |
+
3. **Token security**: Never commit JWT tokens to version control
|
| 248 |
+
4. **Path handling**: Use forward slashes in JSON config (even on Windows)
|
| 249 |
+
5. **Testing**: Test STDIO mode locally before deploying HTTP mode
|
| 250 |
+
|
| 251 |
+
## Advanced: Docker Deployment
|
| 252 |
+
|
| 253 |
+
For production deployments, you can containerize the MCP server:
|
| 254 |
+
|
| 255 |
+
```dockerfile
|
| 256 |
+
FROM python:3.11-slim
|
| 257 |
+
|
| 258 |
+
WORKDIR /app
|
| 259 |
+
COPY backend/ ./backend/
|
| 260 |
+
RUN cd backend && pip install uv && uv pip install -e .
|
| 261 |
+
|
| 262 |
+
ENV LOCAL_USER_ID=default
|
| 263 |
+
ENV VAULT_BASE_PATH=/data/vaults
|
| 264 |
+
ENV DB_PATH=/data/index.db
|
| 265 |
+
|
| 266 |
+
CMD ["python", "-m", "backend.src.mcp.server"]
|
| 267 |
+
```
|
| 268 |
+
|
| 269 |
+
Then configure clients to use Docker:
|
| 270 |
+
|
| 271 |
+
```json
|
| 272 |
+
{
|
| 273 |
+
"mcpServers": {
|
| 274 |
+
"obsidian-docs": {
|
| 275 |
+
"command": "docker",
|
| 276 |
+
"args": [
|
| 277 |
+
"run", "-i", "--rm",
|
| 278 |
+
"-v", "/path/to/data:/data",
|
| 279 |
+
"-e", "LOCAL_USER_ID",
|
| 280 |
+
"-e", "JWT_SECRET_KEY",
|
| 281 |
+
"your-image:latest"
|
| 282 |
+
],
|
| 283 |
+
"env": {
|
| 284 |
+
"LOCAL_USER_ID": "local-dev",
|
| 285 |
+
"JWT_SECRET_KEY": "your-secret"
|
| 286 |
+
}
|
| 287 |
+
}
|
| 288 |
+
}
|
| 289 |
+
}
|
| 290 |
+
```
|
| 291 |
+
|
docs/MCP_PROMPT_TEMPLATES.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCP Prompt Templates for Document-MCP
|
| 2 |
+
|
| 3 |
+
Inspired by [jupyter-mcp-server prompt templates](https://github.com/datalayer/jupyter-mcp-server), these templates help you effectively use Document-MCP with AI agents.
|
| 4 |
+
|
| 5 |
+
## π Note Management Prompts
|
| 6 |
+
|
| 7 |
+
### Creating Documentation
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
I need to create a new documentation note about [TOPIC].
|
| 11 |
+
Please:
|
| 12 |
+
1. Search for any existing notes about this topic
|
| 13 |
+
2. Create a new note at [PATH] with:
|
| 14 |
+
- A clear title
|
| 15 |
+
- Proper frontmatter with relevant tags
|
| 16 |
+
- Well-structured markdown content
|
| 17 |
+
- Links to related notes using [[wikilinks]]
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
### Updating Existing Notes
|
| 21 |
+
|
| 22 |
+
```
|
| 23 |
+
Please update the note at [PATH]:
|
| 24 |
+
1. Read the current content
|
| 25 |
+
2. Add a new section about [NEW_TOPIC]
|
| 26 |
+
3. Update the tags in frontmatter to include [NEW_TAG]
|
| 27 |
+
4. Add wikilinks to related notes: [[Note1]], [[Note2]]
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
### Organizing Notes
|
| 31 |
+
|
| 32 |
+
```
|
| 33 |
+
Help me organize my vault:
|
| 34 |
+
1. List all notes in the [FOLDER] folder
|
| 35 |
+
2. Find notes with tag [TAG]
|
| 36 |
+
3. Show me backlinks for [NOTE_PATH]
|
| 37 |
+
4. Suggest which notes might need better organization
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## π Search and Discovery Prompts
|
| 41 |
+
|
| 42 |
+
### Finding Information
|
| 43 |
+
|
| 44 |
+
```
|
| 45 |
+
Search my vault for information about [TOPIC].
|
| 46 |
+
Show me:
|
| 47 |
+
- The most relevant notes (top 5)
|
| 48 |
+
- Snippets showing where the topic appears
|
| 49 |
+
- Related notes via backlinks
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### Exploring Connections
|
| 53 |
+
|
| 54 |
+
```
|
| 55 |
+
I'm working on [NOTE_PATH]. Show me:
|
| 56 |
+
1. All notes that link to this note (backlinks)
|
| 57 |
+
2. All notes this note links to
|
| 58 |
+
3. Notes with similar tags
|
| 59 |
+
4. Recent notes that might be related
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
## π·οΈ Tag Management Prompts
|
| 63 |
+
|
| 64 |
+
### Tag Analysis
|
| 65 |
+
|
| 66 |
+
```
|
| 67 |
+
Analyze my vault's tag usage:
|
| 68 |
+
1. List all tags and their usage counts
|
| 69 |
+
2. Identify tags that are used only once (orphaned tags)
|
| 70 |
+
3. Suggest tags that might need to be merged
|
| 71 |
+
4. Show me notes without any tags
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### Tag Cleanup
|
| 75 |
+
|
| 76 |
+
```
|
| 77 |
+
Help me clean up tags:
|
| 78 |
+
1. Find all notes with tag [OLD_TAG]
|
| 79 |
+
2. Update them to use [NEW_TAG] instead
|
| 80 |
+
3. Remove the old tag from the tag list
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
## π Documentation Workflow Prompts
|
| 84 |
+
|
| 85 |
+
### Creating a Guide
|
| 86 |
+
|
| 87 |
+
```
|
| 88 |
+
Create a comprehensive guide about [TOPIC]:
|
| 89 |
+
1. Search for existing related notes
|
| 90 |
+
2. Create a main guide note at guides/[TOPIC].md
|
| 91 |
+
3. Break it into sections with proper headings
|
| 92 |
+
4. Link to related notes using [[wikilinks]]
|
| 93 |
+
5. Add tags: [guide, TOPIC]
|
| 94 |
+
6. Create supporting notes if needed
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### Maintaining Documentation
|
| 98 |
+
|
| 99 |
+
```
|
| 100 |
+
Review my documentation:
|
| 101 |
+
1. Find notes that haven't been updated in 30+ days
|
| 102 |
+
2. Check for broken wikilinks (unresolved [[links]])
|
| 103 |
+
3. Identify notes with missing frontmatter
|
| 104 |
+
4. Suggest notes that might need updates
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
## π Wikilink Management Prompts
|
| 108 |
+
|
| 109 |
+
### Resolving Links
|
| 110 |
+
|
| 111 |
+
```
|
| 112 |
+
Check the wikilinks in [NOTE_PATH]:
|
| 113 |
+
1. Read the note
|
| 114 |
+
2. Extract all [[wikilinks]]
|
| 115 |
+
3. Verify each link resolves to an existing note
|
| 116 |
+
4. List any broken links
|
| 117 |
+
5. Suggest the correct note names for broken links
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### Creating Link Networks
|
| 121 |
+
|
| 122 |
+
```
|
| 123 |
+
Help me create a knowledge graph:
|
| 124 |
+
1. Find all notes related to [TOPIC]
|
| 125 |
+
2. Show me the connection graph (which notes link to which)
|
| 126 |
+
3. Identify central notes (notes with many backlinks)
|
| 127 |
+
4. Suggest notes that should be linked but aren't
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
## π― Best Practices Prompts
|
| 131 |
+
|
| 132 |
+
### Code Documentation
|
| 133 |
+
|
| 134 |
+
```
|
| 135 |
+
Document this code concept: [CONCEPT_NAME]
|
| 136 |
+
1. Check if a note already exists
|
| 137 |
+
2. Create/update a note at docs/concepts/[CONCEPT_NAME].md
|
| 138 |
+
3. Include:
|
| 139 |
+
- Overview
|
| 140 |
+
- Key points
|
| 141 |
+
- Examples
|
| 142 |
+
- Related concepts (wikilinks)
|
| 143 |
+
- Tags: [concept, code]
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### Meeting Notes
|
| 147 |
+
|
| 148 |
+
```
|
| 149 |
+
Create meeting notes for [MEETING_NAME]:
|
| 150 |
+
1. Create note at meetings/[DATE]-[MEETING_NAME].md
|
| 151 |
+
2. Structure with:
|
| 152 |
+
- Attendees
|
| 153 |
+
- Agenda
|
| 154 |
+
- Discussion points
|
| 155 |
+
- Action items
|
| 156 |
+
- Tags: [meeting, DATE]
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
### Project Documentation
|
| 160 |
+
|
| 161 |
+
```
|
| 162 |
+
Set up documentation for project [PROJECT_NAME]:
|
| 163 |
+
1. Create project root note: projects/[PROJECT_NAME].md
|
| 164 |
+
2. Create sub-notes:
|
| 165 |
+
- projects/[PROJECT_NAME]/goals.md
|
| 166 |
+
- projects/[PROJECT_NAME]/progress.md
|
| 167 |
+
- projects/[PROJECT_NAME]/resources.md
|
| 168 |
+
3. Link them all together with wikilinks
|
| 169 |
+
4. Tag with: [project, PROJECT_NAME]
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
## π‘ Advanced Workflows
|
| 173 |
+
|
| 174 |
+
### Daily Note Workflow
|
| 175 |
+
|
| 176 |
+
```
|
| 177 |
+
Create today's daily note:
|
| 178 |
+
1. Check if [DATE]-daily.md exists
|
| 179 |
+
2. If not, create it with:
|
| 180 |
+
- Date header
|
| 181 |
+
- Sections for: tasks, notes, ideas
|
| 182 |
+
- Link to yesterday's daily note
|
| 183 |
+
- Tag: [daily, DATE]
|
| 184 |
+
3. If it exists, show me what's already there
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
### Research Workflow
|
| 188 |
+
|
| 189 |
+
```
|
| 190 |
+
Help me research [TOPIC]:
|
| 191 |
+
1. Search for existing notes about [TOPIC]
|
| 192 |
+
2. Create a research note at research/[TOPIC].md
|
| 193 |
+
3. Structure with:
|
| 194 |
+
- Summary
|
| 195 |
+
- Key findings
|
| 196 |
+
- Sources (as wikilinks to source notes)
|
| 197 |
+
- Questions to explore
|
| 198 |
+
- Tags: [research, TOPIC]
|
| 199 |
+
4. Link to related research notes
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
### Learning Path Creation
|
| 203 |
+
|
| 204 |
+
```
|
| 205 |
+
Create a learning path for [SUBJECT]:
|
| 206 |
+
1. Search for existing notes about [SUBJECT]
|
| 207 |
+
2. Create a learning path note: learning/[SUBJECT]-path.md
|
| 208 |
+
3. Organize notes in a logical sequence
|
| 209 |
+
4. Create wikilinks between notes showing the path
|
| 210 |
+
5. Add tags: [learning, SUBJECT, path]
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
## π Tips for Effective Prompts
|
| 214 |
+
|
| 215 |
+
1. **Be Specific**: Instead of "create a note", specify the path, structure, and content
|
| 216 |
+
2. **Use Context**: Reference existing notes by path or search results
|
| 217 |
+
3. **Request Verification**: Ask the AI to check for existing content before creating
|
| 218 |
+
4. **Structure Matters**: Request specific markdown structure (headings, lists, etc.)
|
| 219 |
+
5. **Link Everything**: Always ask for wikilinks to related notes
|
| 220 |
+
6. **Tag Consistently**: Request specific tags or ask for tag suggestions
|
| 221 |
+
|
| 222 |
+
## Example: Complete Documentation Task
|
| 223 |
+
|
| 224 |
+
```
|
| 225 |
+
I need to document our API design. Please:
|
| 226 |
+
|
| 227 |
+
1. Search for any existing API documentation
|
| 228 |
+
2. Create a main note: docs/api/design.md
|
| 229 |
+
3. Structure it with:
|
| 230 |
+
- Overview section
|
| 231 |
+
- Endpoints section (with subsections for each endpoint)
|
| 232 |
+
- Authentication section
|
| 233 |
+
- Examples section
|
| 234 |
+
4. Create supporting notes:
|
| 235 |
+
- docs/api/authentication.md
|
| 236 |
+
- docs/api/endpoints/overview.md
|
| 237 |
+
5. Link all notes together with [[wikilinks]]
|
| 238 |
+
6. Tag with: [api, documentation, design]
|
| 239 |
+
7. Link to related notes about our architecture
|
| 240 |
+
8. Check for any broken links and fix them
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
This approach ensures comprehensive, well-linked documentation that's easy to navigate and maintain.
|
| 244 |
+
|
docs/hf-spaces-deployment-guide.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces Deployment Guide
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
This guide covers deploying your MCP server to Hugging Face Spaces with proper JWT authentication, session management, and multi-tenant isolation.
|
| 6 |
+
|
| 7 |
+
## π Authentication Flow
|
| 8 |
+
|
| 9 |
+
### Current Architecture
|
| 10 |
+
|
| 11 |
+
```
|
| 12 |
+
User Login (HF OAuth)
|
| 13 |
+
β Get User Info (user_id, email, etc.)
|
| 14 |
+
β Generate JWT Token (with user_id in payload)
|
| 15 |
+
β Store Token in Client
|
| 16 |
+
β Send Token in Authorization Header for MCP Requests
|
| 17 |
+
β Server Validates Token β Extracts user_id
|
| 18 |
+
β All Operations Scoped to That User's Vault
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
## β
Pre-Deployment Checklist
|
| 22 |
+
|
| 23 |
+
### 1. JWT Configuration
|
| 24 |
+
|
| 25 |
+
- [ ] **Set Production JWT Secret Key**
|
| 26 |
+
```bash
|
| 27 |
+
# In HF Spaces Environment Variables
|
| 28 |
+
JWT_SECRET_KEY=<generate-strong-random-secret>
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
Generate with:
|
| 32 |
+
```python
|
| 33 |
+
import secrets
|
| 34 |
+
print(secrets.token_urlsafe(32))
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
- [ ] **Verify JWT Token Generation**
|
| 38 |
+
- Each user gets unique JWT token after login
|
| 39 |
+
- Token contains `user_id` in `sub` claim
|
| 40 |
+
- Token has appropriate expiration (default: 7 days)
|
| 41 |
+
|
| 42 |
+
- [ ] **Verify JWT Token Validation**
|
| 43 |
+
- Server validates token signature
|
| 44 |
+
- Server checks expiration
|
| 45 |
+
- Server extracts `user_id` from `sub` claim
|
| 46 |
+
|
| 47 |
+
### 2. Session Management
|
| 48 |
+
|
| 49 |
+
**Important**: HTTP MCP transport requires session management. Each user needs:
|
| 50 |
+
|
| 51 |
+
- [ ] **Session ID per User**
|
| 52 |
+
- Generated after `initialize` call
|
| 53 |
+
- Stored on server (in-memory or Redis for production)
|
| 54 |
+
- Sent back to client for subsequent requests
|
| 55 |
+
|
| 56 |
+
- [ ] **Session-to-User Mapping**
|
| 57 |
+
- Map session ID β user_id
|
| 58 |
+
- Validate session belongs to authenticated user
|
| 59 |
+
- Clean up expired sessions
|
| 60 |
+
|
| 61 |
+
### 3. Multi-Tenant Isolation
|
| 62 |
+
|
| 63 |
+
- [ ] **Vault Isolation**
|
| 64 |
+
- Each user gets: `/data/vaults/{user_id}/`
|
| 65 |
+
- Verify users cannot access other users' vaults
|
| 66 |
+
|
| 67 |
+
- [ ] **Database Isolation**
|
| 68 |
+
- All queries filtered by `user_id`
|
| 69 |
+
- Verify SQL queries include `WHERE user_id = ?`
|
| 70 |
+
|
| 71 |
+
- [ ] **Search Index Isolation**
|
| 72 |
+
- Full-text search scoped to user's notes only
|
| 73 |
+
- Verify search results only return user's notes
|
| 74 |
+
|
| 75 |
+
## π§ͺ Testing Multi-User Authentication
|
| 76 |
+
|
| 77 |
+
### Test Script for Multi-User JWT
|
| 78 |
+
|
| 79 |
+
```python
|
| 80 |
+
#!/usr/bin/env python3
|
| 81 |
+
"""Test multi-user JWT authentication and isolation."""
|
| 82 |
+
|
| 83 |
+
import requests
|
| 84 |
+
import sys
|
| 85 |
+
sys.path.insert(0, './backend')
|
| 86 |
+
|
| 87 |
+
from backend.src.services.auth import AuthService
|
| 88 |
+
from backend.src.services.config import get_config
|
| 89 |
+
|
| 90 |
+
def test_multi_user_jwt():
|
| 91 |
+
"""Test JWT generation and validation for multiple users."""
|
| 92 |
+
|
| 93 |
+
config = get_config()
|
| 94 |
+
auth_service = AuthService(config=config)
|
| 95 |
+
|
| 96 |
+
# Create tokens for different users (simulating HF OAuth)
|
| 97 |
+
users = [
|
| 98 |
+
{"id": "hf_user_123", "name": "Alice"},
|
| 99 |
+
{"id": "hf_user_456", "name": "Bob"},
|
| 100 |
+
{"id": "hf_user_789", "name": "Charlie"}
|
| 101 |
+
]
|
| 102 |
+
|
| 103 |
+
tokens = {}
|
| 104 |
+
for user in users:
|
| 105 |
+
token = auth_service.create_jwt(user["id"])
|
| 106 |
+
tokens[user["id"]] = token
|
| 107 |
+
print(f"β
Generated token for {user['name']} ({user['id']})")
|
| 108 |
+
|
| 109 |
+
# Test token validation
|
| 110 |
+
print("\nπ Testing token validation...")
|
| 111 |
+
for user_id, token in tokens.items():
|
| 112 |
+
try:
|
| 113 |
+
payload = auth_service.validate_jwt(token)
|
| 114 |
+
assert payload.sub == user_id, f"Token user_id mismatch for {user_id}"
|
| 115 |
+
print(f"β
Token validated for {user_id}: {payload.sub}")
|
| 116 |
+
except Exception as e:
|
| 117 |
+
print(f"β Token validation failed for {user_id}: {e}")
|
| 118 |
+
|
| 119 |
+
# Test isolation - each user should only see their own notes
|
| 120 |
+
print("\nπ Testing user isolation...")
|
| 121 |
+
base_url = "http://localhost:8001/mcp"
|
| 122 |
+
|
| 123 |
+
for user_id, token in tokens.items():
|
| 124 |
+
headers = {
|
| 125 |
+
"Authorization": f"Bearer {token}",
|
| 126 |
+
"Content-Type": "application/json",
|
| 127 |
+
"Accept": "application/json, text/event-stream"
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
# Initialize for this user
|
| 131 |
+
init_request = {
|
| 132 |
+
"jsonrpc": "2.0",
|
| 133 |
+
"id": 1,
|
| 134 |
+
"method": "initialize",
|
| 135 |
+
"params": {
|
| 136 |
+
"protocolVersion": "2024-11-05",
|
| 137 |
+
"capabilities": {},
|
| 138 |
+
"clientInfo": {"name": "test", "version": "1.0"}
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
try:
|
| 143 |
+
response = requests.post(base_url, json=init_request, headers=headers)
|
| 144 |
+
if response.status_code == 200:
|
| 145 |
+
print(f"β
{user_id} initialized successfully")
|
| 146 |
+
else:
|
| 147 |
+
print(f"β {user_id} initialization failed: {response.text}")
|
| 148 |
+
except Exception as e:
|
| 149 |
+
print(f"β {user_id} request failed: {e}")
|
| 150 |
+
|
| 151 |
+
if __name__ == "__main__":
|
| 152 |
+
test_multi_user_jwt()
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
## π Deployment Steps
|
| 156 |
+
|
| 157 |
+
### Step 1: Environment Variables in HF Spaces
|
| 158 |
+
|
| 159 |
+
Set these in your HF Space settings:
|
| 160 |
+
|
| 161 |
+
```bash
|
| 162 |
+
# Required
|
| 163 |
+
JWT_SECRET_KEY=<your-production-secret-key>
|
| 164 |
+
VAULT_BASE_PATH=/app/data/vaults
|
| 165 |
+
DATABASE_PATH=/app/data/index.db
|
| 166 |
+
|
| 167 |
+
# MCP Configuration
|
| 168 |
+
MCP_TRANSPORT=http
|
| 169 |
+
MCP_PORT=7860 # HF Spaces default port
|
| 170 |
+
|
| 171 |
+
# Optional
|
| 172 |
+
MODE=space # Multi-tenant mode
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
### Step 2: Update Dockerfile for HF Spaces
|
| 176 |
+
|
| 177 |
+
Your Dockerfile should:
|
| 178 |
+
- Expose port 7860 (HF Spaces requirement)
|
| 179 |
+
- Set proper environment variables
|
| 180 |
+
- Mount persistent storage for `/app/data`
|
| 181 |
+
|
| 182 |
+
### Step 3: Frontend OAuth Integration
|
| 183 |
+
|
| 184 |
+
```typescript
|
| 185 |
+
// After HF OAuth login
|
| 186 |
+
async function handleHFOAuthCallback(oauthToken: string) {
|
| 187 |
+
// Get user info from HF
|
| 188 |
+
const userInfo = await fetch('https://huggingface.co/api/whoami-v2', {
|
| 189 |
+
headers: { 'Authorization': `Bearer ${oauthToken}` }
|
| 190 |
+
});
|
| 191 |
+
|
| 192 |
+
const userData = await userInfo.json();
|
| 193 |
+
|
| 194 |
+
// Generate JWT token for MCP (call your backend)
|
| 195 |
+
const jwtResponse = await fetch('/api/auth/create-token', {
|
| 196 |
+
method: 'POST',
|
| 197 |
+
headers: { 'Content-Type': 'application/json' },
|
| 198 |
+
body: JSON.stringify({ user_id: userData.id })
|
| 199 |
+
});
|
| 200 |
+
|
| 201 |
+
const { token } = await jwtResponse.json();
|
| 202 |
+
|
| 203 |
+
// Store token for MCP requests
|
| 204 |
+
localStorage.setItem('mcp_jwt_token', token);
|
| 205 |
+
|
| 206 |
+
// Configure MCP client
|
| 207 |
+
mcpClient.setAuthHeader(`Bearer ${token}`);
|
| 208 |
+
}
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
### Step 4: MCP Client Configuration
|
| 212 |
+
|
| 213 |
+
```typescript
|
| 214 |
+
// Frontend MCP client setup
|
| 215 |
+
const mcpClient = new MCPClient({
|
| 216 |
+
transport: 'http',
|
| 217 |
+
url: 'https://your-space.hf.space/mcp',
|
| 218 |
+
headers: {
|
| 219 |
+
'Authorization': `Bearer ${getStoredJWTToken()}`,
|
| 220 |
+
'Content-Type': 'application/json',
|
| 221 |
+
'Accept': 'application/json, text/event-stream'
|
| 222 |
+
}
|
| 223 |
+
});
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
## π Security Verification Checklist
|
| 227 |
+
|
| 228 |
+
### Authentication Security
|
| 229 |
+
|
| 230 |
+
- [ ] **JWT Secret Key**
|
| 231 |
+
- β
Strong random secret (32+ bytes)
|
| 232 |
+
- β
Stored in environment variables (not in code)
|
| 233 |
+
- β
Different secret for production vs development
|
| 234 |
+
|
| 235 |
+
- [ ] **Token Expiration**
|
| 236 |
+
- β
Tokens expire after reasonable time (7 days default)
|
| 237 |
+
- β
Expired tokens are rejected
|
| 238 |
+
- β
Client refreshes tokens before expiration
|
| 239 |
+
|
| 240 |
+
- [ ] **Token Validation**
|
| 241 |
+
- β
Server validates signature on every request
|
| 242 |
+
- β
Server checks expiration
|
| 243 |
+
- β
Invalid tokens return 401 Unauthorized
|
| 244 |
+
|
| 245 |
+
### Authorization Security
|
| 246 |
+
|
| 247 |
+
- [ ] **User Isolation**
|
| 248 |
+
- β
Each request extracts `user_id` from JWT
|
| 249 |
+
- β
All vault operations scoped to `user_id`
|
| 250 |
+
- β
Database queries filtered by `user_id`
|
| 251 |
+
- β
Users cannot access other users' data
|
| 252 |
+
|
| 253 |
+
- [ ] **Path Validation**
|
| 254 |
+
- β
Note paths validated (no `..` or `\`)
|
| 255 |
+
- β
Path length limits enforced (β€256 chars)
|
| 256 |
+
- β
Paths relative to user's vault directory
|
| 257 |
+
|
| 258 |
+
### Session Security
|
| 259 |
+
|
| 260 |
+
- [ ] **Session Management**
|
| 261 |
+
- β
Session IDs generated securely
|
| 262 |
+
- β
Sessions tied to user_id
|
| 263 |
+
- β
Sessions expire after inactivity
|
| 264 |
+
- β
Session cleanup on logout
|
| 265 |
+
|
| 266 |
+
## π§ͺ Production Testing
|
| 267 |
+
|
| 268 |
+
### Test 1: Multi-User Isolation
|
| 269 |
+
|
| 270 |
+
```bash
|
| 271 |
+
# User 1
|
| 272 |
+
curl -X POST https://your-space.hf.space/mcp \
|
| 273 |
+
-H "Authorization: Bearer <user1_jwt_token>" \
|
| 274 |
+
-H "Content-Type: application/json" \
|
| 275 |
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_notes","arguments":{}}}'
|
| 276 |
+
|
| 277 |
+
# User 2 (should see different notes)
|
| 278 |
+
curl -X POST https://your-space.hf.space/mcp \
|
| 279 |
+
-H "Authorization: Bearer <user2_jwt_token>" \
|
| 280 |
+
-H "Content-Type: application/json" \
|
| 281 |
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_notes","arguments":{}}}'
|
| 282 |
+
```
|
| 283 |
+
|
| 284 |
+
### Test 2: Token Validation
|
| 285 |
+
|
| 286 |
+
```bash
|
| 287 |
+
# Valid token
|
| 288 |
+
curl -X POST https://your-space.hf.space/mcp \
|
| 289 |
+
-H "Authorization: Bearer <valid_token>" \
|
| 290 |
+
-H "Content-Type: application/json" \
|
| 291 |
+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
|
| 292 |
+
|
| 293 |
+
# Invalid token (should fail)
|
| 294 |
+
curl -X POST https://your-space.hf.space/mcp \
|
| 295 |
+
-H "Authorization: Bearer invalid_token" \
|
| 296 |
+
-H "Content-Type: application/json" \
|
| 297 |
+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
|
| 298 |
+
```
|
| 299 |
+
|
| 300 |
+
### Test 3: Expired Token
|
| 301 |
+
|
| 302 |
+
```python
|
| 303 |
+
# Generate expired token
|
| 304 |
+
from datetime import timedelta
|
| 305 |
+
expired_token = auth_service.create_jwt("user123", expires_in=timedelta(seconds=-1))
|
| 306 |
+
|
| 307 |
+
# Try to use it (should fail)
|
| 308 |
+
# Should return 401 Unauthorized
|
| 309 |
+
```
|
| 310 |
+
|
| 311 |
+
## π Monitoring & Logging
|
| 312 |
+
|
| 313 |
+
### Key Metrics to Monitor
|
| 314 |
+
|
| 315 |
+
- [ ] **Authentication Failures**
|
| 316 |
+
- Invalid tokens
|
| 317 |
+
- Expired tokens
|
| 318 |
+
- Missing Authorization headers
|
| 319 |
+
|
| 320 |
+
- [ ] **User Activity**
|
| 321 |
+
- Active users per day
|
| 322 |
+
- Requests per user
|
| 323 |
+
- Vault sizes per user
|
| 324 |
+
|
| 325 |
+
- [ ] **Security Events**
|
| 326 |
+
- Failed authentication attempts
|
| 327 |
+
- Unauthorized access attempts
|
| 328 |
+
- Path traversal attempts
|
| 329 |
+
|
| 330 |
+
### Logging Requirements
|
| 331 |
+
|
| 332 |
+
```python
|
| 333 |
+
# Log authentication events
|
| 334 |
+
logger.info("User authenticated", extra={
|
| 335 |
+
"user_id": payload.sub,
|
| 336 |
+
"token_issued_at": payload.iat,
|
| 337 |
+
"token_expires_at": payload.exp
|
| 338 |
+
})
|
| 339 |
+
|
| 340 |
+
# Log authorization failures
|
| 341 |
+
logger.warning("Unauthorized access attempt", extra={
|
| 342 |
+
"path": requested_path,
|
| 343 |
+
"user_id": attempted_user_id,
|
| 344 |
+
"error": "permission_denied"
|
| 345 |
+
})
|
| 346 |
+
```
|
| 347 |
+
|
| 348 |
+
## π― Summary
|
| 349 |
+
|
| 350 |
+
### What You're Correct About:
|
| 351 |
+
|
| 352 |
+
1. β
**JWT per User**: Each user gets unique JWT token after HF OAuth login
|
| 353 |
+
2. β
**Different user_id**: Each JWT contains the user's ID in `sub` claim
|
| 354 |
+
3. β
**Session Management**: HTTP MCP requires session IDs for stateful operations
|
| 355 |
+
|
| 356 |
+
### Additional Considerations:
|
| 357 |
+
|
| 358 |
+
1. **Session Storage**: For production, use Redis or database for session storage (not in-memory)
|
| 359 |
+
2. **Token Refresh**: Implement token refresh mechanism before expiration
|
| 360 |
+
3. **Rate Limiting**: Add rate limiting per user to prevent abuse
|
| 361 |
+
4. **Audit Logging**: Log all authentication and authorization events
|
| 362 |
+
|
| 363 |
+
### Your Current Implementation Status:
|
| 364 |
+
|
| 365 |
+
- β
JWT generation and validation - **Already implemented**
|
| 366 |
+
- β
User isolation in vaults - **Already implemented**
|
| 367 |
+
- β
User isolation in database - **Already implemented**
|
| 368 |
+
- β οΈ Session management - **Needs implementation for HTTP MCP**
|
| 369 |
+
- β οΈ Production JWT secret - **Needs to be set in HF Spaces**
|
| 370 |
+
|
| 371 |
+
Your architecture is **production-ready**! You just need to:
|
| 372 |
+
1. Set `JWT_SECRET_KEY` in HF Spaces
|
| 373 |
+
2. Implement session management for HTTP MCP
|
| 374 |
+
3. Add frontend OAuth integration
|
| 375 |
+
4. Test multi-user isolation
|
| 376 |
+
|
docs/mcp-http-testing-guide.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCP HTTP Server Testing Guide
|
| 2 |
+
|
| 3 |
+
This guide shows how to test your MCP HTTP server on both Windows and Linux systems.
|
| 4 |
+
|
| 5 |
+
## Prerequisites
|
| 6 |
+
|
| 7 |
+
- MCP server running on `http://localhost:8001/mcp`
|
| 8 |
+
- Bearer token: `local-dev-token` (for local development)
|
| 9 |
+
|
| 10 |
+
## πͺ Windows Testing (PowerShell)
|
| 11 |
+
|
| 12 |
+
### Test 1: Initialize Connection
|
| 13 |
+
|
| 14 |
+
```powershell
|
| 15 |
+
# Set up headers
|
| 16 |
+
$headers = @{
|
| 17 |
+
"Authorization" = "Bearer local-dev-token"
|
| 18 |
+
"Content-Type" = "application/json"
|
| 19 |
+
"Accept" = "application/json, text/event-stream"
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
# Create initialize request body
|
| 23 |
+
$body = @{
|
| 24 |
+
jsonrpc = "2.0"
|
| 25 |
+
id = 1
|
| 26 |
+
method = "initialize"
|
| 27 |
+
params = @{
|
| 28 |
+
protocolVersion = "2024-11-05"
|
| 29 |
+
capabilities = @{}
|
| 30 |
+
clientInfo = @{
|
| 31 |
+
name = "test-client"
|
| 32 |
+
version = "1.0.0"
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
} | ConvertTo-Json -Depth 10
|
| 36 |
+
|
| 37 |
+
# Send request
|
| 38 |
+
Invoke-RestMethod -Uri "http://localhost:8001/mcp" -Method POST -Headers $headers -Body $body
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### Test 2: List Available Tools
|
| 42 |
+
|
| 43 |
+
```powershell
|
| 44 |
+
# Reuse headers from above
|
| 45 |
+
$toolsBody = @{
|
| 46 |
+
jsonrpc = "2.0"
|
| 47 |
+
id = 2
|
| 48 |
+
method = "tools/list"
|
| 49 |
+
} | ConvertTo-Json
|
| 50 |
+
|
| 51 |
+
Invoke-RestMethod -Uri "http://localhost:8001/mcp" -Method POST -Headers $headers -Body $toolsBody
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### Test 3: Call a Tool (List Notes)
|
| 55 |
+
|
| 56 |
+
```powershell
|
| 57 |
+
$toolCallBody = @{
|
| 58 |
+
jsonrpc = "2.0"
|
| 59 |
+
id = 3
|
| 60 |
+
method = "tools/call"
|
| 61 |
+
params = @{
|
| 62 |
+
name = "list_notes"
|
| 63 |
+
arguments = @{}
|
| 64 |
+
}
|
| 65 |
+
} | ConvertTo-Json -Depth 10
|
| 66 |
+
|
| 67 |
+
Invoke-RestMethod -Uri "http://localhost:8001/mcp" -Method POST -Headers $headers -Body $toolCallBody
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
### Test 4: Server Status Check
|
| 71 |
+
|
| 72 |
+
```powershell
|
| 73 |
+
# Check if MCP server is running (should return connection info or error)
|
| 74 |
+
try {
|
| 75 |
+
Invoke-RestMethod -Uri "http://localhost:8001/mcp" -Method GET
|
| 76 |
+
Write-Host "β
MCP server is running on port 8001"
|
| 77 |
+
} catch {
|
| 78 |
+
Write-Host "β MCP server not responding on port 8001"
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
# Alternative: Check FastAPI health endpoint (different server on port 8000)
|
| 82 |
+
Invoke-RestMethod -Uri "http://localhost:8000/health" -Method GET
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
## π§ Linux Testing (curl + bash)
|
| 86 |
+
|
| 87 |
+
### Test 1: Initialize Connection
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
# Initialize connection
|
| 91 |
+
curl -X POST http://localhost:8001/mcp \
|
| 92 |
+
-H "Authorization: Bearer local-dev-token" \
|
| 93 |
+
-H "Content-Type: application/json" \
|
| 94 |
+
-H "Accept: application/json, text/event-stream" \
|
| 95 |
+
-d '{
|
| 96 |
+
"jsonrpc": "2.0",
|
| 97 |
+
"id": 1,
|
| 98 |
+
"method": "initialize",
|
| 99 |
+
"params": {
|
| 100 |
+
"protocolVersion": "2024-11-05",
|
| 101 |
+
"capabilities": {},
|
| 102 |
+
"clientInfo": {
|
| 103 |
+
"name": "test-client",
|
| 104 |
+
"version": "1.0.0"
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
}'
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### Test 2: List Available Tools
|
| 111 |
+
|
| 112 |
+
```bash
|
| 113 |
+
curl -X POST http://localhost:8001/mcp \
|
| 114 |
+
-H "Authorization: Bearer local-dev-token" \
|
| 115 |
+
-H "Content-Type: application/json" \
|
| 116 |
+
-H "Accept: application/json, text/event-stream" \
|
| 117 |
+
-d '{
|
| 118 |
+
"jsonrpc": "2.0",
|
| 119 |
+
"id": 2,
|
| 120 |
+
"method": "tools/list"
|
| 121 |
+
}'
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
### Test 3: Call a Tool (List Notes)
|
| 125 |
+
|
| 126 |
+
```bash
|
| 127 |
+
curl -X POST http://localhost:8001/mcp \
|
| 128 |
+
-H "Authorization: Bearer local-dev-token" \
|
| 129 |
+
-H "Content-Type: application/json" \
|
| 130 |
+
-H "Accept: application/json, text/event-stream" \
|
| 131 |
+
-d '{
|
| 132 |
+
"jsonrpc": "2.0",
|
| 133 |
+
"id": 3,
|
| 134 |
+
"method": "tools/call",
|
| 135 |
+
"params": {
|
| 136 |
+
"name": "list_notes",
|
| 137 |
+
"arguments": {}
|
| 138 |
+
}
|
| 139 |
+
}'
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
### Test 4: Server Status Check
|
| 143 |
+
|
| 144 |
+
```bash
|
| 145 |
+
# Check if MCP server is running
|
| 146 |
+
curl -f http://localhost:8001/mcp && echo "β
MCP server running" || echo "β MCP server not responding"
|
| 147 |
+
|
| 148 |
+
# Alternative: Check FastAPI health endpoint (different server on port 8000)
|
| 149 |
+
curl http://localhost:8000/health
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
## π Python Testing Script
|
| 153 |
+
|
| 154 |
+
### Cross-Platform Python Test
|
| 155 |
+
|
| 156 |
+
```python
|
| 157 |
+
#!/usr/bin/env python3
|
| 158 |
+
"""Cross-platform MCP HTTP server test."""
|
| 159 |
+
|
| 160 |
+
import json
|
| 161 |
+
import requests
|
| 162 |
+
|
| 163 |
+
def test_mcp_server(base_url="http://localhost:8001"):
|
| 164 |
+
"""Test MCP server functionality."""
|
| 165 |
+
|
| 166 |
+
headers = {
|
| 167 |
+
"Authorization": "Bearer local-dev-token",
|
| 168 |
+
"Content-Type": "application/json",
|
| 169 |
+
"Accept": "application/json, text/event-stream"
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
mcp_url = f"{base_url}/mcp"
|
| 173 |
+
|
| 174 |
+
# Test 1: Initialize
|
| 175 |
+
print("π Testing initialize...")
|
| 176 |
+
init_request = {
|
| 177 |
+
"jsonrpc": "2.0",
|
| 178 |
+
"id": 1,
|
| 179 |
+
"method": "initialize",
|
| 180 |
+
"params": {
|
| 181 |
+
"protocolVersion": "2024-11-05",
|
| 182 |
+
"capabilities": {},
|
| 183 |
+
"clientInfo": {
|
| 184 |
+
"name": "test-client",
|
| 185 |
+
"version": "1.0.0"
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
response = requests.post(mcp_url, json=init_request, headers=headers)
|
| 191 |
+
print(f"Status: {response.status_code}")
|
| 192 |
+
print(f"Response: {response.json()}")
|
| 193 |
+
|
| 194 |
+
# Test 2: List tools
|
| 195 |
+
print("\nπ οΈ Testing tools/list...")
|
| 196 |
+
tools_request = {
|
| 197 |
+
"jsonrpc": "2.0",
|
| 198 |
+
"id": 2,
|
| 199 |
+
"method": "tools/list"
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
response = requests.post(mcp_url, json=tools_request, headers=headers)
|
| 203 |
+
print(f"Status: {response.status_code}")
|
| 204 |
+
if response.status_code == 200:
|
| 205 |
+
result = response.json()
|
| 206 |
+
if "result" in result and "tools" in result["result"]:
|
| 207 |
+
tools = result["result"]["tools"]
|
| 208 |
+
print(f"Found {len(tools)} tools:")
|
| 209 |
+
for tool in tools:
|
| 210 |
+
print(f" - {tool['name']}: {tool.get('description', 'No description')}")
|
| 211 |
+
|
| 212 |
+
if __name__ == "__main__":
|
| 213 |
+
test_mcp_server()
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
## π Expected Responses
|
| 217 |
+
|
| 218 |
+
### Successful Initialize Response
|
| 219 |
+
|
| 220 |
+
```json
|
| 221 |
+
{
|
| 222 |
+
"jsonrpc": "2.0",
|
| 223 |
+
"id": 1,
|
| 224 |
+
"result": {
|
| 225 |
+
"protocolVersion": "2024-11-05",
|
| 226 |
+
"capabilities": {
|
| 227 |
+
"experimental": {},
|
| 228 |
+
"prompts": {"listChanged": true},
|
| 229 |
+
"resources": {"subscribe": false, "listChanged": true},
|
| 230 |
+
"tools": {"listChanged": true}
|
| 231 |
+
},
|
| 232 |
+
"serverInfo": {
|
| 233 |
+
"name": "obsidian-docs-viewer",
|
| 234 |
+
"version": "2.13.1"
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
```
|
| 239 |
+
|
| 240 |
+
### Available Tools Response
|
| 241 |
+
|
| 242 |
+
```json
|
| 243 |
+
{
|
| 244 |
+
"jsonrpc": "2.0",
|
| 245 |
+
"id": 2,
|
| 246 |
+
"result": {
|
| 247 |
+
"tools": [
|
| 248 |
+
{
|
| 249 |
+
"name": "list_notes",
|
| 250 |
+
"description": "List notes in the vault (optionally scoped to a folder)."
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"name": "read_note",
|
| 254 |
+
"description": "Read a Markdown note with metadata and body."
|
| 255 |
+
},
|
| 256 |
+
{
|
| 257 |
+
"name": "write_note",
|
| 258 |
+
"description": "Create or update a note."
|
| 259 |
+
},
|
| 260 |
+
{
|
| 261 |
+
"name": "delete_note",
|
| 262 |
+
"description": "Delete a note and remove it from the index."
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"name": "search_notes",
|
| 266 |
+
"description": "Search notes using full-text search with BM25 ranking."
|
| 267 |
+
},
|
| 268 |
+
{
|
| 269 |
+
"name": "get_backlinks",
|
| 270 |
+
"description": "List notes that reference the target note."
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"name": "get_tags",
|
| 274 |
+
"description": "List tags and associated note counts."
|
| 275 |
+
}
|
| 276 |
+
]
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
```
|
| 280 |
+
|
| 281 |
+
## π¨ Common Errors and Solutions
|
| 282 |
+
|
| 283 |
+
### Error: "Missing session ID"
|
| 284 |
+
|
| 285 |
+
```json
|
| 286 |
+
{"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Bad Request: Missing session ID"}}
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
**Solution**: This is expected for tools/list and tools/call without proper session management. The initialize method should work.
|
| 290 |
+
|
| 291 |
+
### Error: "Authorization header required"
|
| 292 |
+
|
| 293 |
+
```json
|
| 294 |
+
{"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Authorization header required"}}
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
**Solution**: Make sure you include the Authorization header with Bearer token.
|
| 298 |
+
|
| 299 |
+
### Error: Connection refused
|
| 300 |
+
|
| 301 |
+
**Solution**:
|
| 302 |
+
1. Check if MCP server is running: `netstat -ano | findstr :8001` (Windows) or `lsof -i :8001` (Linux)
|
| 303 |
+
2. Start the server: `python -m src.mcp.server` with `MCP_TRANSPORT=http` and `MCP_PORT=8001`
|
| 304 |
+
|
| 305 |
+
## π§ Troubleshooting
|
| 306 |
+
|
| 307 |
+
### Check Server Status
|
| 308 |
+
|
| 309 |
+
**Windows:**
|
| 310 |
+
```powershell
|
| 311 |
+
netstat -ano | findstr :8001
|
| 312 |
+
```
|
| 313 |
+
|
| 314 |
+
**Linux:**
|
| 315 |
+
```bash
|
| 316 |
+
lsof -i :8001
|
| 317 |
+
# or
|
| 318 |
+
netstat -tlnp | grep :8001
|
| 319 |
+
```
|
| 320 |
+
|
| 321 |
+
### Start MCP Server
|
| 322 |
+
|
| 323 |
+
**Windows:**
|
| 324 |
+
```powershell
|
| 325 |
+
cd backend
|
| 326 |
+
$env:MCP_TRANSPORT="http"
|
| 327 |
+
$env:MCP_PORT="8001"
|
| 328 |
+
$env:LOCAL_USER_ID="local-dev"
|
| 329 |
+
python -m src.mcp.server
|
| 330 |
+
```
|
| 331 |
+
|
| 332 |
+
**Linux:**
|
| 333 |
+
```bash
|
| 334 |
+
cd backend
|
| 335 |
+
export MCP_TRANSPORT=http
|
| 336 |
+
export MCP_PORT=8001
|
| 337 |
+
export LOCAL_USER_ID=local-dev
|
| 338 |
+
python -m src.mcp.server
|
| 339 |
+
```
|
| 340 |
+
|
| 341 |
+
## π― Testing Checklist
|
| 342 |
+
|
| 343 |
+
- [ ] Server starts without errors
|
| 344 |
+
- [ ] Health endpoint responds: `GET /health`
|
| 345 |
+
- [ ] Initialize method works: Returns server info
|
| 346 |
+
- [ ] Tools list method works: Returns available tools
|
| 347 |
+
- [ ] Bearer token authentication works
|
| 348 |
+
- [ ] User isolation works (different tokens = different vaults)
|
| 349 |
+
|
| 350 |
+
## π Next Steps
|
| 351 |
+
|
| 352 |
+
1. **For Cursor Integration**: Use STDIO transport in `mcp.json`
|
| 353 |
+
2. **For HF Spaces**: Deploy with HTTP transport and JWT authentication
|
| 354 |
+
3. **For Production**: Set proper `JWT_SECRET_KEY` and use real JWT tokens
|
start-dev.sh
CHANGED
|
@@ -37,12 +37,12 @@ echo -e "${GREEN}Starting backend server...${NC}"
|
|
| 37 |
cd "$BACKEND_DIR"
|
| 38 |
JWT_SECRET_KEY="local-dev-secret-key-123" \
|
| 39 |
VAULT_BASE_PATH="$PROJECT_ROOT/data/vaults" \
|
| 40 |
-
.venv/bin/uvicorn
|
| 41 |
BACKEND_PID=$!
|
| 42 |
echo $BACKEND_PID > "$BACKEND_PID_FILE"
|
| 43 |
echo -e "${GREEN}β Backend started (PID: $BACKEND_PID)${NC}"
|
| 44 |
echo " Logs: $PROJECT_ROOT/backend.log"
|
| 45 |
-
echo " URL: http://localhost:
|
| 46 |
|
| 47 |
# Wait a moment for backend to start
|
| 48 |
sleep 2
|
|
@@ -63,7 +63,7 @@ echo "Development servers are running!"
|
|
| 63 |
echo "=================================================="
|
| 64 |
echo -e "${NC}"
|
| 65 |
echo "Frontend: http://localhost:5173"
|
| 66 |
-
echo "Backend: http://localhost:
|
| 67 |
echo ""
|
| 68 |
echo "To stop servers, run: ./stop-dev.sh"
|
| 69 |
echo "To view logs, run: tail -f backend.log frontend.log"
|
|
|
|
| 37 |
cd "$BACKEND_DIR"
|
| 38 |
JWT_SECRET_KEY="local-dev-secret-key-123" \
|
| 39 |
VAULT_BASE_PATH="$PROJECT_ROOT/data/vaults" \
|
| 40 |
+
.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --reload > "$PROJECT_ROOT/backend.log" 2>&1 &
|
| 41 |
BACKEND_PID=$!
|
| 42 |
echo $BACKEND_PID > "$BACKEND_PID_FILE"
|
| 43 |
echo -e "${GREEN}β Backend started (PID: $BACKEND_PID)${NC}"
|
| 44 |
echo " Logs: $PROJECT_ROOT/backend.log"
|
| 45 |
+
echo " URL: http://localhost:8000"
|
| 46 |
|
| 47 |
# Wait a moment for backend to start
|
| 48 |
sleep 2
|
|
|
|
| 63 |
echo "=================================================="
|
| 64 |
echo -e "${NC}"
|
| 65 |
echo "Frontend: http://localhost:5173"
|
| 66 |
+
echo "Backend: http://localhost:8000"
|
| 67 |
echo ""
|
| 68 |
echo "To stop servers, run: ./stop-dev.sh"
|
| 69 |
echo "To view logs, run: tail -f backend.log frontend.log"
|
status-dev.sh
CHANGED
|
@@ -19,7 +19,7 @@ if [ -f "$BACKEND_PID_FILE" ]; then
|
|
| 19 |
BACKEND_PID=$(cat "$BACKEND_PID_FILE")
|
| 20 |
if ps -p $BACKEND_PID > /dev/null 2>&1; then
|
| 21 |
echo -e "${GREEN}β Backend: RUNNING${NC} (PID: $BACKEND_PID)"
|
| 22 |
-
echo " URL: http://localhost:
|
| 23 |
else
|
| 24 |
echo -e "${RED}β Backend: NOT RUNNING${NC} (stale PID: $BACKEND_PID)"
|
| 25 |
fi
|
|
|
|
| 19 |
BACKEND_PID=$(cat "$BACKEND_PID_FILE")
|
| 20 |
if ps -p $BACKEND_PID > /dev/null 2>&1; then
|
| 21 |
echo -e "${GREEN}β Backend: RUNNING${NC} (PID: $BACKEND_PID)"
|
| 22 |
+
echo " URL: http://localhost:8000"
|
| 23 |
else
|
| 24 |
echo -e "${RED}β Backend: NOT RUNNING${NC} (stale PID: $BACKEND_PID)"
|
| 25 |
fi
|