feat(mcp): persist and display marketplace README

Capture the README markdown from LangBot Space when installing an MCP
server and store it on the mcp_servers record (new readme column +
alembic migration 0004). The detail page can then render docs offline,
independent of the server's runtime/connection state.
This commit is contained in:
RockChinQ
2026-06-06 03:52:00 -04:00
parent e5b3cced1f
commit f54ae4b91c
5 changed files with 119 additions and 0 deletions

View File

@@ -11,6 +11,10 @@ class MCPServer(Base):
enable = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False)
mode = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) # stdio, sse, http
extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={})
# Markdown documentation captured from LangBot Space at install time so the
# detail page can show docs even when the server is offline / has no tools.
# Empty string for manually-created servers that have no marketplace README.
readme = sqlalchemy.Column(sqlalchemy.Text, nullable=False, server_default='', default='')
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
updated_at = sqlalchemy.Column(
sqlalchemy.DateTime,

View File

@@ -0,0 +1,34 @@
"""add readme column to mcp_servers
Revision ID: 0004_add_mcp_readme
Revises: 0003_add_rerank_models
Create Date: 2026-06-06
"""
import sqlalchemy as sa
from alembic import op
revision = '0004_add_mcp_readme'
down_revision = '0003_add_rerank_models'
branch_labels = None
depends_on = None
def upgrade() -> None:
# Add ``readme`` to mcp_servers if the table exists and the column is missing
# (the table may have been created by create_all() with the column already
# present on fresh installs, so guard against duplicate-add).
conn = op.get_bind()
inspector = sa.inspect(conn)
if 'mcp_servers' not in inspector.get_table_names():
return
columns = {col['name'] for col in inspector.get_columns('mcp_servers')}
if 'readme' not in columns:
op.add_column(
'mcp_servers',
sa.Column('readme', sa.Text(), nullable=False, server_default=''),
)
def downgrade() -> None:
op.drop_column('mcp_servers', 'readme')

View File

@@ -248,6 +248,9 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
mode = mcp_data.get('mode') or 'stdio'
extra_args = mcp_data.get('extra_args') or {}
# Marketplace records carry the rendered README markdown; persist it so
# the detail page Docs tab works offline and without a marketplace round-trip.
readme = mcp_data.get('readme') or ''
# Use __ instead of / to avoid URL routing issues with slashes
name = f'{mcp_data.get("author", "")}__{mcp_data.get("name", "")}'
@@ -267,6 +270,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
'enable': True,
'mode': mode,
'extra_args': extra_args,
'readme': readme,
}
await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_mcp.MCPServer).values(server_data))