Skip to main content
Glama
CLAUDE.md8.22 kB
# MCP Outline Server Guide This guide helps you implement and modify the MCP Outline server effectively. ## Purpose This MCP server bridges AI assistants with Outline's document management platform: - REST API integration for Outline services - Tools for documents, collections, and comments - API key authentication - Docker and local development support ## Architecture ### Tool Categories - **Search**: Find documents, collections, hierarchies - **Reading**: Read content, export markdown - **Content**: Create, update, comment - **Organization**: Move documents between collections - **Lifecycle**: Archive, delete, restore operations - **Collaboration**: Comments, backlinks - **Collections**: Create, update, delete, export - **AI**: Natural language queries ## Core Concepts ### Outline Objects - **Documents**: Markdown content with title and metadata - **Collections**: Grouping with name, description, color - **Comments**: Threaded discussions with replies - **Hierarchy**: Parent-child document relationships - **Lifecycle**: Draft → Published → Archived → Deleted ### API Client `OutlineClient` in `utils/outline_client.py` handles async REST API interactions: **Operations** (all async): - Documents: get, search, create, update, move, archive, delete, restore - Collections: list, create, update, delete, export - Comments: create, list, get - AI: answer questions **Configuration**: - `OUTLINE_API_KEY` (required) - `OUTLINE_API_URL` (optional, defaults to https://app.getoutline.com/api) - `OUTLINE_MAX_CONNECTIONS` (optional, default: 100) - Maximum concurrent connections - `OUTLINE_MAX_KEEPALIVE` (optional, default: 20) - Maximum idle connections in pool - `OUTLINE_TIMEOUT` (optional, default: 30.0) - Request timeout in seconds - `OUTLINE_CONNECT_TIMEOUT` (optional, default: 5.0) - Connection timeout in seconds - Authentication via Bearer token **Connection Pooling**: - Uses httpx with class-level connection pool - Shared across all OutlineClient instances - Automatic connection reuse for better performance - Configurable limits via environment variables **Error Handling**: - Raises `OutlineError` for API failures - Tools catch exceptions and return error strings - Supports httpx exceptions (RequestError, HTTPStatusError, TimeoutException) **Rate Limiting**: - Tracks `RateLimit-Remaining` and `RateLimit-Reset` headers, waits proactively when exhausted - Uses asyncio.Lock for thread-safe rate limiting in concurrent scenarios - Automatic handling of HTTP 429 responses - Respects `Retry-After` header - Enabled by default, no configuration required ## Implementation Patterns ### Module Structure Feature modules follow this pattern: ```python # 1. Imports (standard lib → third-party → local) import os from typing import Any, Optional from mcp_outline.utils.outline_client import OutlineClient # 2. Helper formatters (private functions) def _format_search_results(data: dict) -> str: """Format API response for user display.""" # Clean, readable output formatting pass # 3. Tool registration function def register_tools(mcp): """Register all tools in this module.""" @mcp.tool() async def search_documents( query: str, collection_id: Optional[str] = None ) -> str: """ Search for documents by keywords. Args: query: Search keywords collection_id: Optional collection filter Returns: Formatted search results """ try: client = await get_outline_client() result = await client.search_documents(query, collection_id) return _format_search_results(result) except Exception as e: return f"Error: {str(e)}" ``` ### Adding New Tools **Client Method** (if new endpoint needed): ```python async def new_operation(self, param: str) -> dict: """Docstring describing operation.""" response = await self.post("endpoint", {"param": param}) return response.get("data", {}) ``` **Tool Function**: ```python @mcp.tool() async def new_tool_name(param: str) -> str: """Clear description.""" try: client = await get_outline_client() result = await client.new_operation(param) return _format_result(result) except Exception as e: return f"Error: {str(e)}" ``` **Testing**: Mock OutlineClient, test success and error cases ## Technical Requirements ### Code Style - PEP 8 conventions - Type hints for all functions - Max line length: 79 characters (ruff enforced) - Google-style docstrings - Import order: stdlib → third-party → local - Single responsibility per function ### Error Handling ```python # In OutlineClient methods try: response = await self._client_pool.post(url, headers=headers, json=data) response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: if e.response.status_code == 429: raise OutlineError(f"Rate limited") raise OutlineError(f"HTTP {e.response.status_code}: {e.response.text}") except httpx.TimeoutException as e: raise OutlineError(f"Request timeout: {str(e)}") except httpx.RequestError as e: raise OutlineError(f"API request failed: {str(e)}") # In tool functions try: client = await get_outline_client() result = await client.operation() return format_result(result) except OutlineError as e: return f"Outline API error: {str(e)}" except Exception as e: return f"Error: {str(e)}" ``` ### Testing Mock `OutlineClient` in async tests: ```python @pytest.mark.asyncio async def test_tool(): with patch('module.get_outline_client') as mock_get_client: mock_client = AsyncMock() mock_client.method.return_value = {"data": "value"} mock_get_client.return_value = mock_client result = await tool_function("param") assert "expected" in result ``` ### Configuration `.env` file: ```bash OUTLINE_API_KEY=<your_key> # Required OUTLINE_API_URL=<custom_url> # Optional OUTLINE_MAX_CONNECTIONS=100 # Optional - Max connections OUTLINE_MAX_KEEPALIVE=20 # Optional - Max keepalive OUTLINE_TIMEOUT=30.0 # Optional - Request timeout OUTLINE_CONNECT_TIMEOUT=5.0 # Optional - Connect timeout OUTLINE_DISABLE_AI_TOOLS=true # Optional - Disable AI tools OUTLINE_READ_ONLY=true # Optional - Disable all write operations OUTLINE_DISABLE_DELETE=true # Optional - Disable delete operations only ``` **Access Control Notes**: - `OUTLINE_READ_ONLY`: Blocks entire write modules at registration (content, lifecycle, organization, batch_operations) - `OUTLINE_DISABLE_DELETE`: Conditionally registers delete tools within document_lifecycle and collection_tools - Read-only mode takes precedence: If both are set, server operates in read-only mode ### Critical Requirements - No stdout/stderr logging (MCP uses stdio) - Tools return strings, not dicts - Use `async def` for ALL tool functions - Use `await` for ALL client method calls - Always use `await get_outline_client()` to get client instance - Catch exceptions, return error strings - Follow KISS principle ### Pre-Commit Checks **IMPORTANT**: Before committing, run all CI checks locally to ensure they pass: ```bash # Format code uv run ruff format . # Check formatting uv run ruff format --check . # Lint code uv run ruff check . # Type check uv run pyright src/ # Run tests uv run pytest tests/ -v --cov=src/mcp_outline # Run integration tests uv run pytest tests/ -v -m integration ``` ## Common Patterns **Pagination**: Use `offset` and `limit` parameters for large result sets **Tree Formatting**: Recursive formatting with indentation for hierarchies **Document ID Resolution**: `get_document_id_from_title` for user-friendly lookups - When tagging version numbers look at changes since last version. Follow this rule for version number, go from left to right. First one hit is the new version number. Anye feat!: => major version, any feat: => minor version, Only fix: => patch version. Use annotated tag with a short summary of what the release contains.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Vortiago/mcp-outline'

If you have feedback or need assistance with the MCP directory API, please join our Discord server