webflow-mcp 0.3.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.
- package/.webflow/flows/_template/index.ts +14 -0
- package/.webflow/flows/_template/schema.json +11 -0
- package/.webflow/skills/README.md +3 -0
- package/README.md +215 -0
- package/dist/src/anti-block/provider-gate.d.ts +19 -0
- package/dist/src/anti-block/provider-gate.js +116 -0
- package/dist/src/cli/commands/approvals.d.ts +5 -0
- package/dist/src/cli/commands/approvals.js +69 -0
- package/dist/src/cli/commands/config.d.ts +4 -0
- package/dist/src/cli/commands/config.js +56 -0
- package/dist/src/cli/commands/create-flow.d.ts +4 -0
- package/dist/src/cli/commands/create-flow.js +27 -0
- package/dist/src/cli/commands/doctor.d.ts +19 -0
- package/dist/src/cli/commands/doctor.js +241 -0
- package/dist/src/cli/commands/fork.d.ts +4 -0
- package/dist/src/cli/commands/fork.js +19 -0
- package/dist/src/cli/commands/init.d.ts +4 -0
- package/dist/src/cli/commands/init.js +22 -0
- package/dist/src/cli/commands/login.d.ts +26 -0
- package/dist/src/cli/commands/login.js +145 -0
- package/dist/src/cli/commands/profiles.d.ts +40 -0
- package/dist/src/cli/commands/profiles.js +142 -0
- package/dist/src/cli/commands/run.d.ts +10 -0
- package/dist/src/cli/commands/run.js +13 -0
- package/dist/src/cli/commands/setup.d.ts +11 -0
- package/dist/src/cli/commands/setup.js +46 -0
- package/dist/src/cli/commands/worker.d.ts +29 -0
- package/dist/src/cli/commands/worker.js +30 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +336 -0
- package/dist/src/config/webflow-config.d.ts +35 -0
- package/dist/src/config/webflow-config.js +119 -0
- package/dist/src/errors/self-healing.d.ts +62 -0
- package/dist/src/errors/self-healing.js +77 -0
- package/dist/src/mcp/cli.d.ts +2 -0
- package/dist/src/mcp/cli.js +36 -0
- package/dist/src/mcp/relay-gateway.d.ts +24 -0
- package/dist/src/mcp/relay-gateway.js +208 -0
- package/dist/src/mcp/server.d.ts +8 -0
- package/dist/src/mcp/server.js +62 -0
- package/dist/src/mcp/tool-registry.d.ts +10 -0
- package/dist/src/mcp/tool-registry.js +366 -0
- package/dist/src/profiles/profile-manager.d.ts +33 -0
- package/dist/src/profiles/profile-manager.js +37 -0
- package/dist/src/profiles/profile-state.d.ts +31 -0
- package/dist/src/profiles/profile-state.js +122 -0
- package/dist/src/repertoire/local-repertoire.d.ts +3 -0
- package/dist/src/repertoire/local-repertoire.js +59 -0
- package/dist/src/runner/browser-session.d.ts +23 -0
- package/dist/src/runner/browser-session.js +58 -0
- package/dist/src/runner/cdp-session.d.ts +25 -0
- package/dist/src/runner/cdp-session.js +156 -0
- package/dist/src/runner/chrome.d.ts +18 -0
- package/dist/src/runner/chrome.js +56 -0
- package/dist/src/runner/flow-loader.d.ts +11 -0
- package/dist/src/runner/flow-loader.js +115 -0
- package/dist/src/runner/flow-runner.d.ts +70 -0
- package/dist/src/runner/flow-runner.js +378 -0
- package/dist/src/runner/types.d.ts +112 -0
- package/dist/src/runner/types.js +1 -0
- package/dist/src/schema/translator.d.ts +5 -0
- package/dist/src/schema/translator.js +104 -0
- package/dist/src/schema/types.d.ts +32 -0
- package/dist/src/schema/types.js +1 -0
- package/dist/src/security/flow-approvals.d.ts +34 -0
- package/dist/src/security/flow-approvals.js +324 -0
- package/dist/src/security/flow-release.d.ts +82 -0
- package/dist/src/security/flow-release.js +392 -0
- package/dist/src/security/official-flow-trust.d.ts +2 -0
- package/dist/src/security/official-flow-trust.js +16 -0
- package/dist/src/security/trusted-flow-keys.d.ts +8 -0
- package/dist/src/security/trusted-flow-keys.js +132 -0
- package/dist/src/shared/credentials.d.ts +21 -0
- package/dist/src/shared/credentials.js +99 -0
- package/dist/src/shared/errors.d.ts +6 -0
- package/dist/src/shared/errors.js +16 -0
- package/dist/src/shared/fs.d.ts +5 -0
- package/dist/src/shared/fs.js +23 -0
- package/dist/src/shared/paths.d.ts +112 -0
- package/dist/src/shared/paths.js +257 -0
- package/dist/src/shared/semaphore.d.ts +8 -0
- package/dist/src/shared/semaphore.js +23 -0
- package/dist/src/shared/taxonomy.d.ts +21 -0
- package/dist/src/shared/taxonomy.js +20 -0
- package/dist/src/worker/connection.d.ts +72 -0
- package/dist/src/worker/connection.js +427 -0
- package/dist/src/worker/lifecycle.d.ts +50 -0
- package/dist/src/worker/lifecycle.js +154 -0
- package/dist/src/worker/protocol.d.ts +188 -0
- package/dist/src/worker/protocol.js +156 -0
- package/dist/src/worker/worker-service.d.ts +112 -0
- package/dist/src/worker/worker-service.js +258 -0
- package/package.json +50 -0
- package/taxonomy.json +99 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { FlowRunner } from '../runner/flow-runner.js';
|
|
2
|
+
import { discoverFlows, loadFlowModule } from '../runner/flow-loader.js';
|
|
3
|
+
import { getProfileStatus, markInteractiveSetup, markLoginRequired } from '../profiles/profile-state.js';
|
|
4
|
+
import { launchBrowserSession } from '../runner/browser-session.js';
|
|
5
|
+
import { readJsonFile } from '../shared/fs.js';
|
|
6
|
+
import { WebFlowError } from '../shared/errors.js';
|
|
7
|
+
import { sanitizeName } from '../shared/paths.js';
|
|
8
|
+
import { translateSchema } from '../schema/translator.js';
|
|
9
|
+
import { loadConfig, setDefaultBrowserMode, setDefaultBrowserVisibility, setFlowBrowserMode, setFlowBrowserVisibility } from '../config/webflow-config.js';
|
|
10
|
+
import { addToRepertoire, listRepertoire, removeFromRepertoire } from '../repertoire/local-repertoire.js';
|
|
11
|
+
import { assessAndRecordPending } from '../security/flow-approvals.js';
|
|
12
|
+
import { isVerifiedRemoteFlow } from '../security/flow-release.js';
|
|
13
|
+
function defaultBrowserModeFromCapability(capability) {
|
|
14
|
+
if (capability === 'cdp')
|
|
15
|
+
return 'cdp';
|
|
16
|
+
if (capability === 'managed' || capability === true)
|
|
17
|
+
return 'managed';
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
function flowSupportedBrowserModes(capability, declared) {
|
|
21
|
+
if (declared && declared.length > 0)
|
|
22
|
+
return [...new Set(declared)];
|
|
23
|
+
const fallback = defaultBrowserModeFromCapability(capability);
|
|
24
|
+
return fallback ? [fallback] : undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Local, network-agnostic execution surface for the worker transport (RFC 0001,
|
|
28
|
+
* Fase 0/1). Delegates every bit of actual execution to the existing
|
|
29
|
+
* FlowRunner/discoverFlows — this class only adds catalog building, a per-profile
|
|
30
|
+
* lock, and a login pre-check, none of which existed before because a single stdio
|
|
31
|
+
* client never called runFlow twice concurrently and always ran with a human at the
|
|
32
|
+
* keyboard to notice a login wall.
|
|
33
|
+
*/
|
|
34
|
+
export class WorkerService {
|
|
35
|
+
projectRoot;
|
|
36
|
+
runner;
|
|
37
|
+
headed;
|
|
38
|
+
browserMode;
|
|
39
|
+
browserVisibility;
|
|
40
|
+
flowsByName = new Map();
|
|
41
|
+
loginMetaByName = new Map();
|
|
42
|
+
locks = new Map();
|
|
43
|
+
constructor(projectRoot, options = {}) {
|
|
44
|
+
this.projectRoot = projectRoot;
|
|
45
|
+
this.runner = options.runner ?? new FlowRunner(projectRoot);
|
|
46
|
+
this.headed = options.headed;
|
|
47
|
+
this.browserMode = options.browserMode;
|
|
48
|
+
this.browserVisibility = options.browserVisibility;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Discovers flows, compiles their schemas, and loads each flow module once to read
|
|
52
|
+
* its `capabilities` (requiresLogin/profile). Refreshes the internal catalog used by
|
|
53
|
+
* runFlow(). Call again after reconnecting to a relay so a stale catalog (e.g. a flow
|
|
54
|
+
* removed from disk) never lingers (RFC 0001, section 2.3: reconnection resends the
|
|
55
|
+
* full catalog; the relay overwrites, never merges).
|
|
56
|
+
*/
|
|
57
|
+
async buildCatalog() {
|
|
58
|
+
const { flows } = await discoverFlows(this.projectRoot);
|
|
59
|
+
this.flowsByName = new Map(flows.map((flow) => [flow.name, flow]));
|
|
60
|
+
this.loginMetaByName = new Map();
|
|
61
|
+
const entries = [];
|
|
62
|
+
for (const flow of flows) {
|
|
63
|
+
const compiled = translateSchema(await readJsonFile(flow.schemaFile));
|
|
64
|
+
const module = await loadFlowModule(flow.entryFile);
|
|
65
|
+
const meta = {
|
|
66
|
+
requiresLogin: module.capabilities?.requiresLogin ?? false,
|
|
67
|
+
loginProvider: module.capabilities?.profile,
|
|
68
|
+
loginUrl: module.capabilities?.startUrl && module.capabilities.startUrl !== 'about:blank' ? module.capabilities.startUrl : undefined
|
|
69
|
+
};
|
|
70
|
+
this.loginMetaByName.set(flow.name, meta);
|
|
71
|
+
entries.push({
|
|
72
|
+
name: flow.name,
|
|
73
|
+
description: compiled.jsonSchema.description ?? `Run the ${flow.name} flow.`,
|
|
74
|
+
inputSchema: compiled.jsonSchema,
|
|
75
|
+
requiresLogin: meta.requiresLogin,
|
|
76
|
+
loginProvider: meta.loginProvider,
|
|
77
|
+
loginUrl: meta.loginUrl,
|
|
78
|
+
defaultBrowserMode: defaultBrowserModeFromCapability(module.capabilities?.browser),
|
|
79
|
+
supportedBrowserModes: flowSupportedBrowserModes(module.capabilities?.browser, module.capabilities?.supportedBrowserModes)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return entries;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Runs a flow by name against the catalog built by buildCatalog(). Two safety nets
|
|
86
|
+
* that a single local stdio client never needed:
|
|
87
|
+
*
|
|
88
|
+
* 1. Login pre-check: if the flow declares `requiresLogin: true` and its resolved
|
|
89
|
+
* profile directory does not exist yet, fails fast with a `LOGIN_REQUIRED` error
|
|
90
|
+
* instead of letting Playwright launch a fresh, unauthenticated Chrome and time
|
|
91
|
+
* out minutes later on a login wall with a confusing generic error.
|
|
92
|
+
* 2. Per-profile lock: serializes concurrent calls that resolve to the same profile
|
|
93
|
+
* so two jobs never race to launch Chrome against the same user-data-dir (one
|
|
94
|
+
* userDataDir = one process at a time; see modules/sessions-y-profiles.md).
|
|
95
|
+
*/
|
|
96
|
+
async runFlow(name, input, options) {
|
|
97
|
+
const flow = this.flowsByName.get(name);
|
|
98
|
+
if (!flow) {
|
|
99
|
+
throw new WebFlowError('FLOW_NOT_FOUND', `No flow named "${name}" in this worker's catalog. Call buildCatalog() first, or the flow may have been removed.`, { flow: name });
|
|
100
|
+
}
|
|
101
|
+
const meta = this.loginMetaByName.get(name);
|
|
102
|
+
const profileName = this.resolveProfileName(input, options?.profileName, meta?.loginProvider, flow.name);
|
|
103
|
+
if (meta?.requiresLogin) {
|
|
104
|
+
const profile = await this.runner.profileManager.resolve(profileName, flow.name);
|
|
105
|
+
const status = await getProfileStatus(profile, { loginUrl: meta.loginUrl });
|
|
106
|
+
if (status.probablyValid === false) {
|
|
107
|
+
if (status.status === 'missing') {
|
|
108
|
+
await markLoginRequired(profile, name);
|
|
109
|
+
}
|
|
110
|
+
throw new WebFlowError('LOGIN_REQUIRED', `Flow "${name}" requires a logged-in session for profile "${profile.id}", but none was found. Run "${status.setupCommand}" on this machine first.`, { flow: name, profile: profile.id, profileStatus: status.status, loginUrl: meta.loginUrl, setupCommand: status.setupCommand });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const runOptions = {
|
|
114
|
+
...options,
|
|
115
|
+
headed: options?.headed ?? this.headed,
|
|
116
|
+
browserMode: options?.browserMode ?? this.browserMode,
|
|
117
|
+
browserVisibility: options?.browserVisibility ?? this.browserVisibility
|
|
118
|
+
};
|
|
119
|
+
const runPromise = this.enqueue(profileName, () => this.runner.run(flow, input, runOptions));
|
|
120
|
+
return runPromise;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Runs a flow whose code the relay pushed alongside the job (RFC 0001, Fase 3) —
|
|
124
|
+
* there is no local flow definition to look up at all, by design: nothing, paid or
|
|
125
|
+
* free, is meant to persist on this machine. Reuses the same login pre-check and
|
|
126
|
+
* per-profile lock as runFlow(); only where the module comes from differs.
|
|
127
|
+
*/
|
|
128
|
+
async runVerifiedRemoteFlow(verified, input, options) {
|
|
129
|
+
if (!isVerifiedRemoteFlow(verified)) {
|
|
130
|
+
throw new WebFlowError('UNVERIFIED_REMOTE_FLOW', 'Remote flow execution requires a cryptographically verified release.');
|
|
131
|
+
}
|
|
132
|
+
const assertApproved = async () => {
|
|
133
|
+
const approval = await assessAndRecordPending(verified);
|
|
134
|
+
if (approval.status === 'pending') {
|
|
135
|
+
throw new WebFlowError('FLOW_UPDATE_APPROVAL_REQUIRED', `Flow "${verified.flowName}" version ${verified.version} is signed but needs local approval before it can run.`, {
|
|
136
|
+
flow: verified.flowName,
|
|
137
|
+
flowDigest: verified.flowDigest,
|
|
138
|
+
releaseId: verified.releaseId,
|
|
139
|
+
releaseVersion: verified.version,
|
|
140
|
+
releaseSequence: verified.sequence,
|
|
141
|
+
signerKeyId: verified.keyId,
|
|
142
|
+
trustDomain: verified.trustDomain,
|
|
143
|
+
setupCommand: `webflow approvals approve ${verified.flowName}`
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
await assertApproved();
|
|
148
|
+
const capabilities = verified.capabilities;
|
|
149
|
+
const flowName = verified.flowName;
|
|
150
|
+
const loginProvider = typeof capabilities.profile === 'string' ? capabilities.profile : undefined;
|
|
151
|
+
const loginUrl = typeof capabilities.startUrl === 'string' && capabilities.startUrl !== 'about:blank' ? capabilities.startUrl : undefined;
|
|
152
|
+
const profileName = this.resolveProfileName(input, undefined, loginProvider, flowName);
|
|
153
|
+
if (capabilities.requiresLogin) {
|
|
154
|
+
const profile = await this.runner.profileManager.resolve(profileName, flowName);
|
|
155
|
+
const status = await getProfileStatus(profile, { loginUrl });
|
|
156
|
+
if (status.probablyValid === false) {
|
|
157
|
+
if (status.status === 'missing') {
|
|
158
|
+
await markLoginRequired(profile, flowName);
|
|
159
|
+
}
|
|
160
|
+
throw new WebFlowError('LOGIN_REQUIRED', `Flow "${flowName}" requires a logged-in session for profile "${profile.id}", but none was found. Run "${status.setupCommand}" on this machine first.`, { flow: flowName, profile: profile.id, profileStatus: status.status, loginUrl, setupCommand: status.setupCommand });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const runOptions = {
|
|
164
|
+
...options,
|
|
165
|
+
profileName,
|
|
166
|
+
headed: options?.headed ?? this.headed,
|
|
167
|
+
browserMode: options?.browserMode ?? this.browserMode,
|
|
168
|
+
browserVisibility: options?.browserVisibility ?? this.browserVisibility
|
|
169
|
+
};
|
|
170
|
+
return this.enqueue(profileName, async () => {
|
|
171
|
+
await assertApproved();
|
|
172
|
+
return this.runner.runRemote(flowName, verified.code, verified.schema, input, runOptions, capabilities);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Config/repertoire are worker-local disk state (~/.webflow/config.json,
|
|
177
|
+
* ~/.webflow/repertoire.json) — never the relay's database, same principle as
|
|
178
|
+
* login sessions/profiles. These are the only entry points the relay's forwarded
|
|
179
|
+
* config_request/repertoire_request messages call into (see connection.ts). Reuse
|
|
180
|
+
* the same enqueue() lock used for flow runs, under fixed keys, so two concurrent
|
|
181
|
+
* requests never race writing the same file.
|
|
182
|
+
*/
|
|
183
|
+
async getConfig() {
|
|
184
|
+
return this.enqueue('config', () => loadConfig());
|
|
185
|
+
}
|
|
186
|
+
async setDefaultBrowserVisibility(value) {
|
|
187
|
+
return this.enqueue('config', () => setDefaultBrowserVisibility(value));
|
|
188
|
+
}
|
|
189
|
+
async setFlowBrowserVisibility(flowName, value) {
|
|
190
|
+
return this.enqueue('config', () => setFlowBrowserVisibility(flowName, value));
|
|
191
|
+
}
|
|
192
|
+
async setDefaultBrowserMode(value) {
|
|
193
|
+
return this.enqueue('config', () => setDefaultBrowserMode(value));
|
|
194
|
+
}
|
|
195
|
+
async setFlowBrowserMode(flowName, value) {
|
|
196
|
+
return this.enqueue('config', () => setFlowBrowserMode(flowName, value));
|
|
197
|
+
}
|
|
198
|
+
async listRepertoire() {
|
|
199
|
+
return this.enqueue('repertoire', () => listRepertoire());
|
|
200
|
+
}
|
|
201
|
+
/** Fast, read-only disk check — same status a running flow would see before deciding LOGIN_REQUIRED. */
|
|
202
|
+
async checkProfileStatus(flowName, loginProvider, loginUrl) {
|
|
203
|
+
const profile = await this.runner.profileManager.resolve(loginProvider, flowName);
|
|
204
|
+
return getProfileStatus(profile, { loginUrl });
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Opens a real, visible Chrome window on this machine for the human to log in
|
|
208
|
+
* manually, then returns as soon as the window is up — it does not wait for them
|
|
209
|
+
* to finish (that can take minutes, far too long for an MCP tool call). The
|
|
210
|
+
* per-profile lock (same one runFlow/runRemoteFlow use) is still held for the full
|
|
211
|
+
* session in the background, so an automated run against this profile can't start
|
|
212
|
+
* mid-login; call checkProfileStatus afterward to see whether it succeeded.
|
|
213
|
+
*/
|
|
214
|
+
async startProfileLogin(flowName, loginProvider, loginUrl) {
|
|
215
|
+
const profile = await this.runner.profileManager.ensure(loginProvider, flowName);
|
|
216
|
+
const launched = new Promise((resolveLaunch, rejectLaunch) => {
|
|
217
|
+
const backgroundTask = this.enqueue(profile.id, async () => {
|
|
218
|
+
const session = await launchBrowserSession({ userDataDir: profile.dir, headless: false, startUrl: loginUrl });
|
|
219
|
+
resolveLaunch();
|
|
220
|
+
await new Promise((done) => session.context.once('close', () => done()));
|
|
221
|
+
await markInteractiveSetup(profile, flowName);
|
|
222
|
+
});
|
|
223
|
+
backgroundTask.catch(rejectLaunch);
|
|
224
|
+
});
|
|
225
|
+
await launched;
|
|
226
|
+
return getProfileStatus(profile, { loginUrl });
|
|
227
|
+
}
|
|
228
|
+
async addToRepertoire(flowName) {
|
|
229
|
+
return this.enqueue('repertoire', () => addToRepertoire(flowName));
|
|
230
|
+
}
|
|
231
|
+
async removeFromRepertoire(flowName) {
|
|
232
|
+
return this.enqueue('repertoire', () => removeFromRepertoire(flowName));
|
|
233
|
+
}
|
|
234
|
+
enqueue(lockKey, task) {
|
|
235
|
+
const key = sanitizeName(lockKey);
|
|
236
|
+
const previous = this.locks.get(key) ?? Promise.resolve();
|
|
237
|
+
const next = previous.catch(() => undefined).then(task);
|
|
238
|
+
this.locks.set(key, next);
|
|
239
|
+
return next.finally(() => {
|
|
240
|
+
if (this.locks.get(key) === next) {
|
|
241
|
+
this.locks.delete(key);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
resolveProfileName(input, profileNameOverride, loginProvider, flowName) {
|
|
246
|
+
const inputProfile = input?.profile;
|
|
247
|
+
if (profileNameOverride) {
|
|
248
|
+
return profileNameOverride;
|
|
249
|
+
}
|
|
250
|
+
if (typeof inputProfile === 'string' && inputProfile.length > 0) {
|
|
251
|
+
return inputProfile;
|
|
252
|
+
}
|
|
253
|
+
if (loginProvider) {
|
|
254
|
+
return loginProvider;
|
|
255
|
+
}
|
|
256
|
+
return `${flowName}-default`;
|
|
257
|
+
}
|
|
258
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webflow-mcp",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "WebFlow MCP MVP with local flow workspaces and MCP tool exposure",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"webflow": "dist/src/cli/index.js",
|
|
9
|
+
"webflow-mcp": "dist/src/mcp/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/src/",
|
|
13
|
+
".webflow/flows/_template/",
|
|
14
|
+
".webflow/skills/",
|
|
15
|
+
"taxonomy.json",
|
|
16
|
+
"README.md",
|
|
17
|
+
"package.json"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"pretest": "node scripts/link-local-flows.mjs",
|
|
26
|
+
"docs:check": "node scripts/check-project-context.mjs",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"flows:keys:init": "tsx scripts/flows-keys-init.ts",
|
|
29
|
+
"flows:release": "tsx scripts/flows-release.ts",
|
|
30
|
+
"flows:verify": "tsx scripts/verify-official-flow-release.ts",
|
|
31
|
+
"smoke": "npm run build && node --import tsx tests/packaging-smoke.ts && vitest run tests/mcp-server.test.ts tests/mvp-smoke.test.ts",
|
|
32
|
+
"mcp": "node dist/src/mcp/cli.js"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36
|
+
"playwright-core": "^1.61.1",
|
|
37
|
+
"ws": "^8.21.0",
|
|
38
|
+
"zod": "^3.24.3"
|
|
39
|
+
},
|
|
40
|
+
"optionalDependencies": {
|
|
41
|
+
"esbuild": "^0.25.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.15.17",
|
|
45
|
+
"@types/ws": "^8.18.1",
|
|
46
|
+
"tsx": "^4.23.0",
|
|
47
|
+
"typescript": "^5.8.3",
|
|
48
|
+
"vitest": "^3.1.1"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/taxonomy.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sectors": {
|
|
3
|
+
"productividad-e-ia": {
|
|
4
|
+
"label": "Productividad e IA",
|
|
5
|
+
"services": {
|
|
6
|
+
"chatgpt": { "label": "ChatGPT", "description": "Conversación de texto, generación de imágenes e investigación profunda vía sesión de cuenta persistente." },
|
|
7
|
+
"gemini": { "label": "Gemini", "description": "Conversación de texto, generación de imágenes, vídeo e investigación profunda vía sesión de cuenta persistente." },
|
|
8
|
+
"google-ai-studio": { "label": "Google AI Studio", "description": "Generación de audio (texto a voz)." },
|
|
9
|
+
"adobe-express": { "label": "Adobe Express", "description": "Sincronización de labios entre audio y vídeo." }
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"redes-sociales": {
|
|
13
|
+
"label": "Redes Sociales",
|
|
14
|
+
"services": {
|
|
15
|
+
"twitter": { "label": "Twitter / X", "description": "Navegación progresiva de perfiles, timelines, respuestas, multimedia y búsqueda mediante sesión persistente." },
|
|
16
|
+
"reddit": { "label": "Reddit", "description": "Navegación pública progresiva de búsqueda, comunidades, posts y comentarios acotados, sin sesión." }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"investigacion-academica": {
|
|
20
|
+
"label": "Investigación Académica",
|
|
21
|
+
"services": {
|
|
22
|
+
"arxiv": { "label": "arXiv", "description": "Búsqueda y descarga de artículos científicos publicados, sin sesión." },
|
|
23
|
+
"nature": { "label": "Nature", "description": "Investigación progresiva: búsqueda relevante, metadatos e índice, lectura acotada y descarga completa vía sesión persistente." },
|
|
24
|
+
"pubmed": { "label": "PubMed", "description": "Búsqueda e inspección progresiva de literatura biomédica, abstracts, términos MeSH, artículos relacionados y texto disponible en PubMed Central." }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"financiero": {
|
|
28
|
+
"label": "Financiero",
|
|
29
|
+
"services": {
|
|
30
|
+
"yahoo-finance": { "label": "Yahoo Finance", "description": "Navegación progresiva por búsqueda, cotizaciones, noticias, análisis, estadísticas, estados financieros, históricos, opciones, holders y perfiles públicos." },
|
|
31
|
+
"finviz": { "label": "Finviz", "description": "Operaciones públicas de insiders, actividad reciente y enlaces a filings SEC." },
|
|
32
|
+
"dailyfx": { "label": "DailyFX", "description": "Calendario macroeconómico por período, impacto, país y texto, con datos reales, previstos y anteriores." },
|
|
33
|
+
"sec-edgar": { "label": "SEC EDGAR", "description": "Filings corporativos oficiales, índices, documentos principales, anexos y archivos XBRL publicados ante la SEC." },
|
|
34
|
+
"companies-house": { "label": "Companies House", "description": "Registro público británico de empresas, administradores, secretarios y presentaciones corporativas." }
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"software-y-desarrollo": {
|
|
38
|
+
"label": "Software y Desarrollo",
|
|
39
|
+
"services": {
|
|
40
|
+
"official-docs": { "label": "Official Documentation Sites", "description": "Exploración, lectura por secciones, descarga selectiva y búsqueda textual local de documentación técnica pública." },
|
|
41
|
+
"github": { "label": "GitHub", "description": "Navegación pública progresiva de repositorios, código visible, issues, pull requests, releases, perfiles y trending." },
|
|
42
|
+
"hugging-face": { "label": "Hugging Face", "description": "Búsqueda y colección progresiva de modelos con filtros visibles, datasets, Spaces, papers diarios, cards y archivos textuales seleccionados." },
|
|
43
|
+
"stack-overflow": { "label": "Stack Overflow", "description": "Navegación pública progresiva de preguntas, respuestas, comentarios, etiquetas y búsquedas." },
|
|
44
|
+
"npm": { "label": "npm", "description": "Navegación pública progresiva de paquetes, README, versiones, dependencias, metadatos y procedencia." },
|
|
45
|
+
"pypi": { "label": "PyPI", "description": "Navegación pública progresiva de paquetes Python, releases, archivos, hashes, descripciones y metadatos." }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"salud": { "label": "Salud", "services": {
|
|
49
|
+
"cima": { "label": "CIMA", "description": "Búsqueda, inspección y lectura progresiva de medicamentos, principios activos, presentaciones, fichas técnicas y prospectos oficiales de la AEMPS." },
|
|
50
|
+
"ema-medicines": { "label": "EMA Medicines", "description": "Catálogo público de medicamentos, fichas regulatorias y documentos publicados por la Agencia Europea de Medicamentos." }
|
|
51
|
+
} },
|
|
52
|
+
"inmobiliario": { "label": "Inmobiliario", "services": {} },
|
|
53
|
+
"legal": { "label": "Legal", "services": {
|
|
54
|
+
"boe": { "label": "BOE", "description": "Búsqueda y lectura progresiva de publicaciones, legislación consolidada, versiones históricas y documentos oficiales del Boletín Oficial del Estado." }
|
|
55
|
+
} },
|
|
56
|
+
"educacion": { "label": "Educación", "services": {
|
|
57
|
+
"todofp": { "label": "TodoFP", "description": "Consulta progresiva de familias profesionales, ciclos, cursos de especialización, requisitos, planes formativos, documentos y buscadores autonómicos de centros." },
|
|
58
|
+
"qedu": { "label": "QEDU", "description": "Oferta universitaria publicada por el Ministerio de Ciencia, Innovación y Universidades, con titulaciones, universidades, centros, notas de corte y curso académico cuando está disponible." }
|
|
59
|
+
} },
|
|
60
|
+
"comercio-y-retail": { "label": "Comercio y Retail", "services": {
|
|
61
|
+
"pccomponentes": { "label": "PcComponentes", "description": "Búsqueda visible por texto, marca y precio, con orden, continuación, colección acotada, fichas y comparación de productos tecnológicos." },
|
|
62
|
+
"idealo": { "label": "Idealo", "description": "Búsqueda y comparación de productos, precios, comercios, envío, entrega y especificaciones con filtros visibles y colección progresiva acotada." },
|
|
63
|
+
"el-corte-ingles": { "label": "El Corte Inglés", "description": "Búsqueda pública de productos por texto, marca y orden, con colección acotada, fichas y comparación de productos." }
|
|
64
|
+
} },
|
|
65
|
+
"viajes-y-turismo": { "label": "Viajes y Turismo", "services": {
|
|
66
|
+
"spain-info": { "label": "Spain.info", "description": "Búsqueda, inspección y lectura progresiva de destinos, eventos, rutas, atracciones y artículos del portal oficial de turismo de España." },
|
|
67
|
+
"aena": { "label": "Aena", "description": "Consulta progresiva de aeropuertos, páginas operativas y vuelos públicos de la red Aena, con lectura acotada de contenidos aeroportuarios." },
|
|
68
|
+
"tripadvisor": { "label": "Tripadvisor", "description": "Búsqueda y consulta progresiva de destinos, hoteles, restaurantes, atracciones y experiencias públicas con puntuaciones, opiniones, ubicación y datos visibles." }
|
|
69
|
+
} },
|
|
70
|
+
"marketing-y-publicidad": { "label": "Marketing y Publicidad", "services": {
|
|
71
|
+
"google-trends": { "label": "Google Trends", "description": "Tendencias públicas filtradas y recopiladas por páginas, comparación Explore y navegación Rising/Top de temas y consultas relacionadas." }
|
|
72
|
+
} },
|
|
73
|
+
"energia-y-medio-ambiente": { "label": "Energía y Medio Ambiente", "services": {
|
|
74
|
+
"aemet": { "label": "AEMET", "description": "Búsqueda pública de municipios, predicción meteorológica visible a siete días y avisos meteorológicos oficiales de Península, Baleares y Canarias." }
|
|
75
|
+
} },
|
|
76
|
+
"deportes": { "label": "Deportes", "services": {
|
|
77
|
+
"transfermarkt": { "label": "Transfermarkt", "description": "Navegación pública progresiva de jugadores, clubes, competiciones, valores de mercado, plantillas y fichajes con filtros y paginación nativos." }
|
|
78
|
+
} },
|
|
79
|
+
"inmobiliario": { "label": "Inmobiliario", "services": {
|
|
80
|
+
"fotocasa": { "label": "Fotocasa", "description": "Navegación pública de resultados y fichas inmobiliarias de venta, alquiler y vivienda compartida, con comparación acotada de anuncios." },
|
|
81
|
+
"pisos-com": { "label": "pisos.com", "description": "Búsqueda pública de anuncios inmobiliarios de compra, alquiler y obra nueva, con filtros visibles, paginación, fichas y comparación acotada." },
|
|
82
|
+
"idealista": { "label": "Idealista", "description": "Búsqueda y filtrado mediante controles visibles de anuncios inmobiliarios de venta, alquiler y habitaciones, con fichas, comparación acotada y detección de bloqueos anti-bot." }
|
|
83
|
+
} },
|
|
84
|
+
"automocion": { "label": "Automoción", "services": {
|
|
85
|
+
"autoscout24": { "label": "AutoScout24", "description": "Búsqueda pública de vehículos con filtros nativos, paginación progresiva, precios, kilometraje, combustible, transmisión, vendedores, fichas y comparación acotada." }
|
|
86
|
+
} },
|
|
87
|
+
"ciberseguridad": { "label": "Ciberseguridad", "services": {
|
|
88
|
+
"nvd": { "label": "NVD", "description": "Consulta pública de vulnerabilidades CVE, métricas CVSS, productos CPE, debilidades CWE, referencias y estado CISA KEV publicados por NIST." }
|
|
89
|
+
} },
|
|
90
|
+
"medios-y-entretenimiento": { "label": "Medios y Entretenimiento", "services": {
|
|
91
|
+
"justwatch": { "label": "JustWatch", "description": "Búsqueda localizada, descubrimiento y colección paginada por proveedor de películas y series, con disponibilidad, precios y metadatos visibles." },
|
|
92
|
+
"letterboxd": { "label": "Letterboxd", "description": "Películas, listas populares con colección paginada, diarios y críticas públicas con metadatos, valoraciones y actividad visible." }
|
|
93
|
+
} },
|
|
94
|
+
"empleo-y-recursos-humanos": { "label": "Empleo y Recursos Humanos", "services": {
|
|
95
|
+
"eures": { "label": "EURES", "description": "Búsqueda y consulta progresiva de vacantes oficiales europeas por palabras clave, países, orden y página, sin envío de candidaturas." },
|
|
96
|
+
"infojobs": { "label": "InfoJobs", "description": "Búsqueda y consulta progresiva de ofertas españolas, empresas, ubicación, modalidad, salario, contrato y requisitos, sin envío de candidaturas." }
|
|
97
|
+
} }
|
|
98
|
+
}
|
|
99
|
+
}
|