Skip to content

Transform SyllabusMCP to Distributed Microservices Architecture#5

Open
schmerl wants to merge 24 commits intomainfrom
feature/distributed-system
Open

Transform SyllabusMCP to Distributed Microservices Architecture#5
schmerl wants to merge 24 commits intomainfrom
feature/distributed-system

Conversation

@schmerl
Copy link
Copy Markdown
Contributor

@schmerl schmerl commented Nov 27, 2025

Convert SyllabusMCP to Distributed Microservices Architecture

Problem Statement

The current SyllabusMCP system uses FastMCP to provide all services through a single interface. We need to convert this to a distributed microservices architecture where:

  • Each service (syllabus_server, academic_planner, productivity_server) runs as an independent REST API in its own Docker container
  • A local MCP layer provides the same interface but makes REST calls to the appropriate services
  • The orchestrator continues to work with the MCP interface but now communicates with distributed services

Current Architecture

The system currently has three main service components:

  • syllabus_server: Provides parse_syllabus() and answer_syllabus_question() tools via FastMCP
  • academic_planner: Provides create_academic_plan() and show_assignment_summary() tools via FastMCP
  • productivity_server: Provides calendar and reminder management tools via FastMCP
  • orchestrator: Coordinates workflows using the MCP tools

All services currently use FastMCP decorators and run as MCP servers.

Proposed Architecture

Service Layer (REST APIs)

Each service becomes an independent FastAPI-based REST service:

  • syllabus-service: FastAPI app exposing syllabus parsing endpoints
  • academic-planner-service: FastAPI app exposing academic planning endpoints
  • productivity-service: FastAPI app exposing calendar/reminder endpoints

MCP Interface Layer

New MCP wrapper services that maintain the existing tool signatures:

  • mcp/syllabus/mcp_service.py: MCP tools that make REST calls to syllabus-service
  • mcp/academic_planner/mcp_service.py: MCP tools that make REST calls to academic-planner-service
  • mcp/productivity/mcp_service.py: MCP tools that make REST calls to productivity-service

Container Infrastructure

Docker containers for each service:

  • syllabus-service: Container running the syllabus FastAPI service
  • academic-planner-service: Container running the academic planner FastAPI service
  • productivity-service: Container running the productivity FastAPI service
  • mcp-gateway: Container running the MCP interface layer

Implementation Progress

✅ Phase 1: Create REST Service Framework

  1. Add FastAPI dependencies to pyproject.toml
  2. Create service base structure:
    • services/syllabus_service/ directory
    • services/academic_planner_service/ directory
    • services/productivity_service/ directory
  3. Create shared models package (services/shared/models.py) to ensure consistent data structures across services

✅ Phase 2: Convert Syllabus Server

  1. Create FastAPI service (services/syllabus_service/app.py):
    • ✅ Extract core logic from syllabus_server/server.py
    • ✅ Remove MCP decorators, add FastAPI endpoints:
      • POST /parse-syllabus (accepts {"pdf_path_or_url": str}, returns ParsedSyllabus)
      • POST /answer-question (accepts {"syllabus_data": ParsedSyllabus, "question": str}, returns {"answer": str})
    • ✅ Add health check endpoint and proper error handling
    • ✅ Handle long-running LLM operations with proper async patterns
  2. Create MCP wrapper (mcp/syllabus/mcp_service.py):
    • ✅ Implement parse_syllabus() and answer_syllabus_question() with identical signatures
    • ✅ Use httpx to make REST calls to the syllabus service with extended timeouts (5min parse, 2min questions)
    • ✅ Handle serialization/deserialization of ParsedSyllabus objects
    • ✅ Proper error handling and timeout management
  3. ⏸️ Create Dockerfile for syllabus service

✅ TESTING COMPLETED:

  • ✅ Mock service created for testing without LLM calls
  • ✅ Comprehensive test suite validates entire distributed flow
  • ✅ All tests passing: HTTP communication, data conversion, MCP interface compatibility
  • ✅ Extended timeouts verified (5min parse, 2min questions)
  • ✅ Error handling and service communication working correctly

