Skip to content

Commit 5919e27

Browse files
Add MCP resource handling
Allows to list resources and resource templates and reading their contents.
1 parent df0711a commit 5919e27

26 files changed

+1673
-7
lines changed

app/seeders/mcp_configuration_seeder.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ class McpConfigurationSeeder < Seeder
3131
def seed_data!
3232
seed_server_config if server_missing?
3333

34-
seed_tool_configs
34+
seed_resource_and_tool_configs
3535
end
3636

3737
def applicable?
38-
server_missing? || tools_missing?
38+
server_missing? || tools_missing? || resources_missing?
3939
end
4040

4141
def not_applicable_message
@@ -53,8 +53,8 @@ def seed_server_config
5353
)
5454
end
5555

56-
def seed_tool_configs
57-
McpTools.all.each do |thing| # rubocop:disable Rails/FindEach
56+
def seed_resource_and_tool_configs
57+
(McpTools.all + McpResources.all).each do |thing|
5858
next if McpConfiguration.find_by(identifier: thing.qualified_name)
5959

6060
McpConfiguration.create!(
@@ -73,4 +73,8 @@ def server_missing?
7373
def tools_missing?
7474
(McpTools.all.map(&:qualified_name) - McpConfiguration.pluck(:identifier)).any?
7575
end
76+
77+
def resources_missing?
78+
(McpResources.all.map(&:qualified_name) - McpConfiguration.pluck(:identifier)).any?
79+
end
7680
end

app/services/mcp_resources.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
module McpResources
32+
class << self
33+
def all
34+
[
35+
Project,
36+
Status,
37+
StatusList,
38+
Type,
39+
TypeList,
40+
User,
41+
WorkPackage
42+
]
43+
end
44+
45+
def enabled
46+
McpConfiguration.where(enabled: true).pluck(:identifier).filter_map { |name| resources_by_name[name] }
47+
end
48+
49+
def resources_by_name
50+
@resources_by_name ||= all.index_by(&:qualified_name)
51+
end
52+
53+
def enabled_resources
54+
enabled.select(&:uri)
55+
end
56+
57+
def enabled_resource_templates
58+
enabled.select(&:uri_template)
59+
end
60+
61+
def read_resource(uri)
62+
resource = enabled.find { |r| r.uri == uri || r.uri_template&.match?(uri) }
63+
content = resource&.read(uri)
64+
return [] if content.nil?
65+
66+
[
67+
{
68+
uri: uri,
69+
mimeType: "application/json",
70+
text: content.to_json
71+
}
72+
]
73+
end
74+
end
75+
end

app/services/mcp_resources/base.rb

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
module McpResources
32+
class Base
33+
include APIV3Helper
34+
35+
class << self
36+
def qualified_name
37+
"resources/#{name}"
38+
end
39+
40+
def default_title(title = nil)
41+
@default_title = title if title.present?
42+
43+
@default_title
44+
end
45+
46+
def default_description(description = nil)
47+
@default_description = description if description.present?
48+
49+
@default_description
50+
end
51+
52+
def name(name = nil)
53+
@name = name if name.present?
54+
55+
@name
56+
end
57+
58+
def uri(suffix = nil)
59+
@uri_suffix = suffix if suffix.present?
60+
return nil if @uri_suffix.nil?
61+
62+
"#{Setting.protocol}://#{Setting.host_name}#{@uri_suffix}"
63+
end
64+
65+
def uri_template(suffix = nil)
66+
@template_suffix = suffix if suffix.present?
67+
return nil if @template_suffix.nil?
68+
69+
UriTemplate.new("#{Setting.protocol}://#{Setting.host_name}#{@template_suffix}")
70+
end
71+
72+
def resource
73+
raise ArgumentError, "#{self.class.name} can't be used as resource, uri is blank" if uri.blank?
74+
75+
config = McpConfiguration.find_by(identifier: qualified_name)
76+
return nil if config.nil?
77+
78+
MCP::Resource.new(
79+
uri:,
80+
name:,
81+
title: config.title,
82+
description: config.description,
83+
mime_type: "application/json"
84+
)
85+
end
86+
87+
def resource_template
88+
raise ArgumentError, "#{self.class.name} can't be used as resource_template, uri_template is blank" if uri_template.blank?
89+
90+
config = McpConfiguration.find_by(identifier: qualified_name)
91+
return nil if config.nil?
92+
93+
MCP::ResourceTemplate.new(
94+
uri_template:,
95+
name:,
96+
title: config.title,
97+
description: config.description,
98+
mime_type: "application/json"
99+
)
100+
end
101+
102+
def read(uri)
103+
params = uri_template&.parse(uri) || {}
104+
new.read(**params)
105+
end
106+
end
107+
108+
def current_user = ::User.current
109+
110+
def read(**)
111+
raise NotImplemented, "#{self.class} needs to implement #read method"
112+
end
113+
end
114+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
module McpResources
32+
class Project < Base
33+
name "project"
34+
uri_template "/api/v3/projects/{id}"
35+
36+
default_title "Project"
37+
default_description "Access projects of this OpenProject instance."
38+
39+
def read(id:)
40+
project = ::Project.visible(current_user).find_by(id:)
41+
return nil if project.nil?
42+
43+
API::V3::Projects::ProjectRepresenter.create(project, current_user:)
44+
end
45+
end
46+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
module McpResources
32+
class Status < Base
33+
name "status"
34+
uri_template "/api/v3/statuses/{id}"
35+
36+
default_title "Work Package Status"
37+
default_description "Access work package statuses of this OpenProject instance."
38+
39+
def read(id:)
40+
status = ::Status.find_by(id:)
41+
return nil if status.nil?
42+
43+
API::V3::Statuses::StatusRepresenter.new(status, current_user:)
44+
end
45+
end
46+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
module McpResources
32+
class StatusList < Base
33+
name "status_list"
34+
uri "/api/v3/statuses"
35+
36+
default_title "Work Package Statuses List"
37+
default_description "A list of all work package statuses configured in this OpenProject instance."
38+
39+
def read
40+
API::V3::Statuses::StatusCollectionRepresenter.new(::Status.all, self_link: api_v3_paths.statuses, current_user:)
41+
end
42+
end
43+
end

0 commit comments

Comments
 (0)