rails-pg-extras-mcp 0.2.4 → 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: c325326b879c8e447cb8865244a6dba60d3acfe611720bc838c0d71b2d77be91
4
- data.tar.gz: a35cca49706ea0e8ecc7143bdbebe64664ddcdab223221b37bae97c228558ed5
3
+ metadata.gz: 1b7ef071e268c78304c699be7ffef21a2797bff1eac7d16223b8383b7112d755
4
+ data.tar.gz: fb974d7e4ce85b429c8a7b29cbf4c1a2425132afd326a81bc3500d5577c4db81
5
5
  SHA512:
6
- metadata.gz: 6b9af0e9a1c41158e7b231060cc56f8ea97f7eba97674f05446fac42b8299c6ab17ef72b1260a69dc9d1d6ff4cefb364490bf0b6bc65020286676935b786479a
7
- data.tar.gz: 5d55fc2ec81088a21749e56b55c07e50cdd19ce187faceeecfde4933beaf3cd89065a7a8196df997d7de4e073f3f48834ddabf8f2bc1e3b0b771dd7b062f48d6
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,31 +66,19 @@ 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
72
 
88
- connection.execute("BEGIN")
89
- result = connection.execute("#{sql_query}")
90
- connection.execute("ROLLBACK")
91
-
92
- result.to_a
73
+ connection.execute("BEGIN;")
74
+ begin
75
+ result = connection.execute("#{sql_query}")
76
+ connection.execute("ROLLBACK;")
77
+ result.to_a
78
+ rescue => e
79
+ connection.execute("ROLLBACK;") rescue nil
80
+ raise e
81
+ end
93
82
  end
94
83
  end
95
84
 
@@ -105,8 +94,14 @@ class ExplainTool < ExplainBaseTool
105
94
  end
106
95
 
107
96
  def call(sql_query:)
108
- if sql_query.downcase.include?("analyze")
109
- 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
110
105
  end
111
106
 
112
107
  super(sql_query: "EXPLAIN #{sql_query}")
@@ -125,6 +120,16 @@ class ExplainAnalyzeTool < ExplainBaseTool
125
120
  end
126
121
 
127
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
+
128
133
  super(sql_query: "EXPLAIN ANALYZE #{sql_query}")
129
134
  end
130
135
  end
@@ -137,6 +142,10 @@ class IndexInfoTool < FastMcp::Tool
137
142
  end
138
143
 
139
144
  def call(table_name:)
145
+ if table_name.to_s.empty?
146
+ return "table_name param is required"
147
+ end
148
+
140
149
  RailsPgExtras.index_info(args: { table_name: table_name }, in_format: :hash)
141
150
  end
142
151
 
@@ -153,6 +162,10 @@ class TableInfoTool < FastMcp::Tool
153
162
  end
154
163
 
155
164
  def call(table_name:)
165
+ if table_name.to_s.empty?
166
+ return "table_name param is required"
167
+ end
168
+
156
169
  RailsPgExtras.table_info(args: { table_name: table_name }, in_format: :hash)
157
170
  end
158
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.4"
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.4
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-14 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