Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions mcp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyderworkspace

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# Terraform
.terraform/
.terraform.lock.hcl
terraform.tfstate
terraform.tfstate.backup
tfplan
terraform.tfvars
*.plan
*.tfstate
*.tfstate.backup

# VSCode
.vscode/

# PyCharm
.idea/
16 changes: 16 additions & 0 deletions mcp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Use an official Python runtime as a parent image
FROM python:3.11-slim

# Set the working directory in the container
WORKDIR /app

# Copy the pyproject.toml file and install dependencies
COPY pyproject.toml .
COPY ./mcp_registry ./mcp_registry
RUN pip install --no-cache-dir .

# Expose the port the app runs on
EXPOSE 80

# Run the application
CMD ["uvicorn", "mcp_registry.main:app", "--host", "0.0.0.0", "--port", "80"]
465 changes: 465 additions & 0 deletions mcp/MCP_Registry_Plan.md

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions mcp/mcp_registry/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from sqlalchemy.orm import Session
from . import models, schemas

def get_agent(db: Session, agent_id: int):
return db.query(models.Agent).filter(models.Agent.id == agent_id, models.Agent.is_deleted == False).first()

def get_agents(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Agent).filter(models.Agent.is_deleted == False).offset(skip).limit(limit).all()

def create_agent(db: Session, agent: schemas.AgentCreate):
db_agent = models.Agent(
name=agent.name,
version=agent.version,
display_name=agent.display_name,
description=agent.description,
owner=agent.owner,
endpoint=agent.endpoint,
openapi_spec=agent.openapi_spec,
environment=agent.environment,
tags=agent.tags,
openapi_spec_s3_uri=agent.openapi_spec_s3_uri,
openapi_spec_checksum=agent.openapi_spec_checksum
)
for capability_data in agent.capabilities:
capability = get_capability_by_name(db, name=capability_data.name)
if not capability:
capability = create_capability(db, capability_data)
db_agent.capabilities.append(capability)

db.add(db_agent)
db.commit()
db.refresh(db_agent)
return db_agent
Comment on lines +10 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Transaction integrity issue: nested commits can leave orphan capabilities.

When create_agent calls create_capability (line 27), the capability is committed immediately (line 47). If the subsequent agent creation fails, capabilities remain in the database as orphans. Consider deferring commits until the entire operation succeeds.

♻️ Proposed fix: defer commits
+def _get_or_create_capability_no_commit(db: Session, capability_data: schemas.CapabilityCreate):
+    """Get existing capability or create new one without committing."""
+    capability = get_capability_by_name(db, name=capability_data.name)
+    if not capability:
+        capability = models.Capability(**capability_data.dict())
+        db.add(capability)
+        db.flush()  # Assign ID without committing
+    return capability
+
 def create_agent(db: Session, agent: schemas.AgentCreate):
     db_agent = models.Agent(
         name=agent.name,
         version=agent.version,
         display_name=agent.display_name,
         description=agent.description,
         owner=agent.owner,
         endpoint=agent.endpoint,
         openapi_spec=agent.openapi_spec,
         environment=agent.environment,
         tags=agent.tags,
         openapi_spec_s3_uri=agent.openapi_spec_s3_uri,
         openapi_spec_checksum=agent.openapi_spec_checksum
     )
     for capability_data in agent.capabilities:
-        capability = get_capability_by_name(db, name=capability_data.name)
-        if not capability:
-            capability = create_capability(db, capability_data)
+        capability = _get_or_create_capability_no_commit(db, capability_data)
         db_agent.capabilities.append(capability)
 
     db.add(db_agent)
     db.commit()
     db.refresh(db_agent)
     return db_agent
🤖 Prompt for AI Agents
In @mcp/mcp_registry/crud.py around lines 10 - 33, The issue is that
create_agent calls create_capability which currently commits its own
transaction, causing orphaned capability rows if the agent commit later fails;
fix by changing create_capability to not perform its own commit/refresh but
instead accept and use the passed Session to add and return the capability (or
add an optional commit flag defaulting to False), then in create_agent ensure
all new capabilities are added to the same session and perform a single
db.commit()/db.refresh(db_agent) at the end; update any callers of
create_capability to handle the new non-committing behavior or explicitly commit
when needed.


def get_capability(db: Session, capability_id: int):
return db.query(models.Capability).filter(models.Capability.id == capability_id).first()

def get_capability_by_name(db: Session, name: str):
return db.query(models.Capability).filter(models.Capability.name == name).first()

def get_capabilities(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Capability).offset(skip).limit(limit).all()

def create_capability(db: Session, capability: schemas.CapabilityCreate):
db_capability = models.Capability(**capability.dict())
db.add(db_capability)
return db_capability
Comment on lines +44 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing commit when create_capability is called directly from the API.

create_capability adds to the session but doesn't commit. This works correctly when called from create_agent (which commits), but when called directly from the POST /capabilities/ endpoint, changes won't be persisted.

🐛 Proposed fix
 def create_capability(db: Session, capability: schemas.CapabilityCreate):
     db_capability = models.Capability(**capability.dict())
     db.add(db_capability)
+    db.commit()
+    db.refresh(db_capability)
     return db_capability

Alternatively, if you want to keep create_capability non-committing for internal use, commit in the endpoint:

# In main.py
@app.post("/capabilities/", response_model=schemas.Capability)
def create_capability(
    capability: schemas.CapabilityCreate, db: Session = Depends(get_db)
):
    db_capability = crud.create_capability(db=db, capability=capability)
    db.commit()
    db.refresh(db_capability)
    return db_capability
🤖 Prompt for AI Agents
In @mcp/mcp_registry/crud.py around lines 44 - 47, create_capability currently
adds a new models.Capability to the session but never commits, so calls from the
POST /capabilities/ endpoint won't persist; fix by committing and refreshing the
instance before returning: after creating db_capability in create_capability add
db.commit() and db.refresh(db_capability) so it persists and has its generated
fields populated, or if you prefer to keep create_capability non-committing for
internal use (e.g., create_agent), ensure the POST /capabilities/ endpoint calls
db.commit() and db.refresh(db_capability) after crud.create_capability.


def get_agents_by_capability(db: Session, capability_name: str):
return db.query(models.Agent).join(models.Agent.capabilities).filter(models.Capability.name == capability_name, models.Agent.is_deleted == False).all()
81 changes: 81 additions & 0 deletions mcp/mcp_registry/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import os
from dotenv import load_dotenv

from sqlalchemy import create_engine, Column, String, Text, Boolean, DateTime, ARRAY, Integer, UniqueConstraint, Table, ForeignKey
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from datetime import datetime

load_dotenv() # Load environment variables from .env file

# Database connection string from environment variable
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL environment variable not set")

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

agent_capability_association = Table(
'agent_capability_association',
Base.metadata,
Column('agent_id', Integer, ForeignKey('agents.id'), primary_key=True),
Column('capability_id', Integer, ForeignKey('capabilities.id'), primary_key=True)
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

class Agent(Base):
__tablename__ = "agents"

id = Column(Integer, primary_key=True, index=True) # Auto-incrementing primary key
name = Column(String, index=True, nullable=False)
version = Column(String, index=True, nullable=False)
display_name = Column(String, nullable=True)
description = Column(Text, nullable=False)
owner = Column(String, nullable=False)
endpoint = Column(String, nullable=False)
openapi_spec = Column(JSONB, nullable=False) # Storing full OpenAPI spec as JSONB for now
environment = Column(String, index=True, nullable=False)
tags = Column(ARRAY(String), nullable=True)
openapi_spec_s3_uri = Column(String, nullable=True)
openapi_spec_checksum = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
is_deleted = Column(Boolean, default=False)

capabilities = relationship(
"Capability",
secondary=agent_capability_association,
back_populates="agents"
)

# Unique constraint on name and version
__table_args__ = (
UniqueConstraint('name', 'version', name='_name_version_uc'),
)

def __repr__(self):
return f"<Agent(name='{self.name}', version='{self.version}', environment='{self.environment}')>"

class Capability(Base):
__tablename__ = 'capabilities'

id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
description = Column(Text)

agents = relationship(
"Agent",
secondary=agent_capability_association,
back_populates="capabilities"
)

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

def create_db_and_tables():
Base.metadata.create_all(bind=engine)
46 changes: 46 additions & 0 deletions mcp/mcp_registry/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# main.py

from typing import List
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session

from . import crud, models, schemas
from .database import engine, create_db_and_tables, get_db



app = FastAPI()



@app.on_event("startup")
def on_startup():
create_db_and_tables()


@app.post("/agents/", response_model=schemas.Agent)
def create_agent(agent: schemas.AgentCreate, db: Session = Depends(get_db)):
return crud.create_agent(db=db, agent=agent)


@app.get("/agents/", response_model=List[schemas.Agent])
def read_agents(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
agents = crud.get_agents(db, skip=skip, limit=limit)
return agents


@app.get("/search/", response_model=List[schemas.Agent])
def search_agents_by_capability(capability_name: str, db: Session = Depends(get_db)):
return crud.get_agents_by_capability(db=db, capability_name=capability_name)


@app.post("/capabilities/", response_model=schemas.Capability)
def create_capability(
capability: schemas.CapabilityCreate, db: Session = Depends(get_db)
):
return crud.create_capability(db=db, capability=capability)


@app.get("/")
async def root():
return {"message": "Hello MCP Registry!"}
54 changes: 54 additions & 0 deletions mcp/mcp_registry/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import List, Optional
from pydantic import BaseModel, Field

class CapabilityBase(BaseModel):
name: str
description: Optional[str] = None

class CapabilityCreate(CapabilityBase):
pass

class CapabilitySimple(CapabilityBase):
id: int

class Config:
orm_mode = True

class AgentBase(BaseModel):
name: str
version: str
display_name: Optional[str] = None
description: str
owner: str
endpoint: str
openapi_spec: dict
environment: str
tags: Optional[List[str]] = None
openapi_spec_s3_uri: Optional[str] = None
openapi_spec_checksum: Optional[str] = None

class AgentSimple(AgentBase):
id: int

class Config:
orm_mode = True

class AgentCreate(AgentBase):
capabilities: List[CapabilityCreate] = []

class Agent(AgentBase):
id: int
capabilities: List[CapabilitySimple] = Field(default_factory=list)

class Config:
orm_mode = True

class Capability(CapabilityBase):
id: int
agents: List[AgentSimple] = Field(default_factory=list)

class Config:
orm_mode = True

Capability.update_forward_refs()
Agent.update_forward_refs()
Loading