mcp-scraper 0.2.7 → 0.2.9

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 (64) hide show
  1. package/README.md +44 -5
  2. package/dist/bin/api-server.cjs +2417 -648
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/browser-agent-stdio-server.cjs +1 -1
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  7. package/dist/bin/browser-agent-stdio-server.js +2 -2
  8. package/dist/bin/mcp-scraper-cli.cjs +1276 -0
  9. package/dist/bin/mcp-scraper-cli.cjs.map +1 -0
  10. package/dist/bin/mcp-scraper-cli.d.cts +1 -0
  11. package/dist/bin/mcp-scraper-cli.d.ts +1 -0
  12. package/dist/bin/mcp-scraper-cli.js +476 -0
  13. package/dist/bin/mcp-scraper-cli.js.map +1 -0
  14. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +69 -14
  15. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  16. package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
  17. package/dist/bin/mcp-scraper-install.cjs +5 -1
  18. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  19. package/dist/bin/mcp-scraper-install.js +5 -1
  20. package/dist/bin/mcp-scraper-install.js.map +1 -1
  21. package/dist/bin/mcp-stdio-server.cjs +69 -14
  22. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  23. package/dist/bin/mcp-stdio-server.js +2 -2
  24. package/dist/bin/paa-harvest.cjs +42 -6
  25. package/dist/bin/paa-harvest.cjs.map +1 -1
  26. package/dist/bin/paa-harvest.js +1 -1
  27. package/dist/{chunk-3TF6UT2P.js → chunk-5E6KJC26.js} +2 -2
  28. package/dist/{chunk-D4CJBZBY.js → chunk-7GCCOT3M.js} +372 -1
  29. package/dist/chunk-7GCCOT3M.js.map +1 -0
  30. package/dist/chunk-BJDCPCIE.js +7 -0
  31. package/dist/chunk-BJDCPCIE.js.map +1 -0
  32. package/dist/{chunk-MY3S7EX7.js → chunk-CQTAKXBN.js} +43 -7
  33. package/dist/chunk-CQTAKXBN.js.map +1 -0
  34. package/dist/chunk-L6IS63WS.js +869 -0
  35. package/dist/chunk-L6IS63WS.js.map +1 -0
  36. package/dist/{chunk-IQOCZGJJ.js → chunk-ROS67BNV.js} +5 -5
  37. package/dist/{chunk-7SI6XIR3.js → chunk-XAY5U67D.js} +70 -15
  38. package/dist/chunk-XAY5U67D.js.map +1 -0
  39. package/dist/{db-YWCNHBLH.js → db-BVHYI57K.js} +38 -2
  40. package/dist/index.cjs +42 -6
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/{server-RIKRBDOI.js → server-BKJ7LULQ.js} +818 -394
  44. package/dist/server-BKJ7LULQ.js.map +1 -0
  45. package/dist/{worker-NAKGTIF5.js → worker-TDJQ6TH3.js} +8 -8
  46. package/docs/mcp-tool-quality-spec.md +3 -1
  47. package/docs/specs/agent-ready-seo-packet-spec.md +237 -0
  48. package/docs/specs/cli-agent-wiring-spec.md +203 -0
  49. package/docs/specs/deferred-work-spec.md +12 -0
  50. package/docs/specs/local-competitive-audit-spec.md +312 -0
  51. package/docs/specs/scheduled-workflows-api-spec.md +304 -0
  52. package/docs/specs/seo-cli-growth-roadmap-spec.md +179 -0
  53. package/docs/specs/seo-workflow-runner-and-reports-spec.md +241 -0
  54. package/package.json +3 -2
  55. package/dist/chunk-7SI6XIR3.js.map +0 -1
  56. package/dist/chunk-D4CJBZBY.js.map +0 -1
  57. package/dist/chunk-IZE5UW7Y.js +0 -7
  58. package/dist/chunk-IZE5UW7Y.js.map +0 -1
  59. package/dist/chunk-MY3S7EX7.js.map +0 -1
  60. package/dist/server-RIKRBDOI.js.map +0 -1
  61. /package/dist/{chunk-3TF6UT2P.js.map → chunk-5E6KJC26.js.map} +0 -0
  62. /package/dist/{chunk-IQOCZGJJ.js.map → chunk-ROS67BNV.js.map} +0 -0
  63. /package/dist/{db-YWCNHBLH.js.map → db-BVHYI57K.js.map} +0 -0
  64. /package/dist/{worker-NAKGTIF5.js.map → worker-TDJQ6TH3.js.map} +0 -0
