bioruby-mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +31 -0
- data/LICENSE +24 -0
- data/README.md +293 -0
- data/Rakefile +12 -0
- data/bioruby-mcp-server.gemspec +42 -0
- data/exe/bioruby-mcp-server +7 -0
- data/lib/bioruby/mcp/server/core.rb +36 -0
- data/lib/bioruby/mcp/server/kegg_tools.rb +417 -0
- data/lib/bioruby/mcp/server/version.rb +9 -0
- data/lib/bioruby.rb +13 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9af8854132608c26884e31bb79bfa446eec6847f198757afddca808daf879aa8
|
4
|
+
data.tar.gz: 0a7dc98c744e4007cb6b9e785fa592932005480beee802d03fd9cbc0f01d4432
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 81e2e6489387e4cbb564b7881cbc7fddad3742831acc1ba609b54d41205c2d4978062aa02c8eafad90e2ffcc6b8d67eb289ae2ef0979ed3c56d3e70ab6781380
|
7
|
+
data.tar.gz: 6ed869a261b186ec7743d2d1edbda88daf452ccd2a8061af66c040680ae7e9261549811389875bbee05aac771f5e2ddd0ee6a41fbd1ece00d4a1b3c3cd52e200
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.1.0] - 2025-09-19
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Initial release of bioruby-mcp-server
|
12
|
+
- Model Context Protocol (MCP) server implementation using official MCP Ruby SDK
|
13
|
+
- Integration with BioRuby KEGG module
|
14
|
+
- KEGG pathway information retrieval (`kegg_pathway_info`)
|
15
|
+
- KEGG compound information retrieval (`kegg_compound_info`)
|
16
|
+
- KEGG enzyme information retrieval (`kegg_enzyme_info`)
|
17
|
+
- KEGG compound search functionality (`kegg_search_compounds`)
|
18
|
+
- Pathway discovery by compound (`kegg_find_pathways_by_compound`)
|
19
|
+
- KEGG organism listing (`kegg_list_organisms`)
|
20
|
+
- Comprehensive test suite
|
21
|
+
- Command-line executable (`bioruby-mcp-server`)
|
22
|
+
- Full documentation and examples
|
23
|
+
|
24
|
+
### Technical Details
|
25
|
+
- Built on Ruby 3.0+ with BioRuby and official MCP Ruby SDK
|
26
|
+
- Uses KEGG REST API for data access
|
27
|
+
- Follows MCP protocol specification via official SDK
|
28
|
+
- Each KEGG operation implemented as MCP::Tool subclass
|
29
|
+
- Includes proper error handling and logging
|
30
|
+
- Supports filtering and search capabilities
|
31
|
+
- Full MCP protocol compliance through SDK
|
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
BSD 2-Clause License
|
2
|
+
|
3
|
+
Copyright (c) 2025, Kozo Nishida
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
13
|
+
and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,293 @@
|
|
1
|
+
# bioruby-mcp-server
|
2
|
+
|
3
|
+
A Model Context Protocol (MCP) server that provides access to BioRuby KEGG functionality. This server allows AI assistants to query KEGG databases for biological pathways, compounds, enzymes, and other molecular information through the standardized MCP protocol.
|
4
|
+
|
5
|
+
Built using the official [MCP Ruby SDK](https://github.com/modelcontextprotocol/ruby-sdk).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'bioruby-mcp-server'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
$ bundle install
|
19
|
+
```
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
```bash
|
24
|
+
$ gem install bioruby-mcp-server
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
### Starting the Server
|
30
|
+
|
31
|
+
To start the MCP server:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
$ bioruby-mcp-server
|
35
|
+
```
|
36
|
+
|
37
|
+
The server will start and listen for MCP protocol messages on stdin/stdout.
|
38
|
+
|
39
|
+
### Available Tools
|
40
|
+
|
41
|
+
The server provides the following tools for interacting with KEGG databases:
|
42
|
+
|
43
|
+
#### 1. `kegg_pathway_info`
|
44
|
+
Get detailed information about a KEGG pathway.
|
45
|
+
|
46
|
+
**Parameters:**
|
47
|
+
- `pathway_id` (string): KEGG pathway ID (e.g., 'map00010', 'hsa00010')
|
48
|
+
|
49
|
+
**Example:**
|
50
|
+
```json
|
51
|
+
{
|
52
|
+
"name": "kegg_pathway_info",
|
53
|
+
"arguments": {
|
54
|
+
"pathway_id": "map00010"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
#### 2. `kegg_compound_info`
|
60
|
+
Get detailed information about a KEGG compound.
|
61
|
+
|
62
|
+
**Parameters:**
|
63
|
+
- `compound_id` (string): KEGG compound ID (e.g., 'C00002', 'cpd:C00002')
|
64
|
+
|
65
|
+
**Example:**
|
66
|
+
```json
|
67
|
+
{
|
68
|
+
"name": "kegg_compound_info",
|
69
|
+
"arguments": {
|
70
|
+
"compound_id": "C00002"
|
71
|
+
}
|
72
|
+
}
|
73
|
+
```
|
74
|
+
|
75
|
+
#### 3. `kegg_enzyme_info`
|
76
|
+
Get detailed information about a KEGG enzyme.
|
77
|
+
|
78
|
+
**Parameters:**
|
79
|
+
- `enzyme_id` (string): KEGG enzyme ID (e.g., 'ec:1.1.1.1')
|
80
|
+
|
81
|
+
**Example:**
|
82
|
+
```json
|
83
|
+
{
|
84
|
+
"name": "kegg_enzyme_info",
|
85
|
+
"arguments": {
|
86
|
+
"enzyme_id": "ec:1.1.1.1"
|
87
|
+
}
|
88
|
+
}
|
89
|
+
```
|
90
|
+
|
91
|
+
#### 4. `kegg_search_compounds`
|
92
|
+
Search for KEGG compounds by name or formula.
|
93
|
+
|
94
|
+
**Parameters:**
|
95
|
+
- `query` (string): Search query (compound name, formula, etc.)
|
96
|
+
- `database` (string, optional): Database to search (default: 'compound')
|
97
|
+
|
98
|
+
**Example:**
|
99
|
+
```json
|
100
|
+
{
|
101
|
+
"name": "kegg_search_compounds",
|
102
|
+
"arguments": {
|
103
|
+
"query": "glucose",
|
104
|
+
"database": "compound"
|
105
|
+
}
|
106
|
+
}
|
107
|
+
```
|
108
|
+
|
109
|
+
#### 5. `kegg_find_pathways_by_compound`
|
110
|
+
Find pathways containing a specific compound.
|
111
|
+
|
112
|
+
**Parameters:**
|
113
|
+
- `compound_id` (string): KEGG compound ID (e.g., 'C00002')
|
114
|
+
|
115
|
+
**Example:**
|
116
|
+
```json
|
117
|
+
{
|
118
|
+
"name": "kegg_find_pathways_by_compound",
|
119
|
+
"arguments": {
|
120
|
+
"compound_id": "C00002"
|
121
|
+
}
|
122
|
+
}
|
123
|
+
```
|
124
|
+
|
125
|
+
#### 6. `kegg_list_organisms`
|
126
|
+
List available organisms in KEGG.
|
127
|
+
|
128
|
+
**Parameters:**
|
129
|
+
- `filter` (string, optional): Optional filter for organism names
|
130
|
+
|
131
|
+
**Example:**
|
132
|
+
```json
|
133
|
+
{
|
134
|
+
"name": "kegg_list_organisms",
|
135
|
+
"arguments": {
|
136
|
+
"filter": "human"
|
137
|
+
}
|
138
|
+
}
|
139
|
+
```
|
140
|
+
|
141
|
+
### Integration with AI Assistants
|
142
|
+
|
143
|
+
This MCP server can be used with any AI assistant that supports the Model Context Protocol. Configure your AI assistant to connect to this server to enable KEGG database queries.
|
144
|
+
|
145
|
+
#### Claude Desktop Integration
|
146
|
+
|
147
|
+
To connect this server to Claude Desktop, you'll need to configure it in Claude's MCP settings:
|
148
|
+
|
149
|
+
##### 1. Install the Server
|
150
|
+
|
151
|
+
First, install the bioruby-mcp-server gem:
|
152
|
+
|
153
|
+
```bash
|
154
|
+
gem install bioruby-mcp-server
|
155
|
+
```
|
156
|
+
|
157
|
+
##### 2. Configure Claude Desktop
|
158
|
+
|
159
|
+
Add the server configuration to Claude Desktop's configuration file:
|
160
|
+
|
161
|
+
**On macOS:**
|
162
|
+
Edit or create `~/Library/Application Support/Claude/claude_desktop_config.json`
|
163
|
+
|
164
|
+
**On Windows:**
|
165
|
+
Edit or create `%APPDATA%\Claude\claude_desktop_config.json`
|
166
|
+
|
167
|
+
**On Linux:**
|
168
|
+
Edit or create `~/.config/Claude/claude_desktop_config.json`
|
169
|
+
|
170
|
+
Add the following configuration:
|
171
|
+
|
172
|
+
```json
|
173
|
+
{
|
174
|
+
"mcpServers": {
|
175
|
+
"bioruby-kegg": {
|
176
|
+
"command": "bioruby-mcp-server",
|
177
|
+
"args": []
|
178
|
+
}
|
179
|
+
}
|
180
|
+
}
|
181
|
+
```
|
182
|
+
|
183
|
+
If you are Windows WSL user:
|
184
|
+
|
185
|
+
```json
|
186
|
+
{
|
187
|
+
"mcpServers": {
|
188
|
+
"bioruby-kegg": {
|
189
|
+
"command": "wsl.exe",
|
190
|
+
"args": [
|
191
|
+
"bioruby-mcp-server"
|
192
|
+
]
|
193
|
+
}
|
194
|
+
}
|
195
|
+
}
|
196
|
+
```
|
197
|
+
|
198
|
+
##### 3. Restart Claude Desktop
|
199
|
+
|
200
|
+
After saving the configuration file, restart Claude Desktop to load the new MCP server.
|
201
|
+
|
202
|
+
##### 4. Verify Connection
|
203
|
+
|
204
|
+
Once connected, you can test the integration by asking Claude to query KEGG databases. For example:
|
205
|
+
|
206
|
+
- "Get information about KEGG pathway map00010"
|
207
|
+
- "Search for glucose compounds in KEGG"
|
208
|
+
- "Find pathways containing compound C00002"
|
209
|
+
|
210
|
+
##### Troubleshooting
|
211
|
+
|
212
|
+
If the connection fails:
|
213
|
+
|
214
|
+
1. **Check gem installation**: Ensure `bioruby-mcp-server` is installed and accessible in your PATH
|
215
|
+
```bash
|
216
|
+
which bioruby-mcp-server
|
217
|
+
gem list bioruby-mcp-server
|
218
|
+
```
|
219
|
+
|
220
|
+
2. **Verify configuration**: Check that the JSON configuration file is valid and in the correct location
|
221
|
+
|
222
|
+
3. **Check permissions**: Ensure Claude Desktop has permission to execute the bioruby-mcp-server command
|
223
|
+
|
224
|
+
4. **Review logs**: Check Claude Desktop's logs for any error messages related to MCP server connections
|
225
|
+
|
226
|
+
#### General MCP Configuration
|
227
|
+
|
228
|
+
For other AI assistants that support MCP, use this general configuration format:
|
229
|
+
|
230
|
+
```json
|
231
|
+
{
|
232
|
+
"mcpServers": {
|
233
|
+
"bioruby-kegg": {
|
234
|
+
"command": "bioruby-mcp-server",
|
235
|
+
"args": []
|
236
|
+
}
|
237
|
+
}
|
238
|
+
}
|
239
|
+
```
|
240
|
+
|
241
|
+
## Development
|
242
|
+
|
243
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
244
|
+
|
245
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
246
|
+
|
247
|
+
## Testing
|
248
|
+
|
249
|
+
Run the test suite:
|
250
|
+
|
251
|
+
```bash
|
252
|
+
$ bundle exec rake test
|
253
|
+
```
|
254
|
+
|
255
|
+
Run RuboCop for code style checking:
|
256
|
+
|
257
|
+
```bash
|
258
|
+
$ bundle exec rubocop
|
259
|
+
```
|
260
|
+
|
261
|
+
## Requirements
|
262
|
+
|
263
|
+
- Ruby 3.0 or higher
|
264
|
+
- Internet connectivity for accessing KEGG REST API
|
265
|
+
|
266
|
+
## Dependencies
|
267
|
+
|
268
|
+
- [bio](https://github.com/bioruby/bioruby) - BioRuby library for biological data processing
|
269
|
+
- [mcp](https://github.com/modelcontextprotocol/ruby-sdk) - Official MCP Ruby SDK
|
270
|
+
|
271
|
+
## Technical Implementation
|
272
|
+
|
273
|
+
This server is built using the official [MCP Ruby SDK](https://github.com/modelcontextprotocol/ruby-sdk), ensuring full compliance with the Model Context Protocol specification. Each KEGG tool is implemented as an `MCP::Tool` subclass, providing proper input schema validation and structured responses.
|
274
|
+
|
275
|
+
### Architecture
|
276
|
+
|
277
|
+
- **KEGG REST API Integration**: Direct integration with KEGG's REST API for real-time data access
|
278
|
+
- **BioRuby Parser Integration**: Uses BioRuby's KEGG parsers for proper data structure handling
|
279
|
+
- **MCP Protocol Compliance**: Built on the official MCP Ruby SDK for standardized protocol handling
|
280
|
+
- **Tool-based Architecture**: Each KEGG operation is implemented as a separate MCP tool
|
281
|
+
- **Robust Error Handling**: Comprehensive error handling for network issues, invalid IDs, and API failures
|
282
|
+
|
283
|
+
## Contributing
|
284
|
+
|
285
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kozo2/bioruby-mcp-server. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/kozo2/bioruby-mcp-server/blob/main/CODE_OF_CONDUCT.md).
|
286
|
+
|
287
|
+
## License
|
288
|
+
|
289
|
+
The gem is available as open source under the terms of the [BSD 2-Clause License](https://opensource.org/licenses/BSD-2-Clause).
|
290
|
+
|
291
|
+
## Code of Conduct
|
292
|
+
|
293
|
+
Everyone interacting in the bioruby-mcp-server project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/kozo2/bioruby-mcp-server/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/bioruby/mcp/server/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'bioruby-mcp-server'
|
7
|
+
spec.version = BioRuby::MCP::Server::VERSION
|
8
|
+
spec.authors = ['Kozo Nishida']
|
9
|
+
spec.email = ['kozo2@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Model Context Protocol server for BioRuby KEGG module'
|
12
|
+
spec.description = 'An MCP server that provides access to BioRuby KEGG functionality, ' \
|
13
|
+
'allowing AI assistants to query KEGG databases for biological pathways, ' \
|
14
|
+
'compounds, and other molecular information.'
|
15
|
+
spec.homepage = 'https://github.com/kozo2/bioruby-mcp-server'
|
16
|
+
spec.license = 'BSD-2-Clause'
|
17
|
+
spec.required_ruby_version = '>= 3.0.0'
|
18
|
+
|
19
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/kozo2/bioruby-mcp-server'
|
22
|
+
spec.metadata['changelog_uri'] = 'https://github.com/kozo2/bioruby-mcp-server/blob/main/CHANGELOG.md'
|
23
|
+
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = 'exe'
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ['lib']
|
33
|
+
|
34
|
+
# Dependencies
|
35
|
+
spec.add_dependency 'bio', '~> 2.0'
|
36
|
+
spec.add_dependency 'mcp', '~> 0.1'
|
37
|
+
|
38
|
+
# Development dependencies
|
39
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
40
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
41
|
+
spec.add_development_dependency 'rubocop', '~> 1.21'
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mcp'
|
4
|
+
require 'mcp/server/transports/stdio_transport'
|
5
|
+
require_relative 'kegg_tools'
|
6
|
+
|
7
|
+
module BioRuby
|
8
|
+
module MCP
|
9
|
+
module Server
|
10
|
+
# BioRuby MCP Server using the official MCP Ruby SDK
|
11
|
+
class Core
|
12
|
+
def self.create_server
|
13
|
+
# Create MCP server with all KEGG tools
|
14
|
+
::MCP::Server.new(
|
15
|
+
name: 'bioruby-mcp-server',
|
16
|
+
version: VERSION,
|
17
|
+
tools: [
|
18
|
+
KEGGPathwayTool,
|
19
|
+
KEGGCompoundTool,
|
20
|
+
KEGGEnzymeTool,
|
21
|
+
KEGGSearchTool,
|
22
|
+
KEGGPathwayFinderTool,
|
23
|
+
KEGGOrganismTool
|
24
|
+
]
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.start_stdio_server
|
29
|
+
server = create_server
|
30
|
+
transport = ::MCP::Server::Transports::StdioTransport.new(server)
|
31
|
+
transport.open
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,417 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mcp'
|
4
|
+
require 'bio'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module BioRuby
|
9
|
+
module MCP
|
10
|
+
module Server
|
11
|
+
# KEGG pathway information tool
|
12
|
+
class KEGGPathwayTool < ::MCP::Tool
|
13
|
+
tool_name 'kegg_pathway_info'
|
14
|
+
description 'Get information about a KEGG pathway'
|
15
|
+
input_schema(
|
16
|
+
properties: {
|
17
|
+
pathway_id: {
|
18
|
+
type: 'string',
|
19
|
+
description: 'KEGG pathway ID (e.g., "map00010", "hsa00010")'
|
20
|
+
}
|
21
|
+
},
|
22
|
+
required: ['pathway_id']
|
23
|
+
)
|
24
|
+
|
25
|
+
KEGG_REST_BASE = 'http://rest.kegg.jp'
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def call(pathway_id:, server_context: nil)
|
29
|
+
# Clean up pathway ID if needed
|
30
|
+
pathway_id = pathway_id.gsub(/^(pathway:|path:|map:)/, '')
|
31
|
+
|
32
|
+
# Get pathway entry from KEGG REST API
|
33
|
+
pathway_data = kegg_get_entry(pathway_id)
|
34
|
+
|
35
|
+
if pathway_data.nil? || pathway_data.empty?
|
36
|
+
return ::MCP::Tool::Response.new([{
|
37
|
+
type: 'text',
|
38
|
+
text: "Pathway not found: #{pathway_id}"
|
39
|
+
}])
|
40
|
+
end
|
41
|
+
|
42
|
+
# Parse the pathway data using BioRuby
|
43
|
+
pathway = Bio::KEGG::PATHWAY.new(pathway_data)
|
44
|
+
|
45
|
+
result = []
|
46
|
+
result << {
|
47
|
+
type: 'text',
|
48
|
+
text: "KEGG Pathway: #{pathway_id}\n" \
|
49
|
+
"Name: #{pathway.name}\n" \
|
50
|
+
"Description: #{pathway.description}\n" \
|
51
|
+
"Class: #{pathway.pathway_class}\n" \
|
52
|
+
"Genes: #{pathway.genes&.length || 0} genes\n" \
|
53
|
+
"Compounds: #{pathway.compounds&.length || 0} compounds"
|
54
|
+
}
|
55
|
+
|
56
|
+
if pathway.genes && !pathway.genes.empty?
|
57
|
+
gene_list = pathway.genes.first(10).map { |gene_id, name| "#{gene_id}: #{name}" }
|
58
|
+
result << {
|
59
|
+
type: 'text',
|
60
|
+
text: "Sample genes (first 10):\n#{gene_list.join("\n")}"
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
::MCP::Tool::Response.new(result)
|
65
|
+
rescue => e
|
66
|
+
::MCP::Tool::Response.new([{
|
67
|
+
type: 'text',
|
68
|
+
text: "Error retrieving pathway info: #{e.message}"
|
69
|
+
}])
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def kegg_get_entry(entry_id)
|
75
|
+
uri = URI("#{KEGG_REST_BASE}/get/#{entry_id}")
|
76
|
+
begin
|
77
|
+
uri.open.read
|
78
|
+
rescue OpenURI::HTTPError
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# KEGG compound information tool
|
86
|
+
class KEGGCompoundTool < ::MCP::Tool
|
87
|
+
tool_name 'kegg_compound_info'
|
88
|
+
description 'Get information about a KEGG compound'
|
89
|
+
input_schema(
|
90
|
+
properties: {
|
91
|
+
compound_id: {
|
92
|
+
type: 'string',
|
93
|
+
description: 'KEGG compound ID (e.g., "C00002", "cpd:C00002")'
|
94
|
+
}
|
95
|
+
},
|
96
|
+
required: ['compound_id']
|
97
|
+
)
|
98
|
+
|
99
|
+
KEGG_REST_BASE = 'http://rest.kegg.jp'
|
100
|
+
|
101
|
+
class << self
|
102
|
+
def call(compound_id:, server_context: nil)
|
103
|
+
# Clean up compound ID if needed
|
104
|
+
compound_id = compound_id.gsub(/^(cpd:|compound:)/, '')
|
105
|
+
|
106
|
+
# Get compound entry from KEGG REST API
|
107
|
+
compound_data = kegg_get_entry(compound_id)
|
108
|
+
|
109
|
+
if compound_data.nil? || compound_data.empty?
|
110
|
+
return ::MCP::Tool::Response.new([{
|
111
|
+
type: 'text',
|
112
|
+
text: "Compound not found: #{compound_id}"
|
113
|
+
}])
|
114
|
+
end
|
115
|
+
|
116
|
+
# Parse the compound data using BioRuby
|
117
|
+
compound = Bio::KEGG::COMPOUND.new(compound_data)
|
118
|
+
|
119
|
+
::MCP::Tool::Response.new([{
|
120
|
+
type: 'text',
|
121
|
+
text: "KEGG Compound: #{compound_id}\n" \
|
122
|
+
"Name: #{compound.name}\n" \
|
123
|
+
"Formula: #{compound.formula}\n" \
|
124
|
+
"Mass: #{compound.mass}\n" \
|
125
|
+
"Comment: #{compound.comment}\n" \
|
126
|
+
"Pathways: #{compound.pathways&.length || 0} pathways\n" \
|
127
|
+
"Enzymes: #{compound.enzymes&.length || 0} enzymes"
|
128
|
+
}])
|
129
|
+
rescue => e
|
130
|
+
::MCP::Tool::Response.new([{
|
131
|
+
type: 'text',
|
132
|
+
text: "Error retrieving compound info: #{e.message}"
|
133
|
+
}])
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def kegg_get_entry(entry_id)
|
139
|
+
uri = URI("#{KEGG_REST_BASE}/get/#{entry_id}")
|
140
|
+
begin
|
141
|
+
uri.open.read
|
142
|
+
rescue OpenURI::HTTPError
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# KEGG enzyme information tool
|
150
|
+
class KEGGEnzymeTool < ::MCP::Tool
|
151
|
+
tool_name 'kegg_enzyme_info'
|
152
|
+
description 'Get information about a KEGG enzyme'
|
153
|
+
input_schema(
|
154
|
+
properties: {
|
155
|
+
enzyme_id: {
|
156
|
+
type: 'string',
|
157
|
+
description: 'KEGG enzyme ID (e.g., "ec:1.1.1.1")'
|
158
|
+
}
|
159
|
+
},
|
160
|
+
required: ['enzyme_id']
|
161
|
+
)
|
162
|
+
|
163
|
+
KEGG_REST_BASE = 'http://rest.kegg.jp'
|
164
|
+
|
165
|
+
class << self
|
166
|
+
def call(enzyme_id:, server_context: nil)
|
167
|
+
# Clean up enzyme ID if needed
|
168
|
+
enzyme_id = enzyme_id.gsub(/^(ec:|enzyme:)/, '')
|
169
|
+
|
170
|
+
# Get enzyme entry from KEGG REST API
|
171
|
+
enzyme_data = kegg_get_entry(enzyme_id)
|
172
|
+
|
173
|
+
if enzyme_data.nil? || enzyme_data.empty?
|
174
|
+
return ::MCP::Tool::Response.new([{
|
175
|
+
type: 'text',
|
176
|
+
text: "Enzyme not found: #{enzyme_id}"
|
177
|
+
}])
|
178
|
+
end
|
179
|
+
|
180
|
+
# Parse the enzyme data using BioRuby
|
181
|
+
enzyme = Bio::KEGG::ENZYME.new(enzyme_data)
|
182
|
+
|
183
|
+
::MCP::Tool::Response.new([{
|
184
|
+
type: 'text',
|
185
|
+
text: "KEGG Enzyme: #{enzyme_id}\n" \
|
186
|
+
"Name: #{enzyme.name}\n" \
|
187
|
+
"Class: #{enzyme.enzyme_class}\n" \
|
188
|
+
"Reaction: #{enzyme.reaction}\n" \
|
189
|
+
"Substrate: #{enzyme.substrate}\n" \
|
190
|
+
"Product: #{enzyme.product}\n" \
|
191
|
+
"Comment: #{enzyme.comment}"
|
192
|
+
}])
|
193
|
+
rescue => e
|
194
|
+
::MCP::Tool::Response.new([{
|
195
|
+
type: 'text',
|
196
|
+
text: "Error retrieving enzyme info: #{e.message}"
|
197
|
+
}])
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def kegg_get_entry(entry_id)
|
203
|
+
uri = URI("#{KEGG_REST_BASE}/get/#{entry_id}")
|
204
|
+
begin
|
205
|
+
uri.open.read
|
206
|
+
rescue OpenURI::HTTPError
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# KEGG compound search tool
|
214
|
+
class KEGGSearchTool < ::MCP::Tool
|
215
|
+
tool_name 'kegg_search_compounds'
|
216
|
+
description 'Search for KEGG compounds by name or formula'
|
217
|
+
input_schema(
|
218
|
+
properties: {
|
219
|
+
query: {
|
220
|
+
type: 'string',
|
221
|
+
description: 'Search query (compound name, formula, etc.)'
|
222
|
+
},
|
223
|
+
database: {
|
224
|
+
type: 'string',
|
225
|
+
description: 'Database to search (default: "compound")',
|
226
|
+
default: 'compound'
|
227
|
+
}
|
228
|
+
},
|
229
|
+
required: ['query']
|
230
|
+
)
|
231
|
+
|
232
|
+
KEGG_REST_BASE = 'http://rest.kegg.jp'
|
233
|
+
|
234
|
+
class << self
|
235
|
+
def call(query:, database: 'compound', server_context: nil)
|
236
|
+
# Use KEGG REST API to search for compounds
|
237
|
+
results = kegg_find_entries(database, query)
|
238
|
+
|
239
|
+
if results.nil? || results.empty?
|
240
|
+
return ::MCP::Tool::Response.new([{
|
241
|
+
type: 'text',
|
242
|
+
text: "No compounds found for query: #{query}"
|
243
|
+
}])
|
244
|
+
end
|
245
|
+
|
246
|
+
# Parse and format results
|
247
|
+
formatted_results = results.split("\n").first(20).map do |line|
|
248
|
+
parts = line.split("\t")
|
249
|
+
"#{parts[0]}: #{parts[1]}" if parts.length >= 2
|
250
|
+
end.compact
|
251
|
+
|
252
|
+
::MCP::Tool::Response.new([{
|
253
|
+
type: 'text',
|
254
|
+
text: "Search results for '#{query}' (first 20):\n#{formatted_results.join("\n")}"
|
255
|
+
}])
|
256
|
+
rescue => e
|
257
|
+
::MCP::Tool::Response.new([{
|
258
|
+
type: 'text',
|
259
|
+
text: "Error searching compounds: #{e.message}"
|
260
|
+
}])
|
261
|
+
end
|
262
|
+
|
263
|
+
private
|
264
|
+
|
265
|
+
def kegg_find_entries(database, query)
|
266
|
+
uri = URI("#{KEGG_REST_BASE}/find/#{database}/#{query}")
|
267
|
+
begin
|
268
|
+
uri.open.read
|
269
|
+
rescue OpenURI::HTTPError
|
270
|
+
nil
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# KEGG pathway finder tool
|
277
|
+
class KEGGPathwayFinderTool < ::MCP::Tool
|
278
|
+
tool_name 'kegg_find_pathways_by_compound'
|
279
|
+
description 'Find pathways containing a specific compound'
|
280
|
+
input_schema(
|
281
|
+
properties: {
|
282
|
+
compound_id: {
|
283
|
+
type: 'string',
|
284
|
+
description: 'KEGG compound ID (e.g., "C00002")'
|
285
|
+
}
|
286
|
+
},
|
287
|
+
required: ['compound_id']
|
288
|
+
)
|
289
|
+
|
290
|
+
KEGG_REST_BASE = 'http://rest.kegg.jp'
|
291
|
+
|
292
|
+
class << self
|
293
|
+
def call(compound_id:, server_context: nil)
|
294
|
+
# Clean up compound ID
|
295
|
+
compound_id = compound_id.gsub(/^(cpd:|compound:)/, '')
|
296
|
+
|
297
|
+
# Get compound info first to find associated pathways
|
298
|
+
compound_data = kegg_get_entry(compound_id)
|
299
|
+
|
300
|
+
if compound_data.nil? || compound_data.empty?
|
301
|
+
return ::MCP::Tool::Response.new([{
|
302
|
+
type: 'text',
|
303
|
+
text: "Compound not found: #{compound_id}"
|
304
|
+
}])
|
305
|
+
end
|
306
|
+
|
307
|
+
compound = Bio::KEGG::COMPOUND.new(compound_data)
|
308
|
+
|
309
|
+
if compound.pathways.nil? || compound.pathways.empty?
|
310
|
+
return ::MCP::Tool::Response.new([{
|
311
|
+
type: 'text',
|
312
|
+
text: "No pathways found for compound: #{compound_id}"
|
313
|
+
}])
|
314
|
+
end
|
315
|
+
|
316
|
+
pathway_list = compound.pathways.map do |pathway_id, pathway_name|
|
317
|
+
"#{pathway_id}: #{pathway_name}"
|
318
|
+
end
|
319
|
+
|
320
|
+
::MCP::Tool::Response.new([{
|
321
|
+
type: 'text',
|
322
|
+
text: "Pathways containing compound #{compound_id}:\n#{pathway_list.join("\n")}"
|
323
|
+
}])
|
324
|
+
rescue => e
|
325
|
+
::MCP::Tool::Response.new([{
|
326
|
+
type: 'text',
|
327
|
+
text: "Error finding pathways: #{e.message}"
|
328
|
+
}])
|
329
|
+
end
|
330
|
+
|
331
|
+
private
|
332
|
+
|
333
|
+
def kegg_get_entry(entry_id)
|
334
|
+
uri = URI("#{KEGG_REST_BASE}/get/#{entry_id}")
|
335
|
+
begin
|
336
|
+
uri.open.read
|
337
|
+
rescue OpenURI::HTTPError
|
338
|
+
nil
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# KEGG organism list tool
|
345
|
+
class KEGGOrganismTool < ::MCP::Tool
|
346
|
+
tool_name 'kegg_list_organisms'
|
347
|
+
description 'List available organisms in KEGG'
|
348
|
+
input_schema(
|
349
|
+
properties: {
|
350
|
+
filter: {
|
351
|
+
type: 'string',
|
352
|
+
description: 'Optional filter for organism names'
|
353
|
+
}
|
354
|
+
}
|
355
|
+
)
|
356
|
+
|
357
|
+
KEGG_REST_BASE = 'http://rest.kegg.jp'
|
358
|
+
|
359
|
+
class << self
|
360
|
+
def call(filter: nil, server_context: nil)
|
361
|
+
# Get list of organisms from KEGG REST API
|
362
|
+
organism_data = kegg_list_entries('organism')
|
363
|
+
|
364
|
+
if organism_data.nil? || organism_data.empty?
|
365
|
+
return ::MCP::Tool::Response.new([{
|
366
|
+
type: 'text',
|
367
|
+
text: 'No organisms found'
|
368
|
+
}])
|
369
|
+
end
|
370
|
+
|
371
|
+
organisms = organism_data.split("\n").map do |line|
|
372
|
+
parts = line.split("\t")
|
373
|
+
if parts.length >= 2
|
374
|
+
org_code = parts[0]
|
375
|
+
org_name = parts[1]
|
376
|
+
{ code: org_code, name: org_name }
|
377
|
+
end
|
378
|
+
end.compact
|
379
|
+
|
380
|
+
# Apply filter if provided
|
381
|
+
if filter
|
382
|
+
organisms = organisms.select do |org|
|
383
|
+
org[:name].downcase.include?(filter.downcase) ||
|
384
|
+
org[:code].downcase.include?(filter.downcase)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# Limit results and format
|
389
|
+
organisms = organisms.first(50)
|
390
|
+
formatted_list = organisms.map { |org| "#{org[:code]}: #{org[:name]}" }
|
391
|
+
|
392
|
+
::MCP::Tool::Response.new([{
|
393
|
+
type: 'text',
|
394
|
+
text: "KEGG Organisms#{filter ? " (filtered by '#{filter}')" : ''} (first 50):\n#{formatted_list.join("\n")}"
|
395
|
+
}])
|
396
|
+
rescue => e
|
397
|
+
::MCP::Tool::Response.new([{
|
398
|
+
type: 'text',
|
399
|
+
text: "Error listing organisms: #{e.message}"
|
400
|
+
}])
|
401
|
+
end
|
402
|
+
|
403
|
+
private
|
404
|
+
|
405
|
+
def kegg_list_entries(database)
|
406
|
+
uri = URI("#{KEGG_REST_BASE}/list/#{database}")
|
407
|
+
begin
|
408
|
+
uri.open.read
|
409
|
+
rescue OpenURI::HTTPError
|
410
|
+
nil
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
data/lib/bioruby.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'bioruby/mcp/server/version'
|
4
|
+
require_relative 'bioruby/mcp/server/kegg_tools'
|
5
|
+
require_relative 'bioruby/mcp/server/core'
|
6
|
+
|
7
|
+
module BioRuby
|
8
|
+
module MCP
|
9
|
+
module Server
|
10
|
+
class Error < StandardError; end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bioruby-mcp-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kozo Nishida
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-09-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bio
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mcp
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.21'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.21'
|
83
|
+
description: An MCP server that provides access to BioRuby KEGG functionality, allowing
|
84
|
+
AI assistants to query KEGG databases for biological pathways, compounds, and other
|
85
|
+
molecular information.
|
86
|
+
email:
|
87
|
+
- kozo2@gmail.com
|
88
|
+
executables:
|
89
|
+
- bioruby-mcp-server
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- CHANGELOG.md
|
94
|
+
- LICENSE
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bioruby-mcp-server.gemspec
|
98
|
+
- exe/bioruby-mcp-server
|
99
|
+
- lib/bioruby.rb
|
100
|
+
- lib/bioruby/mcp/server/core.rb
|
101
|
+
- lib/bioruby/mcp/server/kegg_tools.rb
|
102
|
+
- lib/bioruby/mcp/server/version.rb
|
103
|
+
homepage: https://github.com/kozo2/bioruby-mcp-server
|
104
|
+
licenses:
|
105
|
+
- BSD-2-Clause
|
106
|
+
metadata:
|
107
|
+
allowed_push_host: https://rubygems.org
|
108
|
+
homepage_uri: https://github.com/kozo2/bioruby-mcp-server
|
109
|
+
source_code_uri: https://github.com/kozo2/bioruby-mcp-server
|
110
|
+
changelog_uri: https://github.com/kozo2/bioruby-mcp-server/blob/main/CHANGELOG.md
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: 3.0.0
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubygems_version: 3.4.20
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: Model Context Protocol server for BioRuby KEGG module
|
130
|
+
test_files: []
|