✅ Phase 3: Convert Academic Planner Service

  1. Create FastAPI service (services/academic_planner_service/app.py):
    • ✅ Extract core logic from academic_planner/server.py
    • ✅ Add endpoints:
      • POST /create-plan (accepts {"syllabi": list[ParsedSyllabus]}, returns Plan)
      • POST /show-assignment-summary (accepts {"plan": Plan}, returns {"summary": str})
    • ✅ Add health check endpoint and proper error handling
    • ✅ Handle long-running LLM operations (5min timeout for plan creation)
  2. Create MCP wrapper (mcp_wrappers/academic_planner/mcp_service.py):
    • ✅ Implement create_academic_plan() and show_assignment_summary() with identical signatures
    • ✅ Use httpx to make REST calls with extended timeouts (5min create, 30sec summary)
    • ✅ Handle serialization/deserialization of Plan objects
    • ✅ Proper error handling and timeout management
  3. ⏸️ Create Dockerfile for academic planner service

✅ Phase 4: Convert Productivity Service

  1. Create FastAPI service (services/productivity_service/app.py):
    • ✅ Extract core logic from productivity_server/server.py
    • ✅ Add endpoints for all calendar and reminder operations (8 total endpoints)
    • ✅ Maintain in-memory storage for fast operations
    • ✅ Add health check endpoint and proper error handling
  2. Create MCP wrapper (mcp_wrappers/productivity/mcp_service.py):
    • ✅ Implement all 8 MCP tools with identical signatures
    • ✅ Use httpx with standard 30-second timeouts (fast operations)
    • ✅ Handle serialization/deserialization of CalendarEvent and Reminder objects
    • ✅ Proper error handling for HTTP communication
  3. ⏸️ Create Dockerfile for productivity service

✅ Phase 5: Create MCP Gateway

  1. Create unified MCP server (mcp_gateway/server.py):
    • ✅ Import raw functions from all MCP wrapper modules
    • ✅ Re-register all tools with unified FastMCP instance
    • ✅ Provide single entry point for all 12 MCP tools
    • ✅ Handle service discovery and status reporting
    • ✅ Clean abstraction - orchestrator only needs to import one gateway
  2. ⏸️ Create Dockerfile for MCP gateway

✅ Phase 6: Docker Compose & Configuration

  1. Create docker-compose.yml:
    • ✅ Define all 4 services with proper networking (syllabus-network)
    • ✅ Set up environment variables and health checks for all services
    • ✅ Configure service discovery with Docker DNS
    • ✅ Proper startup dependencies (MCP gateway waits for all services)
  2. Create Dockerfiles:
    • ✅ One Dockerfile per service (4 total)
    • ✅ Optimized with uv package manager
    • ✅ Non-root user security
    • ✅ Health check integration
  3. Configuration management:
    • ✅ Environment-based service URLs via Docker Compose
    • ✅ .env.example template for easy setup
    • ✅ .dockerignore for optimized builds
    • ✅ Comprehensive deployment documentation

🔄 Phase 7: Update Orchestrator

  1. ⏸️ Update orchestrator to use the MCP gateway instead of direct service imports
  2. ⏸️ Test workflow execution with distributed services
  3. ⏸️ Add resilience patterns (retries, circuit breakers) if needed

Key Design Decisions

Data Serialization

  • Use Pydantic models for FastAPI request/response validation
  • Convert dataclass models to Pydantic where needed for JSON serialization
  • MCP wrappers handle conversion between dataclasses and JSON

Service Communication

  • Use httpx for HTTP client calls from MCP wrappers to REST services
  • Implement proper error handling and extended timeouts for LLM services (syllabus parsing, academic planning)
  • Services communicate only through defined REST APIs
  • LLM Service Considerations:
    • Syllabus parsing can take 30-60 seconds for complex PDFs
    • Academic planning with multiple syllabi can take 1-2 minutes
    • Set HTTP timeouts to 5+ minutes for LLM endpoints
    • Consider async processing patterns for very long operations

