aigroup-stata-mcp 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. aigroup_stata_mcp-1.0.3.dist-info/METADATA +345 -0
  2. aigroup_stata_mcp-1.0.3.dist-info/RECORD +38 -0
  3. aigroup_stata_mcp-1.0.3.dist-info/WHEEL +4 -0
  4. aigroup_stata_mcp-1.0.3.dist-info/entry_points.txt +5 -0
  5. aigroup_stata_mcp-1.0.3.dist-info/licenses/LICENSE +21 -0
  6. stata_mcp/__init__.py +18 -0
  7. stata_mcp/cli/__init__.py +8 -0
  8. stata_mcp/cli/_cli.py +95 -0
  9. stata_mcp/core/__init__.py +14 -0
  10. stata_mcp/core/data_info/__init__.py +11 -0
  11. stata_mcp/core/data_info/_base.py +288 -0
  12. stata_mcp/core/data_info/csv.py +123 -0
  13. stata_mcp/core/data_info/dta.py +70 -0
  14. stata_mcp/core/stata/__init__.py +13 -0
  15. stata_mcp/core/stata/stata_controller/__init__.py +9 -0
  16. stata_mcp/core/stata/stata_controller/controller.py +208 -0
  17. stata_mcp/core/stata/stata_do/__init__.py +9 -0
  18. stata_mcp/core/stata/stata_do/do.py +177 -0
  19. stata_mcp/core/stata/stata_finder/__init__.py +9 -0
  20. stata_mcp/core/stata/stata_finder/base.py +294 -0
  21. stata_mcp/core/stata/stata_finder/finder.py +193 -0
  22. stata_mcp/core/stata/stata_finder/linux.py +43 -0
  23. stata_mcp/core/stata/stata_finder/macos.py +88 -0
  24. stata_mcp/core/stata/stata_finder/windows.py +191 -0
  25. stata_mcp/server/__init__.py +8 -0
  26. stata_mcp/server/main.py +153 -0
  27. stata_mcp/server/prompts/__init__.py +8 -0
  28. stata_mcp/server/prompts/core_prompts.py +122 -0
  29. stata_mcp/server/tools/__init__.py +10 -0
  30. stata_mcp/server/tools/core_tools.py +59 -0
  31. stata_mcp/server/tools/file_tools.py +163 -0
  32. stata_mcp/server/tools/stata_tools.py +221 -0
  33. stata_mcp/utils/Installer/__init__.py +7 -0
  34. stata_mcp/utils/Installer/installer.py +85 -0
  35. stata_mcp/utils/Prompt/__init__.py +74 -0
  36. stata_mcp/utils/Prompt/string.py +91 -0
  37. stata_mcp/utils/__init__.py +23 -0
  38. stata_mcp/utils/usable.py +244 -0
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ import glob
6
+ import logging
7
+ import os
8
+ import platform
9
+ from typing import Optional
10
+
11
+ from .linux import FinderLinux
12
+ from .macos import FinderMacOS
13
+ from .windows import FinderWindows, get_available_drives, windows_stata_match
14
+
15
+
16
+ class StataFinder:
17
+
18
+
19
+
20
+ FINDER_MAPPING = {
21
+ "Darwin": FinderMacOS,
22
+ "Windows": FinderWindows,
23
+ "Linux": FinderLinux,
24
+ }
25
+
26
+ def __init__(self, stata_cli: str = None):
27
+ """简要描述函数功能"""
28
+ finder_cls = self.FINDER_MAPPING.get(platform.system())
29
+ self.finder = finder_cls(stata_cli)
30
+
31
+ @property
32
+ def STATA_CLI(self) -> str | None:
33
+ """简要描述函数功能"""
34
+ try:
35
+ return self.finder.find_stata()
36
+ except (FileNotFoundError, AttributeError):
37
+ return None
38
+
39
+
40
+ class StataFinderOLD:
41
+
42
+
43
+ """A class to find Stata CLI installations across different operating systems."""
44
+
45
+ FINDER_MAPPING = {
46
+ "Darwin": FinderMacOS,
47
+ "Windows": FinderWindows,
48
+ "Linux": FinderLinux,
49
+ }
50
+
51
+ def __init__(self, stata_cli: str = None):
52
+ """
53
+ Initialize the StataFinder.
54
+
55
+ Args:
56
+ stata_cli (str): the user updates stata_cli file path
57
+ """
58
+ self.current_os = platform.system()
59
+ self._os_finders = {
60
+ "Darwin": self._find_stata_macos,
61
+ "Windows": self._find_stata_windows,
62
+ "Linux": self._find_stata_linux,
63
+ }
64
+ self.finder = self._os_finders.get(self.current_os, None)
65
+ # TODO: Change the original finder to the newer
66
+ # self.finder = self.FINDER_MAPPING.get(self.current_os)(stata_cli = stata_cli)
67
+
68
+ # @property
69
+ # def STATA_CLI(self) -> str: # 等前面的都改好了,就可以只保留这个了
70
+ # return self.finder.find_stata()
71
+
72
+ def _stata_version_windows(self, driver: str = "C:\\") -> list:
73
+ """Find Stata installations on Windows."""
74
+ stata_paths = []
75
+ common_patterns = [
76
+ os.path.join(driver, "Program Files", "Stata*", "*.exe"),
77
+ os.path.join(driver, "Program Files(x86)", "Stata*", "*.exe"),
78
+ ]
79
+
80
+ for pattern in common_patterns:
81
+ try:
82
+ matches = glob.glob(pattern)
83
+ for match in matches:
84
+ if "stata" in match.lower() and match.lower().endswith(".exe"):
85
+ stata_paths.append(match)
86
+
87
+ if not stata_paths:
88
+ for root, dirs, files in os.walk(driver):
89
+ if root.count(os.sep) - driver.count(os.sep) > 3:
90
+ dirs.clear()
91
+ continue
92
+
93
+ for file in files:
94
+ if (
95
+ file.lower().endswith(".exe")
96
+ and "stata" in file.lower()
97
+ ):
98
+ stata_paths.append(os.path.join(root, file))
99
+
100
+ except Exception as e:
101
+ logging.warn(e)
102
+
103
+ return stata_paths
104
+
105
+ def _find_stata_macos(self) -> Optional[str]:
106
+ return self.finder.find_stata()
107
+
108
+ def _default_stata_cli_path_windows(self) -> Optional[str]:
109
+ """Get default Stata CLI path on Windows."""
110
+ drives = get_available_drives()
111
+ stata_cli_path_list = []
112
+
113
+ for drive in drives:
114
+ stata_cli_path_list += self._stata_version_windows(drive)
115
+
116
+ if len(stata_cli_path_list) == 0:
117
+ return None
118
+ elif len(stata_cli_path_list) == 1:
119
+ return stata_cli_path_list[0]
120
+ else:
121
+ for path in stata_cli_path_list:
122
+ if windows_stata_match(path):
123
+ return path
124
+ return stata_cli_path_list[0]
125
+
126
+ def _find_stata_windows(self) -> Optional[str]:
127
+ """Find Stata CLI on Windows systems."""
128
+ return self._default_stata_cli_path_windows()
129
+
130
+ def _default_stata_cli_path_linux(self) -> Optional[str]:
131
+ """Get default Stata CLI path on Linux."""
132
+ # TODO: Implement Linux-specific logic
133
+ return None
134
+
135
+ def _find_stata_linux(self) -> Optional[str]:
136
+ """Find the Stata CLI path on Linux systems.
137
+
138
+ For Linux users, this function attempts to locate the Stata CLI executable.
139
+
140
+ Returns:
141
+ The path to the Stata CLI executable, or None if not found.
142
+ """
143
+ return self._default_stata_cli_path_linux()
144
+
145
+ def find_stata(self,
146
+ os_name: Optional[str] = None,
147
+ is_env: bool = True) -> Optional[str]:
148
+ """Find Stata CLI installation.
149
+
150
+ Args:
151
+ os_name: Operating system name. If None, uses current system.
152
+ is_env: Whether to check environment variables first.
153
+
154
+ Returns:
155
+ Path to Stata CLI executable, or None if not found.
156
+
157
+ Raises:
158
+ RuntimeError: If the operating system is not supported.
159
+ """
160
+ if is_env:
161
+ stata_cli = os.getenv("stata_cli", None) or os.getenv("STATA_CLI", None)
162
+ if stata_cli:
163
+ return stata_cli
164
+
165
+ target_os = os_name or self.current_os
166
+ finder = self._os_finders.get(target_os)
167
+
168
+ if not finder:
169
+ raise RuntimeError(f"Unsupported OS: {target_os!r}")
170
+
171
+ return finder()
172
+
173
+ def get_supported_os(self) -> list:
174
+ """Get list of supported operating systems."""
175
+ return list(self._os_finders.keys())
176
+
177
+ def is_stata_available(
178
+ self, os_name: Optional[str] = None, is_env: bool = True
179
+ ) -> bool:
180
+ """Check if Stata is available on the system.
181
+
182
+ Args:
183
+ os_name: Operating system name. If None, uses current system.
184
+ is_env: Whether to check environment variables first.
185
+
186
+ Returns:
187
+ True if Stata is found, False otherwise.
188
+ """
189
+ try:
190
+ stata_path = self.find_stata(os_name=os_name, is_env=is_env)
191
+ return stata_path is not None
192
+ except RuntimeError:
193
+ return False
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ from pathlib import Path
6
+ from typing import Dict, List
7
+
8
+ from .base import FinderBase
9
+
10
+
11
+ class FinderLinux(FinderBase):
12
+
13
+
14
+
15
+ def finder(self) -> str:
16
+ """简要描述函数功能"""
17
+ bin_results = self.find_from_bin()
18
+ if bin_results:
19
+ return max(bin_results).stata_cli_path
20
+ else:
21
+ raise FileNotFoundError("Stata CLI not found")
22
+
23
+ def find_path_base(self) -> Dict[str, List[str]]:
24
+ """简要描述函数功能"""
25
+ # Start with default bin directory
26
+ bin_dirs = ["/usr/local/bin"]
27
+
28
+ # Search for additional directories containing "stata" in /usr/local/bin
29
+ usr_local_bin = Path("/usr/local/bin")
30
+ if usr_local_bin.exists() and usr_local_bin.is_dir():
31
+ # Look for directories containing "stata" in their name
32
+ for item in usr_local_bin.iterdir():
33
+ if item.is_dir() and "stata" in item.name.lower():
34
+ # Add the stata directory path to search directories
35
+ bin_dirs.append(str(item))
36
+
37
+ return {
38
+ "bin": bin_dirs,
39
+ }
40
+
41
+
42
+ if __name__ == "__main__":
43
+ finder = FinderLinux()
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Dict, List
8
+
9
+ from .base import FinderBase, StataEditionConfig
10
+
11
+
12
+ class FinderMacOS(FinderBase):
13
+
14
+
15
+
16
+ def finder(self) -> str | None:
17
+ """简要描述函数功能"""
18
+ bin_results = self.find_from_bin()
19
+ if bin_results:
20
+ return max(bin_results).stata_cli_path
21
+
22
+ application_results = self.find_from_application()
23
+ if application_results:
24
+ return max(application_results).stata_cli_path
25
+ else: # If there is no Stata CLI found, raise an error
26
+ raise FileNotFoundError("Stata CLI not found")
27
+
28
+ def find_path_base(self) -> Dict[str, List[str]]:
29
+ """简要描述函数功能"""
30
+ return {
31
+ "bin": ["/usr/local/bin"],
32
+ "application": ["/Applications"],
33
+ }
34
+
35
+ def _application_find_base(self,
36
+ dot_app: str | Path,
37
+ version: int | float = None) -> StataEditionConfig | None:
38
+ _version = version
39
+ _edition = None
40
+ stata_cli_path = None
41
+
42
+ if not _version:
43
+ for isstata_file in dot_app.glob("isstata.*"):
44
+ if isstata_file.is_file():
45
+ # Extract version number from filename like "isstata.180"
46
+ match = re.search(r'isstata\.(\d+)', isstata_file.name.lower())
47
+ if match:
48
+ _version = float(match.group(1)) / 10
49
+ break
50
+ for stata_app in dot_app.glob("Stata*.app"):
51
+ if stata_app.is_dir():
52
+ # Extract edition from Stata app name (MP, SE, BE, IC)
53
+ # Remove "Stata" prefix and ".app" suffix, then convert to lowercase
54
+ _edition = stata_app.name.replace("Stata", "").replace(".app", "").lower()
55
+ __stata_cli_path = stata_app / "Contents" / "MacOS" / f"stata-{_edition}"
56
+ if self._is_executable(__stata_cli_path):
57
+ stata_cli_path = str(__stata_cli_path)
58
+ break
59
+ if _version and _edition and stata_cli_path:
60
+ return StataEditionConfig(_edition, _version, stata_cli_path)
61
+
62
+ else:
63
+ return None
64
+
65
+ def find_from_application(self) -> List[StataEditionConfig]:
66
+ """简要描述函数功能"""
67
+ found_executables: List[StataEditionConfig] = []
68
+ applications_dir = Path(self.find_path_base().get("application")[0])
69
+
70
+ # Check for /Applications/Stata directory for Multi-Stata Exist
71
+ stata_dir = applications_dir / "Stata"
72
+ if default_stata := self._application_find_base(stata_dir): # If exist default, return directly.
73
+ return [default_stata]
74
+
75
+ # 通过for循环来从applications_dir里找stata*.app
76
+ for stata_app in applications_dir.glob("Stata *"):
77
+ _version = None
78
+ if stata_app.is_dir():
79
+ _version = eval(stata_app.name.split()[-1])
80
+ if stata_app_config := self._application_find_base(stata_app, version=_version):
81
+ found_executables.append(stata_app_config)
82
+
83
+ return found_executables
84
+
85
+
86
+ if __name__ == "__main__":
87
+ finder = FinderMacOS()
88
+ print(finder.finder())
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ import glob
6
+ import os
7
+ import re
8
+ import string
9
+ from typing import Dict, List
10
+
11
+ from .base import FinderBase, StataEditionConfig
12
+
13
+
14
+ def get_available_drives():
15
+ """简要描述函数功能"""
16
+ drives = []
17
+ for letter in string.ascii_uppercase:
18
+ if os.path.exists(f"{letter}:\\"):
19
+ drives.append(f"{letter}:\\")
20
+ return drives
21
+
22
+
23
+ def windows_stata_match(path: str) -> bool:
24
+ """
25
+ Check whether the given path matches the pattern of a Windows
26
+ Stata executable.
27
+
28
+ Args:
29
+ path: Path string to be checked.
30
+
31
+ Returns:
32
+ bool: ``True`` if the path matches a Stata executable pattern,
33
+ otherwise ``False``.
34
+ """
35
+ # Regular expression matching ``Stata\d+\Stata(MP|SE|BE|IC)?.exe``
36
+ # ``\d+`` matches one or more digits (the version number)
37
+ # ``(MP|SE|BE|IC)?`` matches an optional edition suffix
38
+ pattern = r"Stata\d+\\\\Stata(MP|SE|BE|IC)?\.exe$"
39
+
40
+ if re.search(pattern, path):
41
+ return True
42
+ return False
43
+
44
+
45
+ class FinderWindows(FinderBase):
46
+
47
+
48
+
49
+ def finder(self) -> str:
50
+ """简要描述函数功能"""
51
+ default_results = self.find_from_default_install_path()
52
+ if default_results:
53
+ return max(default_results).stata_cli_path
54
+
55
+ driver_results = self.scan_stata_from_drivers()
56
+ if driver_results:
57
+ return max(driver_results).stata_cli_path
58
+
59
+ deep_results = self.scan_stata_deeply()
60
+ if deep_results:
61
+ return max(deep_results).stata_cli_path
62
+ else:
63
+ raise FileNotFoundError("Stata executable not found")
64
+
65
+ def find_path_base(self) -> Dict[str, List[str]]:
66
+ """简要描述函数功能"""
67
+ return {
68
+ "default": [
69
+ r"C:\Program Files\Stata*",
70
+ r"C:\Program Files (x86)\Stata*",
71
+ r"D:\Program Files\Stata*",
72
+ r"D:\Program Files (x86)\Stata*",
73
+ ],
74
+ "drivers": get_available_drives()
75
+ }
76
+
77
+ def find_from_default_install_path(self) -> List[StataEditionConfig]:
78
+ """Find Stata installations in default Windows installation paths.
79
+
80
+ This function searches for Stata in standard Windows locations
81
+ like Program Files directories using simple matching.
82
+
83
+ Returns:
84
+ List of StataEditionConfig objects found in default paths
85
+ """
86
+ found_configs = []
87
+
88
+ # Common Windows installation paths
89
+ common_paths = self.find_path_base().get("default")
90
+
91
+ for path_pattern in common_paths:
92
+ try:
93
+ matches = glob.glob(path_pattern)
94
+ for match in matches:
95
+ # Search in these Stata directories for executables
96
+ executables = glob.glob(os.path.join(match, "*.exe"))
97
+ for exe in executables:
98
+ # Simple matching like the original implementation
99
+ if "stata" in exe.lower() and exe.lower().endswith(".exe"):
100
+ config = StataEditionConfig.from_path(exe)
101
+ found_configs.append(config)
102
+ except Exception:
103
+ pass
104
+
105
+ return found_configs
106
+
107
+ def scan_stata_from_drivers(self) -> List[StataEditionConfig]:
108
+ """Scan all available drivers for Stata installations in non-standard locations.
109
+
110
+ This method searches for Stata in locations that are NOT in standard
111
+ installation paths (Program Files, etc.), focusing on custom or
112
+ alternative installation locations.
113
+
114
+ Returns:
115
+ List of StataEditionConfig objects found in non-standard locations
116
+ """
117
+ drivers_base = self.find_path_base().get("drivers")
118
+ found_configs = []
119
+
120
+ if not drivers_base:
121
+ return found_configs
122
+
123
+ for driver in drivers_base:
124
+ # Skip standard installation directories to avoid duplication
125
+ skip_patterns = [
126
+ os.path.join(driver, "Program Files"),
127
+ os.path.join(driver, "Program Files (x86)"),
128
+ ]
129
+
130
+ try:
131
+ # Limit search depth to avoid excessive scanning
132
+ for root, dirs, files in os.walk(driver):
133
+ # Skip standard installation directories
134
+ should_skip = False
135
+ for skip_pattern in skip_patterns:
136
+ if root.lower().startswith(skip_pattern.lower()):
137
+ should_skip = True
138
+ break
139
+
140
+ if should_skip:
141
+ continue
142
+
143
+ # Limit depth to 4 levels
144
+ if root.count(os.sep) - driver.count(os.sep) > 4:
145
+ dirs.clear()
146
+ continue
147
+
148
+ for file in files:
149
+ if (
150
+ file.lower().endswith(".exe")
151
+ and "stata" in file.lower()
152
+ ):
153
+ full_path = os.path.join(root, file)
154
+ config = StataEditionConfig.from_path(full_path)
155
+ found_configs.append(config)
156
+ except Exception:
157
+ pass
158
+
159
+ return found_configs
160
+
161
+ def scan_stata_deeply(self) -> List[StataEditionConfig]:
162
+ """Perform deep scan across all drives for any Stata installation.
163
+
164
+ This is the final fallback search method that scans everywhere
165
+ without restrictions, including standard paths, to find any possible
166
+ Stata installation that might have been missed.
167
+
168
+ Returns:
169
+ List of StataEditionConfig objects found through deep scanning
170
+ """
171
+ drivers_base = self.find_path_base().get("drivers")
172
+ found_configs = []
173
+
174
+ if not drivers_base:
175
+ return found_configs
176
+
177
+ for driver in drivers_base:
178
+ try:
179
+ # Deep scan without restrictions
180
+ for root, dirs, files in os.walk(driver):
181
+ for file in files:
182
+ if file.lower().endswith(".exe") and "stata" in file.lower():
183
+ full_path = os.path.join(root, file)
184
+ # Less strict matching for deep scan
185
+ if "stata" in full_path.lower():
186
+ config = StataEditionConfig.from_path(full_path)
187
+ found_configs.append(config)
188
+ except Exception:
189
+ pass
190
+
191
+ return found_configs
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ """Stata MCP Server package."""
6
+ from .main import create_stata_server
7
+
8
+ __all__ = ["create_stata_server"]
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ import locale
6
+ import os
7
+ import platform
8
+ from collections.abc import AsyncIterator
9
+ from contextlib import asynccontextmanager
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from mcp.server.fastmcp import Context, FastMCP, Icon
15
+ from mcp.server.session import ServerSession
16
+ from pydantic import BaseModel, Field
17
+
18
+ from ..core.data_info import CsvDataInfo, DtaDataInfo
19
+ from ..core.stata import StataController, StataDo, StataFinder
20
+ from ..utils.Prompt import pmp
21
+
22
+ from .tools.core_tools import register_core_tools
23
+ from .tools.file_tools import register_file_tools
24
+ from .tools.stata_tools import register_stata_tools
25
+ from .prompts.core_prompts import register_core_prompts
26
+
27
+
28
+ class StataServerConfig(BaseModel):
29
+
30
+
31
+ """Configuration for Stata MCP server."""
32
+
33
+ name: str = "stata-mcp"
34
+ instructions: str = "Stata-MCP lets you and LLMs run Stata do-files and fetch results"
35
+ website_url: str = "https://github.com/jackdark425"
36
+ working_directory: Optional[str] = None
37
+ stata_cli: Optional[str] = None
38
+
39
+
40
+ class StataServerContext(BaseModel):
41
+
42
+
43
+ """Application context with typed dependencies."""
44
+
45
+ config: StataServerConfig
46
+ stata_finder: Any = Field(description="StataFinder instance")
47
+ working_directory: Path
48
+ output_base_path: Path
49
+
50
+ class Config:
51
+ arbitrary_types_allowed = True
52
+
53
+
54
+ @asynccontextmanager
55
+ async def server_lifespan(server: FastMCP, config: StataServerConfig) -> AsyncIterator[StataServerContext]:
56
+ """Manage server startup and shutdown lifecycle."""
57
+ # Initialize system configuration
58
+ system_os = platform.system()
59
+ if system_os not in ["Darwin", "Linux", "Windows"]:
60
+ raise RuntimeError(f"Unsupported operating system: {system_os}")
61
+
62
+ # Find Stata CLI
63
+ stata_finder = StataFinder()
64
+ stata_cli = config.stata_cli or stata_finder.STATA_CLI
65
+
66
+ # Determine working directory
67
+ if config.working_directory:
68
+ working_directory = Path(config.working_directory)
69
+ else:
70
+ client = os.getenv("STATA-MCP-CLIENT")
71
+ if client == "cc": # Claude Code
72
+ working_directory = Path.cwd()
73
+ else:
74
+ cwd = os.getenv("STATA_MCP_CWD", os.getenv("STATA-MCP-CWD", None))
75
+ if cwd:
76
+ working_directory = Path(cwd)
77
+ else:
78
+ if system_os in ["Darwin", "Linux"]:
79
+ working_directory = Path.home() / "Documents"
80
+ else:
81
+ working_directory = Path(os.getenv("USERPROFILE", "~")) / "Documents"
82
+
83
+ # Create output directories
84
+ output_base_path = working_directory / "stata-mcp-folder"
85
+ output_base_path.mkdir(exist_ok=True)
86
+
87
+ # Create subdirectories
88
+ log_base_path = output_base_path / "stata-mcp-log"
89
+ dofile_base_path = output_base_path / "stata-mcp-dofile"
90
+ result_doc_path = output_base_path / "stata-mcp-result"
91
+ tmp_base_path = output_base_path / "stata-mcp-tmp"
92
+
93
+ for path in [log_base_path, dofile_base_path, result_doc_path, tmp_base_path]:
94
+ path.mkdir(exist_ok=True)
95
+
96
+ # Set language for prompts
97
+ lang_mapping = {"zh-CN": "cn", "en_US": "en"}
98
+ lang, _ = locale.getdefaultlocale()
99
+ pmp.set_lang(lang_mapping.get(lang, "en"))
100
+
101
+ yield StataServerContext(
102
+ config=config,
103
+ stata_finder=stata_finder,
104
+ working_directory=working_directory,
105
+ output_base_path=output_base_path
106
+ )
107
+
108
+
109
+ def create_stata_server(config: Optional[StataServerConfig] = None) -> FastMCP:
110
+ """Create and configure the Stata MCP server."""
111
+ config = config or StataServerConfig()
112
+
113
+ # Create server with lifespan
114
+ @asynccontextmanager
115
+ async def lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
116
+ async with server_lifespan(server, config) as context:
117
+ yield {"stata_context": context}
118
+
119
+ # Initialize server with latest MCP features
120
+ server = FastMCP(
121
+ name=config.name,
122
+ instructions=config.instructions,
123
+ website_url=config.website_url,
124
+ icons=[
125
+ Icon(
126
+ src="https://avatars.githubusercontent.com/u/201514154?v=4",
127
+ mimeType="image/png",
128
+ sizes=["460*460"]
129
+ )
130
+ ],
131
+ lifespan=lifespan
132
+ )
133
+
134
+ # Register all components
135
+ register_core_tools(server)
136
+ register_file_tools(server)
137
+ register_stata_tools(server)
138
+ register_core_prompts(server)
139
+
140
+ return server
141
+
142
+
143
+ # Export the default server instance
144
+ stata_server = create_stata_server()
145
+
146
+
147
+ def run_server(transport: str = "stdio") -> None:
148
+ """Run the Stata MCP server with the specified transport."""
149
+ stata_server.run(transport=transport)
150
+
151
+
152
+ if __name__ == "__main__":
153
+ run_server()
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ """Stata MCP Prompts package."""
6
+ from .core_prompts import register_core_prompts
7
+
8
+ __all__ = ["register_core_prompts"]