rails-pg-extras-mcp 0.2.5 → 0.2.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b4509d036ac44d7408f298e5809c97517b938a17b767b495ecc49a6829030dd
4
- data.tar.gz: 4de56739fdb9f9a55246468c49389dd797a48f44fa140fd7928fbe0d67525701
3
+ metadata.gz: 1b7ef071e268c78304c699be7ffef21a2797bff1eac7d16223b8383b7112d755
4
+ data.tar.gz: fb974d7e4ce85b429c8a7b29cbf4c1a2425132afd326a81bc3500d5577c4db81
5
5
  SHA512:
6
- metadata.gz: 748259151e1174a0352136ae9168978b19beea269cae6fb56b72e4afd9a8f07e818394174f26b14e1737f6e4c461de8aa2fa527c476b61f7a0178ab1074d772a
7
- data.tar.gz: 0e4ca375b7e0366cc2a3b08c10915576de3fe97b5fbf5f8ebb545e153ce14298a7d38adb6e6da0c69e1b3aa5056991d343678ebeeb75b52558ecb8566137bfa3
6
+ metadata.gz: 3ad442345f1c4879c0310ebe6f0fbc6f3c431176bfd146fb86cda194f50b34a89599c202d5683cf27a52e5a4e0dac9db76ad08d8a1decb5d97ebce68b91adc57
7
+ data.tar.gz: 22e94431fdad9ba2f87a37ab3741592f8ff3074b254c9e09052a9f2f2a6a0fee463f89afe981fb5777aa745ffe3d1a3b9ba9a2d3d56bfae94fff69c0c199b831
@@ -5,6 +5,7 @@ require "rack"
5
5
  require "ruby-pg-extras"
6
6
  require "rails-pg-extras"
7
7
  require "rails_pg_extras_mcp/version"
8
+ require "rails_pg_extras_mcp/validate_query"
8
9
 
