#
tokens: 8771/50000 3/3 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── LICENSE
├── README.md
└── src
    └── spring_boot_mcp_converter.py
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Byte-compiled / optimized / DLL files
  2 | __pycache__/
  3 | *.py[cod]
  4 | *$py.class
  5 | 
  6 | # C extensions
  7 | *.so
  8 | 
  9 | # Distribution / packaging
 10 | .Python
 11 | build/
 12 | develop-eggs/
 13 | dist/
 14 | downloads/
 15 | eggs/
 16 | .eggs/
 17 | lib/
 18 | lib64/
 19 | parts/
 20 | sdist/
 21 | var/
 22 | wheels/
 23 | share/python-wheels/
 24 | *.egg-info/
 25 | .installed.cfg
 26 | *.egg
 27 | MANIFEST
 28 | 
 29 | # PyInstaller
 30 | #  Usually these files are written by a python script from a template
 31 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 32 | *.manifest
 33 | *.spec
 34 | 
 35 | # Installer logs
 36 | pip-log.txt
 37 | pip-delete-this-directory.txt
 38 | 
 39 | # Unit test / coverage reports
 40 | htmlcov/
 41 | .tox/
 42 | .nox/
 43 | .coverage
 44 | .coverage.*
 45 | .cache
 46 | nosetests.xml
 47 | coverage.xml
 48 | *.cover
 49 | *.py,cover
 50 | .hypothesis/
 51 | .pytest_cache/
 52 | cover/
 53 | 
 54 | # Translations
 55 | *.mo
 56 | *.pot
 57 | 
 58 | # Django stuff:
 59 | *.log
 60 | local_settings.py
 61 | db.sqlite3
 62 | db.sqlite3-journal
 63 | 
 64 | # Flask stuff:
 65 | instance/
 66 | .webassets-cache
 67 | 
 68 | # Scrapy stuff:
 69 | .scrapy
 70 | 
 71 | # Sphinx documentation
 72 | docs/_build/
 73 | 
 74 | # PyBuilder
 75 | .pybuilder/
 76 | target/
 77 | 
 78 | # Jupyter Notebook
 79 | .ipynb_checkpoints
 80 | 
 81 | # IPython
 82 | profile_default/
 83 | ipython_config.py
 84 | 
 85 | # pyenv
 86 | #   For a library or package, you might want to ignore these files since the code is
 87 | #   intended to run in multiple environments; otherwise, check them in:
 88 | # .python-version
 89 | 
 90 | # pipenv
 91 | #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 92 | #   However, in case of collaboration, if having platform-specific dependencies or dependencies
 93 | #   having no cross-platform support, pipenv may install dependencies that don't work, or not
 94 | #   install all needed dependencies.
 95 | #Pipfile.lock
 96 | 
 97 | # UV
 98 | #   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
 99 | #   This is especially recommended for binary packages to ensure reproducibility, and is more
