Skip to main content
Glama
server.py23.3 kB
import asyncio import logging import json from typing import Any, Optional, List, Dict from mcp.server import Server, NotificationOptions from mcp.types import ( Tool, TextContent, ) import mcp.server.stdio # Import async wrappers from tool modules from .tools.edit_tools import ( get_document_structure, read_element, replace_content, insert_element, delete_element, undo_changes, search_in_document, get_element_context, move_document_element, update_document_metadata ) from .tools.file_ops import ( list_directory, create_directory, create_file, delete_item ) logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = Server("markdown-editor-mcp-server") @app.list_tools() async def list_tools() -> list[Tool]: """ Returns the complete list of tools available in the server. Adheres to the 2025 Scaling Standard for MCP. """ return [ # --- SCALING & DISCOVERY (2025 Standard) --- Tool( name="search_tools", title="Search Tools", description="Scalability feature: find the right tool for your complex task among all available tools.", inputSchema={ "type": "object", "properties": { "query": { "type": "string", "description": "Description of the operation you want to perform", "examples": [ "find paragraphs", "replace text", "move section", "delete element", "list files" ] } }, "required": ["query"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "tools": { "type": "array", "description": "List of relevant tool names", "items": {"type": "string"} } } } ), # --- NAVIGATION & STRUCTURE --- Tool( name="get_document_structure", title="Get Document Structure", description="Parses the Markdown file and returns a tree of headings and elements. Use this first to navigate.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Absolute path to the .md file", "examples": ["/path/to/document.md", "./README.md"] }, "depth": { "type": "integer", "description": "Maximum depth of headings to return", "default": 2, "examples": [2, 3, 5] } }, "required": ["file_path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "structure": { "type": "array", "description": "Tree of document elements" } } } ), Tool( name="search_text", title="Search Text in Document", description="Performs a semantic search for text strings and returns their structural paths.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md", "/path/to/file.md"] }, "query": { "type": "string", "description": "String to search for", "examples": ["TODO", "urgent", "deadline"] } }, "required": ["file_path", "query"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "results": { "type": "array", "description": "List of matching elements with their paths" } } } ), Tool( name="get_context", title="Get Element Context", description="Returns the target element along with its immediate neighbors (before and after).", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md"] }, "path": { "type": "string", "description": "Path to the element (e.g., 'Intro > paragraph 1')", "examples": ["Introduction > paragraph 2", "Conclusion"] } }, "required": ["file_path", "path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "target": {"type": "object", "description": "The target element"}, "before": {"type": "object", "description": "Element before target"}, "after": {"type": "object", "description": "Element after target"} } } ), # --- CONTENT EDITING --- Tool( name="read_element", title="Read Specific Element", description="Fetches the full content of a specific block by its path.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md"] }, "path": { "type": "string", "examples": ["Features > list 1", "Introduction"] } }, "required": ["file_path", "path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "element": {"type": "object", "description": "The requested element"}, "content": {"type": "string", "description": "Element content"} } } ), Tool( name="replace_content", title="Replace Block Content", description="Overwrites the content of a specific block. Maintains document structure.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md"] }, "path": { "type": "string", "examples": ["Introduction > paragraph 1", "Conclusion", "Features > list 2"] }, "new_content": { "type": "string", "examples": ["Updated paragraph text", "New content here"] } }, "required": ["file_path", "path", "new_content"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"} } } ), Tool( name="insert_element", title="Insert New Element", description="Inserts a new block (heading, paragraph, etc.) relative to an existing one.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md"] }, "path": { "type": "string", "description": "Reference path", "examples": ["Introduction", "Features > paragraph 2"] }, "element_type": { "type": "string", "enum": ["heading", "paragraph", "list", "code_block", "blockquote"], "examples": ["paragraph", "heading"] }, "content": { "type": "string", "examples": ["New paragraph content", "## New Section"] }, "where": { "type": "string", "enum": ["before", "after"], "default": "after", "examples": ["after", "before"] }, "heading_level": { "type": "integer", "default": 1, "examples": [1, 2, 3] } }, "required": ["file_path", "path", "element_type", "content"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"} } } ), Tool( name="move_element", title="Move Structural Block", description="Moves an element (and its children) to a new location in the document.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md"] }, "source_path": { "type": "string", "examples": ["Old Section", "Introduction > paragraph 2"] }, "target_path": { "type": "string", "examples": ["Conclusion", "Features"] }, "where": { "type": "string", "enum": ["before", "after"], "default": "after", "examples": ["after", "before"] } }, "required": ["file_path", "source_path", "target_path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"} } } ), Tool( name="delete_element", title="Delete Block", description="Removes a block from the document.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md"] }, "path": { "type": "string", "examples": ["Introduction > paragraph 3", "Old Section", "Deprecated > list 1"] } }, "required": ["file_path", "path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"} } } ), Tool( name="update_metadata", title="Update YAML Metadata", description="Modifies the document's Frontmatter.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md", "./blog/post.md"] }, "metadata": { "type": "object", "examples": [ {"status": "published", "tags": ["mcp", "ai"]}, {"author": "John Doe", "date": "2025-12-27"} ] } }, "required": ["file_path", "metadata"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"} } } ), Tool( name="undo", title="Undo Last Changes", description="Reverts the last N operations on the document.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "examples": ["./document.md"] }, "count": { "type": "integer", "default": 1, "examples": [1, 2, 5] } }, "required": ["file_path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"}, "reverted_count": {"type": "integer", "description": "Number of operations reverted"} } } ), # --- FILE SYSTEM --- Tool( name="list_directory", title="List Directory", description="Lists files and folders in the workspace.", inputSchema={ "type": "object", "properties": { "path": { "type": "string", "default": ".", "examples": [".", "./docs", "/path/to/directory"] } }, "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "items": { "type": "array", "description": "List of directory entries", "items": { "type": "object", "properties": { "name": {"type": "string"}, "is_dir": {"type": "boolean"}, "size": {"type": "integer"}, "path": {"type": "string"} } } } } } ), Tool( name="create_file", title="Create File", inputSchema={ "type": "object", "properties": { "path": { "type": "string", "examples": ["./new_document.md", "./notes/todo.md"] }, "content": { "type": "string", "default": "", "examples": ["# New Document\n\nContent here", ""] } }, "required": ["path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"}, "path": {"type": "string"}, "size": {"type": "integer"} } } ), Tool( name="create_directory", title="Create Directory", inputSchema={ "type": "object", "properties": { "path": { "type": "string", "examples": ["./new_folder", "./docs/archive"] } }, "required": ["path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"}, "path": {"type": "string"} } } ), Tool( name="delete_item", title="Delete File/Folder", inputSchema={ "type": "object", "properties": { "path": { "type": "string", "examples": ["./old_file.md", "./temp_folder"] } }, "required": ["path"], "additionalProperties": False }, outputSchema={ "type": "object", "properties": { "success": {"type": "boolean"}, "path": {"type": "string"} } } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict) -> Any: """Dispatches tool calls to the appropriate backend functions.""" try: # 1. SPECIAL: Search Tools if name == "search_tools": query = arguments.get("query", "").lower() all_tools = await list_tools() relevant = [t.name for t in all_tools if query in t.name or (t.description and query in t.description.lower())] return { "content": [TextContent(type="text", text=f"Relevant tools: {', '.join(relevant)}")], "structuredContent": {"tools": relevant}, "isError": False } # 2. FILE OPS if name == "list_directory": path = arguments.get("path", ".") items = await list_directory(path) return {"content": [TextContent(type="text", text=str(items))], "structuredContent": {"items": items}, "isError": items is None} elif name == "create_file": res = await create_file(arguments["path"], arguments.get("content", "")) return {"content": [TextContent(type="text", text="File created")], "structuredContent": res, "isError": "error" in res} elif name == "create_directory": res = await create_directory(arguments["path"]) return {"content": [TextContent(type="text", text="Directory created")], "structuredContent": res, "isError": "error" in res} elif name == "delete_item": res = await delete_item(arguments["path"]) return {"content": [TextContent(type="text", text="Item deleted")], "structuredContent": res, "isError": "error" in res} # 3. EDIT OPS (require file_path) file_path = arguments.get("file_path") if not file_path: return {"content": [TextContent(type="text", text="Missing file_path")], "isError": True} if name == "get_document_structure": res = await get_document_structure(file_path, arguments.get("depth", 2)) return {"content": [TextContent(type="text", text="Structure extracted")], "structuredContent": {"structure": res}, "isError": False} elif name == "search_text": res = await search_in_document(file_path, arguments["query"]) return {"content": [TextContent(type="text", text=f"Found {len(res)} matches")], "structuredContent": {"results": res}, "isError": False} elif name == "get_context": res = await get_element_context(file_path, arguments["path"]) return {"content": [TextContent(type="text", text="Context extracted")], "structuredContent": res, "isError": "error" in res} elif name == "read_element": res = await read_element(file_path, arguments["path"]) return {"content": [TextContent(type="text", text="Element read")], "structuredContent": res, "isError": "error" in res} elif name == "replace_content": res = await replace_content(file_path, arguments["path"], arguments["new_content"]) return {"content": [TextContent(type="text", text="Content replaced")], "structuredContent": res, "isError": "error" in res} elif name == "insert_element": res = await insert_element(file_path, arguments["path"], arguments["element_type"], arguments["content"], arguments.get("where", "after"), arguments.get("heading_level", 1)) return {"content": [TextContent(type="text", text="Element inserted")], "structuredContent": res, "isError": "error" in res} elif name == "move_element": res = await move_document_element(file_path, arguments["source_path"], arguments["target_path"], arguments.get("where", "after")) return {"content": [TextContent(type="text", text="Element moved")], "structuredContent": res, "isError": "error" in res} elif name == "delete_element": res = await delete_element(file_path, arguments["path"]) return {"content": [TextContent(type="text", text="Element deleted")], "structuredContent": res, "isError": "error" in res} elif name == "update_metadata": res = await update_document_metadata(file_path, arguments["metadata"]) return {"content": [TextContent(type="text", text="Metadata updated")], "structuredContent": res, "isError": "error" in res} elif name == "undo": res = await undo_changes(file_path, arguments.get("count", 1)) return {"content": [TextContent(type="text", text="Undo performed")], "structuredContent": res, "isError": "error" in res} return {"content": [TextContent(type="text", text=f"Unknown tool: {name}")], "isError": True} except Exception as e: logger.error(f"Error: {e}") return {"content": [TextContent(type="text", text=str(e))], "isError": True} async def main(): async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): # Enable tools/list_changed notification capability notification_options = NotificationOptions(tools_changed=True) await app.run( read_stream, write_stream, app.create_initialization_options(notification_options=notification_options) ) def run(): asyncio.run(main())

Implementation Reference

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/KazKozDev/markdown-editor-mcp-server'

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