firestore-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/GEMINI.md +74 -0
- data/README.md +84 -0
- data/lib/check_db_tool.rb +21 -0
- data/lib/firestore_client.rb +82 -0
- data/lib/firestore_seeder.rb +64 -0
- data/lib/get_product_by_id_tool.rb +43 -0
- data/lib/get_products_tool.rb +24 -0
- data/lib/get_root_tool.rb +18 -0
- data/lib/greet_tool.rb +33 -0
- data/lib/inventory_data.rb +19 -0
- data/lib/reset_tool.rb +27 -0
- data/lib/seed_tool.rb +27 -0
- data/main.rb +60 -0
- metadata +86 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d2a58dc40cd41de1247e8fe51f11b7be2599e14ae664a0a31f5a2795e700e362
|
|
4
|
+
data.tar.gz: de4d5c7f6e3c82e438dcadfb399b0ee278b77aa7b6449dac9891e948d2f16188
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0f70c698bc793104fddba57cf80d7bcbf9cd8e82bea532af4a392bd25ebba24866e7a4d5db1776cf808f5350c38c5c204d14eae4899a3d69e51be17b2cea5af1
|
|
7
|
+
data.tar.gz: 53f7c5d2ba45e26ed422d57f9ededb1eb4757795c1779118907b294e61c8882f743e7c9a4f1cf4ea508face2c2c45ce0df1864a03f590e9efc3848051cc55e8f
|
data/GEMINI.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
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 (like `greet`) over standard input/output (stdio) 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
|
+
|
|
16
|
+
## Project Structure
|
|
17
|
+
|
|
18
|
+
* `main.rb`: Main entry point (Ruby). Registers tools and starts the stdio transport.
|
|
19
|
+
* `lib/`: Contains tool definitions and the Firestore client.
|
|
20
|
+
* `firestore_client.rb`: Singleton client for Firestore interactions.
|
|
21
|
+
* `*_tool.rb`: Individual tool implementations.
|
|
22
|
+
* `Makefile`: Development shortcuts (test, lint, clean).
|
|
23
|
+
|
|
24
|
+
## Ruby MCP Best Practices
|
|
25
|
+
|
|
26
|
+
* **Logging:** Always log to `stderr` (e.g., `Logger.new($stderr)`). `stdout` is exclusively used for MCP JSON-RPC communication.
|
|
27
|
+
* **Tool Definition:**
|
|
28
|
+
* Inherit from `MCP::Tool`.
|
|
29
|
+
* Define `description` and `input_schema`.
|
|
30
|
+
* Implement the logic in a class method `self.call`.
|
|
31
|
+
* Return an `MCP::Tool::Response` containing an array of content blocks (e.g., `{ type: 'text', text: '...' }`).
|
|
32
|
+
* **Modularity:** Keep tools in separate files under `lib/` to maintain a clean `main.rb`.
|
|
33
|
+
* **Error Handling:** Wrap tool logic in `begin...rescue` blocks to return meaningful error messages to the client instead of crashing the server.
|
|
34
|
+
* **Environment Variables:** Use `dotenv` for local development. Ensure sensitive credentials are never committed.
|
|
35
|
+
|
|
36
|
+
## Firestore Integration
|
|
37
|
+
|
|
38
|
+
* **Client:** Uses the `google-cloud-firestore` gem.
|
|
39
|
+
* **Collection:** The primary collection used is `inventory`.
|
|
40
|
+
* **Schema (Inventory):**
|
|
41
|
+
* `name` (String)
|
|
42
|
+
* `price` (Number)
|
|
43
|
+
* `quantity` (Number)
|
|
44
|
+
* `imgfile` (String)
|
|
45
|
+
* `timestamp` (Timestamp)
|
|
46
|
+
* `actualdateadded` (Timestamp)
|
|
47
|
+
* **Operations:**
|
|
48
|
+
* `seed`: Populates the database with sample data.
|
|
49
|
+
* `reset`: Clears the `inventory` collection.
|
|
50
|
+
* `get_products`: Retrieves all items.
|
|
51
|
+
* `get_product_by_id`: Retrieves a specific item by document ID.
|
|
52
|
+
|
|
53
|
+
## Development Setup
|
|
54
|
+
|
|
55
|
+
1. **Install Dependencies:**
|
|
56
|
+
```bash
|
|
57
|
+
bundle install
|
|
58
|
+
```
|
|
59
|
+
2. **Environment Variables:** Create a `.env` file with necessary Google Cloud credentials if not using default application credentials.
|
|
60
|
+
|
|
61
|
+
## Running the Server
|
|
62
|
+
|
|
63
|
+
The server is configured to run using the `stdio` transport.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bundle exec ruby main.rb
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
*Note: Since this is an MCP server running over stdio, it is typically not run directly by a human but rather spawned by an MCP client.*
|
|
70
|
+
|
|
71
|
+
## Ruby MCP Developer Resources
|
|
72
|
+
|
|
73
|
+
* **MCP Ruby SDK (GitHub):** [https://github.com/modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk)
|
|
74
|
+
* **Model Context Protocol Documentation:** [https://modelcontextprotocol.io/](https://modelcontextprotocol.io/)
|
data/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
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 standard input/output (stdio) 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
|
+
* **`greet`**: A simple echo utility for testing basic tool invocation.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
* **Ruby 3.0+**
|
|
22
|
+
* **Google Cloud Project**: You need a Google Cloud project with Firestore enabled.
|
|
23
|
+
* **Authentication**: The environment where this server runs must be authenticated with Google Cloud.
|
|
24
|
+
* Locally, you can use `gcloud auth application-default login`.
|
|
25
|
+
* Or set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to the path of your service account key JSON file.
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
1. **Clone the repository**:
|
|
30
|
+
```bash
|
|
31
|
+
git clone <your-repo-url>
|
|
32
|
+
cd firestore-stdio-ruby
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. **Install Dependencies**:
|
|
36
|
+
```bash
|
|
37
|
+
bundle install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. **Configure Environment**:
|
|
41
|
+
Create a `.env` file if you need to load specific environment variables (handled by `dotenv`).
|
|
42
|
+
|
|
43
|
+
## Running the Server
|
|
44
|
+
|
|
45
|
+
This server communicates over **stdio**. It is not meant to be run manually in a terminal for human interaction, but rather spawned by an MCP client.
|
|
46
|
+
|
|
47
|
+
To test that it starts up correctly (you'll see a log message on stderr, but it will hang waiting for stdin):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bundle exec ruby main.rb
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Integration with MCP Clients
|
|
54
|
+
|
|
55
|
+
Configure your MCP client to run the server. For example:
|
|
56
|
+
|
|
57
|
+
**Command:** `bundle`
|
|
58
|
+
**Args:** `['exec', 'ruby', '/path/to/firestore-stdio-ruby/main.rb']`
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
### Testing
|
|
63
|
+
|
|
64
|
+
Run the test suite using RSpec:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bundle exec rspec
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Linting
|
|
71
|
+
|
|
72
|
+
Check code style with RuboCop:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
bundle exec rubocop
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Building the Gem
|
|
79
|
+
|
|
80
|
+
This project can also be built as a Ruby gem:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
gem build firestore-mcp-server.gemspec
|
|
84
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require_relative 'firestore_client'
|
|
5
|
+
|
|
6
|
+
# Tool to check if the Firestore database is accessible.
|
|
7
|
+
class CheckDbTool < MCP::Tool
|
|
8
|
+
tool_name 'check_db'
|
|
9
|
+
description 'Checks if the inventory database is running.'
|
|
10
|
+
input_schema(type: 'object', properties: {})
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def call(*)
|
|
14
|
+
client = FirestoreClient.instance
|
|
15
|
+
running = client.db_running
|
|
16
|
+
MCP::Tool::Response.new(
|
|
17
|
+
[{ type: 'text', text: "Database running: #{running}" }]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'google/cloud/firestore'
|
|
4
|
+
require 'dotenv/load'
|
|
5
|
+
require_relative 'inventory_data'
|
|
6
|
+
require_relative 'firestore_seeder'
|
|
7
|
+
|
|
8
|
+
# Client for interacting with Google Cloud Firestore for inventory management.
|
|
9
|
+
class FirestoreClient
|
|
10
|
+
include InventoryData
|
|
11
|
+
include FirestoreSeeder
|
|
12
|
+
|
|
13
|
+
attr_reader :client, :db_running
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
# Assuming environment variables for auth are set or will be handled by library defaults
|
|
17
|
+
@client = Google::Cloud::Firestore.new
|
|
18
|
+
@db_running = true
|
|
19
|
+
LOGGER.info 'Firestore client initialized' if defined?(LOGGER)
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
LOGGER.error "Failed to initialize Firestore: #{e.message}" if defined?(LOGGER)
|
|
22
|
+
@db_running = false
|
|
23
|
+
@client = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.instance
|
|
27
|
+
@instance ||= new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def products
|
|
31
|
+
return [] unless @db_running
|
|
32
|
+
|
|
33
|
+
products = @client.col('inventory').get
|
|
34
|
+
products.map { |doc| doc_to_product(doc) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def product_by_id(id)
|
|
38
|
+
return nil unless @db_running
|
|
39
|
+
|
|
40
|
+
doc = @client.col('inventory').doc(id).get
|
|
41
|
+
return nil unless doc.exists?
|
|
42
|
+
|
|
43
|
+
doc_to_product(doc)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def reset
|
|
47
|
+
return unless @db_running
|
|
48
|
+
|
|
49
|
+
clean_firestore_collection
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def doc_to_product(doc)
|
|
55
|
+
data = doc.data
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
id: doc.document_id, name: data[:name], price: data[:price],
|
|
59
|
+
quantity: data[:quantity], imgfile: data[:imgfile],
|
|
60
|
+
timestamp: data[:timestamp], actualdateadded: data[:actualdateadded]
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def clean_firestore_collection
|
|
65
|
+
LOGGER.info 'Cleaning Firestore collection...' if defined?(LOGGER)
|
|
66
|
+
snapshot = @client.col('inventory').get
|
|
67
|
+
batch_delete(snapshot) unless snapshot.empty?
|
|
68
|
+
LOGGER.info 'Firestore collection cleaned.' if defined?(LOGGER)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def batch_delete(snapshot)
|
|
72
|
+
batch = @client.batch
|
|
73
|
+
snapshot.each_with_index do |doc, i|
|
|
74
|
+
batch.delete(doc.ref)
|
|
75
|
+
if ((i + 1) % 400).zero?
|
|
76
|
+
batch.commit
|
|
77
|
+
batch = @client.batch
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
batch.commit unless (snapshot.size % 400).zero?
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'inventory_data'
|
|
4
|
+
|
|
5
|
+
# Module for seeding Firestore collection with sample data.
|
|
6
|
+
module FirestoreSeeder
|
|
7
|
+
include InventoryData
|
|
8
|
+
|
|
9
|
+
def seed
|
|
10
|
+
return unless @db_running
|
|
11
|
+
|
|
12
|
+
seed_old_products
|
|
13
|
+
seed_recent_products
|
|
14
|
+
seed_out_of_stock_products
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def seed_old_products
|
|
20
|
+
OLD_PRODUCTS.each do |name|
|
|
21
|
+
data = {
|
|
22
|
+
name: name, price: rand(1..10), quantity: rand(1..500),
|
|
23
|
+
imgfile: "product-images/#{name.gsub(/\s/, '').downcase}.png",
|
|
24
|
+
timestamp: Time.now - rand(7_776_000_000..31_536_000_000), actualdateadded: Time.now
|
|
25
|
+
}
|
|
26
|
+
LOGGER.info "⬆️ Adding: #{name}" if defined?(LOGGER)
|
|
27
|
+
add_or_update_firestore(data)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def seed_recent_products
|
|
32
|
+
RECENT_PRODUCTS.each do |name|
|
|
33
|
+
data = {
|
|
34
|
+
name: name, price: rand(1..10), quantity: rand(1..100),
|
|
35
|
+
imgfile: "product-images/#{name.gsub(/\s/, '').downcase}.png",
|
|
36
|
+
timestamp: Time.now - rand(1..518_400_000), actualdateadded: Time.now
|
|
37
|
+
}
|
|
38
|
+
LOGGER.info "🆕 Adding: #{name}" if defined?(LOGGER)
|
|
39
|
+
add_or_update_firestore(data)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def seed_out_of_stock_products
|
|
44
|
+
RECENT_PRODUCTS_OUT_OF_STOCK.each do |name|
|
|
45
|
+
data = {
|
|
46
|
+
name: name, price: rand(1..10), quantity: 0,
|
|
47
|
+
imgfile: "product-images/#{name.gsub(/\s/, '').downcase}.png",
|
|
48
|
+
timestamp: Time.now - rand(1..518_400_000), actualdateadded: Time.now
|
|
49
|
+
}
|
|
50
|
+
LOGGER.info "😱 Adding OOS: #{name}" if defined?(LOGGER)
|
|
51
|
+
add_or_update_firestore(data)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def add_or_update_firestore(product)
|
|
56
|
+
query = @client.col('inventory').where('name', '=', product[:name]).get
|
|
57
|
+
|
|
58
|
+
if query.empty?
|
|
59
|
+
@client.col('inventory').add(product)
|
|
60
|
+
else
|
|
61
|
+
query.each { |doc| doc.ref.update(product) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require_relative 'firestore_client'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
# Tool to retrieve a specific product by its ID from Firestore.
|
|
8
|
+
class GetProductByIdTool < MCP::Tool
|
|
9
|
+
tool_name 'get_product_by_id'
|
|
10
|
+
description 'Get a single product from the inventory database by its ID'
|
|
11
|
+
input_schema(
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
id: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'The ID of the product to get'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
required: ['id']
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
def call(id:)
|
|
24
|
+
client = FirestoreClient.instance
|
|
25
|
+
return db_not_running_response unless client.db_running
|
|
26
|
+
|
|
27
|
+
product = client.product_by_id(id)
|
|
28
|
+
return product_not_found_response if product.nil?
|
|
29
|
+
|
|
30
|
+
MCP::Tool::Response.new([{ type: 'text', text: JSON.pretty_generate(product) }])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def db_not_running_response
|
|
36
|
+
MCP::Tool::Response.new([{ type: 'text', text: 'Inventory database is not running.' }])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def product_not_found_response
|
|
40
|
+
MCP::Tool::Response.new([{ type: 'text', text: 'Product not found.' }])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require_relative 'firestore_client'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
# Tool to retrieve all products from Firestore.
|
|
8
|
+
class GetProductsTool < MCP::Tool
|
|
9
|
+
tool_name 'get_products'
|
|
10
|
+
description 'Get a list of all products from the inventory database'
|
|
11
|
+
input_schema(type: 'object', properties: {})
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def call
|
|
15
|
+
client = FirestoreClient.instance
|
|
16
|
+
if client.db_running
|
|
17
|
+
products = client.products
|
|
18
|
+
[{ type: 'text', text: products.to_json }]
|
|
19
|
+
else
|
|
20
|
+
[{ type: 'text', text: 'Inventory database is not running.' }]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
# Tool to get a greeting from the API.
|
|
6
|
+
class GetRootTool < MCP::Tool
|
|
7
|
+
tool_name 'get_root'
|
|
8
|
+
description 'Get a greeting from the Cymbal Superstore Inventory API.'
|
|
9
|
+
input_schema(type: 'object', properties: {})
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def 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
|
data/lib/greet_tool.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
# Define the greet tool
|
|
6
|
+
class GreetTool < MCP::Tool
|
|
7
|
+
tool_name 'greet'
|
|
8
|
+
description 'Get a greeting from a local stdio server.'
|
|
9
|
+
input_schema(
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
message: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
description: 'The message to repeat.'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
required: ['message']
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def call(message:)
|
|
22
|
+
LOGGER.info "GreetTool called with message: #{message}" if defined?(LOGGER)
|
|
23
|
+
MCP::Tool::Response.new(
|
|
24
|
+
[
|
|
25
|
+
{
|
|
26
|
+
type: 'text',
|
|
27
|
+
text: message
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InventoryData
|
|
4
|
+
OLD_PRODUCTS = [
|
|
5
|
+
'Apples', 'Bananas', 'Milk', 'Whole Wheat Bread', 'Eggs', 'Cheddar Cheese',
|
|
6
|
+
'Whole Chicken', 'Rice', 'Black Beans', 'Bottled Water', 'Apple Juice',
|
|
7
|
+
'Cola', 'Coffee Beans', 'Green Tea', 'Watermelon', 'Broccoli',
|
|
8
|
+
'Jasmine Rice', 'Yogurt', 'Beef', 'Shrimp', 'Walnuts',
|
|
9
|
+
'Sunflower Seeds', 'Fresh Basil', 'Cinnamon'
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
RECENT_PRODUCTS = [
|
|
13
|
+
'Parmesan Crisps', 'Pineapple Kombucha', 'Maple Almond Butter',
|
|
14
|
+
'Mint Chocolate Cookies', 'White Chocolate Caramel Corn', 'Acai Smoothie Packs',
|
|
15
|
+
'Smores Cereal', 'Peanut Butter and Jelly Cups'
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
RECENT_PRODUCTS_OUT_OF_STOCK = ['Wasabi Party Mix', 'Jalapeno Seasoning'].freeze
|
|
19
|
+
end
|
data/lib/reset_tool.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require_relative 'firestore_client'
|
|
5
|
+
|
|
6
|
+
# Tool to clear all products from the Firestore collection.
|
|
7
|
+
class ResetTool < MCP::Tool
|
|
8
|
+
tool_name 'reset'
|
|
9
|
+
description 'Clears all products from the inventory database.'
|
|
10
|
+
input_schema(type: 'object', properties: {})
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def call(*)
|
|
14
|
+
client = FirestoreClient.instance
|
|
15
|
+
unless client.db_running
|
|
16
|
+
return MCP::Tool::Response.new(
|
|
17
|
+
[{ type: 'text', text: 'Inventory database is not running.' }]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
client.reset
|
|
22
|
+
MCP::Tool::Response.new(
|
|
23
|
+
[{ type: 'text', text: 'Database reset successfully.' }]
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/seed_tool.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require_relative 'firestore_client'
|
|
5
|
+
|
|
6
|
+
# Tool to seed the Firestore collection with sample products.
|
|
7
|
+
class SeedTool < MCP::Tool
|
|
8
|
+
tool_name 'seed'
|
|
9
|
+
description 'Seed the inventory database with products.'
|
|
10
|
+
input_schema(type: 'object', properties: {})
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def call(*)
|
|
14
|
+
client = FirestoreClient.instance
|
|
15
|
+
unless client.db_running
|
|
16
|
+
return MCP::Tool::Response.new(
|
|
17
|
+
[{ type: 'text', text: 'Inventory database is not running.' }]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
client.seed
|
|
22
|
+
MCP::Tool::Response.new(
|
|
23
|
+
[{ type: 'text', text: 'Database seeded successfully.' }]
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/main.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'mcp'
|
|
5
|
+
require 'logger'
|
|
6
|
+
require 'json'
|
|
7
|
+
require_relative 'lib/greet_tool'
|
|
8
|
+
require_relative 'lib/get_products_tool'
|
|
9
|
+
require_relative 'lib/get_product_by_id_tool'
|
|
10
|
+
require_relative 'lib/seed_tool'
|
|
11
|
+
require_relative 'lib/reset_tool'
|
|
12
|
+
require_relative 'lib/check_db_tool'
|
|
13
|
+
require_relative 'lib/get_root_tool'
|
|
14
|
+
require_relative 'lib/firestore_client'
|
|
15
|
+
|
|
16
|
+
# Set up logging to stderr
|
|
17
|
+
# We use stderr because stdout is used for MCP protocol communication
|
|
18
|
+
LOGGER = Logger.new($stderr)
|
|
19
|
+
LOGGER.formatter = proc do |severity, datetime, progname, msg|
|
|
20
|
+
"#{({
|
|
21
|
+
severity: severity,
|
|
22
|
+
timestamp: datetime.iso8601,
|
|
23
|
+
progname: progname,
|
|
24
|
+
message: msg
|
|
25
|
+
}.to_json)}\n"
|
|
26
|
+
end
|
|
27
|
+
LOGGER.level = Logger::INFO
|
|
28
|
+
|
|
29
|
+
# Initialize Firestore Client
|
|
30
|
+
FirestoreClient.instance
|
|
31
|
+
|
|
32
|
+
# Initialize MCP Server
|
|
33
|
+
server = MCP::Server.new(
|
|
34
|
+
name: 'inventory-server',
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
tools: [
|
|
37
|
+
GreetTool,
|
|
38
|
+
GetProductsTool,
|
|
39
|
+
GetProductByIdTool,
|
|
40
|
+
SeedTool,
|
|
41
|
+
ResetTool,
|
|
42
|
+
CheckDbTool,
|
|
43
|
+
GetRootTool
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Run the server using stdio transport
|
|
48
|
+
if __FILE__ == $PROGRAM_NAME
|
|
49
|
+
begin
|
|
50
|
+
LOGGER.info "Starting MCP server: #{server.name} (v#{server.version})"
|
|
51
|
+
transport = MCP::Server::Transports::StdioTransport.new(server)
|
|
52
|
+
transport.open
|
|
53
|
+
rescue Interrupt
|
|
54
|
+
LOGGER.info 'Shutting down MCP server...'
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
LOGGER.error "Server error: #{e.message}"
|
|
57
|
+
LOGGER.error e.backtrace.join("\n")
|
|
58
|
+
exit 1
|
|
59
|
+
end
|
|
60
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: firestore-mcp-server
|
|
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/check_db_tool.rb
|
|
51
|
+
- lib/firestore_client.rb
|
|
52
|
+
- lib/firestore_seeder.rb
|
|
53
|
+
- lib/get_product_by_id_tool.rb
|
|
54
|
+
- lib/get_products_tool.rb
|
|
55
|
+
- lib/get_root_tool.rb
|
|
56
|
+
- lib/greet_tool.rb
|
|
57
|
+
- lib/inventory_data.rb
|
|
58
|
+
- lib/reset_tool.rb
|
|
59
|
+
- lib/seed_tool.rb
|
|
60
|
+
- main.rb
|
|
61
|
+
homepage: https://github.com/xbill9/gemini-cli-codeassist/firestore-mcp-server
|
|
62
|
+
licenses:
|
|
63
|
+
- MIT
|
|
64
|
+
metadata:
|
|
65
|
+
homepage_uri: https://github.com/xbill9/gemini-cli-codeassist/firestore-mcp-server
|
|
66
|
+
source_code_uri: https://github.com/xbill9/gemini-cli-codeassist/firestore-mcp-server
|
|
67
|
+
bug_tracker_uri: https://github.com/xbill9/gemini-cli-codeassist/firestore-mcp-server/issues
|
|
68
|
+
rubygems_mfa_required: 'true'
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 3.1.0
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 3.6.9
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: A Model Context Protocol (MCP) server for Google Cloud Firestore.
|
|
86
|
+
test_files: []
|