100 | #   commonly ignored for libraries.
101 | #uv.lock
102 | 
103 | # poetry
104 | #   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105 | #   This is especially recommended for binary packages to ensure reproducibility, and is more
106 | #   commonly ignored for libraries.
107 | #   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108 | #poetry.lock
109 | 
110 | # pdm
111 | #   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112 | #pdm.lock
113 | #   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114 | #   in version control.
115 | #   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116 | .pdm.toml
117 | .pdm-python
118 | .pdm-build/
119 | 
120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121 | __pypackages__/
122 | 
123 | # Celery stuff
124 | celerybeat-schedule
125 | celerybeat.pid
126 | 
127 | # SageMath parsed files
128 | *.sage.py
129 | 
130 | # Environments
131 | .env
132 | .venv
133 | env/
134 | venv/
135 | ENV/
136 | env.bak/
137 | venv.bak/
138 | 
139 | # Spyder project settings
140 | .spyderproject
141 | .spyproject
142 | 
143 | # Rope project settings
144 | .ropeproject
145 | 
146 | # mkdocs documentation
147 | /site
148 | 
149 | # mypy
150 | .mypy_cache/
151 | .dmypy.json
152 | dmypy.json
153 | 
154 | # Pyre type checker
155 | .pyre/
156 | 
157 | # pytype static type analyzer
158 | .pytype/
159 | 
160 | # Cython debug symbols
161 | cython_debug/
162 | 
163 | # PyCharm
164 | #  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165 | #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166 | #  and can be added to the global gitignore or merged into this file.  For a more nuclear
167 | #  option (not recommended) you can uncomment the following to ignore the entire idea folder.
168 | #.idea/
169 | 
170 | # Ruff stuff:
171 | .ruff_cache/
172 | 
173 | # PyPI configuration file
174 | .pypirc
175 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Spring MCP Bridge
  2 | 
  3 | ![License](https://img.shields.io/badge/license-MIT-blue.svg)
  4 | ![Python](https://img.shields.io/badge/python-3.8%2B-green.svg)
  5 | ![Spring](https://img.shields.io/badge/spring-boot%203.x-green.svg)
  6 | 
  7 | **Spring MCP Bridge** is a tool that automatically converts REST endpoints from Spring Boot applications into an MCP (Message Conversation Protocol) server, allowing AI assistants like Claude, Cursor, and other MCP-compatible tools to directly interact with your APIs.
  8 | 
  9 | *It does not currently include authentication. If your Spring Boot API requires authentication, you will need to modify the handler code to include the appropriate headers or tokens.
 10 | 
 11 | ## 📖 Overview
 12 | 
 13 | Integrating existing APIs with AI assistants typically requires manual coding or complex configuration. Spring MCP Bridge eliminates this complexity by automatically scanning your Spring Boot project and generating a ready-to-use MCP server.
 14 | 
 15 | ### ✨ Features
 16 | 
 17 | - **Automatic Scanning**: Discovers all REST endpoints (@RestController, @GetMapping, etc.)
 18 | - **Zero Configuration**: No modifications needed to existing Spring Boot code
 19 | - **Model Preservation**: Maintains request and response models as MCP tools
 20 | - **Javadoc Extraction**: Uses existing documentation to enhance MCP tool descriptions
 21 | - **Complete Documentation**: Generates README and clear instructions for use
 22 | 
 23 | ## 🚀 Installation
 24 | 
 25 | ```bash
 26 | # Clone the repository
 27 | git clone https://github.com/brunosantos/spring-mcp-bridge.git
 28 | 
 29 | # Enter the directory
 30 | cd spring-mcp-bridge
 31 | ```
 32 | 
 33 | ## 🛠️ Usage
 34 | 
 35 | 1. **Scan your Spring Boot project**:
 36 | 
 37 | ```bash
 38 | python spring_boot_mcp_converter.py --project /path/to/spring-project --output ./mcp_server --name MyAPI
 39 | ```
 40 | 
 41 | 2. **Run the generated MCP server**:
 42 | 
 43 | ```bash
 44 | cd mcp_server
 45 | pip install -r requirements.txt
 46 | python main.py
 47 | ```
 48 | 
 49 | 3. **Connect via MCP client**:
 50 |    - Configure your MCP client (Claude, Cursor, etc.) to use `http://localhost:8000`
 51 |    - The MCP schema will be available at `http://localhost:8000/.well-known/mcp-schema.json`
 52 | 
 53 | ## 📋 Arguments
 54 | 
 55 | | Argument    | Description                      | Default      |
 56 | |-------------|----------------------------------|--------------|
 57 | | `--project` | Path to Spring Boot project      | (required)   |
 58 | | `--output`  | Output directory                 | ./mcp_server |
 59 | | `--name`    | Application name                 | SpringAPI    |
 60 | | `--debug`   | Enable debug logging             | False        |
 61 | 
 62 | ## 💻 Example
 63 | 
 64 | ```bash
 65 | python spring_mcp_bridge.py --project ~/projects/my-spring-api --output ./mcp_server --name MyAPI
 66 | ```
 67 | 
 68 | The tool will:
 69 | 1. Scan the Spring Boot project for REST controllers
 70 | 2. Identify model types, parameters, and return types
 71 | 3. Generate a fully functional MCP server in Python/FastAPI
 72 | 4. Create a compatible MCP schema that describes all endpoints
 73 | 
 74 | ## 🔄 How It Works
 75 | 
 76 | 1. **Scanning**: Analyzes Java source code to extract metadata from REST endpoints
 77 | 2. **Conversion**: Converts Java types to JSON Schema types for MCP compatibility
 78 | 3. **Generation**: Creates a FastAPI application that maps MCP calls to Spring REST calls
 79 | 4. **Forwarding**: Routes requests from MCP client to the Spring Boot application
 80 | 
 81 | ## 🔍 Spring Boot Features Supported
 82 | 
 83 | - REST Controllers (`@RestController`, `@Controller`)
 84 | - HTTP Methods (`@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, `@PatchMapping`)
 85 | - Request Parameters (`@RequestParam`)
 86 | - Path Variables (`@PathVariable`)
 87 | - Request Bodies (`@RequestBody`)
 88 | - Java Models and DTOs
 89 | 
 90 | ## ⚙️ Configuration
 91 | 
 92 | The generated MCP server can be configured by editing the `main.py` file:
 93 | 
 94 | ```python
 95 | # The Spring Boot base URL - modify this to match your target API
 96 | SPRING_BASE_URL = "http://localhost:8080"
 97 | ```
 98 | 
 99 | ## 🧪 Testing
100 | 
101 | To test the MCP server:
102 | 
103 | 1. Ensure your Spring Boot application is running
104 | 2. Start the MCP server
105 | 3. Visit `http://localhost:8000/docs` to see the FastAPI documentation
106 | 4. Check the MCP schema at `http://localhost:8000/.well-known/mcp-schema.json`
107 | 5. Connect with an MCP client like Claude
108 | 
109 | ## 🤝 Contributing
110 | 
111 | Contributions are welcome! Please feel free to submit a Pull Request.
112 | 
113 | 1. Fork the repository
114 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
115 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
116 | 4. Push to the branch (`git push origin feature/amazing-feature`)
117 | 5. Open a Pull Request
118 | 
119 | ## 📄 License
120 | 
121 | This project is licensed under the MIT License - see the LICENSE file for details.
122 | 
123 | ## 👨‍💻 Author
124 | 
125 | **Bruno Santos**
126 | 
127 | ## 🙏 Acknowledgments
128 | 
129 | - Inspired by FastAPI-MCP and the growing ecosystem of MCP-compatible tools
130 | - Thanks to the Spring Boot and FastAPI communities
```

--------------------------------------------------------------------------------
/src/spring_boot_mcp_converter.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Spring Boot to MCP Converter
  4 | 
  5 | Author: Bruno Santos
  6 | Version: 1.0.0
  7 | Date: April 15, 2025
  8 | License: MIT
  9 | 
 10 | This tool scans a local Spring Boot application and automatically converts
 11 | its REST endpoints into an MCP (Message Conversation Protocol) server.
 12 | This allows Spring Boot APIs to be used with AI assistants and other
 13 | MCP clients like Claude, Cursor, etc.
 14 | 
 15 | Usage:
 16 |     python spring_boot_mcp_converter.py --project /path/to/spring-project --output ./mcp_server --name MyAPI
 17 | 
 18 | For more information about MCP: https://messageconversationprotocol.org
 19 | """
 20 | 
 21 | import os
 22 | import re
 23 | import json
 24 | import logging
 25 | import argparse
 26 | from fastapi import FastAPI
 27 | from pydantic import BaseModel
 28 | from pathlib import Path
 29 | from typing import Dict, List, Optional
 30 | 
 31 | app = FastAPI()
 32 | 
 33 | # Global variable that will be filled with the MCP schema
 34 | MCP_SCHEMA = {}
 35 | 
 36 | class JavaTypeConverter:
 37 |     """Helper class to convert Java types to JSON schema types."""
 38 |     
 39 |     @staticmethod
 40 |     def to_json_schema_type(java_type: str) -> str:
 41 |         """Convert Java type to JSON schema type."""
 42 |         if java_type in ["int", "Integer", "long", "Long", "short", "Short", "byte", "Byte"]:
 43 |             return "integer"
 44 |         elif java_type in ["double", "Double", "float", "Float", "BigDecimal"]:
 45 |             return "number"
 46 |         elif java_type in ["boolean", "Boolean"]:
 47 |             return "boolean"
 48 |         elif java_type in ["List", "ArrayList", "Set", "HashSet", "Collection"] or "<" in java_type:
 49 |             return "array"
 50 |         elif java_type in ["Map", "HashMap", "TreeMap"] or java_type.startswith("Map<"):
 51 |             return "object"
 52 |         else:
 53 |             return "string"
 54 | 
 55 | 
 56 | class SpringEndpointScanner:
 57 |     """Scanner for Spring Boot endpoints and models."""
 58 |     
 59 |     def __init__(self, project_path: str):
 60 |         self.project_path = Path(project_path)
 61 |         self.endpoints = []
 62 |         self.models = {}
 63 |         self.base_package = self._detect_base_package()
 64 |         self.logger = self._setup_logger()
 65 |     
 66 |     def _setup_logger(self) -> logging.Logger:
 67 |         """Set up logger for the scanner."""
 68 |         logger = logging.getLogger("spring_mcp_scanner")
 69 |         logger.setLevel(logging.INFO)
 70 |         
 71 |         # Create console handler
 72 |         handler = logging.StreamHandler()
 73 |         handler.setLevel(logging.INFO)
 74 |         
 75 |         # Create formatter
 76 |         formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 77 |         handler.setFormatter(formatter)
 78 |         
 79 |         # Add handler to logger
 80 |         logger.addHandler(handler)
 81 |         
 82 |         return logger
 83 |     
 84 |     def _detect_base_package(self) -> str:
 85 |         """Detect the base package of the Spring Boot application."""
 86 |         main_class_pattern = re.compile(r'@SpringBootApplication')
 87 |         
 88 |         for java_file in Path(self.project_path).glob('**/src/main/java/**/*.java'):
 89 |             try:
 90 |                 with open(java_file, 'r', encoding='utf-8') as f:
 91 |                     content = f.read()
 92 |                     if main_class_pattern.search(content):
 93 |                         # Extract package from the file
 94 |                         package_match = re.search(r'package\s+([\w.]+);', content)
 95 |                         if package_match:
 96 |                             package = package_match.group(1)
 97 |                             # Remove the last part if it's the filename's package
 98 |                             parts = package.split('.')
 99 |                             if len(parts) > 2:
100 |                                 return '.'.join(parts[:-1])
101 |                             return package
102 |             except UnicodeDecodeError:
103 |                 # Skip files that can't be decoded as UTF-8
104 |                 continue
105 |             except FileNotFoundError:
106 |                 self.logger.error(f"File not found: {java_file}")
107 |                 continue
108 |             except PermissionError:
109 |                 self.logger.error(f"Permission denied: {java_file}")
110 |                 continue
111 |         
112 |         return ""  # Default if not found
113 |     
114 |     def _extract_request_mapping(self, content: str, class_mapping: str = "") -> List[Dict]:
115 |         """Extract request mappings from a controller class."""
116 |         mappings = []
117 |         
118 |         # Extract class-level mapping
119 |         class_mapping_pattern = re.compile(r'@RequestMapping\s*\(\s*(?:value\s*=)?\s*"([^"]+)"\s*\)')
120 |         class_match = class_mapping_pattern.search(content)
121 |         if class_match:
122 |             class_mapping = class_match.group(1)
123 |             if not class_mapping.startswith('/'):
124 |                 class_mapping = '/' + class_mapping
125 |         
126 |         # Extract method mappings
127 |         method_mapping_patterns = [
128 |             (r'@GetMapping\s*\(\s*(?:value\s*=)?\s*"([^"]+)"\s*\)', 'GET'),
129 |             (r'@PostMapping\s*\(\s*(?:value\s*=)?\s*"([^"]+)"\s*\)', 'POST'),
130 |             (r'@PutMapping\s*\(\s*(?:value\s*=)?\s*"([^"]+)"\s*\)', 'PUT'),
131 |             (r'@DeleteMapping\s*\(\s*(?:value\s*=)?\s*"([^"]+)"\s*\)', 'DELETE'),
132 |             (r'@PatchMapping\s*\(\s*(?:value\s*=)?\s*"([^"]+)"\s*\)', 'PATCH')
133 |         ]
134 |         
135 |         # Extract method descriptions from javadoc comments
136 |         javadoc_pattern = re.compile(r'/\*\*([\s\S]*?)\*/')
137 |         javadoc_descriptions = {}
138 |         
139 |         for javadoc_match in javadoc_pattern.finditer(content):
140 |             javadoc = javadoc_match.group(1)
141 |             description = ""
142 |             
143 |             # Extract the main description from the javadoc
144 |             for line in javadoc.split('\n'):
145 |                 line = line.strip().lstrip('*').strip()
146 |                 if line and not line.startswith('@'):
147 |                     description += line + " "
148 |             
149 |             # Find the method name that follows this javadoc
150 |             method_pos = javadoc_match.end()
151 |             method_search = content[method_pos:method_pos+200]  # Look at next 200 chars
152 |             method_name_match = re.search(r'(?:public|private|protected)?\s+\w+\s+(\w+)\s*\(', method_search)
153 |             
154 |             if method_name_match and description:
155 |                 javadoc_descriptions[method_name_match.group(1)] = description.strip()
156 |         
157 |         for pattern, method in method_mapping_patterns:
158 |             for match in re.finditer(pattern, content):
159 |                 path = match.group(1)
160 |                 if not path.startswith('/'):
161 |                     path = '/' + path
162 |                 
163 |                 # Find method name and details
164 |                 try:
165 |                     method_content = content[match.end():].split('}')[0]
166 |                     method_name_match = re.search(r'(?:public|private|protected)?\s+\w+\s+(\w+)\s*\(', method_content)
167 |                 
168 |                     if method_name_match:
169 |                         method_name = method_name_match.group(1)
170 |                         
171 |                         # Extract method parameters
172 |                         param_section = re.search(r'\(\s*(.*?)\s*\)', method_content)
173 |                         parameters = []
174 |                         response_type = "Object"
175 |                         
176 |                         if param_section:
177 |                             # Extract return type
178 |                             return_match = re.search(r'(?:public|private|protected)?\s+(\w+(?:<.*?>)?)\s+\w+\s*\(', method_content)
179 |                             if return_match:
180 |                                 response_type = return_match.group(1)
181 |                             
182 |                             # Extract parameters
183 |                             param_text = param_section.group(1)
184 |                             if param_text.strip():
185 |                                 param_list = param_text.split(',')
186 |                                 for param in param_list:
187 |                                     param = param.strip()
188 |                                     if param:
189 |                                         param_parts = param.split()
190 |                                         if len(param_parts) >= 2:
191 |                                             param_type = param_parts[-2]
192 |                                             param_name = param_parts[-1]
193 |                                             
194 |                                             # Check for request body
195 |                                             is_body = '@RequestBody' in param
196 |                                             
197 |                                             # Check for path variable
198 |                                             path_var_match = re.search(r'@PathVariable\s*(?:\(\s*(?:name|value)\s*=\s*"([^"]+)"\s*\))?', param)
199 |                                             path_var = path_var_match.group(1) if path_var_match else None
200 |                                             
201 |                                             # Check for request param
202 |                                             req_param_match = re.search(r'@RequestParam\s*(?:\(\s*(?:name|value)\s*=\s*"([^"]+)"\s*(?:,\s*required\s*=\s*(true|false))?\))?', param)
203 |                                             req_param = req_param_match.group(1) if req_param_match else None
204 |                                             req_param_required = True
205 |                                             if req_param_match and req_param_match.group(2) == "false":
206 |                                                 req_param_required = False
207 |                                             
208 |                                             parameter = {
209 |                                                 "name": param_name.replace(";", ""),
210 |                                                 "type": param_type,
211 |                                                 "isBody": is_body,
212 |                                             }
213 |                                             
214 |                                             if path_var:
215 |                                                 parameter["pathVariable"] = path_var
216 |                                             elif req_param:
217 |                                                 parameter["requestParam"] = req_param
218 |                                                 parameter["required"] = req_param_required
219 |                                             
220 |                                             parameters.append(parameter)
221 |                         
222 |                         full_path = class_mapping + path if class_mapping else path
223 |                         
224 |                         # Get description from javadoc if available
225 |                         description = javadoc_descriptions.get(method_name, f"{method} endpoint for {full_path}")
226 |                         
227 |                         endpoint = {
228 |                             "path": full_path,
229 |                             "method": method,
230 |                             "methodName": method_name,
231 |                             "parameters": parameters,
232 |                             "responseType": response_type,
233 |                             "description": description
234 |                         }
235 |                         
236 |                         mappings.append(endpoint)
237 |                 except Exception as e:
238 |                     # Log the error and continue with the next match
239 |                     logging.error(f"Error processing mapping: {str(e)}")
240 |                     continue
241 |         
242 |         return mappings
243 |     
244 |     def _extract_models(self, java_file: Path) -> Dict:
245 |         """Extract model classes from Java files."""
246 |         try:
247 |             with open(java_file, 'r', encoding='utf-8') as f:
248 |                 content = f.read()
249 |             
250 |             # Check if it's a model class (typically has @Entity or @Data annotation)
251 |             model_pattern = re.compile(r'@(\w+)\s*\(')
252 |             models = {}
253 |             for match in model_pattern.finditer(content):
254 |                 annotation = match.group(1)
255 |                 if annotation in ['Entity', 'Data', 'Serializable']:
256 |                     # Extract class name
257 |                     class_name_match = re.search(r'class\s+(\w+)', content)
258 |                     if class_name_match:
259 |                         class_name = class_name_match.group(1)
260 |                         fields = []
261 |                         
262 |                         # Extract fields
263 |                         field_pattern = re.compile(r'private\s+(\w+\s+\w+)\s*;')
264 |                         for field_match in field_pattern.finditer(content):
265 |                             field = field_match.group(1).strip()
266 |                             field_parts = field.split()
267 |                             if len(field_parts) == 2:
268 |                                 field_type, field_name = field_parts
269 |                                 fields.append({
270 |                                     'name': field_name,
271 |                                     'type': JavaTypeConverter.to_json_schema_type(field_type)
272 |                                 })
273 |                         models[class_name] = fields
274 |             return models
275 |         except Exception as e:
276 |             self.logger.error(f"Error extracting models from {java_file}: {str(e)}")
277 |             return {}
278 |     
279 |     def scan_project(self):
280 |         """Scan the project directory and extract endpoints and models."""
281 |         if not self.project_path.exists():
282 |             raise FileNotFoundError(f"Project path '{self.project_path}' does not exist.")
283 |         
284 |         # First find all Java files
285 |         java_files = list(self.project_path.glob('**/src/main/java/**/*.java'))
286 |         
287 |         if not java_files:
288 |             self.logger.warning("No Java files found in the project path. Check if the path is correct.")
289 |             return
290 |         
291 |         for java_file in java_files:
292 |             self.logger.info(f"Scanning file: {java_file}")
293 |             
294 |             try:
295 |                 # Extract models from this file
296 |                 models = self._extract_models(java_file)
297 |                 self.models.update(models)
298 |                 
299 |                 with open(java_file, 'r', encoding='utf-8') as f:
300 |                     content = f.read()
301 |                 
302 |                 # Extract endpoints only if contains controller annotations
303 |                 if re.search(r'@(RestController|Controller)', content):
304 |                     endpoints = self._extract_request_mapping(content)
305 |                     self.endpoints.extend(endpoints)
306 |             except Exception as e:
307 |                 self.logger.error(f"Error processing file {java_file}: {str(e)}")
308 |                 continue
309 |         
310 |         self.logger.info(f"Found {len(self.endpoints)} endpoints.")
311 |         self.logger.info(f"Found {len(self.models)} models.")
312 | 
313 | 
314 | class MCPServer:
315 |     """Generate MCP server from extracted endpoints and models."""
316 |     
317 |     def __init__(self, name: str, endpoints: List[Dict], models: Dict):
318 |         self.name = name
319 |         self.endpoints = endpoints
320 |         self.models = models
321 |     
322 |     def generate_schema(self) -> Dict:
323 |         """Generate MCP schema."""
324 |         schema = {
325 |             "name": self.name,
326 |             "endpoints": self.endpoints,
327 |             "models": self.models
328 |         }
329 |         return schema
330 |     
331 |     def generate_server(self, output_dir: Path):
332 |         """Generate the MCP server code."""
333 |         # Create the output directory if it doesn't exist
334 |         output_dir.mkdir(parents=True, exist_ok=True)
335 |         
336 |         # Create the schema file
337 |         schema = self.generate_schema()
338 |         schema_path = output_dir / "mcp_schema.json"
339 |         
340 |         with open(schema_path, 'w', encoding='utf-8') as f:
341 |             json.dump(schema, f, indent=2)
342 |         
343 |         # Create main.py with FastAPI server
344 |         main_py_content = """#!/usr/bin/env python3
345 | '''
346 | MCP Server for {name}
347 | Generated by Spring Boot to MCP Converter
348 | '''
349 | 
350 | import json
351 | from fastapi import FastAPI, Request, Response
352 | from fastapi.middleware.cors import CORSMiddleware
353 | import httpx
354 | import os
355 | from pydantic import BaseModel
356 | from typing import Dict, List, Any, Optional
357 | 
358 | app = FastAPI(title="{name} MCP Server")
359 | 
360 | # CORS configuration
361 | app.add_middleware(
362 |     CORSMiddleware,
363 |     allow_origins=["*"],
364 |     allow_credentials=True,
365 |     allow_methods=["*"],
366 |     allow_headers=["*"],
367 | )
368 | 
369 | # Load the MCP schema
370 | with open("mcp_schema.json", "r") as f:
371 |     MCP_SCHEMA = json.load(f)
372 | 
373 | # Define the base URL for the Spring Boot application
374 | # You should set this to the actual URL of your Spring Boot app
375 | SPRING_BOOT_BASE_URL = os.getenv("SPRING_BOOT_URL", "http://localhost:8080")
376 | 
377 | @app.get("/.well-known/mcp-schema.json")
378 | async def get_mcp_schema():
379 |     ""Return the MCP schema for this server.""
380 |     return MCP_SCHEMA
381 | 
382 | # Generate dynamic endpoints based on the schema
383 | for endpoint in MCP_SCHEMA["endpoints"]:
384 |     path = endpoint["path"]
385 |     method = endpoint["method"].lower()
386 |     method_name = endpoint["methodName"]
387 |     
388 |     # Create a function to handle the request
389 |     async def create_handler(endpoint_info):
390 |         async def handler(request: Request):
391 |             # Extract path params, query params, and request body
392 |             path_params = {{}}
393 |             query_params = {{}}
394 |             
395 |             for param in endpoint_info["parameters"]:
396 |                 if "pathVariable" in param:
397 |                     # Extract path variable from request path
398 |                     path_var = param["pathVariable"]
399 |                     path_params[path_var] = request.path_params.get(path_var)
400 |                 elif "requestParam" in param:
401 |                     # Extract query parameters
402 |                     req_param = param["requestParam"]
403 |                     query_params[req_param] = request.query_params.get(req_param)
404 |             
405 |             # Prepare the URL for the Spring Boot application
406 |             target_url = f"{{SPRING_BOOT_BASE_URL}}{{endpoint_info['path']}}"
407 |             
408 |             # Forward the request to the Spring Boot application
409 |             async with httpx.AsyncClient() as client:
410 |                 if method == "get":
411 |                     response = await client.get(target_url, params=query_params)
412 |                 elif method == "post":
413 |                     body = await request.json() if request.headers.get("content-type") == "application/json" else None
414 |                     response = await client.post(target_url, params=query_params, json=body)
415 |                 elif method == "put":
416 |                     body = await request.json() if request.headers.get("content-type") == "application/json" else None
417 |                     response = await client.put(target_url, params=query_params, json=body)
418 |                 elif method == "delete":
419 |                     response = await client.delete(target_url, params=query_params)
420 |                 elif method == "patch":
421 |                     body = await request.json() if request.headers.get("content-type") == "application/json" else None
422 |                     response = await client.patch(target_url, params=query_params, json=body)
423 |                 else:
424 |                     return {{"error": f"Unsupported method: {{method}}"}}
425 |             
426 |             # Return the response from the Spring Boot application
427 |             return Response(
428 |                 content=response.content,
429 |                 status_code=response.status_code,
430 |                 headers=dict(response.headers),
431 |             )
432 |         
433 |         return handler
434 |     
435 |     # Register the endpoint with FastAPI
436 |     handler = create_handler(endpoint)
437 |     
438 |     # Register the endpoint with FastAPI using the appropriate HTTP method
439 |     if method == "get":
440 |         app.get(path)(handler)
441 |     elif method == "post":
442 |         app.post(path)(handler)
443 |     elif method == "put":
444 |         app.put(path)(handler)
445 |     elif method == "delete":
446 |         app.delete(path)(handler)
447 |     elif method == "patch":
448 |         app.patch(path)(handler)
449 | 
450 | if __name__ == "__main__":
451 |     import uvicorn
452 |     uvicorn.run(app, host="0.0.0.0", port=8000)
453 | """
454 |         main_py = output_dir / "main.py"
455 |         with open(main_py, 'w', encoding='utf-8') as f:
456 |             f.write(main_py_content.format(name=self.name))
457 |         
458 |         # Create requirements.txt
459 |         requirements_txt = output_dir / "requirements.txt"
460 |         with open(requirements_txt, 'w', encoding='utf-8') as f:
461 |             f.write("""fastapi>=0.95.0
462 | uvicorn>=0.21.1
463 | httpx>=0.24.0
464 | pydantic>=1.10.7
465 | """)
466 |         
467 |         # Create README.md
468 |         readme_content = """# {name} MCP Server
469 | 
470 | This is an automatically generated MCP (Message Conversation Protocol) server for the Spring Boot application.
471 | 
472 | ## Getting Started
473 | 
474 | 1. Install the required dependencies:
475 |    ```
476 |    pip install -r requirements.txt
477 |    ```
478 | 
479 | 2. Set the environment variable to your Spring Boot application URL:
480 |    ```
481 |    export SPRING_BOOT_URL="http://localhost:8080"
482 |    ```
483 | 
484 | 3. Start the MCP server:
485 |    ```
486 |    python main.py
487 |    ```
488 | 
489 | 4. The server will be available at http://localhost:8000
490 | 
491 | 5. The MCP schema is available at http://localhost:8000/.well-known/mcp-schema.json
492 | 
493 | ## Endpoints
494 | 
495 | Total endpoints: {endpoint_count}
496 | 
497 | """
498 |         
499 |         readme_md = output_dir / "README.md"
500 |         with open(readme_md, 'w', encoding='utf-8') as f:
501 |             f.write(readme_content.format(name=self.name, endpoint_count=len(self.endpoints)))
502 |             
503 |             # Add endpoint details to README
504 |             for endpoint in self.endpoints:
505 |                 f.write(f"- **{endpoint['method']}** `{endpoint['path']}`: {endpoint['description']}\n")
506 | 
507 | 
508 | @app.get("/.well-known/mcp-schema.json")
509 | async def get_mcp_schema():
510 |     """Return the MCP schema for this server."""
511 |     return MCP_SCHEMA
512 | 
513 | 
514 | # Main function
515 | def main():
516 |     parser = argparse.ArgumentParser(description="Spring Boot to MCP Converter")
517 |     parser.add_argument('--project', required=True, help="Path to the Spring Boot project")
518 |     parser.add_argument('--output', required=True, help="Output directory for MCP server")
519 |     parser.add_argument('--name', default="MyAPI", help="Name of the generated MCP server")
520 |     args = parser.parse_args()
521 |     
522 |     print(f"Starting Spring Boot to MCP conversion...")
523 |     print(f"Project path: {args.project}")
524 |     print(f"Output directory: {args.output}")
525 |     print(f"API name: {args.name}")
526 |     
527 |     try:
528 |         # Scan the Spring Boot project for endpoints and models
529 |         scanner = SpringEndpointScanner(args.project)
530 |         scanner.scan_project()
531 |         
532 |         # Create MCP server schema
533 |         mcp_server = MCPServer(args.name, scanner.endpoints, scanner.models)
534 |         
535 |         # Generate the MCP server
536 |         output_path = Path(args.output)
537 |         mcp_server.generate_server(output_path)
538 |         
539 |         print(f"MCP server generated successfully at: {output_path}")
540 |         print(f"To start the server:")
541 |         print(f"  1. cd {args.output}")
542 |         print(f"  2. pip install -r requirements.txt")
543 |         print(f"  3. python main.py")
544 |         print(f"The server will be available at http://localhost:8000")
545 |         print(f"The MCP schema will be available at http://localhost:8000/.well-known/mcp-schema.json")
546 |         
547 |         # Update the global MCP_SCHEMA
548 |         global MCP_SCHEMA
549 |         MCP_SCHEMA = mcp_server.generate_schema()
550 |         
551 |     except Exception as e:
552 |         print(f"Error: {str(e)}")
553 |         import traceback
554 |         traceback.print_exc()
555 |         return 1
556 |     
557 |     return 0
558 | 
559 | 
560 | if __name__ == "__main__":
561 |     exit(main())
```