firestore-https-ruby 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/GEMINI.md +75 -0
- data/README.md +85 -0
- data/lib/mcp_server/config.rb +47 -0
- data/lib/mcp_server/database_helper.rb +123 -0
- data/lib/mcp_server/tools/check_db.rb +20 -0
- data/lib/mcp_server/tools/get_product_by_id.rb +36 -0
- data/lib/mcp_server/tools/get_products.rb +28 -0
- data/lib/mcp_server/tools/get_root.rb +19 -0
- data/lib/mcp_server/tools/reset.rb +26 -0
- data/lib/mcp_server/tools/seed.rb +26 -0
- data/main.rb +102 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a2a2f507167e36197b1f31f02abe19759531f8115c22f225e3a4ee1c1186a555
|
|
4
|
+
data.tar.gz: 7cb925baacab72c49c5a7c9a9e742d54b76c115bcbba99719e96d5ac3ce5393a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2f8aae71fd7d00a23107f6f3e47faece9ad4e0eeae1caedbc23c43eba2b1fc8f11b379596b699dc0edb91f5bb3336bc4158b9b45a89d2502cff88cea23b32920
|
|
7
|
+
data.tar.gz: 6e655a335e5e4d828db85f3687b260ee86556b1f01c05688dbb1cd2b680aa279dd9715c8cc3cb99bd48599e0cd904aaa106a59f1db163487adfbe1e434f7d2b1
|
data/GEMINI.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Gemini Code Assistant Context
|
|
2
|
+
|
|
3
|
+
This document provides context for the Gemini Code Assistant to understand the project and assist in development.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a **Ruby based Model Context Protocol (MCP) server**. It is designed to expose tools over a streaming HTTP transport for integration with MCP clients (such as Claude Desktop or Gemini clients).
|
|
8
|
+
|
|
9
|
+
## Key Technologies
|
|
10
|
+
|
|
11
|
+
* **Language:** Ruby
|
|
12
|
+
* **SDK:** `mcp` (Model Context Protocol SDK)
|
|
13
|
+
https://github.com/modelcontextprotocol/ruby-sdk
|
|
14
|
+
* **Database:** Google Cloud Firestore
|
|
15
|
+
* **Server:** Puma (via Rack)
|
|
16
|
+
|
|
17
|
+
## Project Structure
|
|
18
|
+
|
|
19
|
+
* `main.rb`: Main entry point (Ruby). Registers tools, initializes Firestore, and starts the HTTP transport using Puma.
|
|
20
|
+
* `Makefile`: Development shortcuts (test, lint, clean).
|
|
21
|
+
* `spec/`: Contains test files.
|
|
22
|
+
|
|
23
|
+
## Ruby MCP Best Practices
|
|
24
|
+
|
|
25
|
+
* **Logging:** Always log to `stderr` (e.g., `LOGGER = Logger.new($stderr)`).
|
|
26
|
+
* **Tool Definition:**
|
|
27
|
+
* Inherit from `MCP::Tool`.
|
|
28
|
+
* Define `description` and `input_schema`.
|
|
29
|
+
* Implement the logic in a class method `self.call`.
|
|
30
|
+
* Return an `MCP::Tool::Response` containing an array of content blocks (e.g., `{ type: 'text', text: '...' }`).
|
|
31
|
+
* **Modularity:** For larger projects, keep tools in separate files. In this project, they are currently in `main.rb` for simplicity.
|
|
32
|
+
* **Error Handling:** Wrap tool logic in `begin...rescue` blocks or check pre-requisites (like database connectivity) to return meaningful error messages to the client.
|
|
33
|
+
* **Environment Variables:** Use `dotenv` for local development. Ensure sensitive credentials are never committed.
|
|
34
|
+
|
|
35
|
+
## Firestore Integration
|
|
36
|
+
|
|
37
|
+
* **Client:** Uses the `google-cloud-firestore` gem.
|
|
38
|
+
* **Collection:** The primary collection used is `inventory`.
|
|
39
|
+
* **Schema (Inventory):**
|
|
40
|
+
* `name` (String)
|
|
41
|
+
* `price` (Number)
|
|
42
|
+
* `quantity` (Number)
|
|
43
|
+
* `imgfile` (String)
|
|
44
|
+
* `timestamp` (Timestamp)
|
|
45
|
+
* `actualdateadded` (Timestamp)
|
|
46
|
+
* **Operations:**
|
|
47
|
+
* `seed`: Populates the database with sample data.
|
|
48
|
+
* `reset`: Clears the `inventory` collection.
|
|
49
|
+
* `get_products`: Retrieves all items.
|
|
50
|
+
* `get_product_by_id`: Retrieves a specific item by document ID.
|
|
51
|
+
* `get_root`: Returns a welcome message.
|
|
52
|
+
* `check_db`: Checks database connectivity.
|
|
53
|
+
|
|
54
|
+
## Development Setup
|
|
55
|
+
|
|
56
|
+
1. **Install Dependencies:**
|
|
57
|
+
```bash
|
|
58
|
+
bundle install
|
|
59
|
+
```
|
|
60
|
+
2. **Environment Variables:** Create a `.env` file with necessary Google Cloud credentials if not using default application credentials.
|
|
61
|
+
|
|
62
|
+
## Running the Server
|
|
63
|
+
|
|
64
|
+
The server is configured to run using a streaming HTTP transport on port 8080.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bundle exec ruby main.rb
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
*Note: Since this is an MCP server, it is typically spawned or connected to by an MCP client.*
|
|
71
|
+
|
|
72
|
+
## Ruby MCP Developer Resources
|
|
73
|
+
|
|
74
|
+
* **MCP Ruby SDK (GitHub):** [https://github.com/modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk)
|
|
75
|
+
* **Model Context Protocol Documentation:** [https://modelcontextprotocol.io/](https://modelcontextprotocol.io/)
|
data/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Firestore Inventory MCP Server (Ruby)
|
|
2
|
+
|
|
3
|
+
A Ruby implementation of a **Model Context Protocol (MCP)** server that connects to **Google Cloud Firestore**. This server exposes tools to manage and inspect a "Cymbal Superstore" inventory database over a streaming HTTP transport.
|
|
4
|
+
|
|
5
|
+
It is designed to be used with MCP clients (like Claude Desktop or Gemini Code Assist) to enable LLMs to interact with your Firestore data.
|
|
6
|
+
|
|
7
|
+
## Features & Tools
|
|
8
|
+
|
|
9
|
+
This server provides the following MCP tools:
|
|
10
|
+
|
|
11
|
+
* **`get_root`**: Returns a welcome message and confirms the server is reachable.
|
|
12
|
+
* **`check_db`**: Checks the connectivity status of the Firestore database.
|
|
13
|
+
* **`get_products`**: Retrieves the full list of products from the inventory.
|
|
14
|
+
* **`get_product_by_id`**: Fetches details for a specific product using its document ID.
|
|
15
|
+
* **`seed`**: Populates the Firestore `inventory` collection with sample data (useful for demos/testing).
|
|
16
|
+
* **`reset`**: Clears all documents from the `inventory` collection.
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
* **Ruby 3.0+**
|
|
21
|
+
* **Google Cloud Project**: You need a Google Cloud project with Firestore enabled.
|
|
22
|
+
* **Authentication**: The environment where this server runs must be authenticated with Google Cloud.
|
|
23
|
+
* Locally, you can use `gcloud auth application-default login`.
|
|
24
|
+
* Or set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to the path of your service account key JSON file.
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
1. **Clone the repository**:
|
|
29
|
+
```bash
|
|
30
|
+
git clone <your-repo-url>
|
|
31
|
+
cd firestore-https-ruby
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Install Dependencies**:
|
|
35
|
+
```bash
|
|
36
|
+
bundle install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. **Configure Environment**:
|
|
40
|
+
Create a `.env` file if you need to load specific environment variables (handled by `dotenv`).
|
|
41
|
+
|
|
42
|
+
## Running the Server
|
|
43
|
+
|
|
44
|
+
This server communicates over **HTTP** using Server-Sent Events (SSE). It is not meant to be run manually in a terminal for human interaction, but rather spawned or connected to by an MCP client.
|
|
45
|
+
|
|
46
|
+
To start the server:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bundle exec ruby main.rb
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The server will start on port 8080 (by default).
|
|
53
|
+
|
|
54
|
+
### Integration with MCP Clients
|
|
55
|
+
|
|
56
|
+
Configure your MCP client to connect to the server's HTTP endpoint or run it as a command if the client supports it. For example:
|
|
57
|
+
|
|
58
|
+
**Command:** `bundle`
|
|
59
|
+
**Args:** `['exec', 'ruby', '/path/to/firestore-https-ruby/main.rb']`
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
### Testing
|
|
64
|
+
|
|
65
|
+
Run the test suite using RSpec:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
bundle exec rspec
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Linting
|
|
72
|
+
|
|
73
|
+
Check code style with RuboCop:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bundle exec rubocop
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Building the Gem
|
|
80
|
+
|
|
81
|
+
This project can also be built as a Ruby gem:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
gem build firestore-mcp-server.gemspec
|
|
85
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'time'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'google/cloud/firestore'
|
|
7
|
+
|
|
8
|
+
module MCPServer
|
|
9
|
+
# Configuration and initialization for the MCP server.
|
|
10
|
+
module Config
|
|
11
|
+
LOGGER = Logger.new($stderr)
|
|
12
|
+
LOGGER.level = Logger::INFO
|
|
13
|
+
LOGGER.formatter = proc do |severity, datetime, _progname, msg|
|
|
14
|
+
log_entry = {
|
|
15
|
+
timestamp: datetime.iso8601,
|
|
16
|
+
level: severity
|
|
17
|
+
}
|
|
18
|
+
if msg.is_a?(Hash)
|
|
19
|
+
log_entry.merge!(msg)
|
|
20
|
+
else
|
|
21
|
+
log_entry[:message] = msg.to_s
|
|
22
|
+
end
|
|
23
|
+
"#{log_entry.to_json}\n"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.logger
|
|
27
|
+
LOGGER
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.firestore
|
|
31
|
+
@firestore
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.db_running?
|
|
35
|
+
@db_running
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.init_firestore
|
|
39
|
+
@firestore = Google::Cloud::Firestore.new
|
|
40
|
+
@db_running = true
|
|
41
|
+
logger.info 'Firestore client initialized'
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
logger.error "Failed to initialize Firestore: #{e.message}"
|
|
44
|
+
@db_running = false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'config'
|
|
4
|
+
|
|
5
|
+
module MCPServer
|
|
6
|
+
# Helper class for database operations.
|
|
7
|
+
class DatabaseHelper
|
|
8
|
+
def self.doc_to_product(doc)
|
|
9
|
+
data = doc.data
|
|
10
|
+
{
|
|
11
|
+
id: doc.document_id,
|
|
12
|
+
name: data[:name],
|
|
13
|
+
price: data[:price],
|
|
14
|
+
quantity: data[:quantity],
|
|
15
|
+
imgfile: data[:imgfile],
|
|
16
|
+
timestamp: data[:timestamp],
|
|
17
|
+
actualdateadded: data[:actualdateadded]
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.add_or_update_firestore(product)
|
|
22
|
+
firestore = MCPServer::Config.firestore
|
|
23
|
+
query = firestore.col('inventory').where('name', '==', product[:name]).get
|
|
24
|
+
|
|
25
|
+
if query.empty?
|
|
26
|
+
firestore.col('inventory').add(product)
|
|
27
|
+
else
|
|
28
|
+
query.each do |doc|
|
|
29
|
+
doc.ref.update(product)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.seed_database
|
|
35
|
+
seed_old_products
|
|
36
|
+
seed_recent_products
|
|
37
|
+
seed_out_of_stock_products
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# rubocop:disable Metrics/MethodLength
|
|
41
|
+
def self.seed_old_products
|
|
42
|
+
names = [
|
|
43
|
+
'Apples', 'Bananas', 'Milk', 'Whole Wheat Bread', 'Eggs', 'Cheddar Cheese',
|
|
44
|
+
'Whole Chicken', 'Rice', 'Black Beans', 'Bottled Water', 'Apple Juice',
|
|
45
|
+
'Cola', 'Coffee Beans', 'Green Tea', 'Watermelon', 'Broccoli',
|
|
46
|
+
'Jasmine Rice', 'Yogurt', 'Beef', 'Shrimp', 'Walnuts',
|
|
47
|
+
'Sunflower Seeds', 'Fresh Basil', 'Cinnamon'
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
names.each do |name|
|
|
51
|
+
product = {
|
|
52
|
+
name: name,
|
|
53
|
+
price: rand(1..10),
|
|
54
|
+
quantity: rand(1..500),
|
|
55
|
+
imgfile: "product-images/#{name.gsub(/\s+/, '').downcase}.png",
|
|
56
|
+
timestamp: Time.now - rand(0..31_536_000) - 7_776_000,
|
|
57
|
+
actualdateadded: Time.now
|
|
58
|
+
}
|
|
59
|
+
MCPServer::Config.logger.info "⬆️ Adding (or updating) product in firestore: #{product[:name]}"
|
|
60
|
+
add_or_update_firestore(product)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.seed_recent_products
|
|
65
|
+
names = [
|
|
66
|
+
'Parmesan Crisps', 'Pineapple Kombucha', 'Maple Almond Butter',
|
|
67
|
+
'Mint Chocolate Cookies', 'White Chocolate Caramel Corn', 'Acai Smoothie Packs',
|
|
68
|
+
'Smores Cereal', 'Peanut Butter and Jelly Cups'
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
names.each do |name|
|
|
72
|
+
product = {
|
|
73
|
+
name: name,
|
|
74
|
+
price: rand(1..10),
|
|
75
|
+
quantity: rand(1..100),
|
|
76
|
+
imgfile: "product-images/#{name.gsub(/\s+/, '').downcase}.png",
|
|
77
|
+
timestamp: Time.now - rand(0..518_400),
|
|
78
|
+
actualdateadded: Time.now
|
|
79
|
+
}
|
|
80
|
+
MCPServer::Config.logger.info "🆕 Adding (or updating) product in firestore: #{product[:name]}"
|
|
81
|
+
add_or_update_firestore(product)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.seed_out_of_stock_products
|
|
86
|
+
names = ['Wasabi Party Mix', 'Jalapeno Seasoning']
|
|
87
|
+
|
|
88
|
+
names.each do |name|
|
|
89
|
+
product = {
|
|
90
|
+
name: name,
|
|
91
|
+
price: rand(1..10),
|
|
92
|
+
quantity: 0,
|
|
93
|
+
imgfile: "product-images/#{name.gsub(/\s+/, '').downcase}.png",
|
|
94
|
+
timestamp: Time.now - rand(0..518_400),
|
|
95
|
+
actualdateadded: Time.now
|
|
96
|
+
}
|
|
97
|
+
MCPServer::Config.logger.info "😱 Adding (or updating) out of stock product in firestore: #{product[:name]}"
|
|
98
|
+
add_or_update_firestore(product)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
# rubocop:enable Metrics/MethodLength
|
|
102
|
+
|
|
103
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
104
|
+
def self.clean_firestore_collection
|
|
105
|
+
MCPServer::Config.logger.info 'Cleaning Firestore collection...'
|
|
106
|
+
firestore = MCPServer::Config.firestore
|
|
107
|
+
snapshot = firestore.col('inventory').get
|
|
108
|
+
return if snapshot.empty?
|
|
109
|
+
|
|
110
|
+
batch = firestore.batch
|
|
111
|
+
snapshot.each_with_index do |doc, index|
|
|
112
|
+
batch.delete(doc.ref)
|
|
113
|
+
if ((index + 1) % 400).zero?
|
|
114
|
+
batch.commit
|
|
115
|
+
batch = firestore.batch
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
batch.commit if (snapshot.size % 400).positive?
|
|
119
|
+
MCPServer::Config.logger.info 'Firestore collection cleaned.'
|
|
120
|
+
end
|
|
121
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require_relative '../config'
|
|
5
|
+
|
|
6
|
+
module MCPServer
|
|
7
|
+
module Tools
|
|
8
|
+
# Tool to check if the database is running.
|
|
9
|
+
class CheckDb < MCP::Tool
|
|
10
|
+
description 'Checks if the inventory database is running.'
|
|
11
|
+
input_schema(type: 'object', properties: {})
|
|
12
|
+
|
|
13
|
+
def self.call(*)
|
|
14
|
+
MCP::Tool::Response.new(
|
|
15
|
+
[{ type: 'text', text: "Database running: #{MCPServer::Config.db_running?}" }]
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative '../config'
|
|
6
|
+
require_relative '../database_helper'
|
|
7
|
+
|
|
8
|
+
module MCPServer
|
|
9
|
+
module Tools
|
|
10
|
+
# Tool to retrieve a specific product by its ID.
|
|
11
|
+
class GetProductById < MCP::Tool
|
|
12
|
+
description 'Get a single product from the inventory database by its ID'
|
|
13
|
+
input_schema(
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
id: { type: 'string', description: 'The ID of the product to get' }
|
|
17
|
+
},
|
|
18
|
+
required: ['id']
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def self.call(id:)
|
|
22
|
+
unless MCPServer::Config.db_running?
|
|
23
|
+
return MCP::Tool::Response.new([{ type: 'text', text: 'Database not running.' }], is_error: true)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
doc = MCPServer::Config.firestore.col('inventory').doc(id).get
|
|
27
|
+
unless doc.exists?
|
|
28
|
+
return MCP::Tool::Response.new([{ type: 'text', text: 'Product not found.' }], is_error: true)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
product = MCPServer::DatabaseHelper.doc_to_product(doc)
|
|
32
|
+
MCP::Tool::Response.new([{ type: 'text', text: JSON.pretty_generate(product) }])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative '../config'
|
|
6
|
+
require_relative '../database_helper'
|
|
7
|
+
|
|
8
|
+
module MCPServer
|
|
9
|
+
module Tools
|
|
10
|
+
# Tool to retrieve all products from the inventory.
|
|
11
|
+
class GetProducts < MCP::Tool
|
|
12
|
+
description 'Get a list of all products from the inventory database'
|
|
13
|
+
input_schema(type: 'object', properties: {})
|
|
14
|
+
|
|
15
|
+
def self.call(*)
|
|
16
|
+
unless MCPServer::Config.db_running?
|
|
17
|
+
return MCP::Tool::Response.new([{ type: 'text', text: 'Inventory database is not running.' }], is_error: true)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
firestore = MCPServer::Config.firestore
|
|
21
|
+
products = firestore.col('inventory').get.map { |doc| MCPServer::DatabaseHelper.doc_to_product(doc) }
|
|
22
|
+
MCP::Tool::Response.new(
|
|
23
|
+
[{ type: 'text', text: JSON.pretty_generate(products) }]
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
module MCPServer
|
|
6
|
+
module Tools
|
|
7
|
+
# Tool to get the root greeting.
|
|
8
|
+
class GetRoot < MCP::Tool
|
|
9
|
+
description 'Get a greeting from the Cymbal Superstore Inventory API.'
|
|
10
|
+
input_schema(type: 'object', properties: {})
|
|
11
|
+
|
|
12
|
+
def self.call(*)
|
|
13
|
+
MCP::Tool::Response.new(
|
|
14
|
+
[{ type: 'text', text: '🍎 Hello! This is the Cymbal Superstore Inventory API.' }]
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require_relative '../config'
|
|
5
|
+
require_relative '../database_helper'
|
|
6
|
+
|
|
7
|
+
module MCPServer
|
|
8
|
+
module Tools
|
|
9
|
+
# Tool to reset the inventory database.
|
|
10
|
+
class Reset < MCP::Tool
|
|
11
|
+
description 'Clears all products from the inventory database.'
|
|
12
|
+
input_schema(type: 'object', properties: {})
|
|
13
|
+
|
|
14
|
+
def self.call(*)
|
|
15
|
+
unless MCPServer::Config.db_running?
|
|
16
|
+
return MCP::Tool::Response.new([{ type: 'text', text: 'Inventory database is not running.' }], is_error: true)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
MCPServer::DatabaseHelper.clean_firestore_collection
|
|
20
|
+
MCP::Tool::Response.new(
|
|
21
|
+
[{ type: 'text', text: 'Database reset successfully.' }]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require_relative '../config'
|
|
5
|
+
require_relative '../database_helper'
|
|
6
|
+
|
|
7
|
+
module MCPServer
|
|
8
|
+
module Tools
|
|
9
|
+
# Tool to seed the inventory database.
|
|
10
|
+
class Seed < MCP::Tool
|
|
11
|
+
description 'Seed the inventory database with products.'
|
|
12
|
+
input_schema(type: 'object', properties: {})
|
|
13
|
+
|
|
14
|
+
def self.call(*)
|
|
15
|
+
unless MCPServer::Config.db_running?
|
|
16
|
+
return MCP::Tool::Response.new([{ type: 'text', text: 'Inventory database is not running.' }], is_error: true)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
MCPServer::DatabaseHelper.seed_database
|
|
20
|
+
MCP::Tool::Response.new(
|
|
21
|
+
[{ type: 'text', text: 'Database seeded successfully.' }]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/main.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Redirect stdout to stderr to ensure all output goes to stderr immediately
|
|
4
|
+
$stdout.reopen($stderr)
|
|
5
|
+
|
|
6
|
+
require 'mcp'
|
|
7
|
+
require 'rack'
|
|
8
|
+
require 'rackup'
|
|
9
|
+
require 'json'
|
|
10
|
+
require 'puma'
|
|
11
|
+
require 'dotenv/load'
|
|
12
|
+
|
|
13
|
+
require_relative 'lib/mcp_server/config'
|
|
14
|
+
require_relative 'lib/mcp_server/database_helper'
|
|
15
|
+
require_relative 'lib/mcp_server/tools/get_products'
|
|
16
|
+
require_relative 'lib/mcp_server/tools/get_product_by_id'
|
|
17
|
+
require_relative 'lib/mcp_server/tools/seed'
|
|
18
|
+
require_relative 'lib/mcp_server/tools/reset'
|
|
19
|
+
require_relative 'lib/mcp_server/tools/get_root'
|
|
20
|
+
require_relative 'lib/mcp_server/tools/check_db'
|
|
21
|
+
|
|
22
|
+
# Initialize Configuration and Firestore
|
|
23
|
+
MCPServer::Config.init_firestore
|
|
24
|
+
LOGGER = MCPServer::Config.logger
|
|
25
|
+
|
|
26
|
+
# Configure MCP
|
|
27
|
+
MCP.configure do |config|
|
|
28
|
+
config.protocol_version = '2024-11-05'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Custom transport to fix response format for SSE
|
|
32
|
+
class FixedStreamableHTTPTransport < MCP::Server::Transports::StreamableHTTPTransport
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def send_response_to_stream(stream, response, _session_id)
|
|
36
|
+
message = JSON.parse(response)
|
|
37
|
+
send_to_stream(stream, message)
|
|
38
|
+
[202, {}, []]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Middleware for JSON request logging
|
|
43
|
+
class JsonRequestLogger
|
|
44
|
+
def initialize(app, logger)
|
|
45
|
+
@app = app
|
|
46
|
+
@logger = logger
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def call(env)
|
|
50
|
+
status, headers, body = @app.call(env)
|
|
51
|
+
@logger.info(
|
|
52
|
+
type: 'request',
|
|
53
|
+
method: env['REQUEST_METHOD'],
|
|
54
|
+
path: env['PATH_INFO'],
|
|
55
|
+
status: status,
|
|
56
|
+
remote_addr: env['REMOTE_ADDR']
|
|
57
|
+
)
|
|
58
|
+
[status, headers, body]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Initialize MCP Server
|
|
63
|
+
server = MCP::Server.new(
|
|
64
|
+
name: 'inventory-server',
|
|
65
|
+
version: '1.0.0',
|
|
66
|
+
tools: [
|
|
67
|
+
MCPServer::Tools::GetProducts,
|
|
68
|
+
MCPServer::Tools::GetProductById,
|
|
69
|
+
MCPServer::Tools::Seed,
|
|
70
|
+
MCPServer::Tools::Reset,
|
|
71
|
+
MCPServer::Tools::GetRoot,
|
|
72
|
+
MCPServer::Tools::CheckDb
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Create the Fixed Streamable HTTP transport
|
|
77
|
+
transport = FixedStreamableHTTPTransport.new(server)
|
|
78
|
+
server.transport = transport
|
|
79
|
+
|
|
80
|
+
# Create the Rack application
|
|
81
|
+
base_app = proc do |env|
|
|
82
|
+
request = Rack::Request.new(env)
|
|
83
|
+
transport.handle_request(request)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Wrap with JSON logger
|
|
87
|
+
app = JsonRequestLogger.new(base_app, LOGGER)
|
|
88
|
+
|
|
89
|
+
# Run the server using streaming HTTP transport
|
|
90
|
+
if __FILE__ == $PROGRAM_NAME
|
|
91
|
+
begin
|
|
92
|
+
port = ENV.fetch('PORT', 8080).to_i
|
|
93
|
+
LOGGER.info "Starting MCP server: #{server.name} (v#{server.version}) on HTTP port #{port}"
|
|
94
|
+
Rackup::Handler.get('puma').run(app, Port: port, Host: '0.0.0.0', Quiet: true)
|
|
95
|
+
rescue Interrupt
|
|
96
|
+
LOGGER.info 'Shutting down MCP server...'
|
|
97
|
+
transport.close
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
LOGGER.error(message: "Server error: #{e.message}", backtrace: e.backtrace)
|
|
100
|
+
exit 1
|
|
101
|
+
end
|
|
102
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: firestore-https-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- xbill
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: dotenv
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: logger
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.7'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.7'
|
|
40
|
+
description: Exposes Firestore operations (inventory management) as MCP tools over
|
|
41
|
+
stdio.
|
|
42
|
+
email:
|
|
43
|
+
- xbill9@gmail.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- GEMINI.md
|
|
49
|
+
- README.md
|
|
50
|
+
- lib/mcp_server/config.rb
|
|
51
|
+
- lib/mcp_server/database_helper.rb
|
|
52
|
+
- lib/mcp_server/tools/check_db.rb
|
|
53
|
+
- lib/mcp_server/tools/get_product_by_id.rb
|
|
54
|
+
- lib/mcp_server/tools/get_products.rb
|
|
55
|
+
- lib/mcp_server/tools/get_root.rb
|
|
56
|
+
- lib/mcp_server/tools/reset.rb
|
|
57
|
+
- lib/mcp_server/tools/seed.rb
|
|
58
|
+
- main.rb
|
|
59
|
+
homepage: https://github.com/xbill9/gemini-cli-codeassist/firestore-https-ruby
|
|
60
|
+
licenses:
|
|
61
|
+
- MIT
|
|
62
|
+
metadata:
|
|
63
|
+
homepage_uri: https://github.com/xbill9/gemini-cli-codeassist/firestore-https-ruby
|
|
64
|
+
source_code_uri: https://github.com/xbill9/gemini-cli-codeassist/firestore-https-ruby
|
|
65
|
+
bug_tracker_uri: https://github.com/xbill9/gemini-cli-codeassist/firestore-https-ruby/issues
|
|
66
|
+
rubygems_mfa_required: 'true'
|
|
67
|
+
rdoc_options: []
|
|
68
|
+
require_paths:
|
|
69
|
+
- lib
|
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 3.1.0
|
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
requirements: []
|
|
81
|
+
rubygems_version: 3.6.9
|
|
82
|
+
specification_version: 4
|
|
83
|
+
summary: A Model Context Protocol (MCP) server for Google Cloud Firestore.
|
|
84
|
+
test_files: []
|