parse-stack-next 5.5.3 → 5.5.5

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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -4
  3. data/README.md +26 -13
  4. data/bin/parse-console +9 -1
  5. data/docs/TEST_SERVER.md +115 -238
  6. data/docs/mcp_guide.md +1 -1
  7. data/docs/mongodb_index_optimization_guide.md +3 -2
  8. data/docs/usage_guide.md +1 -1
  9. data/docs/yard-template/default/fulldoc/html/css/common.css +52 -9
  10. data/docs/yard-template/default/fulldoc/html/css/full_list.css +40 -13
  11. data/lib/parse/agent/constraint_translator.rb +18 -18
  12. data/lib/parse/agent/errors.rb +29 -7
  13. data/lib/parse/agent/metadata_dsl.rb +6 -6
  14. data/lib/parse/agent/tools.rb +250 -59
  15. data/lib/parse/agent.rb +42 -30
  16. data/lib/parse/api/aggregate.rb +3 -3
  17. data/lib/parse/api/cloud_functions.rb +19 -10
  18. data/lib/parse/api/objects.rb +8 -8
  19. data/lib/parse/api/users.rb +9 -9
  20. data/lib/parse/atlas_search/session.rb +34 -34
  21. data/lib/parse/atlas_search.rb +243 -110
  22. data/lib/parse/client/body_builder.rb +10 -10
  23. data/lib/parse/client/logging.rb +5 -2
  24. data/lib/parse/client/profiling.rb +5 -2
  25. data/lib/parse/client/protocol.rb +1 -1
  26. data/lib/parse/client/url_redaction.rb +94 -0
  27. data/lib/parse/client.rb +43 -28
  28. data/lib/parse/embeddings/image_fetch.rb +6 -1
  29. data/lib/parse/embeddings/voyage.rb +16 -17
  30. data/lib/parse/live_query/client.rb +7 -7
  31. data/lib/parse/live_query/subscription.rb +1 -1
  32. data/lib/parse/lock.rb +1 -1
  33. data/lib/parse/lock_backend.rb +118 -2
  34. data/lib/parse/model/acl.rb +24 -24
  35. data/lib/parse/model/classes/job_schedule.rb +8 -8
  36. data/lib/parse/model/classes/job_status.rb +9 -9
  37. data/lib/parse/model/classes/role.rb +49 -49
  38. data/lib/parse/model/classes/session.rb +2 -2
  39. data/lib/parse/model/classes/user.rb +66 -66
  40. data/lib/parse/model/core/builder.rb +7 -7
  41. data/lib/parse/model/core/create_lock.rb +1 -1
  42. data/lib/parse/model/core/properties.rb +4 -4
  43. data/lib/parse/model/file.rb +57 -16
  44. data/lib/parse/model/model.rb +19 -19
  45. data/lib/parse/model/object.rb +38 -38
  46. data/lib/parse/model/pointer.rb +4 -4
  47. data/lib/parse/model/push.rb +5 -5
  48. data/lib/parse/mongodb.rb +84 -26
  49. data/lib/parse/pipeline_security.rb +2 -2
  50. data/lib/parse/query/constraints.rb +38 -38
  51. data/lib/parse/query.rb +151 -75
  52. data/lib/parse/retrieval/reranker/cohere.rb +30 -0
  53. data/lib/parse/schema.rb +1 -1
  54. data/lib/parse/stack/version.rb +1 -1
  55. data/lib/parse/stack.rb +23 -10
  56. data/lib/parse/two_factor_auth/user_extension.rb +25 -25
  57. data/lib/parse/webhooks/payload.rb +35 -35
  58. data/lib/parse/webhooks/registration.rb +2 -2
  59. data/lib/parse/webhooks/replay_protection.rb +16 -16
  60. data/lib/parse/webhooks.rb +11 -11
  61. data/parse-stack-next.gemspec +19 -1
  62. metadata +2 -38
  63. data/.bundle/config +0 -5
  64. data/.env.sample +0 -138
  65. data/.env.test +0 -10
  66. data/.github/ISSUE_TEMPLATE/bug_report.yml +0 -105
  67. data/.github/ISSUE_TEMPLATE/feature_request.yml +0 -67
  68. data/.github/dependabot.yml +0 -13
  69. data/.github/workflows/codeql.yml +0 -44
  70. data/.github/workflows/docs.yml +0 -39
  71. data/.github/workflows/release.yml +0 -43
  72. data/.github/workflows/ruby.yml +0 -38
  73. data/.gitignore +0 -56
  74. data/.ruby-version +0 -1
  75. data/.solargraph.yml +0 -22
  76. data/.vscode/settings.json +0 -3
  77. data/.yardopts +0 -19
  78. data/Gemfile +0 -43
  79. data/Gemfile.lock +0 -198
  80. data/Makefile +0 -63
  81. data/Rakefile +0 -825
  82. data/config/parse-config.json +0 -12
  83. data/scripts/debug-ips.js +0 -35
  84. data/scripts/docker/Dockerfile.parse +0 -17
  85. data/scripts/docker/atlas-init.js +0 -284
  86. data/scripts/docker/docker-compose.atlas.yml +0 -80
  87. data/scripts/docker/docker-compose.test.yml +0 -159
  88. data/scripts/docker/docker-compose.verifyemail.yml +0 -4
  89. data/scripts/docker/mongo-init.js +0 -21
  90. data/scripts/docker/preflight.sh +0 -76
  91. data/scripts/eval_mcp_with_lm_studio.rb +0 -274
  92. data/scripts/start-parse.sh +0 -154
  93. data/scripts/start_mcp_server.rb +0 -78
  94. data/scripts/test_server_connection.rb +0 -82
  95. data/scripts/vector_prototype/create_vector_index.js +0 -105
  96. data/scripts/vector_prototype/fetch_embeddings.py +0 -241
  97. data/scripts/vector_prototype/fixture_manifest.json +0 -9
  98. data/scripts/vector_prototype/query_prototype.rb +0 -84
  99. data/scripts/vector_prototype/run.sh +0 -34
