migrations/versions/8c1e9d4a5b72_add_mcp_registration_tables.py
Rama main · réplica de lectura del repositorio original.
"""add MCP registration and assistant tool provenance tables
Revision ID: 8c1e9d4a5b72
Revises: 371df74151b3
Create Date: 2026-09-26
MCP-5A persistence foundation.
This migration intentionally stores no third-party credentials. Project David
does not yet have an approved reversible secret-reference facility.
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from migrations.utils.safe_ddl import (
create_index_if_not_exists,
drop_index_if_exists,
drop_table_if_exists,
has_table,
)
revision: str = "8c1e9d4a5b72"
down_revision: str | None = "371df74151b3"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
if not has_table("mcp_server_registrations"):
op.create_table(
"mcp_server_registrations",
sa.Column("id", sa.String(length=64), nullable=False),
sa.Column("owner_id", sa.String(length=64), nullable=False),
sa.Column("name", sa.String(length=128), nullable=False),
sa.Column("url", sa.Text(), nullable=False),
sa.Column("normalized_url", sa.Text(), nullable=False),
sa.Column(
"identity_key",
sa.String(length=64),
nullable=False,
comment=(
"Stable SHA-256 identity derived from transport + normalized URL. "
"Combined with owner_id to make registration idempotent."
),
),
sa.Column(
"transport",
sa.String(length=32),
server_default="streamable_http",
nullable=False,
),
sa.Column(
"timeout_seconds",
sa.Float(),
server_default="30",
nullable=False,
),
sa.Column(
"enabled",
sa.Boolean(),
server_default=sa.text("1"),
nullable=False,
),
sa.Column(
"created_at",
sa.DateTime(),
server_default=sa.text("CURRENT_TIMESTAMP"),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(),
server_default=sa.text("CURRENT_TIMESTAMP"),
nullable=False,
),
sa.ForeignKeyConstraint(
["owner_id"],
["users.id"],
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"owner_id",
"identity_key",
name="uq_mcp_registration_owner_identity",
),
)
create_index_if_not_exists(
op.f("ix_mcp_server_registrations_id"),
"mcp_server_registrations",
["id"],
unique=False,
)
create_index_if_not_exists(
op.f("ix_mcp_server_registrations_owner_id"),
"mcp_server_registrations",
["owner_id"],
unique=False,
)
create_index_if_not_exists(
"idx_mcp_registration_owner_enabled",
"mcp_server_registrations",
["owner_id", "enabled"],
unique=False,
)
if not has_table("assistant_mcp_tools"):
op.create_table(
"assistant_mcp_tools",
sa.Column("id", sa.String(length=64), nullable=False),
sa.Column("assistant_id", sa.String(length=64), nullable=False),
sa.Column("registration_id", sa.String(length=64), nullable=False),
sa.Column("remote_name", sa.String(length=255), nullable=False),
sa.Column("canonical_id", sa.String(length=512), nullable=False),
sa.Column("provider_name", sa.String(length=64), nullable=False),
sa.Column(
"enabled",
sa.Boolean(),
server_default=sa.text("1"),
nullable=False,
),
sa.Column(
"created_at",
sa.DateTime(),
server_default=sa.text("CURRENT_TIMESTAMP"),
nullable=False,
),
sa.ForeignKeyConstraint(
["assistant_id"],
["assistants.id"],
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["registration_id"],
["mcp_server_registrations.id"],
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"assistant_id",
"registration_id",
"remote_name",
name="uq_assistant_mcp_remote_tool",
),
sa.UniqueConstraint(
"assistant_id",
"provider_name",
name="uq_assistant_mcp_provider_name",
),
)
create_index_if_not_exists(
op.f("ix_assistant_mcp_tools_id"),
"assistant_mcp_tools",
["id"],
unique=False,
)
create_index_if_not_exists(
op.f("ix_assistant_mcp_tools_assistant_id"),
"assistant_mcp_tools",
["assistant_id"],
unique=False,
)
create_index_if_not_exists(
op.f("ix_assistant_mcp_tools_registration_id"),
"assistant_mcp_tools",
["registration_id"],
unique=False,
)
create_index_if_not_exists(
"idx_assistant_mcp_registration",
"assistant_mcp_tools",
["registration_id"],
unique=False,
)
def downgrade() -> None:
drop_index_if_exists(
"idx_assistant_mcp_registration",
"assistant_mcp_tools",
)
drop_index_if_exists(
"ix_assistant_mcp_tools_registration_id",
"assistant_mcp_tools",
)
drop_index_if_exists(
"ix_assistant_mcp_tools_assistant_id",
"assistant_mcp_tools",
)
drop_index_if_exists(
"ix_assistant_mcp_tools_id",
"assistant_mcp_tools",
)
drop_table_if_exists("assistant_mcp_tools")
drop_index_if_exists(
"idx_mcp_registration_owner_enabled",
"mcp_server_registrations",
)
drop_index_if_exists(
"ix_mcp_server_registrations_owner_id",
"mcp_server_registrations",
)
drop_index_if_exists(
"ix_mcp_server_registrations_id",
"mcp_server_registrations",
)
drop_table_if_exists("mcp_server_registrations")