parse-stack-next 4.5.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/.bundle/config +2 -0
- data/.env.sample +112 -0
- data/.env.test +10 -0
- data/.github/workflows/ruby.yml +36 -0
- data/.gitignore +49 -0
- data/.ruby-version +1 -0
- data/.solargraph.yml +22 -0
- data/CHANGELOG.md +5816 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +175 -0
- data/LICENSE.txt +23 -0
- data/Makefile +63 -0
- data/README.md +5655 -0
- data/Rakefile +573 -0
- data/bin/console +38 -0
- data/bin/parse-console +136 -0
- data/bin/server +17 -0
- data/bin/setup +7 -0
- data/config/parse-config.json +12 -0
- data/docs/TEST_SERVER.md +271 -0
- data/docs/_config.yml +1 -0
- data/docs/mcp_guide.md +3484 -0
- data/docs/mongodb_direct_guide.md +1348 -0
- data/docs/mongodb_index_optimization_guide.md +631 -0
- data/examples/transaction_example.rb +219 -0
- data/lib/parse/acl_scope.rb +728 -0
- data/lib/parse/agent/cancellation_token.rb +80 -0
- data/lib/parse/agent/constraint_translator.rb +480 -0
- data/lib/parse/agent/describe.rb +420 -0
- data/lib/parse/agent/errors.rb +133 -0
- data/lib/parse/agent/mcp_client.rb +557 -0
- data/lib/parse/agent/mcp_dispatcher.rb +1023 -0
- data/lib/parse/agent/mcp_rack_app.rb +1143 -0
- data/lib/parse/agent/mcp_server.rb +376 -0
- data/lib/parse/agent/metadata_audit.rb +259 -0
- data/lib/parse/agent/metadata_dsl.rb +733 -0
- data/lib/parse/agent/metadata_registry.rb +794 -0
- data/lib/parse/agent/pipeline_validator.rb +82 -0
- data/lib/parse/agent/prompts.rb +351 -0
- data/lib/parse/agent/rate_limiter.rb +158 -0
- data/lib/parse/agent/relation_graph.rb +162 -0
- data/lib/parse/agent/result_formatter.rb +453 -0
- data/lib/parse/agent/tools.rb +5489 -0
- data/lib/parse/agent.rb +3249 -0
- data/lib/parse/api/aggregate.rb +79 -0
- data/lib/parse/api/all.rb +26 -0
- data/lib/parse/api/analytics.rb +18 -0
- data/lib/parse/api/batch.rb +33 -0
- data/lib/parse/api/cloud_functions.rb +58 -0
- data/lib/parse/api/config.rb +125 -0
- data/lib/parse/api/files.rb +29 -0
- data/lib/parse/api/hooks.rb +117 -0
- data/lib/parse/api/objects.rb +146 -0
- data/lib/parse/api/path_segment.rb +75 -0
- data/lib/parse/api/push.rb +20 -0
- data/lib/parse/api/schema.rb +49 -0
- data/lib/parse/api/server.rb +50 -0
- data/lib/parse/api/sessions.rb +24 -0
- data/lib/parse/api/users.rb +250 -0
- data/lib/parse/atlas_search/index_manager.rb +353 -0
- data/lib/parse/atlas_search/result.rb +204 -0
- data/lib/parse/atlas_search/search_builder.rb +604 -0
- data/lib/parse/atlas_search/session.rb +253 -0
- data/lib/parse/atlas_search.rb +995 -0
- data/lib/parse/client/authentication.rb +97 -0
- data/lib/parse/client/batch.rb +234 -0
- data/lib/parse/client/body_builder.rb +240 -0
- data/lib/parse/client/caching.rb +203 -0
- data/lib/parse/client/logging.rb +293 -0
- data/lib/parse/client/profiling.rb +181 -0
- data/lib/parse/client/protocol.rb +91 -0
- data/lib/parse/client/request.rb +233 -0
- data/lib/parse/client/response.rb +208 -0
- data/lib/parse/client.rb +1104 -0
- data/lib/parse/clp_scope.rb +361 -0
- data/lib/parse/live_query/circuit_breaker.rb +256 -0
- data/lib/parse/live_query/client.rb +1001 -0
- data/lib/parse/live_query/configuration.rb +224 -0
- data/lib/parse/live_query/event.rb +115 -0
- data/lib/parse/live_query/event_queue.rb +272 -0
- data/lib/parse/live_query/health_monitor.rb +214 -0
- data/lib/parse/live_query/logging.rb +149 -0
- data/lib/parse/live_query/subscription.rb +294 -0
- data/lib/parse/live_query.rb +163 -0
- data/lib/parse/lookup_rewriter.rb +445 -0
- data/lib/parse/model/acl.rb +968 -0
- data/lib/parse/model/associations/belongs_to.rb +275 -0
- data/lib/parse/model/associations/collection_proxy.rb +435 -0
- data/lib/parse/model/associations/has_many.rb +597 -0
- data/lib/parse/model/associations/has_one.rb +158 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +134 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +177 -0
- data/lib/parse/model/bytes.rb +62 -0
- data/lib/parse/model/classes/audience.rb +262 -0
- data/lib/parse/model/classes/installation.rb +363 -0
- data/lib/parse/model/classes/job_schedule.rb +153 -0
- data/lib/parse/model/classes/job_status.rb +264 -0
- data/lib/parse/model/classes/product.rb +75 -0
- data/lib/parse/model/classes/push_status.rb +263 -0
- data/lib/parse/model/classes/role.rb +751 -0
- data/lib/parse/model/classes/session.rb +201 -0
- data/lib/parse/model/classes/user.rb +943 -0
- data/lib/parse/model/clp.rb +544 -0
- data/lib/parse/model/core/actions.rb +1268 -0
- data/lib/parse/model/core/builder.rb +139 -0
- data/lib/parse/model/core/create_lock.rb +386 -0
- data/lib/parse/model/core/describe.rb +382 -0
- data/lib/parse/model/core/enhanced_change_tracking.rb +159 -0
- data/lib/parse/model/core/errors.rb +38 -0
- data/lib/parse/model/core/fetching.rb +566 -0
- data/lib/parse/model/core/field_guards.rb +220 -0
- data/lib/parse/model/core/indexing.rb +382 -0
- data/lib/parse/model/core/parse_reference.rb +407 -0
- data/lib/parse/model/core/properties.rb +809 -0
- data/lib/parse/model/core/querying.rb +491 -0
- data/lib/parse/model/core/schema.rb +202 -0
- data/lib/parse/model/core/search_indexing.rb +174 -0
- data/lib/parse/model/date.rb +88 -0
- data/lib/parse/model/email.rb +213 -0
- data/lib/parse/model/file.rb +527 -0
- data/lib/parse/model/geojson.rb +271 -0
- data/lib/parse/model/geopoint.rb +261 -0
- data/lib/parse/model/model.rb +260 -0
- data/lib/parse/model/object.rb +2068 -0
- data/lib/parse/model/phone.rb +520 -0
- data/lib/parse/model/pointer.rb +443 -0
- data/lib/parse/model/polygon.rb +406 -0
- data/lib/parse/model/push.rb +975 -0
- data/lib/parse/model/shortnames.rb +8 -0
- data/lib/parse/model/time_zone.rb +141 -0
- data/lib/parse/model/validations/uniqueness_validator.rb +97 -0
- data/lib/parse/model/validations.rb +96 -0
- data/lib/parse/mongodb.rb +2300 -0
- data/lib/parse/pipeline_security.rb +554 -0
- data/lib/parse/query/constraint.rb +198 -0
- data/lib/parse/query/constraints.rb +3279 -0
- data/lib/parse/query/cursor.rb +434 -0
- data/lib/parse/query/n_plus_one_detector.rb +445 -0
- data/lib/parse/query/operation.rb +104 -0
- data/lib/parse/query/ordering.rb +66 -0
- data/lib/parse/query.rb +7028 -0
- data/lib/parse/schema/index_migrator.rb +291 -0
- data/lib/parse/schema/search_index_migrator.rb +289 -0
- data/lib/parse/schema.rb +494 -0
- data/lib/parse/stack/generators/rails.rb +40 -0
- data/lib/parse/stack/generators/templates/model.erb +51 -0
- data/lib/parse/stack/generators/templates/model_installation.rb +4 -0
- data/lib/parse/stack/generators/templates/model_role.rb +4 -0
- data/lib/parse/stack/generators/templates/model_session.rb +4 -0
- data/lib/parse/stack/generators/templates/model_user.rb +11 -0
- data/lib/parse/stack/generators/templates/parse.rb +12 -0
- data/lib/parse/stack/generators/templates/webhooks.rb +10 -0
- data/lib/parse/stack/railtie.rb +18 -0
- data/lib/parse/stack/tasks.rb +563 -0
- data/lib/parse/stack/version.rb +11 -0
- data/lib/parse/stack.rb +455 -0
- data/lib/parse/two_factor_auth/user_extension.rb +449 -0
- data/lib/parse/two_factor_auth.rb +310 -0
- data/lib/parse/webhooks/payload.rb +360 -0
- data/lib/parse/webhooks/registration.rb +199 -0
- data/lib/parse/webhooks/replay_protection.rb +189 -0
- data/lib/parse/webhooks.rb +510 -0
- data/lib/parse-stack-next.rb +5 -0
- data/lib/parse-stack.rb +5 -0
- data/parse-stack-next.gemspec +82 -0
- data/parse-stack.png +0 -0
- data/scripts/debug-ips.js +35 -0
- data/scripts/docker/Dockerfile.parse +13 -0
- data/scripts/docker/atlas-init.js +284 -0
- data/scripts/docker/docker-compose.atlas.yml +76 -0
- data/scripts/docker/docker-compose.test.yml +106 -0
- data/scripts/docker/mongo-init.js +21 -0
- data/scripts/eval_mcp_with_lm_studio.rb +274 -0
- data/scripts/start-parse.sh +90 -0
- data/scripts/start_mcp_server.rb +78 -0
- data/scripts/test_server_connection.rb +82 -0
- metadata +377 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Evaluate MCP Server with LM Studio
|
|
6
|
+
#
|
|
7
|
+
# This script connects LM Studio to the Parse MCP Server, allowing the
|
|
8
|
+
# LLM to query Parse data using the MCP tools.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# ruby scripts/eval_mcp_with_lm_studio.rb
|
|
12
|
+
#
|
|
13
|
+
# Prerequisites:
|
|
14
|
+
# - LM Studio running at http://127.0.0.1:1234
|
|
15
|
+
# - MCP Server running at http://localhost:3001
|
|
16
|
+
# - Parse Server running with test data
|
|
17
|
+
|
|
18
|
+
require "net/http"
|
|
19
|
+
require "json"
|
|
20
|
+
require "uri"
|
|
21
|
+
|
|
22
|
+
class MCPLMStudioEvaluator
|
|
23
|
+
LM_STUDIO_URL = ENV["LM_STUDIO_URL"] || "http://127.0.0.1:1234"
|
|
24
|
+
MCP_SERVER_URL = ENV["MCP_SERVER_URL"] || "http://localhost:3001"
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@conversation = []
|
|
28
|
+
@tool_definitions = nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Fetch tool definitions from MCP server
|
|
32
|
+
def fetch_tools
|
|
33
|
+
uri = URI("#{MCP_SERVER_URL}/tools")
|
|
34
|
+
response = Net::HTTP.get_response(uri)
|
|
35
|
+
|
|
36
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
37
|
+
raise "Failed to fetch tools: #{response.code} #{response.message}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
tools = JSON.parse(response.body)
|
|
41
|
+
|
|
42
|
+
# Convert MCP format to OpenAI function calling format
|
|
43
|
+
@tool_definitions = tools.map do |tool|
|
|
44
|
+
{
|
|
45
|
+
type: "function",
|
|
46
|
+
function: {
|
|
47
|
+
name: tool["name"],
|
|
48
|
+
description: tool["description"],
|
|
49
|
+
parameters: tool["inputSchema"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts "Loaded #{@tool_definitions.size} tools from MCP server"
|
|
55
|
+
@tool_definitions
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Call a tool via MCP server
|
|
59
|
+
def call_mcp_tool(tool_name, arguments)
|
|
60
|
+
uri = URI("#{MCP_SERVER_URL}/mcp")
|
|
61
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
62
|
+
|
|
63
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
64
|
+
request["Content-Type"] = "application/json"
|
|
65
|
+
request.body = JSON.generate({
|
|
66
|
+
jsonrpc: "2.0",
|
|
67
|
+
id: rand(10000),
|
|
68
|
+
method: "tools/call",
|
|
69
|
+
params: {
|
|
70
|
+
name: tool_name,
|
|
71
|
+
arguments: arguments
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
response = http.request(request)
|
|
76
|
+
result = JSON.parse(response.body)
|
|
77
|
+
|
|
78
|
+
if result["error"]
|
|
79
|
+
{ error: result["error"]["message"] }
|
|
80
|
+
else
|
|
81
|
+
result["result"]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Send a message to LM Studio
|
|
86
|
+
def chat_with_lm(user_message, tools: true)
|
|
87
|
+
uri = URI("#{LM_STUDIO_URL}/v1/chat/completions")
|
|
88
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
89
|
+
http.read_timeout = 300 # LLMs can be slow, especially larger models
|
|
90
|
+
http.open_timeout = 30
|
|
91
|
+
|
|
92
|
+
@conversation << { role: "user", content: user_message }
|
|
93
|
+
|
|
94
|
+
request_body = {
|
|
95
|
+
model: "qwen2.5-32b-instruct",
|
|
96
|
+
messages: @conversation,
|
|
97
|
+
temperature: 0.1,
|
|
98
|
+
max_tokens: 2000
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Add tools if available and requested
|
|
102
|
+
if tools && @tool_definitions
|
|
103
|
+
request_body[:tools] = @tool_definitions
|
|
104
|
+
request_body[:tool_choice] = "auto"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
108
|
+
request["Content-Type"] = "application/json"
|
|
109
|
+
request.body = JSON.generate(request_body)
|
|
110
|
+
|
|
111
|
+
puts "\n>>> Sending to LM Studio..."
|
|
112
|
+
response = http.request(request)
|
|
113
|
+
|
|
114
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
115
|
+
raise "LM Studio error: #{response.code} #{response.message}\n#{response.body}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result = JSON.parse(response.body)
|
|
119
|
+
assistant_message = result["choices"][0]["message"]
|
|
120
|
+
|
|
121
|
+
@conversation << assistant_message
|
|
122
|
+
|
|
123
|
+
assistant_message
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Check if message has actual tool calls
|
|
127
|
+
def has_tool_calls?(message)
|
|
128
|
+
return false unless message
|
|
129
|
+
tool_calls = message["tool_calls"]
|
|
130
|
+
return false unless tool_calls.is_a?(Array) && !tool_calls.empty?
|
|
131
|
+
tool_calls.any? { |tc| tc["function"] && tc["function"]["name"] }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Process tool calls from the LLM
|
|
135
|
+
def process_tool_calls(message)
|
|
136
|
+
tool_calls = message["tool_calls"]
|
|
137
|
+
return nil unless tool_calls && !tool_calls.empty?
|
|
138
|
+
|
|
139
|
+
tool_results = []
|
|
140
|
+
|
|
141
|
+
tool_calls.each do |tool_call|
|
|
142
|
+
function = tool_call["function"]
|
|
143
|
+
next unless function && function["name"]
|
|
144
|
+
|
|
145
|
+
tool_name = function["name"]
|
|
146
|
+
arguments = JSON.parse(function["arguments"] || "{}")
|
|
147
|
+
|
|
148
|
+
puts "\n🔧 LLM calling tool: #{tool_name}"
|
|
149
|
+
puts " Arguments: #{JSON.pretty_generate(arguments)}"
|
|
150
|
+
|
|
151
|
+
result = call_mcp_tool(tool_name, arguments)
|
|
152
|
+
|
|
153
|
+
puts " Result preview: #{result.to_s[0..200]}..."
|
|
154
|
+
|
|
155
|
+
tool_results << {
|
|
156
|
+
role: "tool",
|
|
157
|
+
tool_call_id: tool_call["id"],
|
|
158
|
+
content: JSON.generate(result)
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
return message if tool_results.empty?
|
|
163
|
+
|
|
164
|
+
# Add tool results to conversation
|
|
165
|
+
tool_results.each { |r| @conversation << r }
|
|
166
|
+
|
|
167
|
+
# Get LLM's response after tool calls - allow more tool calls
|
|
168
|
+
chat_with_lm("", tools: true)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Run a full evaluation with a user prompt
|
|
172
|
+
def evaluate(prompt)
|
|
173
|
+
puts "=" * 60
|
|
174
|
+
puts "MCP + LM Studio Evaluation"
|
|
175
|
+
puts "=" * 60
|
|
176
|
+
puts "\nLM Studio: #{LM_STUDIO_URL}"
|
|
177
|
+
puts "MCP Server: #{MCP_SERVER_URL}"
|
|
178
|
+
puts "\nUser prompt: #{prompt}"
|
|
179
|
+
puts "=" * 60
|
|
180
|
+
|
|
181
|
+
# Initialize
|
|
182
|
+
fetch_tools
|
|
183
|
+
|
|
184
|
+
# Add system message
|
|
185
|
+
@conversation = [{
|
|
186
|
+
role: "system",
|
|
187
|
+
content: <<~SYSTEM
|
|
188
|
+
You are a helpful assistant with access to a Parse database.
|
|
189
|
+
Use the available tools to answer questions about the data.
|
|
190
|
+
Always start by getting the schema if you need to understand the database structure.
|
|
191
|
+
When querying, be specific and use appropriate constraints.
|
|
192
|
+
SYSTEM
|
|
193
|
+
}]
|
|
194
|
+
|
|
195
|
+
# Send user message
|
|
196
|
+
response = chat_with_lm(prompt)
|
|
197
|
+
|
|
198
|
+
# Handle tool calls in a loop
|
|
199
|
+
max_iterations = 5
|
|
200
|
+
iterations = 0
|
|
201
|
+
|
|
202
|
+
while has_tool_calls?(response) && iterations < max_iterations
|
|
203
|
+
iterations += 1
|
|
204
|
+
puts "\n--- Tool call iteration #{iterations} ---"
|
|
205
|
+
new_response = process_tool_calls(response)
|
|
206
|
+
break if new_response.nil? || new_response == response
|
|
207
|
+
response = new_response
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
puts "\n" + "=" * 60
|
|
211
|
+
puts "Final Response:"
|
|
212
|
+
puts "=" * 60
|
|
213
|
+
puts response["content"]
|
|
214
|
+
puts "=" * 60
|
|
215
|
+
|
|
216
|
+
response["content"]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Check if services are available
|
|
220
|
+
def check_services
|
|
221
|
+
puts "Checking services..."
|
|
222
|
+
|
|
223
|
+
# Check LM Studio
|
|
224
|
+
begin
|
|
225
|
+
uri = URI("#{LM_STUDIO_URL}/v1/models")
|
|
226
|
+
response = Net::HTTP.get_response(uri)
|
|
227
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
228
|
+
models = JSON.parse(response.body)
|
|
229
|
+
puts "✓ LM Studio is running"
|
|
230
|
+
puts " Available models: #{models["data"]&.map { |m| m["id"] }&.join(", ") || "unknown"}"
|
|
231
|
+
else
|
|
232
|
+
puts "✗ LM Studio returned: #{response.code}"
|
|
233
|
+
return false
|
|
234
|
+
end
|
|
235
|
+
rescue => e
|
|
236
|
+
puts "✗ Cannot connect to LM Studio at #{LM_STUDIO_URL}: #{e.message}"
|
|
237
|
+
return false
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Check MCP Server
|
|
241
|
+
begin
|
|
242
|
+
uri = URI("#{MCP_SERVER_URL}/health")
|
|
243
|
+
response = Net::HTTP.get_response(uri)
|
|
244
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
245
|
+
puts "✓ MCP Server is running"
|
|
246
|
+
else
|
|
247
|
+
puts "✗ MCP Server returned: #{response.code}"
|
|
248
|
+
return false
|
|
249
|
+
end
|
|
250
|
+
rescue => e
|
|
251
|
+
puts "✗ Cannot connect to MCP Server at #{MCP_SERVER_URL}: #{e.message}"
|
|
252
|
+
return false
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
puts ""
|
|
256
|
+
true
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Main execution
|
|
261
|
+
if __FILE__ == $0
|
|
262
|
+
evaluator = MCPLMStudioEvaluator.new
|
|
263
|
+
|
|
264
|
+
unless evaluator.check_services
|
|
265
|
+
puts "\nPlease ensure both LM Studio and MCP Server are running."
|
|
266
|
+
puts "Start MCP Server with: ruby scripts/start_mcp_server.rb"
|
|
267
|
+
exit 1
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Default prompt if none provided
|
|
271
|
+
prompt = ARGV[0] || "What tables are in the database? Show me a sample of data from one of them."
|
|
272
|
+
|
|
273
|
+
evaluator.evaluate(prompt)
|
|
274
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Parse Server startup script — TEST STACK ONLY.
|
|
5
|
+
#
|
|
6
|
+
# This script is bind-mounted into the parse-server container started by
|
|
7
|
+
# scripts/docker/docker-compose.test.yml for `rake test:integration`. It
|
|
8
|
+
# is NOT a template for production deployment.
|
|
9
|
+
#
|
|
10
|
+
# Required environment variables (provided by the test docker-compose
|
|
11
|
+
# via `${PARSE_APP_ID}`, `${PARSE_MASTER_KEY}`, … which themselves fall
|
|
12
|
+
# through to the compose's own defaults). The script aborts immediately
|
|
13
|
+
# if any required variable is missing — preventing a silent boot with
|
|
14
|
+
# placeholder credentials when an env-var name has drifted or a secret-
|
|
15
|
+
# manager binding fails.
|
|
16
|
+
|
|
17
|
+
echo "=== Parse Server Startup Script (test stack) ==="
|
|
18
|
+
|
|
19
|
+
# Hard-fail on any required env var being missing. The compose file
|
|
20
|
+
# is expected to supply each one via env interpolation; if it's not,
|
|
21
|
+
# we want a loud failure here rather than a silent boot with whatever
|
|
22
|
+
# default Parse Server itself would have applied.
|
|
23
|
+
require_env() {
|
|
24
|
+
eval "value=\${$1:-}"
|
|
25
|
+
if [ -z "$value" ]; then
|
|
26
|
+
echo "[start-parse] Refusing to start: required environment variable $1 is not set." >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
require_env PARSE_SERVER_APPLICATION_ID
|
|
32
|
+
require_env PARSE_SERVER_MASTER_KEY
|
|
33
|
+
require_env PARSE_SERVER_DATABASE_URI
|
|
34
|
+
|
|
35
|
+
# masterKeyIps restricts which client IPs are allowed to present the
|
|
36
|
+
# master key. Default to loopback only. To allow other ranges (e.g. a
|
|
37
|
+
# private VPC subnet hosting the Ruby app dynos), override before
|
|
38
|
+
# invoking this script:
|
|
39
|
+
#
|
|
40
|
+
# PARSE_SERVER_MASTER_KEY_IPS="10.0.0.0/8,::1/128" ./start-parse.sh
|
|
41
|
+
#
|
|
42
|
+
# DO NOT set this to "0.0.0.0/0,::/0" in any environment reachable from
|
|
43
|
+
# the public internet. Doing so lets any caller that knows the master
|
|
44
|
+
# key bypass every ACL and CLP from any source IP.
|
|
45
|
+
export PARSE_SERVER_MASTER_KEY_IPS="${PARSE_SERVER_MASTER_KEY_IPS:-127.0.0.1/32,::1/128}"
|
|
46
|
+
|
|
47
|
+
# Optional REST API key — accept a default of empty when not provided
|
|
48
|
+
# by the compose file. Parse Server tolerates an unset REST key.
|
|
49
|
+
export PARSE_SERVER_REST_API_KEY="${PARSE_SERVER_REST_API_KEY:-}"
|
|
50
|
+
export PARSE_SERVER_MOUNT_PATH="${PARSE_SERVER_MOUNT_PATH:-/parse}"
|
|
51
|
+
export PARSE_SERVER_CLOUD="${PARSE_SERVER_CLOUD:-/parse-server/cloud/main.js}"
|
|
52
|
+
export PARSE_SERVER_LOG_LEVEL="${PARSE_SERVER_LOG_LEVEL:-info}"
|
|
53
|
+
export PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION="${PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION:-true}"
|
|
54
|
+
# Accept client-supplied objectId on create. Required by the
|
|
55
|
+
# `parse_reference precompute: true` DSL, which client-generates an
|
|
56
|
+
# objectId in a before_create callback and embeds it in the initial
|
|
57
|
+
# POST body. The SDK only forwards the client objectId when the save
|
|
58
|
+
# runs with master-key authority; the server flag is global, so any
|
|
59
|
+
# additional non-master client-objectId enforcement must be applied
|
|
60
|
+
# in cloud code (see lib/parse/model/core/parse_reference.rb).
|
|
61
|
+
export PARSE_SERVER_ALLOW_CUSTOM_OBJECT_ID="${PARSE_SERVER_ALLOW_CUSTOM_OBJECT_ID:-true}"
|
|
62
|
+
|
|
63
|
+
# LiveQuery configuration via environment variables
|
|
64
|
+
export PARSE_SERVER_LIVE_QUERY="${PARSE_SERVER_LIVE_QUERY:-{\"classNames\":[\"Song\",\"Album\",\"User\",\"_User\",\"TestLiveQuery\"]}}"
|
|
65
|
+
export PARSE_SERVER_START_LIVE_QUERY_SERVER="${PARSE_SERVER_START_LIVE_QUERY_SERVER:-true}"
|
|
66
|
+
|
|
67
|
+
echo "Environment configured:"
|
|
68
|
+
echo " PARSE_SERVER_APPLICATION_ID: $PARSE_SERVER_APPLICATION_ID"
|
|
69
|
+
echo " PARSE_SERVER_LIVE_QUERY: $PARSE_SERVER_LIVE_QUERY"
|
|
70
|
+
echo " PARSE_SERVER_START_LIVE_QUERY_SERVER: $PARSE_SERVER_START_LIVE_QUERY_SERVER"
|
|
71
|
+
|
|
72
|
+
# Start Parse Server
|
|
73
|
+
echo "Starting Parse Server..."
|
|
74
|
+
echo "PATH: $PATH"
|
|
75
|
+
echo "Looking for parse-server..."
|
|
76
|
+
which node
|
|
77
|
+
ls -la /parse-server/
|
|
78
|
+
|
|
79
|
+
# Try different ways to start parse-server
|
|
80
|
+
if [ -f "/parse-server/bin/parse-server" ]; then
|
|
81
|
+
echo "Using /parse-server/bin/parse-server"
|
|
82
|
+
exec /parse-server/bin/parse-server
|
|
83
|
+
elif [ -f "/usr/src/app/bin/parse-server" ]; then
|
|
84
|
+
echo "Using /usr/src/app/bin/parse-server"
|
|
85
|
+
exec /usr/src/app/bin/parse-server
|
|
86
|
+
else
|
|
87
|
+
echo "Trying with node and index.js"
|
|
88
|
+
cd /parse-server
|
|
89
|
+
exec node ./bin/parse-server
|
|
90
|
+
fi
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Start the Parse MCP Server for AI agent integration.
|
|
6
|
+
#
|
|
7
|
+
# Required environment variables (no defaults — the script aborts if any
|
|
8
|
+
# is missing):
|
|
9
|
+
# PARSE_SERVER_URL - Parse Server URL (e.g. http://localhost:2337/parse)
|
|
10
|
+
# PARSE_APP_ID - Application ID
|
|
11
|
+
# PARSE_MASTER_KEY - Master Key
|
|
12
|
+
#
|
|
13
|
+
# Optional:
|
|
14
|
+
# PARSE_API_KEY - REST API Key (defaults unset; configure if your
|
|
15
|
+
# deployment requires it)
|
|
16
|
+
# MCP_PORT - MCP Server port (default: 3001)
|
|
17
|
+
# MCP_API_KEY - Bearer token gating the /mcp endpoint when
|
|
18
|
+
# binding to a non-loopback host
|
|
19
|
+
#
|
|
20
|
+
# Why no fallback values: previously this script accepted
|
|
21
|
+
# `ENV["PARSE_APP_ID"] || "myAppId"` and the equivalent for the master
|
|
22
|
+
# key. A deployment that forgot to set the env var (typo'd name, missing
|
|
23
|
+
# secret manager binding, container startup race) would silently boot
|
|
24
|
+
# with the placeholder credentials documented in the README — credentials
|
|
25
|
+
# that any reader of this repo knows. Failing closed here is a one-line
|
|
26
|
+
# safety net against that class of foot-gun.
|
|
27
|
+
|
|
28
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
29
|
+
|
|
30
|
+
require "parse-stack"
|
|
31
|
+
|
|
32
|
+
# Hard-fail on any missing required env var. The error message names the
|
|
33
|
+
# variable so the operator can fix it without having to re-read the
|
|
34
|
+
# header comment.
|
|
35
|
+
def require_env!(name)
|
|
36
|
+
value = ENV[name]
|
|
37
|
+
if value.nil? || value.empty?
|
|
38
|
+
abort "[start_mcp_server] Refusing to start: required environment variable #{name} is not set."
|
|
39
|
+
end
|
|
40
|
+
value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
server_url = require_env!("PARSE_SERVER_URL")
|
|
44
|
+
application_id = require_env!("PARSE_APP_ID")
|
|
45
|
+
master_key = require_env!("PARSE_MASTER_KEY")
|
|
46
|
+
api_key = ENV["PARSE_API_KEY"] # optional
|
|
47
|
+
|
|
48
|
+
# Configure Parse client
|
|
49
|
+
Parse.setup(
|
|
50
|
+
server_url: server_url,
|
|
51
|
+
application_id: application_id,
|
|
52
|
+
api_key: api_key,
|
|
53
|
+
master_key: master_key,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
port = (ENV["MCP_PORT"] || 3001).to_i
|
|
57
|
+
|
|
58
|
+
puts "=" * 60
|
|
59
|
+
puts "Parse MCP Server"
|
|
60
|
+
puts "=" * 60
|
|
61
|
+
puts ""
|
|
62
|
+
puts "Parse Server: #{Parse.client.server_url}"
|
|
63
|
+
puts "MCP Port: #{port}"
|
|
64
|
+
puts ""
|
|
65
|
+
puts "Endpoints:"
|
|
66
|
+
puts " Health: http://localhost:#{port}/health"
|
|
67
|
+
puts " Tools: http://localhost:#{port}/tools"
|
|
68
|
+
puts " MCP: http://localhost:#{port}/mcp (POST)"
|
|
69
|
+
puts ""
|
|
70
|
+
puts "For LM Studio, configure the API endpoint as:"
|
|
71
|
+
puts " http://localhost:#{port}/mcp"
|
|
72
|
+
puts ""
|
|
73
|
+
puts "=" * 60
|
|
74
|
+
|
|
75
|
+
# Enable MCP server feature (experimental)
|
|
76
|
+
Parse.mcp_server_enabled = true
|
|
77
|
+
Parse::Agent.enable_mcp!(port: port)
|
|
78
|
+
Parse::Agent::MCPServer.run(port: port)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require_relative '../lib/parse/stack'
|
|
3
|
+
require_relative '../test/support/test_server'
|
|
4
|
+
require_relative '../test/support/docker_helper'
|
|
5
|
+
|
|
6
|
+
puts "Parse Stack Test Server Connection Test"
|
|
7
|
+
puts "=" * 40
|
|
8
|
+
|
|
9
|
+
# Try to start Docker containers
|
|
10
|
+
puts "\n1. Starting Docker containers..."
|
|
11
|
+
if Parse::Test::DockerHelper.start!
|
|
12
|
+
puts "✓ Docker containers started successfully"
|
|
13
|
+
else
|
|
14
|
+
puts "✗ Failed to start Docker containers"
|
|
15
|
+
exit 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Wait a moment for services to fully initialize
|
|
19
|
+
puts "\n2. Waiting for services to initialize..."
|
|
20
|
+
sleep 5
|
|
21
|
+
|
|
22
|
+
# Test Parse Server connection
|
|
23
|
+
puts "\n3. Testing Parse Server connection..."
|
|
24
|
+
if Parse::Test::ServerHelper.setup
|
|
25
|
+
puts "✓ Parse Server connection successful"
|
|
26
|
+
|
|
27
|
+
# Test a basic operation
|
|
28
|
+
puts "\n4. Testing basic Parse operations..."
|
|
29
|
+
begin
|
|
30
|
+
# Check client configuration
|
|
31
|
+
client = Parse::Client.client
|
|
32
|
+
puts " Client server_url: #{client.server_url}"
|
|
33
|
+
puts " Client app_id: #{client.app_id}"
|
|
34
|
+
puts " Client has master_key: #{client.master_key.present?}"
|
|
35
|
+
|
|
36
|
+
# Reset any existing data
|
|
37
|
+
Parse::Test::ServerHelper.reset_database!
|
|
38
|
+
|
|
39
|
+
# Create a test user
|
|
40
|
+
user = Parse::Test::ServerHelper.create_test_user(
|
|
41
|
+
username: 'testuser',
|
|
42
|
+
password: 'testpass',
|
|
43
|
+
email: 'test@example.com'
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
puts "✓ Created test user: #{user.username} (ID: #{user.id})"
|
|
47
|
+
|
|
48
|
+
# Create a test object
|
|
49
|
+
test_obj = Parse::Object.new({'className' => 'TestObject', 'name' => 'Test Item', 'value' => 42})
|
|
50
|
+
test_obj.save
|
|
51
|
+
|
|
52
|
+
puts "✓ Created test object: #{test_obj['name']} (ID: #{test_obj.id})"
|
|
53
|
+
|
|
54
|
+
# Query the object back
|
|
55
|
+
query = Parse::Query.new('TestObject')
|
|
56
|
+
query = query.limit(10) # Use limit() method instead of limit=
|
|
57
|
+
results = query.results
|
|
58
|
+
puts "✓ Retrieved #{results.count} test objects"
|
|
59
|
+
|
|
60
|
+
# Test cloud function
|
|
61
|
+
result = Parse.call_function('hello', name: 'Parse Stack')
|
|
62
|
+
puts "✓ Cloud function result: #{result}"
|
|
63
|
+
|
|
64
|
+
puts "\n✅ All tests passed! Parse Server is working correctly."
|
|
65
|
+
|
|
66
|
+
rescue => e
|
|
67
|
+
puts "✗ Error during testing: #{e.message}"
|
|
68
|
+
puts e.backtrace.first(3) if ENV['DEBUG']
|
|
69
|
+
exit 1
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
puts "✗ Parse Server connection failed"
|
|
73
|
+
exit 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
puts "\n5. Connection information:"
|
|
77
|
+
puts " Parse Server: http://localhost:1337/parse"
|
|
78
|
+
puts " Parse Dashboard: http://localhost:4040"
|
|
79
|
+
puts " Dashboard login: admin/admin"
|
|
80
|
+
|
|
81
|
+
puts "\nTo stop the containers, run:"
|
|
82
|
+
puts " docker-compose -f docker-compose.test.yml down"
|