mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-14 14:31:00 +00:00
fix(plugin): install marketplace MCP from canonical mode + extra_args
_install_mcp_from_marketplace read the dropped `mcp_data.config` field and reconstructed mode/extra_args by guessing from the URL — which lost stdio's command/args/env/box entirely, so stdio MCP installs from the marketplace always failed. Use the Space record's canonical `mode` and `extra_args` directly (the same shape stored in mcp_servers), and gate the install on `mode` instead of the removed `config`. After a successful install, best-effort POST to the marketplace install endpoint to bump install_count.
This commit is contained in:
@@ -224,30 +224,23 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
|||||||
mcp_data: dict[str, Any],
|
mcp_data: dict[str, Any],
|
||||||
task_context: taskmgr.TaskContext | None = None,
|
task_context: taskmgr.TaskContext | None = None,
|
||||||
):
|
):
|
||||||
"""Install an MCP server from marketplace data."""
|
"""Install an MCP server from marketplace data.
|
||||||
|
|
||||||
|
Marketplace MCP records carry the runtime-ready ``mode`` and
|
||||||
|
``extra_args`` directly (the same shape LangBot stores in
|
||||||
|
``mcp_servers``), so they are used as-is rather than reconstructed.
|
||||||
|
For ``stdio`` this preserves ``command``/``args``/``env``/``box``;
|
||||||
|
for ``http``/``sse`` it preserves ``url``/``headers``/``timeout``/
|
||||||
|
``ssereadtimeout``.
|
||||||
|
"""
|
||||||
from ..entity.persistence import mcp as persistence_mcp
|
from ..entity.persistence import mcp as persistence_mcp
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
config = mcp_data.get('config', {})
|
mode = mcp_data.get('mode') or 'stdio'
|
||||||
url = config.get('url', '')
|
extra_args = mcp_data.get('extra_args') or {}
|
||||||
# Use __ instead of / to avoid URL routing issues with slashes
|
# Use __ instead of / to avoid URL routing issues with slashes
|
||||||
name = f'{mcp_data.get("author", "")}__{mcp_data.get("name", "")}'
|
name = f'{mcp_data.get("author", "")}__{mcp_data.get("name", "")}'
|
||||||
|
|
||||||
# Determine mode from URL
|
|
||||||
if 'sse' in url.lower():
|
|
||||||
mode = 'sse'
|
|
||||||
elif url.startswith('http'):
|
|
||||||
mode = 'http'
|
|
||||||
else:
|
|
||||||
mode = 'stdio'
|
|
||||||
|
|
||||||
# Build extra_args from config
|
|
||||||
extra_args = {
|
|
||||||
'url': url,
|
|
||||||
'timeout': config.get('timeout', 30),
|
|
||||||
'sse_read_timeout': config.get('sse_read_timeout', 300),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if MCP server already exists
|
# Check if MCP server already exists
|
||||||
existing = await self.ap.persistence_mgr.execute_async(
|
existing = await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.select(persistence_mcp.MCPServer).where(persistence_mcp.MCPServer.name == name)
|
sqlalchemy.select(persistence_mcp.MCPServer).where(persistence_mcp.MCPServer.name == name)
|
||||||
@@ -376,15 +369,22 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
|||||||
mcp_resp = await client.get(f'{space_url}/api/v1/marketplace/mcps/{plugin_author}/{plugin_name}')
|
mcp_resp = await client.get(f'{space_url}/api/v1/marketplace/mcps/{plugin_author}/{plugin_name}')
|
||||||
if mcp_resp.status_code == 200:
|
if mcp_resp.status_code == 200:
|
||||||
mcp_data = mcp_resp.json().get('data', {}).get('mcp', {})
|
mcp_data = mcp_resp.json().get('data', {}).get('mcp', {})
|
||||||
if mcp_data.get('config'):
|
if mcp_data.get('mode'):
|
||||||
# It's an MCP - create server locally
|
# It's an MCP - create server locally
|
||||||
self.ap.logger.info(f'Installing MCP from marketplace: {plugin_author}/{plugin_name}')
|
self.ap.logger.info(f'Installing MCP from marketplace: {plugin_author}/{plugin_name}')
|
||||||
if task_context:
|
if task_context:
|
||||||
task_context.set_current_action('installing mcp server')
|
task_context.set_current_action('installing mcp server')
|
||||||
await self._install_mcp_from_marketplace(mcp_data, task_context)
|
await self._install_mcp_from_marketplace(mcp_data, task_context)
|
||||||
|
# Best-effort install report (bumps marketplace install_count).
|
||||||
|
try:
|
||||||
|
await client.post(
|
||||||
|
f'{space_url}/api/v1/marketplace/mcps/{plugin_author}/{plugin_name}/install'
|
||||||
|
)
|
||||||
|
except Exception as report_err:
|
||||||
|
self.ap.logger.debug(f'Failed to report MCP install: {report_err}')
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
raise Exception(f'MCP {plugin_author}/{plugin_name} has no config')
|
raise Exception(f'MCP {plugin_author}/{plugin_name} has no mode')
|
||||||
elif mcp_resp.status_code == 404:
|
elif mcp_resp.status_code == 404:
|
||||||
# Try skill endpoint - download ZIP and install
|
# Try skill endpoint - download ZIP and install
|
||||||
self.ap.logger.info(f'Trying skill endpoint for: {plugin_author}/{plugin_name}')
|
self.ap.logger.info(f'Trying skill endpoint for: {plugin_author}/{plugin_name}')
|
||||||
|
|||||||
Reference in New Issue
Block a user