mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Add MCP resource handling
Allows to list resources and resource templates and reading their contents.
This commit is contained in:
@@ -31,11 +31,11 @@ class McpConfigurationSeeder < Seeder
|
||||
def seed_data!
|
||||
seed_server_config if server_missing?
|
||||
|
||||
seed_tool_configs
|
||||
seed_resource_and_tool_configs
|
||||
end
|
||||
|
||||
def applicable?
|
||||
server_missing? || tools_missing?
|
||||
server_missing? || tools_missing? || resources_missing?
|
||||
end
|
||||
|
||||
def not_applicable_message
|
||||
@@ -53,8 +53,8 @@ class McpConfigurationSeeder < Seeder
|
||||
)
|
||||
end
|
||||
|
||||
def seed_tool_configs
|
||||
McpTools.all.each do |thing| # rubocop:disable Rails/FindEach
|
||||
def seed_resource_and_tool_configs
|
||||
(McpTools.all + McpResources.all).each do |thing|
|
||||
next if McpConfiguration.find_by(identifier: thing.qualified_name)
|
||||
|
||||
McpConfiguration.create!(
|
||||
@@ -73,4 +73,8 @@ class McpConfigurationSeeder < Seeder
|
||||
def tools_missing?
|
||||
(McpTools.all.map(&:qualified_name) - McpConfiguration.pluck(:identifier)).any?
|
||||
end
|
||||
|
||||
def resources_missing?
|
||||
(McpResources.all.map(&:qualified_name) - McpConfiguration.pluck(:identifier)).any?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class << self
|
||||
def all
|
||||
[
|
||||
Project,
|
||||
Status,
|
||||
StatusList,
|
||||
Type,
|
||||
TypeList,
|
||||
User,
|
||||
Version,
|
||||
WorkPackage
|
||||
]
|
||||
end
|
||||
|
||||
def enabled
|
||||
McpConfiguration.where(enabled: true).pluck(:identifier).filter_map { |name| resources_by_name[name] }
|
||||
end
|
||||
|
||||
def resources_by_name
|
||||
@resources_by_name ||= all.index_by(&:qualified_name)
|
||||
end
|
||||
|
||||
def enabled_resources
|
||||
enabled.select(&:uri)
|
||||
end
|
||||
|
||||
def enabled_resource_templates
|
||||
enabled.select(&:uri_template)
|
||||
end
|
||||
|
||||
def read_resource(uri)
|
||||
resource_class = enabled.find { |r| r.uri == uri || r.uri_template&.match?(uri) }
|
||||
content = resource_class&.read(uri)
|
||||
return [] if content.nil?
|
||||
|
||||
[
|
||||
{
|
||||
uri: uri,
|
||||
mimeType: "application/json",
|
||||
text: content.to_json
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,114 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class Base
|
||||
include APIV3Helper
|
||||
|
||||
class << self
|
||||
def qualified_name
|
||||
"resources/#{name}"
|
||||
end
|
||||
|
||||
def default_title(title = nil)
|
||||
@default_title = title if title.present?
|
||||
|
||||
@default_title
|
||||
end
|
||||
|
||||
def default_description(description = nil)
|
||||
@default_description = description if description.present?
|
||||
|
||||
@default_description
|
||||
end
|
||||
|
||||
def name(name = nil)
|
||||
@name = name if name.present?
|
||||
|
||||
@name
|
||||
end
|
||||
|
||||
def uri(suffix = nil)
|
||||
@uri_suffix = suffix if suffix.present?
|
||||
return nil if @uri_suffix.nil?
|
||||
|
||||
"#{Setting.protocol}://#{Setting.host_name}#{@uri_suffix}"
|
||||
end
|
||||
|
||||
def uri_template(suffix = nil)
|
||||
@template_suffix = suffix if suffix.present?
|
||||
return nil if @template_suffix.nil?
|
||||
|
||||
UriTemplate.new("#{Setting.protocol}://#{Setting.host_name}#{@template_suffix}")
|
||||
end
|
||||
|
||||
def resource
|
||||
raise ArgumentError, "#{self.class.name} can't be used as resource, uri is blank" if uri.blank?
|
||||
|
||||
config = McpConfiguration.find_by(identifier: qualified_name)
|
||||
return nil if config.nil?
|
||||
|
||||
MCP::Resource.new(
|
||||
uri:,
|
||||
name:,
|
||||
title: config.title,
|
||||
description: config.description,
|
||||
mime_type: "application/json"
|
||||
)
|
||||
end
|
||||
|
||||
def resource_template
|
||||
raise ArgumentError, "#{self.class.name} can't be used as resource_template, uri_template is blank" if uri_template.blank?
|
||||
|
||||
config = McpConfiguration.find_by(identifier: qualified_name)
|
||||
return nil if config.nil?
|
||||
|
||||
MCP::ResourceTemplate.new(
|
||||
uri_template:,
|
||||
name:,
|
||||
title: config.title,
|
||||
description: config.description,
|
||||
mime_type: "application/json"
|
||||
)
|
||||
end
|
||||
|
||||
def read(uri)
|
||||
params = uri_template&.parse(uri) || {}
|
||||
new.read(**params)
|
||||
end
|
||||
end
|
||||
|
||||
def current_user = ::User.current
|
||||
|
||||
def read(**)
|
||||
raise NotImplemented, "#{self.class} needs to implement #read method"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class Project < Base
|
||||
name "project"
|
||||
uri_template "/api/v3/projects/{id}"
|
||||
|
||||
default_title "Project"
|
||||
default_description "Access projects of this OpenProject instance."
|
||||
|
||||
def read(id:)
|
||||
project = ::Project.visible(current_user).find_by(id:)
|
||||
return nil if project.nil?
|
||||
|
||||
API::V3::Projects::ProjectRepresenter.create(project, current_user:)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class Status < Base
|
||||
name "status"
|
||||
uri_template "/api/v3/statuses/{id}"
|
||||
|
||||
default_title "Work Package Status"
|
||||
default_description "Access work package statuses of this OpenProject instance."
|
||||
|
||||
def read(id:)
|
||||
status = ::Status.find_by(id:)
|
||||
return nil if status.nil?
|
||||
|
||||
API::V3::Statuses::StatusRepresenter.new(status, current_user:)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class StatusList < Base
|
||||
name "status_list"
|
||||
uri "/api/v3/statuses"
|
||||
|
||||
default_title "Work Package Statuses List"
|
||||
default_description "A list of all work package statuses configured in this OpenProject instance."
|
||||
|
||||
def read
|
||||
API::V3::Statuses::StatusCollectionRepresenter.new(::Status.all, self_link: api_v3_paths.statuses, current_user:)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class Type < Base
|
||||
name "type"
|
||||
uri_template "/api/v3/types/{id}"
|
||||
|
||||
default_title "Work Package Type"
|
||||
default_description "Access work package types of this OpenProject instance."
|
||||
|
||||
def read(id:)
|
||||
type = ::Type.find_by(id:)
|
||||
return nil if type.nil?
|
||||
|
||||
API::V3::Types::TypeRepresenter.new(type, current_user:)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class TypeList < Base
|
||||
name "type_list"
|
||||
uri "/api/v3/types"
|
||||
|
||||
default_title "Work Package Types List"
|
||||
default_description "A list of all work package types configured in this OpenProject instance."
|
||||
|
||||
def read
|
||||
API::V3::Types::TypeCollectionRepresenter.new(::Type.includes(:color).all, self_link: api_v3_paths.types, current_user:)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class User < Base
|
||||
name "user"
|
||||
uri_template "/api/v3/users/{id}"
|
||||
|
||||
default_title "User"
|
||||
default_description "Access users of this OpenProject instance."
|
||||
|
||||
def read(id:)
|
||||
user = ::User.visible(current_user).find_by_unique(id) # rubocop:disable Rails/DynamicFindBy
|
||||
return nil if user.nil?
|
||||
|
||||
API::V3::Users::UserRepresenter.create(user, current_user:)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class Version < Base
|
||||
name "version"
|
||||
uri_template "/api/v3/versions/{id}"
|
||||
|
||||
default_title "Work Package Version"
|
||||
default_description "Access work package versions of this OpenProject instance."
|
||||
|
||||
def read(id:)
|
||||
version = ::Version.visible.find_by(id:)
|
||||
return nil if version.nil?
|
||||
|
||||
API::V3::Versions::VersionRepresenter.create(version, current_user:)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module McpResources
|
||||
class WorkPackage < Base
|
||||
name "work_package"
|
||||
uri_template "/api/v3/work_packages/{id}"
|
||||
|
||||
default_title "Work Package"
|
||||
default_description "Access work packages of this OpenProject instance."
|
||||
|
||||
def read(id:)
|
||||
work_package = ::WorkPackage.find_by(id:)
|
||||
return nil if work_package.nil?
|
||||
return nil unless current_user.allowed_in_work_package?(:view_work_packages, work_package)
|
||||
|
||||
API::V3::WorkPackages::WorkPackageRepresenter.create(work_package, current_user:, embed_links: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -41,7 +41,7 @@ module McpTools
|
||||
end
|
||||
|
||||
def tools_by_name
|
||||
all.index_by(&:qualified_name)
|
||||
@tools_by_name ||= all.index_by(&:qualified_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
# A simple implementation of the essential parts of https://datatracker.ietf.org/doc/html/rfc6570
|
||||
# So far we only need it to parse simple URI templates we defined ourselves and match them, so this is what's implemented.
|
||||
# If we start needing more, we either need to add more or start double checking existing solutions.
|
||||
class UriTemplate
|
||||
def initialize(template_string)
|
||||
raise ArgumentError, "template_string can't be nil" if template_string.nil?
|
||||
|
||||
@template_string = template_string
|
||||
@variables = template_string.scan(/\{(\w+)\}/).flatten
|
||||
matcher_string = "^#{Regexp.escape(template_string)}$"
|
||||
@variables.each { |v| matcher_string.gsub!(/\\\{#{v}\\\}/, "(?<#{v}>[\\w-]+)") }
|
||||
@matcher = Regexp.new(matcher_string)
|
||||
end
|
||||
|
||||
delegate :match?, to: :@matcher
|
||||
delegate :as_json, to: :to_s
|
||||
|
||||
def parse(uri)
|
||||
match = @matcher.match(uri)
|
||||
return nil if match.nil?
|
||||
|
||||
@variables.to_h { |v| [v.to_sym, match[v]] }
|
||||
end
|
||||
|
||||
def to_s
|
||||
@template_string
|
||||
end
|
||||
end
|
||||
@@ -21,7 +21,9 @@ properties:
|
||||
description: Type name
|
||||
readOnly: true
|
||||
color:
|
||||
type: string
|
||||
type:
|
||||
- "string"
|
||||
- "null"
|
||||
description: The color used to represent this type
|
||||
readOnly: true
|
||||
position:
|
||||
|
||||
@@ -57,9 +57,13 @@ module API
|
||||
# description: server_config.description, # not yet supported by mcp gem
|
||||
version: "1.0.0",
|
||||
tools: McpTools.enabled.map(&:tool),
|
||||
resources: McpResources.enabled_resources.map(&:resource),
|
||||
resource_templates: McpResources.enabled_resource_templates.map(&:resource_template),
|
||||
server_context: { user_id: User.current.id }
|
||||
)
|
||||
|
||||
server.resources_read_handler { |params| McpResources.read_resource(params[:uri]) }
|
||||
|
||||
status 200
|
||||
|
||||
# HACK: Grape is JSON-serializing whatever we return here, but handle_json already returns serialized JSON
|
||||
|
||||
@@ -79,7 +79,52 @@ RSpec.shared_examples_for "MCP response with structured content" do
|
||||
|
||||
it "fulfills the schema of a structured MCP response" do
|
||||
subject
|
||||
expect(last_response.body).to match_json_schema(json_rpc_response_schema)
|
||||
expect(last_response.body).to match_json_schema(result_schema)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples_for "MCP text resource response" do
|
||||
let(:result_schema) do
|
||||
{
|
||||
required: %w[result],
|
||||
properties: {
|
||||
result: {
|
||||
type: "object",
|
||||
required: %w[contents],
|
||||
properties: {
|
||||
contents: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
required: %w[uri text],
|
||||
properties: {
|
||||
uri: { type: "string" },
|
||||
mimeType: { type: "string" },
|
||||
text: { type: "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
include_context "MCP result response"
|
||||
|
||||
it "fulfills the schema of a text resource" do
|
||||
subject
|
||||
expect(last_response.body).to match_json_schema(result_schema)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples_for "MCP empty resource response" do
|
||||
include_context "MCP text resource response"
|
||||
|
||||
it "has no contents" do
|
||||
subject
|
||||
parsed = JSON.parse(last_response.body)
|
||||
expect(parsed.dig("result", "contents")).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe McpResources::Project, with_flag: { mcp_server: true } do # rubocop:disable RSpec/SpecFilePathFormat
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp", resource_owner: user) }
|
||||
let(:user) { create(:admin) } # using an admin, to ensure visibility of everything
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/read",
|
||||
params: { uri: resource_uri }
|
||||
}
|
||||
end
|
||||
let(:resource_uri) { "http://test.host/api/v3/projects/#{project.id}" }
|
||||
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let(:project) { create(:project) }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP text resource response"
|
||||
|
||||
it "responds with a properly formatted project" do
|
||||
subject
|
||||
text_content = parsed_results.fetch("contents").first
|
||||
wp = text_content.fetch("text")
|
||||
expect(wp).to match_json_schema.from_docs("project_model")
|
||||
end
|
||||
|
||||
context "when the resource is disabled via configuration" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a non-existing project" do
|
||||
let(:resource_uri) { "http://test.host/api/v3/projects/#{project.id + 1}" }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a project not visible to the user" do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe McpResources::Status, with_flag: { mcp_server: true } do # rubocop:disable RSpec/SpecFilePathFormat
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp", resource_owner: user) }
|
||||
let(:user) { create(:admin) } # using an admin, to ensure visibility of everything
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/read",
|
||||
params: { uri: resource_uri }
|
||||
}
|
||||
end
|
||||
let(:resource_uri) { "http://test.host/api/v3/statuses/#{status.id}" }
|
||||
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let(:status) { create(:status) }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP text resource response"
|
||||
|
||||
it "responds with a properly formatted status" do
|
||||
subject
|
||||
text_content = parsed_results.fetch("contents").first
|
||||
status = text_content.fetch("text")
|
||||
expect(status).to match_json_schema.from_docs("status_model")
|
||||
end
|
||||
|
||||
context "when the resource is disabled via configuration" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a non-existing status" do
|
||||
let(:resource_uri) { "http://test.host/api/v3/statuses/#{status.id + 1}" }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe McpResources::Type, with_flag: { mcp_server: true } do # rubocop:disable RSpec/SpecFilePathFormat
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp", resource_owner: user) }
|
||||
let(:user) { create(:admin) } # using an admin, to ensure visibility of everything
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/read",
|
||||
params: { uri: resource_uri }
|
||||
}
|
||||
end
|
||||
let(:resource_uri) { "http://test.host/api/v3/types/#{type.id}" }
|
||||
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let(:type) { create(:type) }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP text resource response"
|
||||
|
||||
it "responds with a properly formatted type" do
|
||||
subject
|
||||
text_content = parsed_results.fetch("contents").first
|
||||
type = text_content.fetch("text")
|
||||
expect(type).to match_json_schema.from_docs("type_model")
|
||||
end
|
||||
|
||||
context "when the resource is disabled via configuration" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a non-existing type" do
|
||||
let(:resource_uri) { "http://test.host/api/v3/types/#{type.id + 1}" }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe McpResources::User, with_flag: { mcp_server: true } do # rubocop:disable RSpec/SpecFilePathFormat
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp", resource_owner: user) }
|
||||
let(:user) { create(:admin) } # using an admin, to ensure visibility of everything
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/read",
|
||||
params: { uri: resource_uri }
|
||||
}
|
||||
end
|
||||
let(:resource_uri) { "http://test.host/api/v3/users/#{requested_user.id}" }
|
||||
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let(:requested_user) { create(:user) }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP text resource response"
|
||||
|
||||
it "responds with a properly formatted user" do
|
||||
subject
|
||||
text_content = parsed_results.fetch("contents").first
|
||||
user_json = text_content.fetch("text")
|
||||
expect(user_json).to match_json_schema.from_docs("user_model")
|
||||
end
|
||||
|
||||
context "when the resource is disabled via configuration" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a non-existing user" do
|
||||
let(:resource_uri) { "http://test.host/api/v3/users/#{requested_user.id + 10}" }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a user not visible to the user" do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe McpResources::Version, with_flag: { mcp_server: true } do # rubocop:disable RSpec/SpecFilePathFormat
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp", resource_owner: user) }
|
||||
let(:user) { create(:admin) } # using an admin, to ensure visibility of everything
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/read",
|
||||
params: { uri: resource_uri }
|
||||
}
|
||||
end
|
||||
let(:resource_uri) { "http://test.host/api/v3/versions/#{version.id}" }
|
||||
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let(:version) { create(:version) }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP text resource response"
|
||||
|
||||
it "responds with a properly formatted version" do
|
||||
subject
|
||||
text_content = parsed_results.fetch("contents").first
|
||||
version = text_content.fetch("text")
|
||||
expect(version).to match_json_schema.from_docs("version_model")
|
||||
end
|
||||
|
||||
context "when the resource is disabled via configuration" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a non-existing version" do
|
||||
let(:resource_uri) { "http://test.host/api/v3/versions/#{version.id + 1}" }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a version not visible to the user" do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe McpResources::WorkPackage, with_flag: { mcp_server: true } do # rubocop:disable RSpec/SpecFilePathFormat
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp", resource_owner: user) }
|
||||
let(:user) { create(:admin) } # using an admin, to ensure visibility of everything
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/read",
|
||||
params: { uri: resource_uri }
|
||||
}
|
||||
end
|
||||
let(:resource_uri) { "http://test.host/api/v3/work_packages/#{work_package.id}" }
|
||||
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let(:work_package) { create(:work_package) }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP text resource response"
|
||||
|
||||
it "responds with a properly formatted work package" do
|
||||
subject
|
||||
text_content = parsed_results.fetch("contents").first
|
||||
wp = text_content.fetch("text")
|
||||
expect(wp).to match_json_schema.from_docs("work_package_model")
|
||||
end
|
||||
|
||||
context "when the resource is disabled via configuration" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a non-existing work package" do
|
||||
let(:resource_uri) { "http://test.host/api/v3/work_packages/#{work_package.id + 1}" }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
|
||||
context "when requesting a work package not visible to the user" do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,135 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "MCP resources/templates/list", with_flag: { mcp_server: true } do
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp") }
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/templates/list",
|
||||
params: {}
|
||||
}
|
||||
end
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: McpResources::StatusList.qualified_name) }
|
||||
let(:resource_template_config) { create(:mcp_configuration, identifier: McpResources::Status.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
resource_template_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP result response"
|
||||
|
||||
it "includes the status resource template" do
|
||||
subject
|
||||
|
||||
resource = parsed_results.fetch("resourceTemplates").find { |t| t.fetch("name") == "status" }
|
||||
expect(resource).not_to be_nil
|
||||
expect(resource.fetch("title")).to eq(resource_config.title)
|
||||
expect(resource.fetch("description")).to eq(resource_config.description)
|
||||
end
|
||||
|
||||
it "returns a fully qualified uriTemplate" do
|
||||
subject
|
||||
|
||||
resource = parsed_results.fetch("resourceTemplates").find { |t| t.fetch("name") == "status" }
|
||||
expect(resource.fetch("uriTemplate")).to eq("http://test.host/api/v3/statuses/{id}")
|
||||
end
|
||||
|
||||
it "does not include resources" do
|
||||
subject
|
||||
|
||||
resource = parsed_results.fetch("resourceTemplates").find { |t| t.fetch("name") == "status_list" }
|
||||
expect(resource).to be_nil
|
||||
end
|
||||
|
||||
context "when not passing a Bearer token" do
|
||||
subject do
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
it_behaves_like "MCP unauthenticated response"
|
||||
end
|
||||
|
||||
context "when passing a Bearer token with a wrong scope" do
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "api_v3") }
|
||||
|
||||
it_behaves_like "MCP unauthenticated response"
|
||||
end
|
||||
|
||||
context "when the MCP server is disabled via configuration" do
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server", enabled: false) }
|
||||
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the status resource template is disabled" do
|
||||
let(:resource_template_config) do
|
||||
create(:mcp_configuration, identifier: McpResources::Status.qualified_name, enabled: false)
|
||||
end
|
||||
|
||||
it_behaves_like "MCP result response"
|
||||
|
||||
it "does not include the status resource template" do
|
||||
subject
|
||||
|
||||
resource = parsed_results.fetch("resourceTemplates").find { |t| t.fetch("name") == "status" }
|
||||
expect(resource).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe McpResources::StatusList, with_flag: { mcp_server: true } do # rubocop:disable RSpec/SpecFilePathFormat
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp", resource_owner: user) }
|
||||
let(:user) { create(:admin) } # using an admin, to ensure visibility of everything
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/read",
|
||||
params: {
|
||||
uri: "http://test.host/api/v3/statuses"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let!(:status) { create(:status) }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP text resource response"
|
||||
|
||||
it "responds with a properly formatted status list" do
|
||||
subject
|
||||
text_content = parsed_results.fetch("contents").first
|
||||
statuses = text_content.fetch("text")
|
||||
expect(statuses).to match_json_schema.from_docs("status_collection_model")
|
||||
end
|
||||
|
||||
context "when the resource is disabled via configuration" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe McpResources::TypeList, with_flag: { mcp_server: true } do # rubocop:disable RSpec/SpecFilePathFormat
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp", resource_owner: user) }
|
||||
let(:user) { create(:admin) } # using an admin, to ensure visibility of everything
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/read",
|
||||
params: {
|
||||
uri: "http://test.host/api/v3/types"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let!(:type) { create(:type) }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP text resource response"
|
||||
|
||||
it "responds with a properly formatted type list" do
|
||||
subject
|
||||
text_content = parsed_results.fetch("contents").first
|
||||
types = text_content.fetch("text")
|
||||
expect(types).to match_json_schema.from_docs("types_model")
|
||||
end
|
||||
|
||||
context "when the resource is disabled via configuration" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: described_class.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP empty resource response"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,133 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "MCP resources/list", with_flag: { mcp_server: true } do
|
||||
subject do
|
||||
header "Authorization", "Bearer #{access_token.plaintext_token}"
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "mcp") }
|
||||
let(:request_body) do
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: "Test-Request",
|
||||
method: "resources/list",
|
||||
params: {}
|
||||
}
|
||||
end
|
||||
let(:parsed_results) { JSON.parse(last_response.body).fetch("result") }
|
||||
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server") }
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: McpResources::StatusList.qualified_name) }
|
||||
let(:resource_template_config) { create(:mcp_configuration, identifier: McpResources::Status.qualified_name) }
|
||||
|
||||
before do
|
||||
server_config.save!
|
||||
resource_config.save!
|
||||
resource_template_config.save!
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is enabled", with_ee: %i[mcp_server] do
|
||||
it_behaves_like "MCP result response"
|
||||
|
||||
it "includes the status_list resource" do
|
||||
subject
|
||||
|
||||
resource = parsed_results.fetch("resources").find { |t| t.fetch("name") == "status_list" }
|
||||
expect(resource).not_to be_nil
|
||||
expect(resource.fetch("title")).to eq(resource_config.title)
|
||||
expect(resource.fetch("description")).to eq(resource_config.description)
|
||||
end
|
||||
|
||||
it "returns a fully qualified uri" do
|
||||
subject
|
||||
|
||||
resource = parsed_results.fetch("resources").find { |t| t.fetch("name") == "status_list" }
|
||||
expect(resource.fetch("uri")).to eq("http://test.host/api/v3/statuses")
|
||||
end
|
||||
|
||||
it "does not include resource templates" do
|
||||
subject
|
||||
|
||||
resource = parsed_results.fetch("resources").find { |t| t.fetch("name") == "status" }
|
||||
expect(resource).to be_nil
|
||||
end
|
||||
|
||||
context "when not passing a Bearer token" do
|
||||
subject do
|
||||
header "X-Authentication-Scheme", "Bearer"
|
||||
header "Content-Type", "application/json"
|
||||
post "/mcp", request_body.to_json
|
||||
end
|
||||
|
||||
it_behaves_like "MCP unauthenticated response"
|
||||
end
|
||||
|
||||
context "when passing a Bearer token with a wrong scope" do
|
||||
let(:access_token) { create(:oauth_access_token, scopes: "api_v3") }
|
||||
|
||||
it_behaves_like "MCP unauthenticated response"
|
||||
end
|
||||
|
||||
context "when the MCP server is disabled via configuration" do
|
||||
let(:server_config) { create(:mcp_configuration, identifier: "mcp_server", enabled: false) }
|
||||
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the status_list resource is disabled" do
|
||||
let(:resource_config) { create(:mcp_configuration, identifier: McpResources::StatusList.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP result response"
|
||||
|
||||
it "does not include the status_list resource" do
|
||||
subject
|
||||
|
||||
resource = parsed_results.fetch("resources").find { |t| t.fetch("name") == "status_list" }
|
||||
expect(resource).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
it "responds in a 404" do
|
||||
subject
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -93,6 +93,19 @@ RSpec.describe "MCP tools/list", with_flag: { mcp_server: true } do
|
||||
expect(last_response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the search_project tool is disabled" do
|
||||
let(:tool_config) { create(:mcp_configuration, identifier: McpTools::SearchProject.qualified_name, enabled: false) }
|
||||
|
||||
it_behaves_like "MCP result response"
|
||||
|
||||
it "does not include the search_project tool" do
|
||||
subject
|
||||
|
||||
tool = parsed_results.fetch("tools").find { |t| t.fetch("name") == "search_project" }
|
||||
expect(tool).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the mcp_server enterprise feature is disabled" do
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe UriTemplate do
|
||||
let(:uri_template) { described_class.new(template_string) }
|
||||
let(:template_string) { "https://openproject.local/statuses/{id}" }
|
||||
let(:uri) { "https://openproject.local/statuses/123" }
|
||||
|
||||
describe "initializer" do
|
||||
subject { uri_template }
|
||||
|
||||
it "raises no errors" do
|
||||
subject
|
||||
end
|
||||
|
||||
context "when passing a nil string" do
|
||||
let(:template_string) { nil }
|
||||
|
||||
it "raises an ArgumentError" do
|
||||
expect { subject }.to raise_error(ArgumentError, "template_string can't be nil")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#match?" do
|
||||
subject { uri_template.match?(uri) }
|
||||
|
||||
context "when the URL matches" do
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context "when the URL is different" do
|
||||
let(:uri) { "https://openproject.local/types/123" }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context "when the URL would leave a variable empty" do
|
||||
let(:uri) { "https://openproject.local/statuses/" }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context "when the template placeholder contains unsupported characters" do
|
||||
let(:template_string) { "https://openproject.local/statuses/{the id}" }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#parse" do
|
||||
subject { uri_template.parse(uri) }
|
||||
|
||||
context "when the URL matches" do
|
||||
it { is_expected.to eq({ id: "123" }) }
|
||||
end
|
||||
|
||||
context "when the URL is different" do
|
||||
let(:uri) { "https://openproject.local/types/123" }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context "when expanded variable contains dashes" do
|
||||
let(:uri) { "https://openproject.local/statuses/red-alert" }
|
||||
|
||||
it { is_expected.to eq({ id: "red-alert" }) }
|
||||
end
|
||||
|
||||
context "when templating multiple variables" do
|
||||
let(:template_string) { "https://openproject.local/statuses/{id}/something/{thing}" }
|
||||
let(:uri) { "https://openproject.local/statuses/123/something/unicorn" }
|
||||
|
||||
it { is_expected.to eq({ id: "123", thing: "unicorn" }) }
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user