-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Add MCP resource handling #21661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Add MCP resource handling #21661
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 = enabled.find { |r| r.uri == uri || r.uri_template&.match?(uri) } | ||
| content = resource&.read(uri) | ||
| return [] if content.nil? | ||
|
|
||
| [ | ||
| { | ||
| uri: uri, | ||
| mimeType: "application/json", | ||
| text: content.to_json | ||
| } | ||
| ] | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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}") | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am open for ideas to improve efficiency here. I find it slightly sad that every call to On the other hand, we can't properly cache this in a simple instance variable: The setter version of this method is only called once, when the class gets first defined. However, the actual template should change immediately if the For now I am happy to live with the fact that this code is not 100% efficient, but it still makes me a tad sad. |
||
| 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟢 I like how the specific resource in the end looks like. very small, easy to read. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 use somehow a variable name, that indicates, that this is actually not a resource, but a resource class.