9
10
  SKIP_QUERIES = %i[
10
11
  add_extensions
@@ -65,28 +66,9 @@ class DiagnoseTool < FastMcp::Tool
65
66
  end
66
67
 
67
68
  class ExplainBaseTool < FastMcp::Tool
68
- DENYLIST = %w[
69
- delete,
70
- insert,
71
- update,
72
- truncate,
73
- drop,
74
- alter,
75
- create,
76
- grant,
77
- begin,
78
- commit
79
- ]
80
-
81
- def call(sql_query:)
69
+ def call(sql_query: nil)
82
70
  connection = RailsPgExtras.connection
83
71
 
84
- if DENYLIST.any? { |deny| sql_query.downcase.include?(deny) }
85
- raise "This query is not allowed. It contains a denied keyword. Denylist: #{DENYLIST.join(", ")}"
86
- end
87
-
88
- # Prevent multiple queries in one request
89
- sql_query = sql_query.gsub(/;/, "")
90
72
 
91
73
  connection.execute("BEGIN;")
92
74
  begin
@@ -112,8 +94,14 @@ class ExplainTool < ExplainBaseTool
112
94
  end
113
95
 
114
96
  def call(sql_query:)
115
- if sql_query.downcase.include?("analyze")
116
- raise "This query is not allowed. It contains a denied ANALYZE keyword."
97
+ if sql_query.to_s.empty?
98
+ return "sql_query param is required"
99
+ end
100
+
101
+ begin
102
+ ValidateQuery.new(sql_query).call
103
+ rescue ValidateQuery::InvalidQueryError => e
104
+ return e.message
117
105
  end
118
106
 
119
107
  super(sql_query: "EXPLAIN #{sql_query}")
@@ -132,6 +120,16 @@ class ExplainAnalyzeTool < ExplainBaseTool
132
120
  end
133
121
 
134
122
  def call(sql_query:)
123
+ if sql_query.to_s.empty?
124
+ return "sql_query param is required"
125
+ end
126
+
127
+ begin
128
+ ValidateQuery.new(sql_query).call
129
+ rescue ValidateQuery::InvalidQueryError => e
130
+ return e.message
131
+ end
132
+
135
133
  super(sql_query: "EXPLAIN ANALYZE #{sql_query}")
136
134
  end
137
135
  end
@@ -144,6 +142,10 @@ class IndexInfoTool < FastMcp::Tool
144
142
  end
145
143
 
146
144
  def call(table_name:)
145
+ if table_name.to_s.empty?
146
+ return "table_name param is required"
147
+ end
148
+
147
149
  RailsPgExtras.index_info(args: { table_name: table_name }, in_format: :hash)
148
150
  end
149
151
 
@@ -160,6 +162,10 @@ class TableInfoTool < FastMcp::Tool
160
162
  end
161
163
 
162
164
  def call(table_name:)
165
+ if table_name.to_s.empty?
166
+ return "table_name param is required"
167
+ end
168
+
163
169
  RailsPgExtras.table_info(args: { table_name: table_name }, in_format: :hash)
164
170
  end
165
171
 
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pg_query'
4
+
5
+ class ValidateQuery
6
+ InvalidQueryError = Class.new(StandardError)
7
+
8
+ DENYLIST = %w[
9
+ delete
10
+ insert
11
+ update
12
+ truncate
13
+ drop
14
+ alter
15
+ create
16
+ grant
17
+ begin
18
+ commit
19
+ explain
20
+ analyze
21
+ ].freeze
22
+
23
+ def initialize(sql_query)
24
+ @sql_query = sql_query.to_s.strip
25
+ end
26
+
27
+ def call
28
+ raise InvalidQueryError, "Query is empty" if @sql_query.empty?
29
+
30
+ if DENYLIST.any? { |word| @sql_query.downcase.include?(word) }
31
+ raise InvalidQueryError, "Query contains denied keyword. Denylist: #{DENYLIST.join(', ')}"
32
+ end
33
+
34
+ begin
35
+ tree = PgQuery.parse(@sql_query)
36
+ rescue PgQuery::ParseError => e
37
+ raise InvalidQueryError, "Invalid SQL syntax: #{e.message}"
38
+ end
39
+
40
+ if tree.tree.stmts.size > 1
41
+ raise InvalidQueryError, "Multiple SQL statements are not allowed"
42
+ end
43
+
44
+ unless tree.tree.stmts.all? { |stmt| stmt.stmt.select_stmt }
45
+ raise InvalidQueryError, "Only SELECT statements are allowed"
46
+ end
47
+
48
+ true
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsPgExtrasMcp
4
- VERSION = "0.2.5"
4
+ VERSION = "0.2.6"
5
5
  end
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.add_dependency "rails-pg-extras", "~> 5.6.12"
19
19
  s.add_dependency "rails"
20
20
  s.add_dependency "fast-mcp"
21
+ s.add_dependency "pg_query"
21
22
  s.add_development_dependency "rake"
22
23
  s.add_development_dependency "rspec"
23
24
  s.add_development_dependency "rufo"
data/spec/smoke_spec.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "spec_helper"
4
- require "rails-pg-extras"
4
+ require "rails-pg-extras-mcp"
5
5
 
6
6
  describe RailsPgExtrasMcp do
7
7
  it "works" do
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "rails-pg-extras-mcp"
5
+
6
+ RSpec.describe ValidateQuery do
7
+ subject { described_class.new(query) }
8
+
9
+ describe "#call" do
10
+ context "with a valid SELECT query" do
11
+ let(:query) { "SELECT * FROM users" }
12
+
13
+ it "returns true" do
14
+ expect(subject.call).to eq(true)
15
+ end
16
+ end
17
+
18
+ context "with nested SELECT statements" do
19
+ let(:query) { "SELECT * FROM users WHERE id IN (SELECT user_id FROM posts WHERE published = true)" }
20
+
21
+ it "returns true" do
22
+ expect(subject.call).to eq(true)
23
+ end
24
+ end
25
+
26
+ context "with multiple statements" do
27
+ let(:query) { "SELECT * FROM users; SELECT * FROM posts" }
28
+
29
+ it "raises an error" do
30
+ expect { subject.call }.to raise_error(ValidateQuery::InvalidQueryError, /Multiple SQL statements/)
31
+ end
32
+ end
33
+
34
+ context "with a denied keyword" do
35
+ let(:query) { "DROP TABLE users" }
36
+
37
+ it "raises an error" do
38
+ expect { subject.call }.to raise_error(ValidateQuery::InvalidQueryError, /denied keyword/)
39
+ end
40
+ end
41
+
42
+ context "with a non-SELECT query" do
43
+ let(:query) { "EXPLAIN SELECT * FROM users" }
44
+
45
+ it "raises an error" do
46
+ expect { subject.call }.to raise_error(ValidateQuery::InvalidQueryError)
47
+ end
48
+ end
49
+
50
+ context "with invalid SQL syntax" do
51
+ let(:query) { "SELECT FROM WHERE" }
52
+
53
+ it "raises an error" do
54
+ expect { subject.call }.to raise_error(ValidateQuery::InvalidQueryError, /Invalid SQL syntax/)
55
+ end
56
+ end
57
+
58
+ context "with an empty query" do
59
+ let(:query) { " " }
60
+
61
+ it "raises an error" do
62
+ expect { subject.call }.to raise_error(ValidateQuery::InvalidQueryError, /Query is empty/)
63
+ end
64
+ end
65
+ end
66
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-pg-extras-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - pawurb
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-20 00:00:00.000000000 Z
11
+ date: 2025-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails-pg-extras
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pg_query
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -110,11 +124,13 @@ files:
110
124
  - README.md
111
125
  - Rakefile
112
126
  - lib/rails-pg-extras-mcp.rb
127
+ - lib/rails_pg_extras_mcp/validate_query.rb
113
128
  - lib/rails_pg_extras_mcp/version.rb
114
129
  - pg-extras-mcp.png
115
130
  - rails-pg-extras-mcp.gemspec
116
131
  - spec/smoke_spec.rb
117
132
  - spec/spec_helper.rb
133
+ - spec/validate_query_spec.rb
118
134
  homepage: http://github.com/pawurb/rails-pg-extras-mcp
119
135
  licenses:
120
136
  - MIT
@@ -142,3 +158,4 @@ summary: MCP interface for rails-pg-extras
142
158
  test_files:
143
159
  - spec/smoke_spec.rb
144
160
  - spec/spec_helper.rb
161
+ - spec/validate_query_spec.rb