Skip to main content
Glama
dhevenb

Spec3 MCP Server

by dhevenb
server.py7.67 kB
"""Spec3 MCP Server implementation using FastMCP.""" import logging from typing import Any import base64 from io import BytesIO import boto3 from fastmcp import FastMCP from pdf2image import convert_from_bytes from PIL import Image import pypdf # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) # AWS Configuration AWS_REGION = "us-east-1" S3_BUCKET = "spec3-chatbot-data-091702001436-us-east-1" # Car-specific context CAR_CONTEXT = """ # My Spec3 E36 Build ## Vehicle Information - Year/Model: 1994 BMW E36 325is - Build Status: [In-progress] """ # Available documents mapping AVAILABLE_DOCS = { "spec3_constructor_guide": { "name": "Spec3 E36 Race Car Constructor's Guide", "s3_key": "Spec3 E36 Race Car Contsructor's Guide.pdf", "description": "Comprehensive guide for building a Spec3 E36 race car" }, "bentley_manual_general": { "name": "Bentley General Manual", "s3_key": "bentley_general.pdf", "description": "Bentley BMW E36 Manual - GENERAL SECTION" }, "nasa_ccr": { "name": "2025 NASA Competition Comp Rules (CCR)", "s3_key": "2025.4_NASACCR.pdf", "description": "2025 NASA Club Championship Racing rules" }, "spec3_rules": { "name": "2025 Spec3 Rules", "s3_key": "2025_Spec3_Rules.pdf", "description": "2025 Spec3 racing class specific rules and regulations" } } # Initialize AWS clients s3_client = boto3.client("s3", region_name=AWS_REGION) def create_server() -> FastMCP: """Create and configure the Spec3 MCP server.""" logger.info("Creating Spec3 MCP Server") # Initialize FastMCP server mcp = FastMCP("spec3-mcp-server") @mcp.tool() async def get_car_context() -> dict[str, Any]: """ Get information about the user's 1994 BMW E36 325is Spec3 race car build. Returns current configuration, build status, modifications, and car-specific details. Call this tool when providing personalized advice, troubleshooting, or planning modifications. Returns: dict: Car configuration, history, and current state """ logger.info("get_car_context called") return { "context": CAR_CONTEXT, "last_updated": "2025-10-05" } @mcp.tool() async def list_documents() -> dict[str, Any]: """ List all available Spec3 racing reference documents. Available documents include: Spec3 Constructor's Guide, Bentley E36 Manual, 2025 NASA CCR rules, and 2025 Spec3 class rules. Returns: dict: Document IDs, names, and descriptions """ logger.info("list_documents called") docs_list = [] for doc_id, doc_info in AVAILABLE_DOCS.items(): docs_list.append({ "id": doc_id, "name": doc_info["name"], "description": doc_info["description"] }) return { "documents": docs_list, "count": len(docs_list) } @mcp.tool() async def get_document( document_id: str, page_start: int = 1, page_end: int | None = None, include_images: bool = True ) -> dict[str, Any]: """ Retrieve full text and visual content of Spec3 racing reference documents. Fetches complete PDF content from S3 including text and page images. Page images preserve diagrams, tables, and formatting that text extraction cannot capture. Args: document_id: Document ID from list_documents (e.g., "spec3_rules") page_start: Starting page number (default: 1) page_end: Ending page number (default: None for all remaining pages) include_images: Include page images for diagrams/tables (default: True) Returns: dict: Document text, page images (base64), metadata, and page range """ logger.info(f"get_document called for: {document_id}, pages {page_start}-{page_end}, images={include_images}") if document_id not in AVAILABLE_DOCS: return { "error": f"Document ID '{document_id}' not found. Use list_documents to see available documents.", "available_ids": list(AVAILABLE_DOCS.keys()) } try: doc_info = AVAILABLE_DOCS[document_id] s3_key = doc_info["s3_key"] # Download PDF from S3 logger.info(f"Downloading {s3_key} from S3") response = s3_client.get_object(Bucket=S3_BUCKET, Key=s3_key) pdf_content = response['Body'].read() # Parse PDF for text pdf_file = BytesIO(pdf_content) pdf_reader = pypdf.PdfReader(pdf_file) total_pages = len(pdf_reader.pages) # Validate and adjust page range page_start = max(1, page_start) if page_end is None: page_end = total_pages else: page_end = min(page_end, total_pages) if page_start > total_pages: return { "error": f"page_start ({page_start}) exceeds total pages ({total_pages})", "total_pages": total_pages } # Extract text from specified pages text_content = [] for page_num in range(page_start - 1, page_end): page = pdf_reader.pages[page_num] page_text = page.extract_text() text_content.append(f"--- Page {page_num + 1} ---\n{page_text}") full_text = "\n\n".join(text_content) # Extract page images if requested page_images = [] if include_images: logger.info(f"Converting pages {page_start}-{page_end} to images") # Convert PDF pages to images images = convert_from_bytes( pdf_content, first_page=page_start, last_page=page_end, dpi=150 # Balance between quality and size ) for idx, img in enumerate(images): # Convert to base64 buffered = BytesIO() img.save(buffered, format="PNG", optimize=True) img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8') page_images.append({ "page_number": page_start + idx, "image": img_base64, "format": "png" }) result = { "document_name": doc_info["name"], "document_id": document_id, "total_pages": total_pages, "pages_retrieved": f"{page_start}-{page_end}", "text": full_text, "images": page_images, "num_images": len(page_images), "size_bytes": len(pdf_content) } logger.info(f"Successfully retrieved {page_end - page_start + 1} pages ({len(page_images)} images) from {doc_info['name']}") return result except Exception as e: logger.error(f"Error retrieving document: {str(e)}") return { "error": f"Error retrieving document: {str(e)}", "document_id": document_id } logger.info("Spec3 MCP Server created successfully") return mcp

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/dhevenb/dheven-spec3-mcp-server'

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