@@ -0,0 +1,312 @@
1
+ # Local Competitive Audit Workflow — Implementation Spec
2
+
3
+ `local-competitive-audit` turns city selection, Maps search, and review/profile hydration into an SEO market audit. It builds on existing `directory_workflow`, `maps_search`, and `maps_place_intel`.
4
+
5
+ ## User promise
6
+
7
+ ```bash
8
+ mcp-scraper-cli workflow run local-competitive-audit \
9
+ --query "roofers" \
10
+ --state TN \
11
+ --min-pop 100000 \
12
+ --per-city 20 \
13
+ --hydrate-top 5 \
14
+ --reviews 50 \
15
+ --concurrency 5
16
+ ```
17
+
18
+ The workflow answers:
19
+
20
+ - Which businesses rank in each city?
21
+ - What position are they in?
22
+ - How many reviews and what star rating?
23
+ - What categories are competitors using?
24
+ - What do reviews say customers praise or complain about?
25
+ - Which cities look easiest or hardest to compete in?
26
+ - Which competitors should the client study?
27
+
28
+ ## Current capabilities to reuse
29
+
30
+ Existing `directory_workflow` already:
31
+
32
+ - Selects US cities from Census population data.
33
+ - Optionally enriches with US ZIP groups.
34
+ - Runs Maps searches city-by-city.
35
+ - Saves CSV with `source_location`, `result_position`, `business_name`, `review_stars`, `category`, address, phone, URLs, CIDs, population, ZIP groups.
36
+
37
+ Existing `maps_place_intel` already:
38
+
39
+ - Hydrates one known/named business.
40
+ - Returns rating, review count, category, hours, phone, website, booking URL, review histogram, review topics, about attributes, and optional review cards.
41
+
42
+ ## Required base improvement
43
+
44
+ Add `review_count` to Maps search and directory CSV output.
45
+
46
+ Current Maps search extraction includes `reviewCount` in structured result types. Directory CSV currently writes `review_stars` but not review count. Add:
47
+
48
+ ```text
49
+ review_count
50
+ ```
51
+
52
+ Next to `review_stars`.
53
+
54
+ Files:
55
+
56
+ - `src/directory/directory-workflow.ts`
57
+ - `src/mcp/mcp-tool-schemas.ts` descriptions/output schema if needed.
58
+ - `src/mcp/mcp-response-formatter.ts` if table output should show count.
59
+ - Tests that assert CSV header and row value.
60
+
61
+ ## Inputs
62
+
63
+ CLI flags:
64
+
65
+ ```bash
66
+ --query <business category> # required, e.g. roofers
67
+ --state <US state> # required
68
+ --min-pop <number> # default 100000
69
+ --max-cities <number> # optional
70
+ --per-city <number> # default 20, cap 50
71
+ --hydrate-top <number> # default 5, cap 10
72
+ --reviews <number> # default 50, cap 200
73
+ --concurrency <number> # default 5
74
+ --proxy-mode location # default
75
+ --include-reviews # default true when reviews > 0
76
+ --output-dir <path>
77
+ --return-partial # default true
78
+ ```
79
+
80
+ Structured input:
81
+
82
+ ```ts
83
+ interface LocalCompetitiveAuditInput {
84
+ query: string
85
+ state: string
86
+ minPopulation: number
87
+ maxCities?: number
88
+ maxResultsPerCity: number
89
+ hydrateTop: number
90
+ maxReviews: number
91
+ concurrency: number
92
+ proxyMode: 'location' | 'configured' | 'none'
93
+ returnPartial: boolean
94
+ }
95
+ ```
96
+
97
+ ## Workflow steps
98
+
99
+ ### 1. Market seed
100
+
101
+ Call `directory_workflow` with:
102
+
103
+ - `query`
104
+ - `state`
105
+ - `minPopulation`
106
+ - `maxResultsPerCity`
107
+ - `concurrency`
108
+ - `proxyMode: "location"` by default
109
+
110
+ Save the raw structured result and directory CSV.
111
+
112
+ ### 2. Competitor selection
113
+
114
+ For each successful city:
115
+
116
+ - Select top `hydrateTop` businesses by `result_position`.
117
+ - Deduplicate by CID first, then place URL, then normalized name + city.
118
+ - Carry city/rank context forward into hydration.
119
+
120
+ ### 3. Profile hydration
121
+
122
+ For each selected competitor:
123
+
124
+ Call `maps_place_intel` with:
125
+
126
+ ```ts
127
+ {
128
+ businessName,
129
+ location: sourceLocation,
130
+ includeReviews: maxReviews > 0,
131
+ maxReviews
132
+ }
133
+ ```
134
+
135
+ Concurrency:
136
+
137
+ - Default 3 profile hydrations at a time.
138
+ - Keep city Maps search concurrency separate from hydration concurrency.
139
+
140
+ ### 4. Review insight extraction
141
+
142
+ No server-side LLM required for first version. Use structured Maps profile evidence and extractive review analysis.
143
+
144
+ Inputs:
145
+
146
+ - `reviewCount`
147
+ - `reviewHistogram`
148
+ - `reviewTopics`
149
+ - `reviews[].stars`
150
+ - `reviews[].text`
151
+ - `reviews[].ownerResponse`
152
+ - `aboutAttributes`
153
+
154
+ Derived fields:
155
+
156
+ ```ts
157
+ interface ReviewInsights {
158
+ reviewCount: string | null
159
+ averageRating: string | null
160
+ histogram: Array<{ stars: number; count: string }>
161
+ topics: Array<{ label: string; count: string }>
162
+ praiseTerms: Array<{ term: string; count: number }>
163
+ complaintTerms: Array<{ term: string; count: number }>
164
+ samplePositiveReviews: Array<{ stars: string | null; text: string }>
165
+ sampleNegativeReviews: Array<{ stars: string | null; text: string }>
166
+ ownerResponseRate: number | null
167
+ }
168
+ ```
169
+
170
+ Simple extraction rules:
171
+
172
+ - Positive sample: 5-star reviews with text, longest useful snippets first.
173
+ - Negative sample: 1-3 star reviews with text, longest useful snippets first.
174
+ - Praise/complaint terms: n-grams from review text after stopword removal, separated by star band.
175
+ - Owner response rate: reviews with `ownerResponse` / reviews with text.
176
+
177
+ ### 5. Market scoring
178
+
179
+ Calculate city-level and competitor-level scores.
180
+
181
+ City fields:
182
+
183
+ ```ts
184
+ interface CityAuditSummary {
185
+ city: string
186
+ state: string
187
+ population: number | null
188
+ resultCount: number
189
+ medianReviewCount: number | null
190
+ medianRating: number | null
191
+ topThreeAverageReviewCount: number | null
192
+ categories: Array<{ category: string; count: number }>
193
+ opportunityScore: number
194
+ difficultyScore: number
195
+ notes: string[]
196
+ }
197
+ ```
198
+
199
+ Initial scoring:
200
+
201
+ - Difficulty increases with top-three review count, rating, number of results with websites, and category consistency.
202
+ - Opportunity increases when top results have low review counts, missing websites, weak ratings, sparse categories, or incomplete profiles.
203
+
204
+ Do not overstate precision. Label these as heuristic scores.
205
+
206
+ ## Artifacts
207
+
208
+ ```text
209
+ local-competitive-audit/
210
+ manifest.json
211
+ report.html
212
+ summary.md
213
+ evidence.json
214
+ city-summary.csv
215
+ competitors.csv
216
+ review-insights.csv
217
+ tasks.md
218
+ raw/directory-workflow.json
219
+ raw/maps-place-intel/*.json
220
+ logs/attempts.jsonl
221
+ ```
222
+
223
+ ### `competitors.csv`
224
+
225
+ Columns:
226
+
227
+ ```text
228
+ city,state,source_location,result_position,business_name,category,review_stars,review_count,phone,website_url,place_url,cid,cid_decimal,hydrated,review_topics,owner_response_rate
229
+ ```
230
+
231
+ ### `review-insights.csv`
232
+
233
+ Columns:
234
+
235
+ ```text
236
+ city,business_name,cid,review_count,average_rating,topic,praise_terms,complaint_terms,positive_sample,negative_sample
237
+ ```
238
+
239
+ ### `city-summary.csv`
240
+
241
+ Columns:
242
+
243
+ ```text
244
+ city,state,population,result_count,median_review_count,median_rating,top_three_average_review_count,top_categories,opportunity_score,difficulty_score
245
+ ```
246
+
247
+ ## HTML report sections
248
+
249
+ 1. Executive summary.
250
+ 2. City opportunity table.
251
+ 3. Top competitors by city.
252
+ 4. Review count/rating distribution.
253
+ 5. Category patterns.
254
+ 6. Review themes: praise and complaints.
255
+ 7. Weak competitor opportunities.
256
+ 8. Recommended content/GBP actions.
257
+ 9. Diagnostics: failed cities, failed profile hydrations, attempts.
258
+
259
+ ## Agent task output
260
+
261
+ `tasks.md` should include:
262
+
263
+ ```md
264
+ - [ ] Build city landing page recommendations from `city-summary.csv`.
265
+ - [ ] Use review praise/complaint themes as customer-language evidence.
266
+ - [ ] Identify competitors with low review count but high rank.
267
+ - [ ] Compare target GBP category and review profile against top competitors.
268
+ - [ ] Draft GBP/content improvements without inventing review claims.
269
+ ```
270
+
271
+ ## MCP tool option
272
+
273
+ After CLI proof, add an MCP tool:
274
+
275
+ ```text
276
+ local_competitive_audit
277
+ ```
278
+
279
+ Description:
280
+
281
+ ```text
282
+ Run a local SEO competitive audit by selecting city markets, searching Google Maps competitors, hydrating top profiles, collecting review/category evidence, and saving CSV/HTML artifacts. Use when the user asks who ranks locally, how strong competitors are, what reviews say, or where market opportunities exist.
283
+ ```
284
+
285
+ This tool should return `structuredContent` with artifact paths and city/competitor summaries.
286
+
287
+ ## Credit model
288
+
289
+ Estimate:
290
+
291
+ - Directory seed: `cities * maps_search`.
292
+ - Profile hydration: `hydrated_profiles * maps_place_intel`.
293
+ - Reviews: `collected_review_cards * maps_review`.
294
+
295
+ For CLI runs, print estimated costs before hosted execution unless `--yes` is passed.
296
+
297
+ ## Partial-result behavior
298
+
299
+ - If some cities fail, still audit successful cities.
300
+ - If hydration fails, keep base directory row and mark `hydrated=false`.
301
+ - If reviews are unavailable, keep profile metadata and mark `reviewsStatus`.
302
+ - If all directory cities fail, workflow fails with no audit report except diagnostics.
303
+
304
+ ## Tests
305
+
306
+ - Directory fixture + profile fixtures produce all three CSVs.
307
+ - `review_count` is preserved from Maps search into directory CSV.
308
+ - Deduping by CID prevents duplicate hydration.
309
+ - Review insight extraction is deterministic.
310
+ - Partial hydration failure writes warning and keeps report.
311
+ - HTML report escapes review text.
312
+
@@ -0,0 +1,304 @@
1
+ # Scheduled Workflows API — Implementation Spec
2
+
3
+ Scheduled workflows turn MCP Scraper from one-off data pulls into recurring SEO intelligence. This spec adds hosted schedule CRUD, run history, webhook delivery, and report retrieval for workflow runs.
4
+
5
+ ## Goals
6
+
7
+ - Schedule recurring workflow runs through API/CLI.
8
+ - Persist run history and artifacts.
9
+ - Send webhook callbacks on completion/failure.
10
+ - Support rank-monitor style use cases without replacing `rank_tracker_blueprint`.
11
+ - Keep local CLI and hosted scheduler using the same workflow registry.
12
+
13
+ ## Non-goals
14
+
15
+ - Do not build a full dashboard in this phase.
16
+ - Do not make `rank_tracker_blueprint` execute work; it remains a planning tool.
17
+ - Do not require OAuth; API key auth is enough for this phase.
18
+
19
+ ## Command surface
20
+
21
+ ```bash
22
+ mcp-scraper-cli schedule create local-competitive-audit \
23
+ --weekly \
24
+ --query roofers \
25
+ --state TN \
26
+ --min-pop 100000 \
27
+ --webhook https://example.com/mcp-scraper-hook
28
+
29
+ mcp-scraper-cli schedule list
30
+ mcp-scraper-cli schedule run <schedule-id>
31
+ mcp-scraper-cli schedule pause <schedule-id>
32
+ mcp-scraper-cli schedule resume <schedule-id>
33
+ mcp-scraper-cli schedule delete <schedule-id>
34
+ mcp-scraper-cli runs list
35
+ mcp-scraper-cli runs status <run-id>
36
+ mcp-scraper-cli runs download <run-id>
37
+ ```
38
+
39
+ ## API routes
40
+
41
+ Mount:
42
+
43
+ ```text
44
+ /workflows
45
+ ```
46
+
47
+ Routes:
48
+
49
+ | Method | Path | Purpose |
50
+ |---|---|---|
51
+ | `GET` | `/workflows/definitions` | List available workflows and input schemas. |
52
+ | `POST` | `/workflows/run` | Start a manual workflow run. |
53
+ | `GET` | `/workflows/runs/:id` | Get run status, manifest, artifact links. |
54
+ | `GET` | `/workflows/runs/:id/artifacts/:artifactId` | Download artifact. |
55
+ | `POST` | `/workflows/schedules` | Create schedule. |
56
+ | `GET` | `/workflows/schedules` | List schedules for user. |
57
+ | `PATCH` | `/workflows/schedules/:id` | Pause/resume/update. |
58
+ | `DELETE` | `/workflows/schedules/:id` | Delete schedule. |
59
+ | `POST` | `/workflows/schedules/:id/run` | Manual run of a schedule. |
60
+
61
+ All routes use `createApiKeyAuth`.
62
+
63
+ ## Database schema
64
+
65
+ Add to `src/api/db.ts` migrations:
66
+
67
+ ```sql
68
+ CREATE TABLE IF NOT EXISTS workflow_schedules (
69
+ id TEXT PRIMARY KEY,
70
+ user_id INTEGER NOT NULL,
71
+ workflow_id TEXT NOT NULL,
72
+ name TEXT NOT NULL,
73
+ input_json TEXT NOT NULL,
74
+ cadence TEXT NOT NULL, -- daily | weekly | monthly | custom
75
+ cron_expr TEXT,
76
+ timezone TEXT NOT NULL DEFAULT 'UTC',
77
+ webhook_url TEXT,
78
+ status TEXT NOT NULL DEFAULT 'active', -- active | paused | deleted
79
+ next_run_at TEXT,
80
+ last_run_at TEXT,
81
+ created_at TEXT NOT NULL,
82
+ updated_at TEXT NOT NULL
83
+ );
84
+
85
+ CREATE TABLE IF NOT EXISTS workflow_runs (
86
+ id TEXT PRIMARY KEY,
87
+ user_id INTEGER NOT NULL,
88
+ schedule_id TEXT,
89
+ workflow_id TEXT NOT NULL,
90
+ input_json TEXT NOT NULL,
91
+ status TEXT NOT NULL DEFAULT 'queued', -- queued | running | succeeded | partial | failed | cancelled
92
+ manifest_json TEXT,
93
+ error_message TEXT,
94
+ queued_at TEXT NOT NULL,
95
+ started_at TEXT,
96
+ completed_at TEXT,
97
+ idempotency_key TEXT
98
+ );
99
+
100
+ CREATE TABLE IF NOT EXISTS workflow_artifacts (
101
+ id TEXT PRIMARY KEY,
102
+ run_id TEXT NOT NULL,
103
+ user_id INTEGER NOT NULL,
104
+ kind TEXT NOT NULL,
105
+ label TEXT NOT NULL,
106
+ path TEXT NOT NULL,
107
+ content_type TEXT NOT NULL,
108
+ bytes INTEGER,
109
+ rows_count INTEGER,
110
+ created_at TEXT NOT NULL
111
+ );
112
+
113
+ CREATE TABLE IF NOT EXISTS workflow_webhook_deliveries (
114
+ id TEXT PRIMARY KEY,
115
+ run_id TEXT NOT NULL,
116
+ schedule_id TEXT,
117
+ user_id INTEGER NOT NULL,
118
+ webhook_url TEXT NOT NULL,
119
+ status TEXT NOT NULL, -- pending | delivered | failed
120
+ attempts INTEGER NOT NULL DEFAULT 0,
121
+ last_error TEXT,
122
+ created_at TEXT NOT NULL,
123
+ delivered_at TEXT
124
+ );
125
+ ```
126
+
127
+ Indexes:
128
+
129
+ ```sql
130
+ CREATE INDEX IF NOT EXISTS idx_workflow_schedules_due ON workflow_schedules(status, next_run_at);
131
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_user_created ON workflow_runs(user_id, queued_at);
132
+ CREATE INDEX IF NOT EXISTS idx_workflow_artifacts_run ON workflow_artifacts(run_id);
133
+ ```
134
+
135
+ ## Worker model
136
+
137
+ Initial implementation can use a hosted cron route:
138
+
139
+ ```text
140
+ POST /workflows/cron/dispatch
141
+ ```
142
+
143
+ Protected by `CRON_SECRET`.
144
+
145
+ Dispatch steps:
146
+
147
+ 1. Find active schedules where `next_run_at <= now`.
148
+ 2. Create `workflow_runs` row with idempotency key `<scheduleId>:<scheduledTime>`.
149
+ 3. Enqueue/run workflow.
150
+ 4. Advance `next_run_at`.
151
+
152
+ If no queue is available, run small workflows inline and mark long workflows as queued for later worker integration. The workflow registry should not care whether a run is local, HTTP, or queued.
153
+
154
+ ## Run execution
155
+
156
+ `WorkflowRunService`:
157
+
158
+ ```ts
159
+ interface WorkflowRunService {
160
+ startManualRun(userId: number, workflowId: string, input: unknown): Promise<WorkflowRun>
161
+ startScheduledRun(schedule: WorkflowSchedule, scheduledFor: string): Promise<WorkflowRun>
162
+ executeRun(runId: string): Promise<void>
163
+ getRun(userId: number, runId: string): Promise<WorkflowRunWithArtifacts>
164
+ }
165
+ ```
166
+
167
+ Execution rules:
168
+
169
+ - Debit estimated credits before paid API work.
170
+ - Refund failed units when workflow has per-item partial failures.
171
+ - Persist `manifest_json` after every major phase so status pages are useful during partial failure.
172
+ - Save artifacts through a hosted artifact writer. Local paths are not enough for hosted runs.
173
+
174
+ ## Artifact storage
175
+
176
+ Phase 1:
177
+
178
+ - Store artifacts on local/Vercel ephemeral disk only for immediate response is not durable enough.
179
+ - Therefore hosted scheduled workflows need object storage before public rollout.
180
+
181
+ Recommended minimal storage:
182
+
183
+ - S3/R2-compatible bucket.
184
+ - Store by `user_id/run_id/artifact_id`.
185
+ - DB stores key/path and content type.
186
+
187
+ Local CLI can continue writing to `~/Downloads/mcp-scraper`.
188
+
189
+ ## Webhooks
190
+
191
+ Payload:
192
+
193
+ ```ts
194
+ interface WorkflowWebhookPayload {
195
+ event: 'workflow.run.succeeded' | 'workflow.run.partial' | 'workflow.run.failed'
196
+ runId: string
197
+ scheduleId: string | null
198
+ workflowId: string
199
+ status: string
200
+ completedAt: string
201
+ summary: {
202
+ title: string
203
+ counts: Record<string, number>
204
+ warnings: string[]
205
+ errors: string[]
206
+ }
207
+ artifacts: Array<{
208
+ kind: string
209
+ label: string
210
+ downloadUrl: string
211
+ }>
212
+ }
213
+ ```
214
+
215
+ Security:
216
+
217
+ - Sign payloads with `MCP_SCRAPER_WEBHOOK_SECRET`.
218
+ - Header: `x-mcp-scraper-signature`.
219
+ - Include timestamp header to prevent replay.
220
+
221
+ Retries:
222
+
223
+ - 3 attempts with exponential backoff.
224
+ - Persist every attempt in `workflow_webhook_deliveries`.
225
+
226
+ ## Scheduling modes
227
+
228
+ Supported:
229
+
230
+ ```ts
231
+ type WorkflowCadence = 'daily' | 'weekly' | 'monthly' | 'custom'
232
+ ```
233
+
234
+ For `custom`, require cron expression and timezone.
235
+
236
+ Validation:
237
+
238
+ - Minimum cadence: daily for expensive live-web workflows unless account tier allows more.
239
+ - Cap concurrent active schedules per account tier.
240
+ - Cap max run timeout.
241
+
242
+ ## Integration with `rank_tracker_blueprint`
243
+
244
+ `rank_tracker_blueprint` should mention scheduled workflows as an execution option:
245
+
246
+ - Use `scheduled-workflows` for hosted recurring MCP Scraper runs.
247
+ - Use generated rank-tracker schema when the user wants their own database/dashboard.
248
+
249
+ Do not make the blueprint depend on schedules.
250
+
251
+ ## Tests
252
+
253
+ - Schedule CRUD auth and ownership tests.
254
+ - Cron dispatch only runs due active schedules.
255
+ - Idempotency prevents duplicate runs for same scheduled time.
256
+ - Manual run creates run row and artifacts.
257
+ - Webhook signing and retry tests.
258
+ - Artifact download requires owning API key.
259
+ - Schedule create rejects unknown workflow IDs and invalid inputs.
260
+
261
+ ## Rollout phases
262
+
263
+ ## Implementation status
264
+
265
+ Implemented in the first working pass:
266
+
267
+ - Database migrations and helpers for `workflow_schedules`, `workflow_runs`, `workflow_artifacts`, and `workflow_webhook_deliveries`.
268
+ - Authenticated hosted routes for definitions, manual runs, run status, artifact downloads, schedule CRUD, manual schedule runs, and cron dispatch.
269
+ - Cron integration from the main `/cron/tick` route.
270
+ - CLI commands: `schedule create/list/run/pause/resume/delete` and `runs list/status/download`.
271
+ - Signed webhook delivery when `MCP_SCRAPER_WEBHOOK_SECRET` is configured, with delivery history.
272
+
273
+ Still required before broad public rollout:
274
+
275
+ - Durable object storage for hosted artifacts. Current hosted artifacts are filesystem-backed, which is enough for local/server testing and immediate retrieval but not durable across serverless cold starts or instance changes.
276
+ - More route coverage for cron idempotency, ownership on artifact downloads, and webhook retries.
277
+ - Account-tier schedule caps and preflight cost estimation before expensive hosted runs.
278
+
279
+ ### Phase 1 — API definitions and manual runs
280
+
281
+ - `/workflows/definitions`
282
+ - `/workflows/run`
283
+ - `/workflows/runs/:id`
284
+ - Local or test artifact storage.
285
+
286
+ ### Phase 2 — Durable artifact storage
287
+
288
+ - S3/R2 bucket.
289
+ - Artifact download routes.
290
+
291
+ ### Phase 3 — Schedules and cron dispatch
292
+
293
+ - Schedule CRUD.
294
+ - Cron-protected dispatcher.
295
+
296
+ ### Phase 4 — Webhooks
297
+
298
+ - Signed completion callbacks.
299
+ - Retry/delivery history.
300
+
301
+ ### Phase 5 — CLI schedule commands
302
+
303
+ - `schedule create/list/run/pause/resume/delete`.
304
+ - `runs list/status/download`.