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.
- package/README.md +44 -5
- package/dist/bin/api-server.cjs +2417 -648
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/browser-agent-stdio-server.cjs +1 -1
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1276 -0
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -0
- package/dist/bin/mcp-scraper-cli.d.cts +1 -0
- package/dist/bin/mcp-scraper-cli.d.ts +1 -0
- package/dist/bin/mcp-scraper-cli.js +476 -0
- package/dist/bin/mcp-scraper-cli.js.map +1 -0
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +69 -14
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +5 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +5 -1
- package/dist/bin/mcp-scraper-install.js.map +1 -1
- package/dist/bin/mcp-stdio-server.cjs +69 -14
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/bin/paa-harvest.cjs +42 -6
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/{chunk-3TF6UT2P.js → chunk-5E6KJC26.js} +2 -2
- package/dist/{chunk-D4CJBZBY.js → chunk-7GCCOT3M.js} +372 -1
- package/dist/chunk-7GCCOT3M.js.map +1 -0
- package/dist/chunk-BJDCPCIE.js +7 -0
- package/dist/chunk-BJDCPCIE.js.map +1 -0
- package/dist/{chunk-MY3S7EX7.js → chunk-CQTAKXBN.js} +43 -7
- package/dist/chunk-CQTAKXBN.js.map +1 -0
- package/dist/chunk-L6IS63WS.js +869 -0
- package/dist/chunk-L6IS63WS.js.map +1 -0
- package/dist/{chunk-IQOCZGJJ.js → chunk-ROS67BNV.js} +5 -5
- package/dist/{chunk-7SI6XIR3.js → chunk-XAY5U67D.js} +70 -15
- package/dist/chunk-XAY5U67D.js.map +1 -0
- package/dist/{db-YWCNHBLH.js → db-BVHYI57K.js} +38 -2
- package/dist/index.cjs +42 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-RIKRBDOI.js → server-BKJ7LULQ.js} +818 -394
- package/dist/server-BKJ7LULQ.js.map +1 -0
- package/dist/{worker-NAKGTIF5.js → worker-TDJQ6TH3.js} +8 -8
- package/docs/mcp-tool-quality-spec.md +3 -1
- package/docs/specs/agent-ready-seo-packet-spec.md +237 -0
- package/docs/specs/cli-agent-wiring-spec.md +203 -0
- package/docs/specs/deferred-work-spec.md +12 -0
- package/docs/specs/local-competitive-audit-spec.md +312 -0
- package/docs/specs/scheduled-workflows-api-spec.md +304 -0
- package/docs/specs/seo-cli-growth-roadmap-spec.md +179 -0
- package/docs/specs/seo-workflow-runner-and-reports-spec.md +241 -0
- package/package.json +3 -2
- package/dist/chunk-7SI6XIR3.js.map +0 -1
- package/dist/chunk-D4CJBZBY.js.map +0 -1
- package/dist/chunk-IZE5UW7Y.js +0 -7
- package/dist/chunk-IZE5UW7Y.js.map +0 -1
- package/dist/chunk-MY3S7EX7.js.map +0 -1
- package/dist/server-RIKRBDOI.js.map +0 -1
- /package/dist/{chunk-3TF6UT2P.js.map → chunk-5E6KJC26.js.map} +0 -0
- /package/dist/{chunk-IQOCZGJJ.js.map → chunk-ROS67BNV.js.map} +0 -0
- /package/dist/{db-YWCNHBLH.js.map → db-BVHYI57K.js.map} +0 -0
- /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`.
|