diff --git a/apps/dbagent/migrations/0009_mcp_server_type_and_url.sql b/apps/dbagent/migrations/0009_mcp_server_type_and_url.sql new file mode 100644 index 00000000..8aec5949 --- /dev/null +++ b/apps/dbagent/migrations/0009_mcp_server_type_and_url.sql @@ -0,0 +1,12 @@ +-- Add mcp_server_type enum type if it doesn't exist +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'mcp_server_type') THEN + CREATE TYPE mcp_server_type AS ENUM ('stdio', 'sse', 'streamable-http'); + END IF; +END$$; + +-- Add the new columns to the mcp_servers table +ALTER TABLE mcp_servers +ADD COLUMN type mcp_server_type DEFAULT 'stdio', +ADD COLUMN url TEXT; diff --git a/apps/dbagent/migrations/0009_remote_mcp.sql b/apps/dbagent/migrations/0009_remote_mcp.sql new file mode 100644 index 00000000..8069cd98 --- /dev/null +++ b/apps/dbagent/migrations/0009_remote_mcp.sql @@ -0,0 +1,5 @@ +ALTER TABLE "mcp_servers" DROP CONSTRAINT "uq_mcp_servers_server_name";--> statement-breakpoint +ALTER TABLE "mcp_servers" ADD COLUMN "config" jsonb NOT NULL;--> statement-breakpoint +ALTER TABLE "mcp_servers" DROP COLUMN "server_name";--> statement-breakpoint +ALTER TABLE "mcp_servers" DROP COLUMN "file_path";--> statement-breakpoint +ALTER TABLE "mcp_servers" DROP COLUMN "env_vars"; \ No newline at end of file diff --git a/apps/dbagent/migrations/meta/0009_snapshot.json b/apps/dbagent/migrations/meta/0009_snapshot.json new file mode 100644 index 00000000..cebaa29a --- /dev/null +++ b/apps/dbagent/migrations/meta/0009_snapshot.json @@ -0,0 +1,2074 @@ +{ + "id": "03f75f0b-c694-4a4c-aa66-4f7fd21d157b", + "prevId": "e79cf7af-0cbc-43c4-b7e5-939e09229989", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.artifact_documents": { + "name": "artifact_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_artifact_documents_project_id": { + "name": "idx_artifact_documents_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_artifact_documents_user_id": { + "name": "idx_artifact_documents_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_artifact_documents_project": { + "name": "fk_artifact_documents_project", + "tableFrom": "artifact_documents", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "artifact_documents_id_pk": { + "name": "artifact_documents_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "policies": { + "artifact_documents_policy": { + "name": "artifact_documents_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = artifact_documents.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.artifact_suggestions": { + "name": "artifact_suggestions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_created_at": { + "name": "document_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "original_text": { + "name": "original_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "suggested_text": { + "name": "suggested_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_resolved": { + "name": "is_resolved", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_artifact_suggestions_document_id": { + "name": "idx_artifact_suggestions_document_id", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_artifact_suggestions_project_id": { + "name": "idx_artifact_suggestions_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_artifact_suggestions_user_id": { + "name": "idx_artifact_suggestions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_artifact_suggestions_project": { + "name": "fk_artifact_suggestions_project", + "tableFrom": "artifact_suggestions", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_artifact_suggestions_document": { + "name": "fk_artifact_suggestions_document", + "tableFrom": "artifact_suggestions", + "tableTo": "artifact_documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "artifact_suggestions_id_pk": { + "name": "artifact_suggestions_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "policies": { + "artifact_suggestions_policy": { + "name": "artifact_suggestions_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = artifact_suggestions.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.aws_cluster_connections": { + "name": "aws_cluster_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cluster_id": { + "name": "cluster_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_aws_cluster_connections_cluster_id": { + "name": "idx_aws_cluster_connections_cluster_id", + "columns": [ + { + "expression": "cluster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_aws_cluster_connections_connection_id": { + "name": "idx_aws_cluster_connections_connection_id", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_aws_cluster_connections_project_id": { + "name": "idx_aws_cluster_connections_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_aws_cluster_connections_project": { + "name": "fk_aws_cluster_connections_project", + "tableFrom": "aws_cluster_connections", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_aws_cluster_connections_cluster": { + "name": "fk_aws_cluster_connections_cluster", + "tableFrom": "aws_cluster_connections", + "tableTo": "aws_clusters", + "columnsFrom": ["cluster_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_aws_cluster_connections_connection": { + "name": "fk_aws_cluster_connections_connection", + "tableFrom": "aws_cluster_connections", + "tableTo": "connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "aws_cluster_connections_policy": { + "name": "aws_cluster_connections_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = aws_cluster_connections.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.aws_clusters": { + "name": "aws_clusters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cluster_identifier": { + "name": "cluster_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'us-east-1'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_aws_clusters_project_id": { + "name": "idx_aws_clusters_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_aws_clusters_project": { + "name": "fk_aws_clusters_project", + "tableFrom": "aws_clusters", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_aws_clusters_integration_identifier": { + "name": "uq_aws_clusters_integration_identifier", + "nullsNotDistinct": false, + "columns": ["cluster_identifier"] + } + }, + "policies": { + "aws_clusters_policy": { + "name": "aws_clusters_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = aws_clusters.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_chats_project_id": { + "name": "idx_chats_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chats_user_id": { + "name": "idx_chats_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_chats_project": { + "name": "fk_chats_project", + "tableFrom": "chats", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "chats_policy": { + "name": "chats_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_info": { + "name": "connection_info", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_connection_info_connection_id": { + "name": "idx_connection_info_connection_id", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_connection_info_project_id": { + "name": "idx_connection_info_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_connection_info_project": { + "name": "fk_connection_info_project", + "tableFrom": "connection_info", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_connection_info_connection": { + "name": "fk_connection_info_connection", + "tableFrom": "connection_info", + "tableTo": "connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_connection_info": { + "name": "uq_connection_info", + "nullsNotDistinct": false, + "columns": ["connection_id", "type"] + } + }, + "policies": { + "connection_info_policy": { + "name": "connection_info_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = connection_info.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connections": { + "name": "connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "connection_string": { + "name": "connection_string", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_connections_project_id": { + "name": "idx_connections_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_connections_project": { + "name": "fk_connections_project", + "tableFrom": "connections", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_connections_name": { + "name": "uq_connections_name", + "nullsNotDistinct": false, + "columns": ["project_id", "name"] + }, + "uq_connections_connection_string": { + "name": "uq_connections_connection_string", + "nullsNotDistinct": false, + "columns": ["project_id", "connection_string"] + } + }, + "policies": { + "connections_policy": { + "name": "connections_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = connections.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gcp_instance_connections": { + "name": "gcp_instance_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_gcp_instance_connections_instance_id": { + "name": "idx_gcp_instance_connections_instance_id", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gcp_instance_connections_connection_id": { + "name": "idx_gcp_instance_connections_connection_id", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gcp_instance_connections_project_id": { + "name": "idx_gcp_instance_connections_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_gcp_instance_connections_project": { + "name": "fk_gcp_instance_connections_project", + "tableFrom": "gcp_instance_connections", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_gcp_instance_connections_instance": { + "name": "fk_gcp_instance_connections_instance", + "tableFrom": "gcp_instance_connections", + "tableTo": "gcp_instances", + "columnsFrom": ["instance_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_gcp_instance_connections_connection": { + "name": "fk_gcp_instance_connections_connection", + "tableFrom": "gcp_instance_connections", + "tableTo": "connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "gcp_instance_connections_policy": { + "name": "gcp_instance_connections_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = gcp_instance_connections.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gcp_instances": { + "name": "gcp_instances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instance_name": { + "name": "instance_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gcp_project_id": { + "name": "gcp_project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_gcp_instances_project_id": { + "name": "idx_gcp_instances_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_gcp_instances_project": { + "name": "fk_gcp_instances_project", + "tableFrom": "gcp_instances", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_gcp_instances_instance_name": { + "name": "uq_gcp_instances_instance_name", + "nullsNotDistinct": false, + "columns": ["project_id", "gcp_project_id", "instance_name"] + } + }, + "policies": { + "gcp_instances_policy": { + "name": "gcp_instances_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = gcp_instances.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integrations": { + "name": "integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_integrations_project_id": { + "name": "idx_integrations_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_integrations_project": { + "name": "fk_integrations_project", + "tableFrom": "integrations", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_integrations_name": { + "name": "uq_integrations_name", + "nullsNotDistinct": false, + "columns": ["project_id", "name"] + } + }, + "policies": { + "integrations_policy": { + "name": "integrations_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = integrations.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_mcp_servers_name": { + "name": "uq_mcp_servers_name", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": { + "mcp_servers_policy": { + "name": "mcp_servers_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_votes": { + "name": "message_votes", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_upvoted": { + "name": "is_upvoted", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_message_votes_chat_id": { + "name": "idx_message_votes_chat_id", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_votes_message_id": { + "name": "idx_message_votes_message_id", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_votes_project_id": { + "name": "idx_message_votes_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_votes_user_id": { + "name": "idx_message_votes_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_votes_chat": { + "name": "fk_votes_chat", + "tableFrom": "message_votes", + "tableTo": "chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_message_votes_message": { + "name": "fk_message_votes_message", + "tableFrom": "message_votes", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_message_votes_project": { + "name": "fk_message_votes_project", + "tableFrom": "message_votes", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_votes_chat_id_message_id_user_id_pk": { + "name": "message_votes_chat_id_message_id_user_id_pk", + "columns": ["chat_id", "message_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": { + "message_votes_policy": { + "name": "message_votes_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = message_votes.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "parts": { + "name": "parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_messages_project_id": { + "name": "idx_messages_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_chat_id": { + "name": "idx_messages_chat_id", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_messages_project": { + "name": "fk_messages_project", + "tableFrom": "messages", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_messages_chat": { + "name": "fk_messages_chat", + "tableFrom": "messages", + "tableTo": "chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "messages_policy": { + "name": "messages_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = messages.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playbooks": { + "name": "playbooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_playbooks_project_id": { + "name": "idx_playbooks_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_playbooks_project": { + "name": "fk_playbooks_project", + "tableFrom": "playbooks", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_playbooks_name": { + "name": "uq_playbooks_name", + "nullsNotDistinct": false, + "columns": ["project_id", "name"] + } + }, + "policies": { + "playbooks_policy": { + "name": "playbooks_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = playbooks.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_members": { + "name": "project_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "added_at": { + "name": "added_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_project_members_project_id": { + "name": "idx_project_members_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_project_members_project": { + "name": "fk_project_members_project", + "tableFrom": "project_members", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_project_members_user_project": { + "name": "uq_project_members_user_project", + "nullsNotDistinct": false, + "columns": ["project_id", "user_id"] + } + }, + "policies": { + "project_members_policy": { + "name": "project_members_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cloud_provider": { + "name": "cloud_provider", + "type": "cloud_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "projects_view_policy": { + "name": "projects_view_policy", + "as": "PERMISSIVE", + "for": "SELECT", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = projects.id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + }, + "projects_create_policy": { + "name": "projects_create_policy", + "as": "PERMISSIVE", + "for": "INSERT", + "to": ["authenticated_user"], + "withCheck": "true" + }, + "projects_update_policy": { + "name": "projects_update_policy", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = projects.id AND user_id = current_setting('app.current_user', true)::TEXT AND role = 'owner'\n )" + }, + "projects_delete_policy": { + "name": "projects_delete_policy", + "as": "PERMISSIVE", + "for": "DELETE", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = projects.id AND user_id = current_setting('app.current_user', true)::TEXT AND role = 'owner'\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule_runs": { + "name": "schedule_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notification_level": { + "name": "notification_level", + "type": "notification_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_schedule_runs_created_at": { + "name": "idx_schedule_runs_created_at", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedule_runs_schedule_id": { + "name": "idx_schedule_runs_schedule_id", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedule_runs_project_id": { + "name": "idx_schedule_runs_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedule_runs_notification_level": { + "name": "idx_schedule_runs_notification_level", + "columns": [ + { + "expression": "notification_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_schedule_runs_project": { + "name": "fk_schedule_runs_project", + "tableFrom": "schedule_runs", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_schedule_runs_schedule": { + "name": "fk_schedule_runs_schedule", + "tableFrom": "schedule_runs", + "tableTo": "schedules", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "schedule_runs_policy": { + "name": "schedule_runs_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = schedule_runs.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedules": { + "name": "schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "playbook": { + "name": "playbook", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "schedule_type": { + "name": "schedule_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "additional_instructions": { + "name": "additional_instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "min_interval": { + "name": "min_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_interval": { + "name": "max_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_run": { + "name": "last_run", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_run": { + "name": "next_run", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "schedule_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + }, + "failures": { + "name": "failures", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "keep_history": { + "name": "keep_history", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_steps": { + "name": "max_steps", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "notify_level": { + "name": "notify_level", + "type": "notification_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'alert'" + }, + "extra_notification_text": { + "name": "extra_notification_text", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_schedules_project_id": { + "name": "idx_schedules_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_connection_id": { + "name": "idx_schedules_connection_id", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_status": { + "name": "idx_schedules_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_next_run": { + "name": "idx_schedules_next_run", + "columns": [ + { + "expression": "next_run", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_enabled": { + "name": "idx_schedules_enabled", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_schedules_project": { + "name": "fk_schedules_project", + "tableFrom": "schedules", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_schedules_connection": { + "name": "fk_schedules_connection", + "tableFrom": "schedules", + "tableTo": "connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "schedules_policy": { + "name": "schedules_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = schedules.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.cloud_provider": { + "name": "cloud_provider", + "schema": "public", + "values": ["aws", "gcp", "other"] + }, + "public.member_role": { + "name": "member_role", + "schema": "public", + "values": ["owner", "member"] + }, + "public.notification_level": { + "name": "notification_level", + "schema": "public", + "values": ["info", "warning", "alert"] + }, + "public.schedule_status": { + "name": "schedule_status", + "schema": "public", + "values": ["disabled", "scheduled", "running"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": { + "authenticated_user": { + "name": "authenticated_user", + "createDb": false, + "createRole": false, + "inherit": true + } + }, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/dbagent/migrations/meta/_journal.json b/apps/dbagent/migrations/meta/_journal.json index 0c045aa2..1ec8d43b 100644 --- a/apps/dbagent/migrations/meta/_journal.json +++ b/apps/dbagent/migrations/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1746433645178, "tag": "0008_mcp_servers", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1747728975039, + "tag": "0009_remote_mcp", + "breakpoints": true } ] } diff --git a/apps/dbagent/src/app/(main)/projects/[project]/mcp/[server]/page.tsx b/apps/dbagent/src/app/(main)/projects/[project]/mcp/[server]/page.tsx index cf726f58..690fbd88 100644 --- a/apps/dbagent/src/app/(main)/projects/[project]/mcp/[server]/page.tsx +++ b/apps/dbagent/src/app/(main)/projects/[project]/mcp/[server]/page.tsx @@ -29,11 +29,13 @@ export default async function McpServerPage({ params }: { params: Promise }) { +export async function GET(_: Request, { params }: { params: { server: string } }) { try { - const { server } = await params; - const filePath = path.join(mcpSourceDir, `${server}.ts`); - - // Check if file exists - try { - await fs.access(filePath); - } catch (error) { - return NextResponse.json({ error: 'Server file not found' }, { status: 404 }); - } - - // Read file content - const content = await fs.readFile(filePath, 'utf-8'); - - // Extract metadata from file content - const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/); - const versionMatch = content.match(/version:\s*['"]([^'"]+)['"]/); - const descriptionMatch = content.match(/description:\s*['"]([^'"]+)['"]/); - - const metadata = { - name: server, - serverName: nameMatch ? nameMatch[1] : server, - version: versionMatch ? versionMatch[1] : '1.0.0', - description: descriptionMatch ? descriptionMatch[1] : '', - filePath: `${server}.ts`, - enabled: false - }; - - return NextResponse.json(metadata); + const serverName = params.server; // This is the 'name' field of the server + if (!serverName) { + return NextResponse.json({ error: 'Server name parameter is missing' }, { status: 400 }); + } + + const dbAccess = await getUserSessionDBAccess(); + const server = await getUserMcpServer(dbAccess, serverName); + + if (!server) { + return NextResponse.json({ error: 'Server not found' }, { status: 404 }); + } + return NextResponse.json(server); + } catch (error) { + console.error('Error fetching MCP server:', error); + const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; + return NextResponse.json({ error: 'Failed to fetch MCP server', details: errorMessage }, { status: 500 }); + } +} + +export async function PUT(request: Request, { params }: { params: { server: string } }) { + try { + const serverNameFromParams = params.server; // This is the 'name' field to identify the server + if (!serverNameFromParams) { + return NextResponse.json({ error: 'Server name parameter is missing' }, { status: 400 }); + } + + const body = await request.json(); + const validatedData = mcpServerUpdateSchema.safeParse(body); + + if (!validatedData.success) { + return NextResponse.json({ error: 'Invalid input', details: validatedData.error.flatten() }, { status: 400 }); + } + + const dbAccess = await getUserSessionDBAccess(); + const updatedServer = await updateUserMcpServer(dbAccess, { + ...validatedData.data, + name: serverNameFromParams + }); + return NextResponse.json(updatedServer); + } catch (error) { + console.error('Error updating MCP server:', error); + const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; + if ( + errorMessage.includes('not found or no values changed') || + errorMessage.includes('not found or update failed') + ) { + return NextResponse.json({ error: errorMessage }, { status: 404 }); + } + if (errorMessage.includes('already exists')) { + return NextResponse.json({ error: errorMessage }, { status: 409 }); // Conflict + } + return NextResponse.json({ error: 'Failed to update MCP server', details: errorMessage }, { status: 500 }); + } +} + +export async function DELETE(_: Request, { params }: { params: { server: string } }) { + try { + const serverName = params.server; // This is the 'name' field of the server + if (!serverName) { + return NextResponse.json({ error: 'Server name parameter is missing' }, { status: 400 }); + } + + const dbAccess = await getUserSessionDBAccess(); + await deleteUserMcpServer(dbAccess, serverName); + return NextResponse.json({ message: `Server "${serverName}" deleted successfully` }, { status: 200 }); // Or 204 No Content } catch (error) { - console.error('Error reading server file:', error); - return NextResponse.json({ error: 'Failed to read server file' }, { status: 500 }); + console.error('Error deleting MCP server:', error); + const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; + if (errorMessage.includes('not found')) { + return NextResponse.json({ error: errorMessage }, { status: 404 }); + } + return NextResponse.json({ error: 'Failed to delete MCP server', details: errorMessage }, { status: 500 }); } } diff --git a/apps/dbagent/src/app/api/mcp/servers/route.ts b/apps/dbagent/src/app/api/mcp/servers/route.ts index 10f50d24..ce10363e 100644 --- a/apps/dbagent/src/app/api/mcp/servers/route.ts +++ b/apps/dbagent/src/app/api/mcp/servers/route.ts @@ -1,37 +1,50 @@ -import { promises as fs } from 'fs'; import { NextResponse } from 'next/server'; -import path from 'path'; -import { getMCPSourceDir } from '~/lib/ai/tools/user-mcp'; +import { z } from 'zod'; +import { getUserSessionDBAccess } from '~/lib/db/db'; // Assuming dbAccess is available like this +import { addUserMcpServerToDB, getUserMcpServers } from '~/lib/db/mcp-servers'; +import { mcpServerConfigSchema } from '~/lib/db/schema'; // Import the enum type for Zod -const mcpSourceDir = getMCPSourceDir(); +// Zod schema for MCPServerInsert +const mcpServerInsertSchema = z.object({ + name: z.string().min(1, 'Name is required'), + version: z.string(), + config: mcpServerConfigSchema +}); export async function GET() { try { - const files = await fs.readdir(mcpSourceDir); - const serverFiles = files.filter((file) => file.endsWith('.ts') && !file.endsWith('.d.ts')); - - const servers = await Promise.all( - serverFiles.map(async (file) => { - const filePath = path.join(mcpSourceDir, file); - const content = await fs.readFile(filePath, 'utf-8'); + const dbAccess = await getUserSessionDBAccess(); + const servers = await getUserMcpServers(dbAccess); + return NextResponse.json(servers); + } catch (error) { + console.error('Error fetching MCP servers from database:', error); + const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; + return NextResponse.json( + { error: 'Failed to fetch MCP servers from database', details: errorMessage }, + { status: 500 } + ); + } +} - // Extract server name and version from the file content - const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/); - const versionMatch = content.match(/version:\s*['"]([^'"]+)['"]/); +export async function POST(request: Request) { + try { + const body = await request.json(); + const validatedData = mcpServerInsertSchema.safeParse(body); - return { - name: path.basename(file, '.ts'), - serverName: nameMatch ? nameMatch[1] : path.basename(file, '.ts'), - version: versionMatch ? versionMatch[1] : '1.0.0', - filePath: file, - enabled: false - }; - }) - ); + if (!validatedData.success) { + return NextResponse.json({ error: 'Invalid input', details: validatedData.error.flatten() }, { status: 400 }); + } - return NextResponse.json(servers); + const dbAccess = await getUserSessionDBAccess(); + const newServer = await addUserMcpServerToDB(dbAccess, validatedData.data); + return NextResponse.json(newServer, { status: 201 }); } catch (error) { - console.error('Error reading MCP servers:', error); - return NextResponse.json({ error: 'Failed to read MCP servers' }, { status: 500 }); + console.error('Error creating MCP server:', error); + const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; + // Check for specific error messages from addUserMcpServerToDB (e.g., unique constraints) + if (errorMessage.includes('already exists')) { + return NextResponse.json({ error: errorMessage }, { status: 409 }); // Conflict + } + return NextResponse.json({ error: 'Failed to create MCP server', details: errorMessage }, { status: 500 }); } } diff --git a/apps/dbagent/src/components/mcp/action.ts b/apps/dbagent/src/components/mcp/action.ts index dbab1add..d847c52b 100644 --- a/apps/dbagent/src/components/mcp/action.ts +++ b/apps/dbagent/src/components/mcp/action.ts @@ -8,7 +8,7 @@ import { addUserMcpServerToDB, deleteUserMcpServer, getUserMcpServer, updateUser import { MCPServer, MCPServerInsert } from '~/lib/db/schema'; //playbook db insert -export async function actionAddUserMcpServerToDB(input: MCPServer): Promise { +export async function actionAddUserMcpServerToDB(input: MCPServerInsert): Promise { const dbAccess = await getUserSessionDBAccess(); return await addUserMcpServerToDB(dbAccess, input); } diff --git a/apps/dbagent/src/components/mcp/mcp-table.tsx b/apps/dbagent/src/components/mcp/mcp-table.tsx index e0dd9d16..6a4ee820 100644 --- a/apps/dbagent/src/components/mcp/mcp-table.tsx +++ b/apps/dbagent/src/components/mcp/mcp-table.tsx @@ -108,6 +108,18 @@ export function McpTable() {
+ +
+ + +
+ + +
+ + +
+ ); @@ -131,8 +143,9 @@ export function McpTable() { - Sever Name - File + Server Name + Type + URL Enabled Actions @@ -150,7 +163,7 @@ export function McpTable() {
- {server.serverName} + {server.name} {!mcpServerInDb[server.name] && ( @@ -164,7 +177,29 @@ export function McpTable() { )}
- {server.filePath} + + {server.config.type} + + + {server.config.type === 'sse' || server.config.type === 'streamable-http' ? ( + + + + {server.config.url} + + + {server.config.url} + + ) : ( + {server.config.filePath} + )} + handleToggleEnabled(server)} /> diff --git a/apps/dbagent/src/components/mcp/mcp-view.tsx b/apps/dbagent/src/components/mcp/mcp-view.tsx index 4f9c7dc7..da922296 100644 --- a/apps/dbagent/src/components/mcp/mcp-view.tsx +++ b/apps/dbagent/src/components/mcp/mcp-view.tsx @@ -19,8 +19,9 @@ import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { actionGetConnections, actionGetCustomToolsFromMCPServer } from '~/components/tools/action'; -import { Connection, MCPServerInsert } from '~/lib/db/schema'; +import { Connection, McpServerConfig, MCPServerInsert } from '~/lib/db/schema'; import { + actionAddUserMcpServerToDB, actionCheckUserMcpServerExists, actionDeleteUserMcpServerFromDBAndFiles, actionUpdateUserMcpServer @@ -42,12 +43,12 @@ export function McpView({ server: initialServer }: { server: MCPServerInsert }) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [isInDb, setIsInDb] = useState(false); const [isCheckingDb, setIsCheckingDb] = useState(true); - const [envVars, setEnvVars] = useState>(initialServer.envVars || {}); + const [config, setConfig] = useState(initialServer.config); const [isSavingEnvVars, setIsSavingEnvVars] = useState(false); useEffect(() => { setServer(initialServer); - setEnvVars(initialServer.envVars || {}); + setConfig(initialServer.config); }, [initialServer]); useEffect(() => { @@ -93,24 +94,32 @@ export function McpView({ server: initialServer }: { server: MCPServerInsert }) }; const handleAddEnvVar = () => { - setEnvVars({ ...envVars, '': '' }); + if (config.type !== 'local') return; + + setConfig({ ...config, env: { ...config.env, '': '' } }); }; const handleEnvVarChange = (index: number, key: string, value: string) => { - const entries = Object.entries(envVars); + if (config.type !== 'local') return; + + const entries = Object.entries(config.env ?? {}); const oldKey = entries[index]?.[0]; const newEntries = entries.filter(([k]) => k !== oldKey); newEntries.splice(index, 0, [key, value]); - setEnvVars(Object.fromEntries(newEntries)); + setConfig({ ...config, env: Object.fromEntries(newEntries) }); }; const handleRemoveEnvVar = (keyToRemove: string) => { - const newEnvVars = { ...envVars }; - delete newEnvVars[keyToRemove]; - setEnvVars(newEnvVars); + if (config.type !== 'local') return; + + const newConfig = { ...config }; + delete newConfig.env?.[keyToRemove]; + setConfig(newConfig); }; const handleSaveEnvVars = async () => { + if (config.type !== 'local') return; + setIsSavingEnvVars(true); try { if (!isInDb) { @@ -119,11 +128,11 @@ export function McpView({ server: initialServer }: { server: MCPServerInsert }) return; } - const varsToSave = Object.fromEntries(Object.entries(envVars).filter(([key]) => key.trim() !== '')); + const varsToSave = Object.fromEntries(Object.entries(config.env ?? {}).filter(([key]) => key.trim() !== '')); const updatedServerData = { ...server, envVars: varsToSave }; await actionUpdateUserMcpServer(updatedServerData); setServer(updatedServerData); - setEnvVars(varsToSave); + setConfig({ ...config, env: varsToSave }); toast.success('Environment variables saved successfully.'); } catch (error) { console.error('Error saving environment variables:', error); @@ -145,15 +154,15 @@ export function McpView({ server: initialServer }: { server: MCPServerInsert }) - MCP Server: {server.serverName} + MCP Server: {server.name}

Version: {server.version}

-

File Path

-

{server.filePath}

+

Type

+

{server.config.type}

Status

@@ -170,14 +179,14 @@ export function McpView({ server: initialServer }: { server: MCPServerInsert })

- {isInDb && ( + {isInDb && config.type === 'local' && (

Environment Variables

These variables will be passed to the MCP server process.

- {Object.entries(envVars).map(([key, value], index) => ( + {Object.entries(config.env ?? {}).map(([key, value], index) => (
)} + + {/* Placeholder for a general Save Server button */} +
+ +
+

Available Tools

{isLoading ? ( diff --git a/apps/dbagent/src/lib/ai/tools/user-mcp.ts b/apps/dbagent/src/lib/ai/tools/user-mcp.ts index 5332b6d8..934c9a0c 100644 --- a/apps/dbagent/src/lib/ai/tools/user-mcp.ts +++ b/apps/dbagent/src/lib/ai/tools/user-mcp.ts @@ -1,8 +1,10 @@ +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { experimental_createMCPClient, type ToolSet } from 'ai'; //, import { Experimental_StdioMCPTransport } from 'ai/mcp-stdio'; import { promises as fs } from 'fs'; import path from 'path'; import { actionGetUserMcpServer } from '~/components/mcp/action'; +import { McpServerConfig } from '~/lib/db/schema'; import { env } from '~/lib/env/server'; export function getMCPSourceDistDir() { @@ -60,6 +62,27 @@ async function getMCPToolForServer(serverFileName: string): Promise { } } +function createTransport(config: McpServerConfig) { + switch (config.type) { + case 'local': + return new Experimental_StdioMCPTransport({ + command: 'node', + args: [config.filePath], + env: config.env + }); + case 'sse': + return { + type: 'sse', + url: config.url, + headers: config.headers + } as const; + case 'streamable-http': + return new StreamableHTTPClientTransport(new URL(config.url), { + sessionId: undefined + }); + } +} + async function loadToolsFromFile(filePath: string): Promise { try { const serverName = path.basename(filePath, '.js'); @@ -69,14 +92,8 @@ async function loadToolsFromFile(filePath: string): Promise { return {}; } - const transport = new Experimental_StdioMCPTransport({ - command: 'node', - args: [filePath], - env: serverDetails?.envVars - }); - const client = await experimental_createMCPClient({ - transport + transport: createTransport(serverDetails.config) }); return await client.tools(); diff --git a/apps/dbagent/src/lib/db/mcp-servers.ts b/apps/dbagent/src/lib/db/mcp-servers.ts index 5ca61373..3bde7f8d 100644 --- a/apps/dbagent/src/lib/db/mcp-servers.ts +++ b/apps/dbagent/src/lib/db/mcp-servers.ts @@ -20,44 +20,50 @@ export async function getUserMcpServer(dbAccess: DBAccess, serverName: string) { }); } -//might need to update this for version and filepath aswell -export async function updateUserMcpServer(dbAccess: DBAccess, input: MCPServerInsert) { +export async function updateUserMcpServer(dbAccess: DBAccess, input: Partial & { name: string }) { return await dbAccess.query(async ({ db }) => { + if (!input.name) { + throw new Error('Server name (input.name) is required for update.'); + } + const result = await db .update(mcpServers) .set({ enabled: input.enabled, - envVars: input.envVars + config: input.config }) .where(eq(mcpServers.name, input.name)) .returning(); if (result.length === 0) { - throw new Error(`[UPDATE]Server with name "${input.name}" not found`); + throw new Error( + `[UPDATE]Server with name "${input.name}" not found, or update failed due to constraints (e.g., serverName already exists).` + ); } return result[0]; }); } -export async function addUserMcpServerToDB(dbAccess: DBAccess, input: MCPServer): Promise { +export async function addUserMcpServerToDB(dbAccess: DBAccess, input: MCPServerInsert): Promise { return await dbAccess.query(async ({ db }) => { // Check if server with same name exists - const existingServer = await db.select().from(mcpServers).where(eq(mcpServers.name, input.serverName)).limit(1); + const existingServer = await db.select().from(mcpServers).where(eq(mcpServers.name, input.name)).limit(1); if (existingServer.length > 0) { - throw new Error(`Server with name "${input.serverName}" already exists`); + throw new Error(`Server with name "${input.name}" already exists`); } // Create new server + // All fields in MCPServerInsert should be passed. + // Drizzle handles undefined for optional fields (inserts NULL or uses default) const result = await db .insert(mcpServers) .values({ name: input.name, - serverName: input.serverName, version: input.version, - filePath: input.filePath, - enabled: input.enabled + enabled: input.enabled, + config: input.config }) .returning(); diff --git a/apps/dbagent/src/lib/db/schema.ts b/apps/dbagent/src/lib/db/schema.ts index dc97f5d0..b6828967 100644 --- a/apps/dbagent/src/lib/db/schema.ts +++ b/apps/dbagent/src/lib/db/schema.ts @@ -18,6 +18,7 @@ import { uuid, varchar } from 'drizzle-orm/pg-core'; +import z from 'zod'; import { RDSClusterDetailedInfo } from '../aws/rds'; import { CloudSQLInstanceInfo } from '../gcp/cloudsql'; @@ -674,21 +675,37 @@ export const playbooks = pgTable( export type Playbook = InferSelectModel; export type PlaybookInsert = InferInsertModel; +export const mcpServerConfigSchema = z.discriminatedUnion('type', [ + z.object({ + type: z.literal('local'), + filePath: z.string(), + env: z.record(z.string()).optional() + }), + z.object({ + type: z.literal('sse'), + url: z.string(), + headers: z.record(z.string()).optional() + }), + z.object({ + type: z.literal('streamable-http'), + url: z.string() + }) +]); + +export type McpServerConfig = z.infer; + export const mcpServers = pgTable( 'mcp_servers', { id: uuid('id').primaryKey().defaultRandom().notNull(), name: text('name').notNull(), - serverName: text('server_name').notNull(), - filePath: text('file_path').notNull(), + config: jsonb('config').$type().notNull(), version: text('version').notNull(), enabled: boolean('enabled').default(true).notNull(), - envVars: jsonb('env_vars').$type>().default({}).notNull(), createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull() }, (table) => [ unique('uq_mcp_servers_name').on(table.name), - unique('uq_mcp_servers_server_name').on(table.serverName), pgPolicy('mcp_servers_policy', { to: authenticatedUser, for: 'all',