Skip to main content
Glama
server.py6.78 kB
import asyncio import json import logging import os import shutil import subprocess from pathlib import Path from typing import List, Optional from mcp.server import Server from mcp.server.stdio import stdio_server import mcp.types as types # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("fabric-mcp-server") # Configuration REPO_URL = "https://github.com/danielmiessler/fabric" REPO_PATH = Path("/app/data/fabric") PATTERNS_DIRS = ["data/patterns", "patterns"] STRATEGIES_DIRS = ["data/strategies", "strategies"] server = Server("fabric-mcp-server", version="0.1.0") def run_command(command: List[str], cwd: Optional[Path] = None) -> None: try: subprocess.run( command, cwd=cwd, check=True, capture_output=True, text=True ) except subprocess.CalledProcessError as e: logger.error(f"Command failed: {e.cmd}") logger.error(f"Stdout: {e.stdout}") logger.error(f"Stderr: {e.stderr}") raise def sync_repo(): if REPO_PATH.exists(): if (REPO_PATH / ".git").exists(): logger.info("Updating existing repository...") run_command(["git", "pull"], cwd=REPO_PATH) logger.info("Repository updated successfully") else: logger.warning("Directory exists but is not a git repo. Removing and cloning...") shutil.rmtree(REPO_PATH) run_command(["git", "clone", REPO_URL, str(REPO_PATH)]) logger.info("Repository cloned successfully") else: logger.info("Cloning repository...") REPO_PATH.parent.mkdir(parents=True, exist_ok=True) run_command(["git", "clone", REPO_URL, str(REPO_PATH)]) logger.info("Repository cloned successfully") def get_dir_content(base_path: Path, possible_dirs: List[str]) -> Path: for d in possible_dirs: path = base_path / d if path.exists() and path.is_dir(): return path return base_path / possible_dirs[0] def get_strategy_content(name: str) -> Optional[dict]: strategies_dir = get_dir_content(REPO_PATH, STRATEGIES_DIRS) strategy_file = strategies_dir / f"{name}.json" if not strategy_file.exists(): return None try: with open(strategy_file, "r", encoding="utf-8") as f: data = json.load(f) return { "description": data.get("description", ""), "prompt": data.get("prompt", "") or data.get("content", "") } except Exception: return None @server.list_tools() async def handle_list_tools() -> List[types.Tool]: return [ types.Tool( name="list_strategies", description="List all available strategies with their descriptions", inputSchema={ "type": "object", "properties": {}, "required": [] } ) ] @server.call_tool() async def handle_call_tool(name: str, arguments: dict) -> List[types.TextContent]: if name == "list_strategies": strategies_dir = get_dir_content(REPO_PATH, STRATEGIES_DIRS) if not strategies_dir.exists(): return [types.TextContent(type="text", text="No strategies found")] result = [] for item in sorted(strategies_dir.glob("*.json")): strategy = get_strategy_content(item.stem) if strategy: desc = strategy.get("description", "No description") result.append(f"- **{item.stem}**: {desc}") if not result: return [types.TextContent(type="text", text="No strategies found")] return [types.TextContent(type="text", text="\n".join(result))] raise ValueError(f"Unknown tool: {name}") @server.list_prompts() async def handle_list_prompts() -> List[types.Prompt]: patterns_dir = get_dir_content(REPO_PATH, PATTERNS_DIRS) if not patterns_dir.exists(): return [] # Collect strategies to list them in description or arguments? # The requirement is "option to include a strategies to each prompt". # We can add an argument "strategy" to each prompt. prompts = [] for item in patterns_dir.iterdir(): if item.is_dir() and (item / "system.md").exists(): prompts.append( types.Prompt( name=item.name, description=f"Fabric pattern: {item.name}", arguments=[ types.PromptArgument( name="strategy", description="Optional strategy to apply (e.g. 'cot')", required=False ), types.PromptArgument( name="input", description="User input to process (text, URL, etc.)", required=True # Force client to ask for input ) ] ) ) return sorted(prompts, key=lambda x: x.name) @server.get_prompt() async def handle_get_prompt(name: str, arguments: dict[str, str] | None) -> types.GetPromptResult: patterns_dir = get_dir_content(REPO_PATH, PATTERNS_DIRS) pattern_file = patterns_dir / name / "system.md" if not pattern_file.exists(): raise ValueError(f"Pattern '{name}' not found") content = pattern_file.read_text(encoding="utf-8") # Prepend strategy if provided if arguments and arguments.get("strategy"): strategy_name = arguments["strategy"] strategy = get_strategy_content(strategy_name) if not strategy: raise ValueError(f"Strategy '{strategy_name}' not found") content = f"{strategy.get('prompt')}\n\n{content}" # Append user input if provided if arguments and arguments.get("input"): user_input = arguments["input"] content += f"\n{user_input}" return types.GetPromptResult( messages=[ types.PromptMessage( role="user", content=types.TextContent( type="text", text=content ) ) ] ) async def main(): # Sync repo on startup try: sync_repo() except Exception as e: logger.error(f"Failed to sync repo: {e}") # Continue? If repo fails, prompts will be empty. async with stdio_server() as (read_stream, write_stream): await server.run( read_stream, write_stream, server.create_initialization_options() ) if __name__ == "__main__": asyncio.run(main())

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/brneto/fabric-mcp-server'

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