Backward Compatibility

  • MCP tool signatures remain identical to current implementation
  • Orchestrator requires no changes to its tool usage
  • Existing workflows continue to work unchanged

Service Independence

  • Each service manages its own dependencies and models
  • Services can be developed, deployed, and scaled independently
  • Clear API boundaries between services

Files to Modify/Create

New Files

  • services/syllabus_service/app.py
  • services/syllabus_service/mock_app.py (for testing)
  • services/academic_planner_service/app.py
  • services/productivity_service/app.py
  • services/shared/models.py
  • mcp_wrappers/syllabus/mcp_service.py (renamed to avoid conflicts)
  • test_syllabus_service.py (comprehensive test suite)
  • mcp_wrappers/academic_planner/mcp_service.py
  • mcp_wrappers/productivity/mcp_service.py
  • mcp_gateway/server.py
  • docker-compose.yml
  • services/syllabus_service/Dockerfile
  • services/academic_planner_service/Dockerfile
  • services/productivity_service/Dockerfile
  • mcp_gateway/Dockerfile
  • .dockerignore
  • .env.example
  • DOCKER_DEPLOYMENT.md

Files to Modify

  • pyproject.toml - Add FastAPI, httpx dependencies
  • Existing server files - Remove MCP decorators, extract business logic

Legend

  • ✅ Complete
  • 🔄 In Progress
  • ⏸️ Not Started
  • ❌ Blocked/Issues

- Add FastAPI, httpx, uvicorn dependencies to pyproject.toml
- Create services/shared/models.py with Pydantic versions of dataclass models
- Set up infrastructure for microservices architecture
- Prepare for HTTP-based service communication
- Create FastAPI service at services/syllabus_service/app.py
- Add /parse-syllabus and /answer-question endpoints
- Configure 5min timeout for parsing, 2min for questions
- Create HTTP client wrapper at mcp_wrappers/syllabus/mcp_service.py
- Maintain existing MCP tool signatures while routing to HTTP service
- Add comprehensive test suite with mock service testing
- Handle long LLM operations with appropriate timeouts
- Create FastAPI service at services/academic_planner_service/app.py
- Add /create-plan and /assignment-summary endpoints
- Configure 5min timeout for plan creation, 30sec for summaries
- Create HTTP client wrapper at mcp_wrappers/academic_planner/mcp_service.py
- Handle AI-powered academic planning with extended timeouts
- Maintain compatibility with existing academic planner functionality
- Add Docker containerization support
- Create FastAPI service at services/productivity_service/app.py
- Implement 8 endpoints: calendar/reminder CRUD + bulk operations
- Add fast 30-second timeouts optimized for CRUD operations
- Create HTTP client wrapper at mcp_wrappers/productivity/mcp_service.py
- Support both individual and bulk operations for events/reminders
- Include formatted display endpoints for rich console output
- Maintain in-memory storage with structured data models
- Add Docker containerization with health checks
- Implement mcp_gateway/server.py as single entry point for all tools
- Import raw functions from all HTTP wrappers and re-register with FastMCP
- Provide unified interface with 14 tools across 3 distributed services
- Add service status and tool discovery functionality
- Enable clean orchestrator integration: single import gives access to all tools
- Maintain backward compatibility with existing MCP interfaces
- Support both development and production deployment patterns
- Create docker-compose.yml with 3 services and proper networking
- Configure health checks for all services with /health endpoints
- Add .env.example for environment variable configuration
- Create .dockerignore for optimized Docker builds
- Add comprehensive DOCKER_DEPLOYMENT.md guide
- Enable production-ready containerized deployment
- Support service scaling and health monitoring
- Include security best practices in Dockerfiles
- Create orchestrator/shared.py to eliminate code duplication
- Update registry to route all services through unified MCP Gateway
- Modify orchestrator/run.py to use async MCP Gateway functions
- Update orchestrator/run_agent.py for shared module integration
- Maintain full backward compatibility with existing workflows
- Enable orchestrator to work seamlessly with distributed architecture
- Add proper async/await patterns throughout orchestrator
- Complete the transformation: monolithic -> distributed architecture
- Completely rewrite README.md for distributed deployment focus
- Add comprehensive Mermaid architecture diagram showing all layers
- Update all examples to reflect Docker-first deployment approach
- Emphasize production features: scaling, health checks, monitoring
- Document microservices architecture with 14 available tools
- Add educational context for CMU 17-625 API Design course
- Include deployment options and production considerations
- Complete transformation: monolithic documentation -> distributed system guide
@schmerl schmerl force-pushed the feature/distributed-system branch from 9b37a73 to 7573c57 Compare November 27, 2025 18:47
schmerl and others added 15 commits November 28, 2025 08:14
- Add support for sending PDF content as base64 in API requests
  - Allows Docker services to process PDFs without filesystem access
  - Falls back to URL/path handling for remote PDFs
