Skip to content
Merged
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
4 changes: 4 additions & 0 deletions apiclient/types/mcpserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ type MCPServer struct {

// DeploymentConditions contains key deployment conditions that indicate deployment health.
DeploymentConditions []DeploymentCondition `json:"deploymentConditions,omitempty"`

// Template indicates whether this MCP server is a template server.
// Template servers are hidden from user views and are used for creating project instances.
Template bool `json:"template,omitempty"`
}

type DeploymentCondition struct {
Expand Down
4 changes: 4 additions & 0 deletions apiclient/types/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ type Project struct {
SourceProjectID string `json:"sourceProjectID,omitempty"`
UserID string `json:"userID,omitempty"`
WorkflowNamesFromIntegration WorkflowNamesFromIntegration `json:"workflowNamesFromIntegration,omitempty"`
TemplateUpgradeAvailable bool `json:"templateUpgradeAvailable,omitempty"`
TemplateUpgradeInProgress bool `json:"templateUpgradeInProgress,omitempty"`
TemplateLastUpgraded *Time `json:"templateLastUpgraded,omitempty"`
TemplatePublicID string `json:"templatePublicID,omitempty"`
}

type WorkflowNamesFromIntegration struct {
Expand Down
22 changes: 9 additions & 13 deletions apiclient/types/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@ package types

type ProjectTemplate struct {
Metadata
ProjectTemplateManifest
ProjectSnapshot ThreadManifest `json:"projectSnapshot,omitempty"`
MCPServers []string `json:"mcpServers,omitempty"`
AssistantID string `json:"assistantID,omitempty"`
ProjectID string `json:"projectID,omitempty"`
PublicID string `json:"publicID,omitempty"`
Ready bool `json:"ready,omitempty"`
}

type ProjectTemplateManifest struct {
Name string `json:"name,omitempty"`
Public bool `json:"public,omitempty"`
Featured bool `json:"featured,omitempty"`
ProjectSnapshot ThreadManifest `json:"projectSnapshot,omitempty"`
ProjectSnapshotLastUpgraded *Time `json:"projectSnapshotLastUpgraded,omitempty"`
ProjectSnapshotStale bool `json:"projectSnapshotStale,omitempty"`
ProjectSnapshotUpgradeInProgress bool `json:"projectSnapshotUpgradeInProgress,omitempty"`
MCPServers []string `json:"mcpServers,omitempty"`
AssistantID string `json:"assistantID,omitempty"`
ProjectID string `json:"projectID,omitempty"`
PublicID string `json:"publicID,omitempty"`
Ready bool `json:"ready,omitempty"`
}

type ProjectTemplateList List[ProjectTemplate]
24 changes: 8 additions & 16 deletions apiclient/types/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions pkg/api/authz/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,9 @@ var apiResources = []string{
"GET /api/assistants/{assistant_id}/projects/{project_id}/tasks/{task_id}/runs/{run_id}/files/{file...}",
"POST /api/assistants/{assistant_id}/projects/{project_id}/tasks/{task_id}/runs/{run_id}/files/{file...}",
"POST /api/assistants/{assistant_id}/projects/{project_id}/tasks/{task_id}/runs/{run_id}/steps/{step_id}/run",
"PUT /api/assistants/{assistant_id}/projects/{project_id}/templates/{template_id}",
"DELETE /api/assistants/{assistant_id}/projects/{project_id}/templates/{template_id}",
"GET /api/assistants/{assistant_id}/projects/{project_id}/templates/{template_id}",
"POST /api/assistants/{assistant_id}/projects/{project_id}/templates",
"GET /api/assistants/{assistant_id}/projects/{project_id}/templates",
"POST /api/assistants/{assistant_id}/projects/{project_id}/template",
"GET /api/assistants/{assistant_id}/projects/{project_id}/template",
"DELETE /api/assistants/{assistant_id}/projects/{project_id}/template",
"GET /api/assistants/{assistant_id}/projects/{project_id}/threads",
"POST /api/assistants/{assistant_id}/projects/{project_id}/threads",
"DELETE /api/assistants/{assistant_id}/projects/{project_id}/threads/{thread_id}",
Expand Down Expand Up @@ -149,6 +147,7 @@ var apiResources = []string{
"GET /api/assistants/{assistant_id}/projects/{project_id}/tools/{tool_id}/local-authenticate",
"DELETE /api/assistants/{assistant_id}/projects/{project_id}/tools/{tool_id}/local-deauthenticate",
"POST /api/assistants/{assistant_id}/projects/{project_id}/tools/{tool_id}/test",
"POST /api/assistants/{assistant_id}/projects/{project_id}/upgrade-from-template",
"GET /api/mcp-server-instances",
"GET /api/mcp-server-instances/{mcp_server_instance_id}",
"POST /api/mcp-server-instances",
Expand Down Expand Up @@ -293,6 +292,7 @@ func (a *Authorizer) evaluateResources(req *http.Request, vars GetVar, user user
MCPID: vars("mcp_id"), // this will be either a server ID or a server instance ID
PendingAuthorizationID: vars("pending_authorization_id"),
ThreadShareID: vars("share_public_id"),
TemplateID: vars("template_public_id"),
ToolID: vars("tool_id"),
WorkspaceID: vars("workspace_id"),
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/handlers/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ func (m *MCPHandler) ListServer(req api.Context) error {
items := make([]types.MCPServer, 0, len(servers.Items))

for _, server := range servers.Items {
if server.Spec.Template {
continue
}

// Add extracted env vars to the server definition
addExtractedEnvVars(&server)

Expand Down Expand Up @@ -1726,6 +1730,7 @@ func convertMCPServer(server v1.MCPServer, credEnv map[string]string, serverURL,
DeploymentReadyReplicas: server.Status.DeploymentReadyReplicas,
DeploymentReplicas: server.Status.DeploymentReplicas,
DeploymentConditions: conditions,
Template: server.Spec.Template,
}
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/api/handlers/mcpgateway/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1"
"github.com/tidwall/gjson"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
)

Expand Down Expand Up @@ -60,6 +61,11 @@ func (h *Handler) StreamableHTTP(req api.Context) error {
sessionID := req.Request.Header.Get("Mcp-Session-Id")

mcpID, mcpServer, mcpServerConfig, err := handlers.ServerForActionWithConnectID(req, req.PathValue("mcp_id"))
if err == nil && mcpServer.Spec.Template {
// Prevent connections to MCP server templates by returning a 404.
err = apierrors.NewNotFound(schema.GroupResource{Group: "obot.obot.ai", Resource: "mcpserver"}, mcpID)
}

if err != nil {
if apierrors.IsNotFound(err) {
// If the MCP server is not found, remove the session.
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/handlers/projectmcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func convertProjectMCPServer(projectServer *v1.ProjectMCPServer, mcpServer *v1.M
}
pmcp.Alias = mcpServer.Spec.Alias

if cred != nil && mcpServer.Spec.MCPCatalogID == "" {
if mcpServer.Spec.MCPCatalogID == "" {
// For single-user servers, grab more status information from the MCP server.
// We don't show this for shared servers, because the user can't do anything about it
// if something is wrong with one of those; only the admin can.
Expand Down
62 changes: 62 additions & 0 deletions pkg/api/handlers/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"net/http"
"slices"
"strings"

Expand Down Expand Up @@ -247,6 +248,60 @@ func (h *ProjectsHandler) GetProject(req api.Context) error {
return req.Write(convertProject(&thread, nil))
}

// UpgradeFromTemplate upgrades a project to the latest snapshot of a project if the snapshot has changed since
// the project was created or the last time the project was upgraded.
func (h *ProjectsHandler) UpgradeFromTemplate(req api.Context) error {
var (
projectID = strings.Replace(req.PathValue("project_id"), system.ProjectPrefix, system.ThreadPrefix, 1)
thread v1.Thread
)

if err := req.Get(&thread, projectID); err != nil {
return err
}

if thread.Spec.SourceThreadName == "" || !thread.Spec.Project {
return types.NewErrBadRequest("project was not created from a template")
}

if thread.Status.UpgradeInProgress {
return types.NewErrHTTP(http.StatusTooEarly, "project upgrade already in progress")
}

if !thread.Status.UpgradeAvailable {
// Project is ineligable for an upgrade due to one of the following reasons:
// - the project is already at the latest revision of the project snapshot
// - the user has manually modified the project
return types.NewErrBadRequest("project not eligible for an upgrade")
}

if thread.Spec.UpgradeApproved {
// Project is already approved for an upgrade, nothing to do
return nil
}

// Get the source thread to verify it's a template
var source v1.Thread
if err := req.Get(&source, thread.Spec.SourceThreadName); err != nil {
return err
}

// Verify the source is actually a template
if !source.Spec.Template {
return types.NewErrBadRequest("source project is not a template")
}

// Ensure the template isn't currently being upgraded from its own source project
if source.Status.UpgradeInProgress {
return types.NewErrHTTP(http.StatusTooEarly, "the project snapshot is currently being upgraded")
}

// Project has diverged from the snapshot, upgrade to the latest snapshot by setting the upgrade flag
thread.Spec.UpgradeApproved = true

return req.Update(&thread)
}

func (h *ProjectsHandler) ListProjects(req api.Context) error {
var (
assistantID = req.PathValue("assistant_id")
Expand Down Expand Up @@ -469,6 +524,13 @@ func convertProject(thread *v1.Thread, parentThread *v1.Thread) types.Project {
Editor: thread.IsEditor(),
UserID: thread.Spec.UserID,
WorkflowNamesFromIntegration: thread.Status.WorkflowNamesFromIntegration,
TemplateUpgradeAvailable: (thread.Status.UpgradeAvailable && !thread.Spec.UpgradeApproved),
TemplateUpgradeInProgress: thread.Status.UpgradeInProgress,
TemplatePublicID: thread.Status.UpgradePublicID,
}

if !thread.Status.LastUpgraded.IsZero() {
p.TemplateLastUpgraded = types.NewTime(thread.Status.LastUpgraded.Time)
}

// Include tools from parent project
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/handlers/serverinstances.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ func (h *ServerInstancesHandler) ListServerInstances(req api.Context) error {

convertedInstances := make([]types.MCPServerInstance, 0, len(instances.Items))
for _, instance := range instances.Items {
if instance.Spec.Template {
// Hide template instances from user list view
continue
}

slug, err := slugForMCPServerInstance(req.Context(), req.Storage, instance, req.User.GetUID())
if err != nil {
return fmt.Errorf("failed to determine slug for instance %s: %w", instance.Name, err)
Expand Down
Loading
Loading