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
57 changes: 57 additions & 0 deletions cogsol/core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,63 @@ def get_script(self, script_id: int) -> Any:
def get_retrieval_tool(self, tool_id: int) -> Any:
return self.request("GET", f"/tools/retrievals/{tool_id}/")

# =========================================================================
# MCP Servers & Tools
# =========================================================================

def list_mcp_servers(self) -> Any:
"""List all MCP servers."""
return self.request("GET", "/mcp-servers/")

def create_mcp_server(self, payload: dict[str, Any]) -> int:
"""Create an MCP server and return its id."""
data = self.request("POST", "/mcp-servers/", payload)
return self._ensure_id(data, "MCPServer")

def upsert_mcp_server(self, *, remote_id: int | None, payload: dict[str, Any]) -> int:
"""Create or update an MCP server."""
if remote_id:
data = self.request("PUT", f"/mcp-servers/{remote_id}/", payload)
else:
data = self.request("POST", "/mcp-servers/", payload)
return self._ensure_id(data, "MCPServer")

def delete_mcp_server(self, server_id: int) -> None:
"""Delete an MCP server by id."""
self.request("DELETE", f"/mcp-servers/{server_id}/")

def get_mcp_server(self, server_id: int) -> Any:
"""Retrieve an MCP server by id."""
return self.request("GET", f"/mcp-servers/{server_id}/")

def discover_mcp_oauth(self, server_id: int) -> Any:
"""Discover OAuth metadata for an MCP server."""
return self.request("POST", f"/mcp-servers/{server_id}/oauth/discover/")

def get_mcp_oauth_authorization_url(self, server_id: int) -> Any:
"""Get OAuth authorization URL for an MCP server."""
return self.request("GET", f"/mcp-servers/{server_id}/oauth/authorize/")

def list_mcp_server_tools(self, server_id: int) -> Any:
"""List tools currently configured on an MCP server."""
return self.request("GET", f"/mcp-servers/{server_id}/tools/")

def sync_mcp_server_tools(self, server_id: int, selected_tools: list[str]) -> Any:
"""Sync (create/update) the selected tools on an MCP server.

The backend uses a POST with ``{"selected_tools": [...]}``
to reconcile the tool set.
"""
return self.request(
"POST",
f"/mcp-servers/{server_id}/tools/",
{"selected_tools": selected_tools},
)

def delete_mcp_tool(self, tool_id: int) -> None:
"""Delete an MCP tool by id."""
self.request("DELETE", f"/mcp-tools/{tool_id}/")