@@ -1,76 +0,0 @@
1
- #!/bin/sh
2
- # Preflight guard for the integration stack — TEST STACK ONLY.
3
- #
4
- # The Compose defaults bind every service to loopback (127.0.0.1) and fall
5
- # back to KNOWN, COMMITTED test credentials (master key psnextItMasterKey,
6
- # Mongo root admin:password, Dashboard admin:admin). That combination is
7
- # safe on loopback — nothing on the LAN can reach it.
8
- #
9
- # It is NOT safe the moment a developer overrides a `*_BIND` to a
10
- # non-loopback address (e.g. MONGO_BIND=0.0.0.0 to attach a remote client):
11
- # the stack is then reachable from the LAN while protected only by
12
- # credentials that are published in this very repository. An admin-
13
- # credentialed Mongo / Parse master key exposed to a shared network is a
14
- # real footgun, and the failure is silent — `docker compose up` just works
15
- # and the developer never sees it.
16
- #
17
- # This guard runs first (every other service gates on it via
18
- # `service_completed_successfully`) and FAILS THE STACK CLOSED whenever a
19
- # non-loopback bind is combined with still-default privileged credentials.
20
- # It is invisible to the normal loopback run.
21
- #
22
- # Escape hatches (pick one):
23
- # 1. Keep it loopback — unset the *_BIND override (the default).
24
- # 2. Use real secrets — set PARSE_MASTER_KEY and MONGO_ROOT_PASSWORD
25
- # (inject them with `op run` / `doppler run` —
26
- # see the README "Secret injection" recipe).
27
- # 3. Acknowledge the risk — ALLOW_INSECURE_BIND=1 on a trusted/isolated
28
- # network where exposure is intentional.
29
-
30
- set -eu
31
-
32
- # Treat an empty value as loopback: the compose interpolation passes the
33
- # resolved bind, and an unset override resolves to the 127.0.0.1 default.
34
- is_loopback() {
35
- case "$1" in
36
- "" | 127.0.0.1 | ::1 | localhost) return 0 ;;
37
- *) return 1 ;;
38
- esac
39
- }
40
-
41
- exposed=""
42
- for pair in "PARSE_BIND=${PARSE_BIND:-}" "MONGO_BIND=${MONGO_BIND:-}" \
43
- "REDIS_BIND=${REDIS_BIND:-}" "DASHBOARD_BIND=${DASHBOARD_BIND:-}"; do
44
- val=${pair#*=}
45
- is_loopback "$val" || exposed="$exposed ${pair%%=*}=$val"
46
- done
47
-
48
- # Privileged credentials are "default" unless BOTH have been overridden.
49
- # *_SET is "1" only when Compose interpolated a non-empty override
50
- # (`${VAR:+1}`); empty means the committed default is in force.
51
- if [ -n "${PARSE_MASTER_KEY_SET:-}" ] && [ -n "${MONGO_ROOT_PASSWORD_SET:-}" ]; then
52
- default_creds=0
53
- else
54
- default_creds=1
55
- fi
56
-
57
- if [ -n "$exposed" ] && [ "$default_creds" = "1" ] && [ "${ALLOW_INSECURE_BIND:-}" != "1" ]; then
58
- echo "[preflight] REFUSING TO START — non-loopback bind with default credentials." >&2
59
- echo "[preflight]" >&2
60
- echo "[preflight] Exposed (non-loopback) bind(s):$exposed" >&2
61
- echo "[preflight] ...while still using the committed test credentials" >&2
62
- echo "[preflight] (master key psnextItMasterKey / Mongo admin:password)." >&2
63
- echo "[preflight] This publishes an admin-credentialed stack onto your LAN." >&2
64
- echo "[preflight]" >&2
65
- echo "[preflight] Resolve ONE of:" >&2
66
- echo "[preflight] 1. Keep it loopback — unset the *_BIND override." >&2
67
- echo "[preflight] 2. Set real secrets — PARSE_MASTER_KEY=... MONGO_ROOT_PASSWORD=..." >&2
68
- echo "[preflight] 3. Acknowledge intent — ALLOW_INSECURE_BIND=1 (trusted network only)." >&2
69
- exit 1
70
- fi
71
-
72
- if [ -n "$exposed" ]; then
73
- echo "[preflight] OK — non-loopback bind(s)$exposed permitted (credentials overridden or ALLOW_INSECURE_BIND=1)."
74
- else
75
- echo "[preflight] OK — all services bound to loopback."
76
- fi
@@ -1,274 +0,0 @@
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
@@ -1,154 +0,0 @@
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
- # Push configuration — test-stack only. Points at a no-op adapter bind-mounted
68
- # from test/cloud (see test/cloud/dummy-push-adapter.js). It does NOT deliver to
69
- # any real device gateway; it lets Parse Server accept `POST /parse/push` and
70
- # create/complete a real `_PushStatus` so the push send+status lifecycle is
71
- # integration-testable offline. Without this, `POST /push` returns code 115
72
- # "Missing push configuration". DO NOT use a no-op adapter in a deployed
73
- # environment — it silently drops every notification.
74
- export PARSE_SERVER_PUSH="${PARSE_SERVER_PUSH:-{\"adapter\":\"/parse-server/cloud/dummy-push-adapter.js\"}}"
75
-
76
- # MFA / 2FA configuration — test-stack only. Enables Parse Server's built-in
77
- # TOTP MFA adapter so the Parse::MFA / two_factor_auth integration tests can
78
- # enroll a user (authData.mfa.{secret,token}) and log in with a time-based code.
79
- # Params match rotp's defaults (SHA1 / 6 digits / 30s period) so codes generated
80
- # client-side validate server-side.
81
- #
82
- # NOTE: the `auth` option is the one Parse Server option that CANNOT be passed
83
- # as a JSON env var — its Definitions entry has no objectParser, so
84
- # PARSE_SERVER_AUTH_PROVIDERS is taken as a raw string and never JSON-parsed
85
- # (the MFA adapter then receives `undefined` options and 500s). It must come
86
- # from a config file, which parse-server JSON-parses natively. We write a
87
- # minimal config file holding only the `auth` block and pass it to parse-server
88
- # below; env vars still provide — and take precedence for — everything else
89
- # (parse-server applies env first, then fills gaps from the file).
90
- PARSE_AUTH_CONFIG_FILE="${PARSE_AUTH_CONFIG_FILE:-/tmp/psnext-parse-auth-config.json}"
91
- cat > "$PARSE_AUTH_CONFIG_FILE" <<'AUTHCFG'
92
- { "auth": { "mfa": { "options": ["TOTP"], "digits": 6, "period": 30, "algorithm": "SHA1" } } }
93
- AUTHCFG
94
-
95
- # Email — test-stack only. Captures outgoing mail into an `EmailCapture` class
96
- # (see test/cloud/capturing-email-adapter.js) instead of sending it, so the
97
- # client-side password-reset / verification integration tests can assert
98
- # delivery and read back the reset link. `PARSE_PUBLIC_SERVER_URL` is required
99
- # for Parse Server to build those links. Email verification is NOT enabled, so
100
- # ordinary signups still work without a verification round-trip. DO NOT use a
101
- # capturing adapter in a deployed environment — it drops every email.
102
- export PARSE_SERVER_EMAIL_ADAPTER="${PARSE_SERVER_EMAIL_ADAPTER:-/parse-server/cloud/capturing-email-adapter.js}"
103
- export PARSE_PUBLIC_SERVER_URL="${PARSE_PUBLIC_SERVER_URL:-http://localhost:${PARSE_HOST_PORT:-29337}/parse}"
104
- export PARSE_SERVER_APP_NAME="${PARSE_SERVER_APP_NAME:-parse-stack-next-it}"
105
- # Keep email verification OFF. Configuring an email adapter otherwise flips
106
- # Parse Server into requiring verification, which makes signup return a user
107
- # with NO session token until the address is verified — breaking the
108
- # signup-on-save suite. Password reset does not need verification, only the
109
- # adapter + public URL above.
110
- export PARSE_SERVER_VERIFY_USER_EMAILS="${PARSE_SERVER_VERIFY_USER_EMAILS:-false}"
111
- export PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL="${PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL:-false}"
112
-
113
- # File upload — test-stack only. Authenticated session-token uploads are
114
- # permitted; public/anonymous uploads are NOT (mirrors a typical hardened
115
- # Parse Server config). The client_rest_files integration tests assert
116
- # both pathways: authed upload succeeds, anon upload is rejected.
117
- export PARSE_SERVER_FILE_UPLOAD="${PARSE_SERVER_FILE_UPLOAD:-{\"enableForPublic\":false,\"enableForAnonymousUser\":false,\"enableForAuthenticatedUser\":true}}"
118
-
119
- # Request-id idempotency — test-stack only, scoped to a single probe class so
120
- # it deduplicates ONLY writes the request-id integration test targets and has
121
- # zero effect on every other suite. Parse Server dedups POST/PUT carrying the
122
- # same X-Parse-Request-Id within the TTL for paths matching `paths` (regex).
123
- # The SDK's idempotent-retry feature relies on exactly this server-side dedup
124
- # when `Parse::Request.assume_server_idempotency = true`. NOTE: Parse Server
125
- # names this env var with an `EXPERIMENTAL_` infix and treats the value as a
126
- # JSON object (objectParser). Override the whole JSON to widen coverage.
127
- export PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS="${PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS:-{\"paths\":[\"classes/IdempotencyProbe\"],\"ttl\":120}}"
128
-
129
- echo "Environment configured:"
130
- echo " PARSE_SERVER_APPLICATION_ID: $PARSE_SERVER_APPLICATION_ID"
131
- echo " PARSE_SERVER_LIVE_QUERY: $PARSE_SERVER_LIVE_QUERY"
132
- echo " PARSE_SERVER_START_LIVE_QUERY_SERVER: $PARSE_SERVER_START_LIVE_QUERY_SERVER"
133
-
134
- # Start Parse Server
135
- echo "Starting Parse Server..."
136
- echo "PATH: $PATH"
137
- echo "Looking for parse-server..."
138
- which node
139
- ls -la /parse-server/
140
-
141
- # Try different ways to start parse-server. The config file argument supplies
142
- # the `auth` (MFA) block; every other option still comes from the environment.
143
- echo " Auth config file: $PARSE_AUTH_CONFIG_FILE"
144
- if [ -f "/parse-server/bin/parse-server" ]; then
145
- echo "Using /parse-server/bin/parse-server"
146
- exec /parse-server/bin/parse-server "$PARSE_AUTH_CONFIG_FILE"
147
- elif [ -f "/usr/src/app/bin/parse-server" ]; then
148
- echo "Using /usr/src/app/bin/parse-server"
149
- exec /usr/src/app/bin/parse-server "$PARSE_AUTH_CONFIG_FILE"
150
- else
151
- echo "Trying with node and index.js"
152
- cd /parse-server
153
- exec node ./bin/parse-server "$PARSE_AUTH_CONFIG_FILE"
154
- fi
@@ -1,78 +0,0 @@
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"] || "psnextItAppId"` 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)
@@ -1,82 +0,0 @@
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:29040"
79
- puts " Dashboard login: admin/admin"
80
-
81
- puts "\nTo stop the containers, run:"
82
- puts " docker-compose -f docker-compose.test.yml down"