testmcpy 0.2.9__tar.gz → 0.2.11__tar.gz
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.
- {testmcpy-0.2.9/testmcpy.egg-info → testmcpy-0.2.11}/PKG-INFO +1 -1
- {testmcpy-0.2.9 → testmcpy-0.2.11}/pyproject.toml +1 -1
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/__init__.py +8 -1
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/mcp_profiles.py +46 -20
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/api.py +61 -67
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/mcp_profiles.py +3 -2
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/App.jsx +13 -1
- {testmcpy-0.2.9 → testmcpy-0.2.11/testmcpy.egg-info}/PKG-INFO +1 -1
- {testmcpy-0.2.9 → testmcpy-0.2.11}/LICENSE +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/MANIFEST.in +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/NOTICE +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/README.md +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/setup.cfg +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/auth_debugger.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/auth_flow_recorder.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/cli/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/cli/app.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/cli/commands/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/cli/commands/mcp.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/cli/commands/run.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/cli/commands/server.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/cli/commands/tools.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/cli/commands/tui.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/config.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/core/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/core/chat_session.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/core/docs_optimizer.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/core/mcp_manager.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/core/tool_comparison.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/core/tool_discovery.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/error_handlers.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/evals/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/evals/auth_evaluators.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/evals/base_evaluators.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/base.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/curl.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/graphql.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/javascript_client.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/json_yaml.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/protobuf.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/python.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/python_client.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/thrift.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/typescript.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/formatters/typescript_client.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/llm_profiles.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/research/claude_sdk_detailed_exploration.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/research/claude_sdk_poc.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/research/claude_sdk_working_poc.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/research/test_ollama_tools.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/api.py.bak +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/helpers/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/helpers/mcp_config.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/models.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/auth.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/generation_logs.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/llm.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/results.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/smoke_reports.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/test_profiles.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/tests.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/routers/tools.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/state.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/tool_compare_endpoint.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/server/websocket.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/smoke_test.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/src/__init__.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/src/llm_integration.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/src/mcp_client.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/src/model_registry.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/src/models.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/src/runner_tools.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/src/test_runner.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/storage.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/test_profiles.py +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/README.md +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/dist/assets/index-CaEBvXci.css +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/dist/assets/index-DbhZ0GnU.js +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/dist/index.html +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/index.html +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/package-lock.json +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/package.json +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/postcss.config.js +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/CompareToolsTab.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/ErrorAlert.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/ErrorBoundary.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/LLMProfileSelector.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/LoadingSpinner.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/MCPProfileSelector.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/OptimizeDocsModal.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/ParameterCard.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/SchemaCodeViewer.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/SkeletonLoader.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/TestGenerationModal.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/TestProfileSelector.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/TestResultPanel.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/TestStatusIndicator.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/ToolComparison.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/ToolDebugModal.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/components/TypeBadge.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/contexts/TestRunContext.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/hooks/useKeyboardShortcuts.js +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/hooks/useSafeFetch.js +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/index.css +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/main.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/AuthDebugger.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/ChatInterface.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/Configuration.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/GenerationHistory.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/LLMProfiles.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/MCPExplorer.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/MCPProfiles.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/ProfilesManager.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/Reports.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/pages/TestManager.jsx +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/utils/__tests__/formatConverters.test.js +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/src/utils/formatConverters.js +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/tailwind.config.js +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy/ui/vite.config.js +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy.egg-info/SOURCES.txt +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy.egg-info/dependency_links.txt +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy.egg-info/entry_points.txt +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy.egg-info/requires.txt +0 -0
- {testmcpy-0.2.9 → testmcpy-0.2.11}/testmcpy.egg-info/top_level.txt +0 -0
|
@@ -88,7 +88,7 @@ testmcpy = [
|
|
|
88
88
|
|
|
89
89
|
[project]
|
|
90
90
|
name = "testmcpy"
|
|
91
|
-
version = "0.2.
|
|
91
|
+
version = "0.2.11"
|
|
92
92
|
description = "A comprehensive testing framework for validating LLM tool calling capabilities with MCP services"
|
|
93
93
|
authors = [{name = "Amin Ghadersohi"}]
|
|
94
94
|
license = "Apache-2.0"
|
|
@@ -5,5 +5,12 @@ A comprehensive testing framework for validating LLM tool calling
|
|
|
5
5
|
capabilities with MCP (Model Context Protocol) services.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
try:
|
|
9
|
+
from importlib.metadata import version
|
|
10
|
+
|
|
11
|
+
__version__ = version("testmcpy")
|
|
12
|
+
except Exception:
|
|
13
|
+
# Fallback for development or when package not installed
|
|
14
|
+
__version__ = "0.2.11"
|
|
15
|
+
|
|
9
16
|
__author__ = "testmcpy Contributors"
|
|
@@ -120,12 +120,7 @@ class MCPProfileConfig:
|
|
|
120
120
|
"""
|
|
121
121
|
Find MCP services configuration file.
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
1. Provided config_path
|
|
125
|
-
2. .mcp_services.yaml in current directory
|
|
126
|
-
3. .mcp_services.yaml in parent directories (up to 5 levels)
|
|
127
|
-
|
|
128
|
-
Note: User home directory (~/.mcp_services.yaml) is NOT searched.
|
|
123
|
+
Looks for .mcp_services.yaml in the current working directory.
|
|
129
124
|
"""
|
|
130
125
|
if config_path:
|
|
131
126
|
path = Path(config_path)
|
|
@@ -133,15 +128,10 @@ class MCPProfileConfig:
|
|
|
133
128
|
return path
|
|
134
129
|
return None
|
|
135
130
|
|
|
136
|
-
# Check current directory
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
config_file
|
|
140
|
-
if config_file.exists():
|
|
141
|
-
return config_file
|
|
142
|
-
if current.parent == current:
|
|
143
|
-
break
|
|
144
|
-
current = current.parent
|
|
131
|
+
# Check current directory only (same as llm_profiles)
|
|
132
|
+
config_file = Path.cwd() / ".mcp_services.yaml"
|
|
133
|
+
if config_file.exists():
|
|
134
|
+
return config_file
|
|
145
135
|
|
|
146
136
|
return None
|
|
147
137
|
|
|
@@ -196,14 +186,33 @@ class MCPProfileConfig:
|
|
|
196
186
|
|
|
197
187
|
# Load profiles
|
|
198
188
|
profiles_config = config.get("profiles", {})
|
|
199
|
-
|
|
200
|
-
|
|
189
|
+
if not isinstance(profiles_config, dict):
|
|
190
|
+
print(
|
|
191
|
+
f"Warning: 'profiles' in config should be a dict, got {type(profiles_config).__name__}"
|
|
192
|
+
)
|
|
193
|
+
return
|
|
201
194
|
|
|
195
|
+
for profile_id, profile_data in profiles_config.items():
|
|
196
|
+
if not isinstance(profile_data, dict):
|
|
197
|
+
print(
|
|
198
|
+
f"Warning: Skipping profile '{profile_id}' - invalid format (expected dict)"
|
|
199
|
+
)
|
|
200
|
+
continue
|
|
201
|
+
try:
|
|
202
|
+
self.profiles[profile_id] = self._parse_profile(profile_id, profile_data)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
print(f"Warning: Failed to parse profile '{profile_id}': {e}")
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
except yaml.YAMLError as e:
|
|
208
|
+
print(f"Warning: Invalid YAML in {self.config_path}: {e}")
|
|
202
209
|
except Exception as e:
|
|
203
210
|
print(f"Warning: Failed to load MCP profile config from {self.config_path}: {e}")
|
|
204
211
|
|
|
205
|
-
def _parse_auth(self, auth_data: dict[str, Any]) -> AuthConfig:
|
|
212
|
+
def _parse_auth(self, auth_data: dict[str, Any] | None) -> AuthConfig:
|
|
206
213
|
"""Parse auth configuration."""
|
|
214
|
+
if not auth_data or not isinstance(auth_data, dict):
|
|
215
|
+
return AuthConfig(auth_type="none")
|
|
207
216
|
auth_type = auth_data.get("type", "none")
|
|
208
217
|
|
|
209
218
|
return AuthConfig(
|
|
@@ -252,10 +261,27 @@ class MCPProfileConfig:
|
|
|
252
261
|
mcps.append(mcp_server)
|
|
253
262
|
else:
|
|
254
263
|
# New format: multiple MCPs per profile
|
|
255
|
-
|
|
264
|
+
if not isinstance(mcps_data, list):
|
|
265
|
+
print(
|
|
266
|
+
f"Warning: 'mcps' in profile '{profile_id}' should be a list, got {type(mcps_data).__name__}"
|
|
267
|
+
)
|
|
268
|
+
mcps_data = []
|
|
269
|
+
|
|
270
|
+
for idx, mcp_data in enumerate(mcps_data):
|
|
271
|
+
if not isinstance(mcp_data, dict):
|
|
272
|
+
print(
|
|
273
|
+
f"Warning: Skipping MCP entry {idx} in profile '{profile_id}' - invalid format"
|
|
274
|
+
)
|
|
275
|
+
continue
|
|
276
|
+
mcp_url = mcp_data.get("mcp_url")
|
|
277
|
+
if not mcp_url:
|
|
278
|
+
print(
|
|
279
|
+
f"Warning: Skipping MCP entry {idx} in profile '{profile_id}' - missing 'mcp_url'"
|
|
280
|
+
)
|
|
281
|
+
continue
|
|
256
282
|
mcp_server = MCPServer(
|
|
257
283
|
name=mcp_data.get("name", "Unnamed MCP"),
|
|
258
|
-
mcp_url=
|
|
284
|
+
mcp_url=mcp_url,
|
|
259
285
|
auth=self._parse_auth(mcp_data.get("auth", {})),
|
|
260
286
|
timeout=mcp_data.get("timeout", timeout),
|
|
261
287
|
rate_limit_rpm=mcp_data.get("rate_limit_rpm", rate_limit_rpm),
|
|
@@ -373,6 +373,14 @@ async def health_check():
|
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
|
|
376
|
+
@app.get("/api/version")
|
|
377
|
+
async def get_version():
|
|
378
|
+
"""Get the testmcpy version."""
|
|
379
|
+
from testmcpy import __version__
|
|
380
|
+
|
|
381
|
+
return {"version": __version__}
|
|
382
|
+
|
|
383
|
+
|
|
376
384
|
@app.get("/api/config")
|
|
377
385
|
async def get_configuration():
|
|
378
386
|
"""Get current configuration."""
|
|
@@ -521,17 +529,15 @@ async def list_mcp_tools(profiles: list[str] = Query(default=None)):
|
|
|
521
529
|
@app.get("/api/mcp/resources")
|
|
522
530
|
async def list_mcp_resources(profiles: list[str] = Query(default=None)):
|
|
523
531
|
"""List all MCP resources. Supports optional ?profiles=xxx&profiles=yyy parameters."""
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
profile_id, mcp_name = server_id.split(":", 1)
|
|
534
|
-
accessed_servers.append(f"{profile_id}:{mcp_name}")
|
|
532
|
+
all_resources = []
|
|
533
|
+
|
|
534
|
+
if profiles:
|
|
535
|
+
# Parse server IDs in format "profileId:mcpName"
|
|
536
|
+
for server_id in profiles:
|
|
537
|
+
if ":" in server_id:
|
|
538
|
+
# New format: specific server selection
|
|
539
|
+
profile_id, mcp_name = server_id.split(":", 1)
|
|
540
|
+
try:
|
|
535
541
|
client = await get_mcp_client_for_server(profile_id, mcp_name)
|
|
536
542
|
if client:
|
|
537
543
|
resources = await client.list_resources()
|
|
@@ -539,47 +545,40 @@ async def list_mcp_resources(profiles: list[str] = Query(default=None)):
|
|
|
539
545
|
if isinstance(resource, dict):
|
|
540
546
|
resource["mcp_source"] = mcp_name
|
|
541
547
|
all_resources.append(resource)
|
|
542
|
-
|
|
543
|
-
#
|
|
548
|
+
except Exception as e:
|
|
549
|
+
# Server doesn't support resources or connection failed - skip silently
|
|
550
|
+
print(f"Warning: Could not list resources from {mcp_name}: {e}")
|
|
551
|
+
else:
|
|
552
|
+
# Legacy format: entire profile
|
|
553
|
+
try:
|
|
544
554
|
clients = await get_mcp_clients_for_profile(server_id)
|
|
545
555
|
for mcp_name, client in clients:
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
556
|
+
try:
|
|
557
|
+
resources = await client.list_resources()
|
|
558
|
+
for resource in resources:
|
|
559
|
+
if isinstance(resource, dict):
|
|
560
|
+
resource["mcp_source"] = mcp_name
|
|
561
|
+
all_resources.append(resource)
|
|
562
|
+
except Exception as e:
|
|
563
|
+
print(f"Warning: Could not list resources from {mcp_name}: {e}")
|
|
564
|
+
except Exception as e:
|
|
565
|
+
print(f"Warning: Could not get clients for profile {server_id}: {e}")
|
|
552
566
|
|
|
553
|
-
|
|
554
|
-
except HTTPException:
|
|
555
|
-
raise
|
|
556
|
-
except Exception as e:
|
|
557
|
-
error_msg = str(e)
|
|
558
|
-
if is_connection_error(error_msg):
|
|
559
|
-
# Clear stale cached clients so retry can get fresh connection
|
|
560
|
-
for cache_key in accessed_servers:
|
|
561
|
-
await clear_cached_client(cache_key)
|
|
562
|
-
raise HTTPException(
|
|
563
|
-
status_code=503,
|
|
564
|
-
detail=f"Service unavailable: Unable to connect to MCP server. {error_msg}",
|
|
565
|
-
)
|
|
566
|
-
raise HTTPException(status_code=500, detail=error_msg)
|
|
567
|
+
return all_resources
|
|
567
568
|
|
|
568
569
|
|
|
569
570
|
@app.get("/api/mcp/prompts")
|
|
570
571
|
async def list_mcp_prompts(profiles: list[str] = Query(default=None)):
|
|
571
572
|
"""List all MCP prompts. Supports optional ?profiles=xxx&profiles=yyy parameters."""
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
profile_id, mcp_name = server_id.split(":", 1)
|
|
582
|
-
accessed_servers.append(f"{profile_id}:{mcp_name}")
|
|
573
|
+
all_prompts = []
|
|
574
|
+
|
|
575
|
+
if profiles:
|
|
576
|
+
# Parse server IDs in format "profileId:mcpName"
|
|
577
|
+
for server_id in profiles:
|
|
578
|
+
if ":" in server_id:
|
|
579
|
+
# New format: specific server selection
|
|
580
|
+
profile_id, mcp_name = server_id.split(":", 1)
|
|
581
|
+
try:
|
|
583
582
|
client = await get_mcp_client_for_server(profile_id, mcp_name)
|
|
584
583
|
if client:
|
|
585
584
|
prompts = await client.list_prompts()
|
|
@@ -587,31 +586,26 @@ async def list_mcp_prompts(profiles: list[str] = Query(default=None)):
|
|
|
587
586
|
if isinstance(prompt, dict):
|
|
588
587
|
prompt["mcp_source"] = mcp_name
|
|
589
588
|
all_prompts.append(prompt)
|
|
590
|
-
|
|
591
|
-
#
|
|
589
|
+
except Exception as e:
|
|
590
|
+
# Server doesn't support prompts or connection failed - skip silently
|
|
591
|
+
print(f"Warning: Could not list prompts from {mcp_name}: {e}")
|
|
592
|
+
else:
|
|
593
|
+
# Legacy format: entire profile
|
|
594
|
+
try:
|
|
592
595
|
clients = await get_mcp_clients_for_profile(server_id)
|
|
593
596
|
for mcp_name, client in clients:
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if is_connection_error(error_msg):
|
|
607
|
-
# Clear stale cached clients so retry can get fresh connection
|
|
608
|
-
for cache_key in accessed_servers:
|
|
609
|
-
await clear_cached_client(cache_key)
|
|
610
|
-
raise HTTPException(
|
|
611
|
-
status_code=503,
|
|
612
|
-
detail=f"Service unavailable: Unable to connect to MCP server. {error_msg}",
|
|
613
|
-
)
|
|
614
|
-
raise HTTPException(status_code=500, detail=error_msg)
|
|
597
|
+
try:
|
|
598
|
+
prompts = await client.list_prompts()
|
|
599
|
+
for prompt in prompts:
|
|
600
|
+
if isinstance(prompt, dict):
|
|
601
|
+
prompt["mcp_source"] = mcp_name
|
|
602
|
+
all_prompts.append(prompt)
|
|
603
|
+
except Exception as e:
|
|
604
|
+
print(f"Warning: Could not list prompts from {mcp_name}: {e}")
|
|
605
|
+
except Exception as e:
|
|
606
|
+
print(f"Warning: Could not get clients for profile {server_id}: {e}")
|
|
607
|
+
|
|
608
|
+
return all_prompts
|
|
615
609
|
|
|
616
610
|
|
|
617
611
|
# Chat endpoint
|
|
@@ -166,10 +166,11 @@ global:
|
|
|
166
166
|
@router.get("/profiles")
|
|
167
167
|
async def list_mcp_profiles():
|
|
168
168
|
"""List available MCP profiles from .mcp_services.yaml."""
|
|
169
|
-
from testmcpy.mcp_profiles import
|
|
169
|
+
from testmcpy.mcp_profiles import reload_profile_config
|
|
170
170
|
|
|
171
171
|
try:
|
|
172
|
-
|
|
172
|
+
# Always reload to pick up file changes
|
|
173
|
+
profile_config = reload_profile_config()
|
|
173
174
|
if not profile_config.has_profiles():
|
|
174
175
|
return {
|
|
175
176
|
"profiles": [],
|
|
@@ -36,6 +36,7 @@ function AppContent() {
|
|
|
36
36
|
const [selectedLlmProfile, setSelectedLlmProfile] = useState(null)
|
|
37
37
|
const [apiReady, setApiReady] = useState(false)
|
|
38
38
|
const [healthCheckAttempts, setHealthCheckAttempts] = useState(0)
|
|
39
|
+
const [appVersion, setAppVersion] = useState('v0.0.0')
|
|
39
40
|
const navigate = useNavigate()
|
|
40
41
|
const location = useLocation()
|
|
41
42
|
|
|
@@ -48,6 +49,7 @@ function AppContent() {
|
|
|
48
49
|
loadConfig()
|
|
49
50
|
loadProfiles()
|
|
50
51
|
loadLlmProfiles()
|
|
52
|
+
loadVersion()
|
|
51
53
|
}
|
|
52
54
|
}, [apiReady])
|
|
53
55
|
|
|
@@ -84,6 +86,16 @@ function AppContent() {
|
|
|
84
86
|
setApiReady(true)
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
const loadVersion = async () => {
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetch('/api/version')
|
|
92
|
+
const data = await res.json()
|
|
93
|
+
setAppVersion(`v${data.version}`)
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('Failed to load version:', error)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
const loadConfig = async () => {
|
|
88
100
|
try {
|
|
89
101
|
const res = await fetch('/api/config')
|
|
@@ -419,7 +431,7 @@ function AppContent() {
|
|
|
419
431
|
{sidebarOpen && (
|
|
420
432
|
<div className="text-xs text-text-tertiary space-y-0.5">
|
|
421
433
|
<div className="font-medium">MCP Testing Framework</div>
|
|
422
|
-
<div className="text-text-disabled">
|
|
434
|
+
<div className="text-text-disabled">{appVersion}</div>
|
|
423
435
|
</div>
|
|
424
436
|
)}
|
|
425
437
|
</div>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|