# =========================================================================
# Content API - Nodes (Topics)
# =========================================================================
Expand Down
104 changes: 104 additions & 0 deletions cogsol/core/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
BaseFAQ,
BaseFixedResponse,
BaseLesson,
BaseMCPServer,
BaseMCPTool,
BaseRetrievalTool,
BaseTool,
)
Expand Down Expand Up @@ -69,6 +71,8 @@ def serialize_value(value: Any) -> Any:
return (
getattr(value, "name", None) or getattr(value, "key", None) or value.__class__.__name__
)
if isinstance(value, type) and issubclass(value, BaseMCPServer):
return getattr(value, "name", None) or value.__name__
if isinstance(value, type):
if issubclass(value, BaseRetrieval):
return getattr(value, "name", None) or value.__name__
Expand Down Expand Up @@ -294,6 +298,8 @@ def collect_definitions(
"agents": {},
"tools": {},
"retrieval_tools": {},
"mcp_servers": {},
"mcp_tools": {},
"faqs": {},
"fixed_responses": {},
"lessons": {},
Expand Down Expand Up @@ -346,6 +352,64 @@ def collect_definitions(
if not _ignore_missing_module(exc, f"{app_name}.searches"):
_raise_import_error("retrieval tools module", f"{app_name}.searches", exc)

# MCP servers (global)
try:
mcp_server_module = _import_module(f"{app_name}.mcp_servers", project_path)
mcp_srv_prefix = f"{mcp_server_module.__name__}."
for _, obj in inspect.getmembers(mcp_server_module, inspect.isclass):
if (
issubclass(obj, BaseMCPServer)
and obj is not BaseMCPServer
and (
obj.__module__ == mcp_server_module.__name__
or obj.__module__.startswith(mcp_srv_prefix)
)
):
fields, meta = _extract_class_fields(obj)
name = fields.get("name") or obj.__name__
fields["name"] = name
# Resolve env-var references so values are evaluated at collect time
if hasattr(obj, "url") and obj.url is not None:
fields["url"] = obj.url
if hasattr(obj, "headers") and obj.headers:
fields["headers"] = obj.headers
# OAuth fields
if hasattr(obj, "auth_type"):
fields["auth_type"] = obj.auth_type
if hasattr(obj, "oauth_client_id") and obj.oauth_client_id is not None:
fields["oauth_client_id"] = obj.oauth_client_id
if hasattr(obj, "oauth_scopes") and obj.oauth_scopes is not None:
fields["oauth_scopes"] = obj.oauth_scopes
definitions["mcp_servers"][name] = {"fields": fields, "meta": meta}
except ModuleNotFoundError as exc:
if not _ignore_missing_module(exc, f"{app_name}.mcp_servers"):
_raise_import_error("MCP servers module", f"{app_name}.mcp_servers", exc)

# MCP tools (global)
try:
mcp_tool_module = _import_module(f"{app_name}.mcp_tools", project_path)
mcp_tool_prefix = f"{mcp_tool_module.__name__}."
for _, obj in inspect.getmembers(mcp_tool_module, inspect.isclass):
if (
issubclass(obj, BaseMCPTool)
and obj is not BaseMCPTool
and (
obj.__module__ == mcp_tool_module.__name__
or obj.__module__.startswith(mcp_tool_prefix)
)
):
fields, meta = _extract_class_fields(obj)
name = fields.get("name") or obj.__name__
fields["name"] = name
# Serialize server reference as the server class name
server_cls = getattr(obj, "server", None)
if server_cls is not None and isinstance(server_cls, type):
fields["server"] = getattr(server_cls, "name", None) or server_cls.__name__
definitions["mcp_tools"][name] = {"fields": fields, "meta": meta}
except ModuleNotFoundError as exc:
if not _ignore_missing_module(exc, f"{app_name}.mcp_tools"):
_raise_import_error("MCP tools module", f"{app_name}.mcp_tools", exc)

# Per-agent packages (agents/<slug>/agent.py)
for sub in sorted(app_path.iterdir()):
if not sub.is_dir():
Expand Down Expand Up @@ -483,6 +547,8 @@ def collect_classes(project_path: Path, app_name: str = "agents") -> dict[str, d
"agents": {},
"tools": {},
"retrieval_tools": {},
"mcp_servers": {},
"mcp_tools": {},
}

# Tools
Expand Down Expand Up @@ -521,6 +587,44 @@ def collect_classes(project_path: Path, app_name: str = "agents") -> dict[str, d
if not _ignore_missing_module(exc, f"{app_name}.searches"):
_raise_import_error("retrieval tools module", f"{app_name}.searches", exc)

# MCP servers
try:
mcp_server_module = _import_module(f"{app_name}.mcp_servers", project_path)
mcp_srv_prefix = f"{mcp_server_module.__name__}."
for _, obj in inspect.getmembers(mcp_server_module, inspect.isclass):
if (
issubclass(obj, BaseMCPServer)
and obj is not BaseMCPServer
and (
obj.__module__ == mcp_server_module.__name__
or obj.__module__.startswith(mcp_srv_prefix)
)
):
name = getattr(obj, "name", None) or obj.__name__
classes["mcp_servers"][name] = obj
except ModuleNotFoundError as exc:
if not _ignore_missing_module(exc, f"{app_name}.mcp_servers"):
_raise_import_error("MCP servers module", f"{app_name}.mcp_servers", exc)

# MCP tools
try:
mcp_tool_module = _import_module(f"{app_name}.mcp_tools", project_path)
mcp_tool_prefix = f"{mcp_tool_module.__name__}."
for _, obj in inspect.getmembers(mcp_tool_module, inspect.isclass):
if (
issubclass(obj, BaseMCPTool)
and obj is not BaseMCPTool
and (
obj.__module__ == mcp_tool_module.__name__
or obj.__module__.startswith(mcp_tool_prefix)
)
):
name = getattr(obj, "name", None) or obj.__name__
classes["mcp_tools"][name] = obj
except ModuleNotFoundError as exc:
if not _ignore_missing_module(exc, f"{app_name}.mcp_tools"):
_raise_import_error("MCP tools module", f"{app_name}.mcp_tools", exc)

# Agents per folder
for sub in sorted(app_path.iterdir()):
if not sub.is_dir():
Expand Down
1 change: 1 addition & 0 deletions cogsol/core/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def _command_registry() -> dict[str, str]:
"importagent": "cogsol.management.commands.importagent",
"makemigrations": "cogsol.management.commands.makemigrations",
"migrate": "cogsol.management.commands.migrate",
"addmcptools": "cogsol.management.commands.addmcptools",
"chat": "cogsol.management.commands.chat",
}

Expand Down
Loading
Loading