- Implement answer_question_about_syllabi tool for comparing multiple syllabi
  - New FastAPI endpoint for multi-syllabus questions
  - MCP wrapper function for cross-syllabus comparisons
- Add extract_pdf_pages_from_content() for in-memory PDF processing

Fixes file not found errors when orchestrator communicates with
syllabus service running in Docker container.
- Import and expose answer_question_about_syllabi tool in MCP gateway
- Add tool to registry service mapping under syllabus_server
- Update orchestrator prompt to guide LLM to use multi-syllabus tool
  for comparison/consolidation questions
- Add tool to gateway's list_available_tools output

This enables the orchestrator to efficiently answer questions that
compare or consolidate information across multiple syllabi using a
single LLM call instead of separate calls per syllabus.
Load environment variables from .env file in orchestrator/shared.py
to support local development configuration.
- Rename 'Client Layer' to 'Orchestration Layer' for clarity
- Remove Infrastructure subgraph (Docker Compose and Health Monitoring)
- Clean up diagram to focus on application architecture layers
- Update class definitions to match renamed layer
- Added new 'exec' subcommand to run_agent CLI that allows executing specific MCP tools directly
- Supports tools with or without arguments (JSON format via --args flag)
- Useful for quickly testing tools like show_reminders and show_calendar_events
- Updated README with examples of using the exec command
- Updated README.md architecture diagram with new endpoint paths
- Updated README.md service descriptions and curl examples
- Updated DOCKER_DEPLOYMENT.md with new endpoint documentation
- Updated DISTRIBUTED_IMPLEMENTATION_PLAN.md with current endpoint structure
- Updated test_syllabus_service.py to test new endpoints
- Updated services/syllabus_service/mock_app.py with new routes

All documentation now consistent with the REST API changes:
- Syllabus: /syllabus:parse, /syllabus/qa, /syllabi/qa
- Academic Planner: /academics/plan, /academics/assignments
- Productivity: /calendar/event, /reminders/reminder, etc.
- Replace sync OpenAI client with AsyncOpenAI in all services
- Enables true parallel processing of LLM requests
- Prevents serialization bottleneck when multiple requests arrive
- Services affected: syllabus, academic_planner, productivity
- Configure uvicorn with --workers 4 flag in Dockerfile
- Allows handling 4 simultaneous PDF parsing requests
- Prevents request queuing when parsing multiple syllabi in parallel
- Replace httpx.Client with requests library for better asyncio.to_thread() compatibility
- Add detailed timing logs to track request flow and identify bottlenecks
- Increase academic planner timeout from 300s to 600s
- Fixes timeout issues when running parallel HTTP requests from asyncio threads

This resolves the issue where only some requests would complete when
4 PDFs were parsed in parallel, as httpx had event loop conflicts
when used inside asyncio.to_thread().
* Expose MCP wrapper instances for registry discovery

Co-Authored-By: Warp <agent@warp.dev>

* Load MCP servers dynamically from wrapper modules

Co-Authored-By: Warp <agent@warp.dev>

* Split syllabus parsing into extract and structure stages

Co-Authored-By: Warp <agent@warp.dev>

* Skip PDF extract test when fixture file is missing

Co-Authored-By: Warp <agent@warp.dev>

---------

Co-authored-by: Warp <agent@warp.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant