Coverage for src / mcp_server_langgraph / __init__.py: 53%

130 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-03 00:43 +0000

1""" 

2MCP Server with LangGraph. 

3 

4A production-ready MCP (Model Context Protocol) server built with LangGraph, 

5featuring multi-LLM support, fine-grained authorization, and comprehensive observability. 

6""" 

7 

8import sys 

9import tomllib 

10from pathlib import Path 

11from typing import TYPE_CHECKING 

12 

13# TYPE_CHECKING imports satisfy static analysis (CodeQL, mypy) while keeping lazy loading 

14# These imports only run during type checking, not at runtime 

15if TYPE_CHECKING: 

16 from mcp_server_langgraph.core.agent import agent_graph as agent_graph 

17 from mcp_server_langgraph.observability.telemetry import tracer as tracer 

18 

19# Read version from pyproject.toml (single source of truth) 

20try: 

21 pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml" 

22 with open(pyproject_path, "rb") as f: 

23 pyproject_data = tomllib.load(f) 

24 __version__ = pyproject_data["project"]["version"] 

25except Exception: 

26 # Fallback if reading fails 

27 __version__ = "2.8.0" 

28 

29# Core exports (lightweight - eagerly imported) 

30from mcp_server_langgraph.core.config import settings 

31 

32__all__ = [ 

33 "AgentState", 

34 "AuthMiddleware", 

35 "OpenFGAClient", 

36 "__version__", 

37 "agent_graph", 

38 "create_llm_from_config", 

39 "logger", 

40 "metrics", 

41 "settings", 

42 "tracer", 

43] 

44 

45 

46def __getattr__(name: str): # type: ignore[no-untyped-def] # noqa: C901 

47 """ 

48 Lazy import heavy dependencies on demand. 

49 

50 This prevents ModuleNotFoundError for users who don't have optional dependencies 

51 installed. Heavy modules are only loaded when actually accessed. 

52 

53 Pattern: 

54 import mcp_server_langgraph # ✓ Fast, no heavy deps loaded 

55 from mcp_server_langgraph import settings # ✓ Fast, lightweight 

56 from mcp_server_langgraph import AuthMiddleware # Loads FastAPI, OpenFGA only when needed 

57 

58 Raises: 

59 AttributeError: If module attribute doesn't exist 

60 """ 

61 # Heavy auth modules (require FastAPI, OpenFGA SDK) 

62 if name == "AuthMiddleware": 

63 from mcp_server_langgraph.auth.middleware import AuthMiddleware 

64 

65 return AuthMiddleware 

66 elif name == "OpenFGAClient": 

67 from mcp_server_langgraph.auth.openfga import OpenFGAClient 

68 

69 return OpenFGAClient 

70 

71 # Heavy agent modules (require LangGraph, LangChain) 

72 elif name == "AgentState": 

73 from mcp_server_langgraph.core.agent import AgentState 

74 

75 return AgentState 

76 elif name == "agent_graph": 

77 from mcp_server_langgraph.core.agent import agent_graph 

78 

79 return agent_graph 

80 

81 # Heavy LLM modules (require langchain_core, sentence_transformers, etc.) 

82 elif name == "create_llm_from_config": 

83 from mcp_server_langgraph.llm.factory import create_llm_from_config 

84 

85 return create_llm_from_config 

86 

87 # Observability modules (potentially heavy with OpenTelemetry) 

88 elif name == "logger": 

89 from mcp_server_langgraph.observability.telemetry import logger 

90 

91 return logger 

92 elif name == "tracer": 

93 from mcp_server_langgraph.observability.telemetry import tracer 

94 

95 return tracer 

96 elif name == "metrics": 

97 from mcp_server_langgraph.observability.telemetry import metrics 

98 

99 return metrics 

100 elif name == "auth": 

101 # Allow access to auth submodule for importlib.reload() scenarios 

102 # Used in tests/regression/test_bearer_scheme_isolation.py 

103 # Prevent recursion: if auth module is already being imported, return it from sys.modules 

104 module_name = f"{__name__}.auth" 

105 if module_name in sys.modules: 105 ↛ 108line 105 didn't jump to line 108 because the condition on line 105 was always true

106 return sys.modules[module_name] 

107 

108 import mcp_server_langgraph.auth as auth_module 

109 

110 return auth_module 

111 elif name == "api": 

112 # Allow access to api submodule for importlib.reload() scenarios 

113 # Used in tests/regression/test_bearer_scheme_isolation.py 

114 # Prevent recursion: if api module is already being imported, return it from sys.modules 

115 module_name = f"{__name__}.api" 

116 if module_name in sys.modules: 116 ↛ 119line 116 didn't jump to line 119 because the condition on line 116 was always true

117 return sys.modules[module_name] 

118 

119 import mcp_server_langgraph.api as api_module 

120 

121 return api_module 

122 elif name == "health": 122 ↛ 126line 122 didn't jump to line 126 because the condition on line 122 was never true

123 # Allow access to health submodule for health check endpoints 

124 # Used in tests/integration/test_health_check.py 

125 # Prevent recursion: if health module is already being imported, return it from sys.modules 

126 module_name = f"{__name__}.health" 

127 if module_name in sys.modules: 

128 return sys.modules[module_name] 

129 

130 import mcp_server_langgraph.health as health_module 

131 

132 return health_module 

133 elif name == "tools": 133 ↛ 136line 133 didn't jump to line 136 because the condition on line 133 was never true

134 # Allow access to tools submodule for patch/mock scenarios in tests 

135 # Prevent recursion: if tools module is already being imported, return it from sys.modules 

136 module_name = f"{__name__}.tools" 

137 if module_name in sys.modules: 

138 return sys.modules[module_name] 

139 

140 import mcp_server_langgraph.tools as tools_module 

141 

142 return tools_module 

143 elif name == "llm": 143 ↛ 145line 143 didn't jump to line 145 because the condition on line 143 was never true

144 # Allow access to llm submodule for tests (needed for Python 3.10 mock compatibility) 

145 module_name = f"{__name__}.llm" 

146 if module_name in sys.modules: 

147 return sys.modules[module_name] 

148 

149 import mcp_server_langgraph.llm as llm_module 

150 

151 return llm_module 

152 elif name == "core": 

153 # Allow access to core submodule for tests (needed for Python 3.10 mock compatibility) 

154 module_name = f"{__name__}.core" 

155 if module_name in sys.modules: 155 ↛ 158line 155 didn't jump to line 158 because the condition on line 155 was always true

156 return sys.modules[module_name] 

157 

158 import mcp_server_langgraph.core as core_module 

159 

160 return core_module 

161 elif name == "monitoring": 

162 # Allow access to monitoring submodule for tests (needed for Python 3.10 mock compatibility) 

163 module_name = f"{__name__}.monitoring" 

164 if module_name in sys.modules: 164 ↛ 167line 164 didn't jump to line 167 because the condition on line 164 was always true

165 return sys.modules[module_name] 

166 

167 import mcp_server_langgraph.monitoring as monitoring_module 

168 

169 return monitoring_module 

170 elif name == "app": 170 ↛ 172line 170 didn't jump to line 172 because the condition on line 170 was never true

171 # Allow access to app submodule for tests (needed for Python 3.10 mock compatibility) 

172 module_name = f"{__name__}.app" 

173 if module_name in sys.modules: 

174 return sys.modules[module_name] 

175 

176 import mcp_server_langgraph.app as app_module 

177 

178 return app_module 

179 elif name == "middleware": 179 ↛ 181line 179 didn't jump to line 181 because the condition on line 179 was never true

180 # Allow access to middleware submodule for tests (needed for Python 3.10 mock compatibility) 

181 module_name = f"{__name__}.middleware" 

182 if module_name in sys.modules: 

183 return sys.modules[module_name] 

184 

185 import mcp_server_langgraph.middleware as middleware_module 

186 

187 return middleware_module 

188 elif name == "execution": 188 ↛ 190line 188 didn't jump to line 190 because the condition on line 188 was never true

189 # Allow access to execution submodule for tests (needed for Python 3.10 mock compatibility) 

190 module_name = f"{__name__}.execution" 

191 if module_name in sys.modules: 

192 return sys.modules[module_name] 

193 

194 import mcp_server_langgraph.execution as execution_module 

195 

196 return execution_module 

197 elif name == "compliance": 

198 # Allow access to compliance submodule for tests (needed for Python 3.10 mock compatibility) 

199 module_name = f"{__name__}.compliance" 

200 if module_name in sys.modules: 200 ↛ 203line 200 didn't jump to line 203 because the condition on line 200 was always true

201 return sys.modules[module_name] 

202 

203 import mcp_server_langgraph.compliance as compliance_module 

204 

205 return compliance_module 

206 elif name == "schedulers": 206 ↛ 208line 206 didn't jump to line 208 because the condition on line 206 was never true

207 # Allow access to schedulers submodule for tests (needed for Python 3.10 mock compatibility) 

208 module_name = f"{__name__}.schedulers" 

209 if module_name in sys.modules: 

210 return sys.modules[module_name] 

211 

212 import mcp_server_langgraph.schedulers as schedulers_module 

213 

214 return schedulers_module 

215 elif name == "resilience": 215 ↛ 217line 215 didn't jump to line 217 because the condition on line 215 was never true

216 # Allow access to resilience submodule for resilience pattern tests 

217 module_name = f"{__name__}.resilience" 

218 if module_name in sys.modules: 

219 return sys.modules[module_name] 

220 

221 import mcp_server_langgraph.resilience as resilience_module 

222 

223 return resilience_module 

224 elif name == "mcp": 224 ↛ 226line 224 didn't jump to line 226 because the condition on line 224 was never true

225 # Allow access to mcp submodule for MCP server tests 

226 module_name = f"{__name__}.mcp" 

227 if module_name in sys.modules: 

228 return sys.modules[module_name] 

229 

230 import mcp_server_langgraph.mcp as mcp_module 

231 

232 return mcp_module 

233 elif name == "observability": 233 ↛ 235line 233 didn't jump to line 235 because the condition on line 233 was never true

234 # Allow access to observability submodule for telemetry tests 

235 module_name = f"{__name__}.observability" 

236 if module_name in sys.modules: 

237 return sys.modules[module_name] 

238 

239 import mcp_server_langgraph.observability as observability_module 

240 

241 return observability_module 

242 

243 # Not found 

244 msg = f"module {__name__!r} has no attribute {name!r}" 

245 raise AttributeError(msg)