@microlink/mcp 1.0.0 → 1.1.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.
@@ -1,239 +1,52 @@
1
- import mql from '@microlink/mql'
1
+ import createClient, { MicrolinkError } from 'microlink.io'
2
2
 
3
- const RESPONSE_HEADER_KEYS = [
4
- 'x-request-id',
5
- 'x-pricing-plan',
6
- 'x-cache-status',
7
- 'x-cache-ttl',
8
- 'cf-cache-status',
9
- 'cache-control',
10
- 'x-rate-limit-limit',
11
- 'x-rate-limit-remaining',
12
- 'x-rate-limit-reset',
13
- 'x-response-time',
14
- 'content-type',
15
- 'content-encoding'
16
- ]
17
-
18
- const FREE_ENDPOINT_ORIGIN = 'https://api.microlink.io'
19
3
  const FREE_QUOTA_EXCEEDED_HINT =
20
4
  'Free daily quota reached (50 requests/day). Extend your limit by getting an API key at https://microlink.io/#pricing.'
21
- const EMPTY_OBJECT_MEANS_TRUE_FLAGS = ['screenshot', 'pdf', 'insights']
22
-
23
- function isPlainObject (value) {
24
- return Object.prototype.toString.call(value) === '[object Object]'
25
- }
26
-
27
- function isObject (value) {
28
- return value !== null && typeof value === 'object'
29
- }
30
-
31
- function hasSerializableValue (value) {
32
- if (value === undefined || value === null) {
33
- return false
34
- }
35
-
36
- if (Array.isArray(value)) {
37
- return value.length > 0
38
- }
39
-
40
- if (isPlainObject(value)) {
41
- return Object.values(value).some(hasSerializableValue)
42
- }
43
-
44
- return true
45
- }
46
-
47
- function getHeaderValue (headers, headerName) {
48
- if (!headers) {
49
- return null
50
- }
51
-
52
- if (typeof headers.get === 'function') {
53
- const value = headers.get(headerName)
54
- return value ?? null
55
- }
56
-
57
- if (typeof headers !== 'object') {
58
- return null
59
- }
60
-
61
- const lookup = headerName.toLowerCase()
62
-
63
- for (const [key, value] of Object.entries(headers)) {
64
- if (key.toLowerCase() === lookup) {
65
- return value
66
- }
67
- }
68
-
69
- return null
70
- }
71
-
72
- function pickResponseHeaders (headers) {
73
- const output = {}
74
-
75
- for (const headerName of RESPONSE_HEADER_KEYS) {
76
- const value = getHeaderValue(headers, headerName)
77
- if (value !== null) {
78
- output[headerName] = value
79
- }
80
- }
81
-
82
- return output
83
- }
84
-
85
- function withForcedFlags (opts, forcedFlags = {}) {
86
- const nextOpts = { ...opts }
87
-
88
- // Microlink treats empty objects for these toggles as enabled defaults.
89
- // Normalize `{}` -> `true` so query serialization always keeps the flag.
90
- for (const flagName of EMPTY_OBJECT_MEANS_TRUE_FLAGS) {
91
- if (isPlainObject(nextOpts[flagName]) && !hasSerializableValue(nextOpts[flagName])) {
92
- nextOpts[flagName] = true
93
- }
94
- }
95
5
 
96
- for (const [flagName, flagValue] of Object.entries(forcedFlags)) {
97
- if (!hasSerializableValue(nextOpts[flagName])) {
98
- nextOpts[flagName] = flagValue
99
- }
100
- }
101
-
102
- return nextOpts
103
- }
104
-
105
- function getEndpointFromRequestUrl (requestUrl) {
106
- try {
107
- return new URL(requestUrl).origin
108
- } catch (_) {
109
- return requestUrl
110
- }
111
- }
112
-
113
- function stripResponseFromBody (body) {
114
- if (!isPlainObject(body)) {
115
- return body
116
- }
6
+ // A single shared client; the per-request apiKey travels in the options bag.
7
+ export const client = createClient()
117
8
 
118
- const { response, ...payload } = body
119
- return payload
120
- }
121
-
122
- function withFreeQuotaHint (message, { requestUrl, statusCode }) {
123
- const endpoint = getEndpointFromRequestUrl(requestUrl)
124
-
125
- if (endpoint !== FREE_ENDPOINT_ORIGIN || statusCode !== 429) {
126
- return message
127
- }
128
-
129
- if (typeof message === 'string' && message.includes('50 requests/day')) {
130
- return message
131
- }
132
-
133
- if (typeof message === 'string' && message.trim().length > 0) {
134
- return `${message} ${FREE_QUOTA_EXCEEDED_HINT}`
135
- }
9
+ export { MicrolinkError }
136
10
 
137
- return FREE_QUOTA_EXCEEDED_HINT
11
+ export function resolveApiKey (inputApiKey, headerApiKey) {
12
+ return (
13
+ inputApiKey || headerApiKey || process.env.MICROLINK_API_KEY || undefined
14
+ )
138
15
  }
139
16
 
140
- function toErrorBody (error, requestUrl, statusCode) {
141
- if (!isObject(error)) {
142
- return {
143
- status: 'error',
144
- message: withFreeQuotaHint(error instanceof Error ? error.message : String(error), { requestUrl, statusCode }),
145
- url: requestUrl
146
- }
147
- }
148
-
149
- const payload = {}
150
-
151
- for (const key of ['status', 'data', 'more', 'code', 'id', 'report', 'message', 'url']) {
152
- if (error[key] !== undefined) {
153
- payload[key] = error[key]
154
- }
155
- }
156
-
157
- if (payload.status === undefined) {
158
- payload.status = 'error'
159
- }
160
-
161
- if (payload.message === undefined) {
162
- payload.message = error.message || 'Microlink request failed.'
163
- }
164
- payload.message = withFreeQuotaHint(payload.message, { requestUrl, statusCode })
165
-
166
- if (payload.url === undefined) {
167
- payload.url = requestUrl
17
+ // Every tool returns the library's direct result (a string, array, or object).
18
+ // MCP `structuredContent` must be an object, so wrap the value under `data`.
19
+ export function asToolResult (value) {
20
+ const data = value ?? null
21
+ return {
22
+ isError: false,
23
+ structuredContent: { data },
24
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }]
168
25
  }
169
-
170
- return payload
171
26
  }
172
27
 
173
- export async function callMicrolink ({ params, forcedFlags = {} }) {
174
- const { url, apiKey: requestApiKey, ...rawOpts } = params
175
- const envApiKey = process.env.MICROLINK_API_KEY
176
- const apiKey = requestApiKey || envApiKey
177
- const opts = withForcedFlags(rawOpts, forcedFlags)
178
-
179
- if (apiKey) {
180
- opts.apiKey = apiKey
181
- }
182
-
183
- const [requestUrl, requestOpts] = mql.getApiUrl(url, opts, {
184
- headers: {
185
- accept: 'application/json'
186
- }
187
- })
188
- const endpoint = getEndpointFromRequestUrl(requestUrl)
28
+ export function asErrorResult (error) {
29
+ const isMql = error instanceof MicrolinkError
30
+ const statusCode = isMql ? error.statusCode : undefined
189
31
 
190
- try {
191
- const body = await mql.fetchFromApi(requestUrl, requestOpts)
192
- const response = body?.response
193
-
194
- return {
195
- ok: true,
196
- statusCode: response?.statusCode ?? 200,
197
- requestUrl,
198
- finalUrl: response?.url ?? requestUrl,
199
- endpoint,
200
- headers: pickResponseHeaders(response?.headers),
201
- body: stripResponseFromBody(body)
202
- }
203
- } catch (error) {
204
- return {
205
- ok: false,
206
- statusCode: error?.statusCode ?? 500,
207
- requestUrl,
208
- finalUrl: error?.url ?? requestUrl,
209
- endpoint,
210
- headers: pickResponseHeaders(error?.headers),
211
- body: toErrorBody(error, requestUrl, error?.statusCode ?? 500)
212
- }
32
+ const payload = {
33
+ message:
34
+ (isMql ? error.description : undefined) || error?.message || String(error)
213
35
  }
214
- }
215
36
 
216
- export function asToolResult (result) {
217
- const output = {
218
- endpoint: result.endpoint,
219
- requestUrl: result.requestUrl,
220
- finalUrl: result.finalUrl,
221
- statusCode: result.statusCode,
222
- responseHeaders: result.headers,
223
- microlink: result.body
37
+ if (isMql) {
38
+ if (error.code) payload.code = error.code
39
+ if (error.status) payload.status = error.status
40
+ if (statusCode) payload.statusCode = statusCode
41
+ if (error.url) payload.url = error.url
42
+ if (error.more) payload.more = error.more
224
43
  }
225
44
 
226
- const isMicrolinkError = result.body?.status && result.body.status !== 'success'
227
- const isError = !result.ok || isMicrolinkError
45
+ if (statusCode === 429) payload.hint = FREE_QUOTA_EXCEEDED_HINT
228
46
 
229
47
  return {
230
- isError,
231
- structuredContent: output,
232
- content: [
233
- {
234
- type: 'text',
235
- text: JSON.stringify(output, null, 2)
236
- }
237
- ]
48
+ isError: true,
49
+ structuredContent: { error: payload },
50
+ content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }]
238
51
  }
239
52
  }
package/src/schemas.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { z } from 'zod'
2
2
 
3
- const stringOrStringArraySchema = z.union([z.string(), z.array(z.string()).min(1)])
3
+ const stringOrStringArraySchema = z.union([
4
+ z.string(),
5
+ z.array(z.string()).min(1)
6
+ ])
4
7
  const stringOrNumberSchema = z.union([z.string(), z.number()])
5
8
 
6
9
  function coerceJsonObjectString (value) {
@@ -15,7 +18,11 @@ function coerceJsonObjectString (value) {
15
18
 
16
19
  try {
17
20
  const parsed = JSON.parse(input)
18
- if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
21
+ if (
22
+ parsed !== null &&
23
+ typeof parsed === 'object' &&
24
+ !Array.isArray(parsed)
25
+ ) {
19
26
  return parsed
20
27
  }
21
28
  } catch {
@@ -37,17 +44,27 @@ function coerceBooleanString (value) {
37
44
 
38
45
  const booleanSchema = z.preprocess(coerceBooleanString, z.boolean())
39
46
  const objectLikeSchema = schema => z.preprocess(coerceJsonObjectString, schema)
40
- const toggledObjectSchema = z.union([booleanSchema, objectLikeSchema(z.object({}).catchall(z.unknown()))])
47
+ const toggledObjectSchema = z.union([
48
+ booleanSchema,
49
+ objectLikeSchema(z.object({}).catchall(z.unknown()))
50
+ ])
41
51
  const proxySchema = objectLikeSchema(
42
52
  z.union([z.string().min(1), z.object({}).catchall(z.unknown())])
43
53
  )
44
54
 
55
+ // A CSS selector or an array of selectors (ordered fallbacks / unioned matches),
56
+ // matching what the Microlink API and the microlink.io library accept.
57
+ const selectorSchema = z.union([
58
+ z.string().min(1),
59
+ z.array(z.string().min(1)).min(1)
60
+ ])
61
+
45
62
  // A single data-extraction rule: CSS selector(s) + optional attr/type/evaluate/nested data.
46
63
  const dataSingleRuleSchema = objectLikeSchema(
47
64
  z
48
65
  .object({
49
- selector: z.string().min(1).optional(),
50
- selectorAll: z.string().min(1).optional(),
66
+ selector: selectorSchema.optional(),
67
+ selectorAll: selectorSchema.optional(),
51
68
  attr: z.string().min(1).optional(),
52
69
  type: z.string().min(1).optional(),
53
70
  evaluate: z.string().min(1).optional(),
@@ -57,9 +74,18 @@ const dataSingleRuleSchema = objectLikeSchema(
57
74
  )
58
75
 
59
76
  // A rule can also be an array of rules used as ordered fallback selectors.
60
- const dataRuleSchema = z.union([dataSingleRuleSchema, z.array(dataSingleRuleSchema).min(1)])
61
-
62
- const waitUntilEventSchema = z.enum(['auto', 'load', 'domcontentloaded', 'networkidle0', 'networkidle2'])
77
+ const dataRuleSchema = z.union([
78
+ dataSingleRuleSchema,
79
+ z.array(dataSingleRuleSchema).min(1)
80
+ ])
81
+
82
+ const waitUntilEventSchema = z.enum([
83
+ 'auto',
84
+ 'load',
85
+ 'domcontentloaded',
86
+ 'networkidle0',
87
+ 'networkidle2'
88
+ ])
63
89
 
64
90
  const viewportSchema = objectLikeSchema(
65
91
  z
@@ -86,11 +112,14 @@ const screenshotOverlaySchema = objectLikeSchema(
86
112
  export const screenshotConfigSchema = objectLikeSchema(
87
113
  z
88
114
  .object({
115
+ animated: booleanSchema.optional(),
89
116
  codeScheme: z.string().min(1).optional(),
90
117
  element: z.string().min(1).optional(),
91
118
  fullPage: booleanSchema.optional(),
92
119
  omitBackground: booleanSchema.optional(),
120
+ optimizeForSpeed: booleanSchema.optional(),
93
121
  overlay: screenshotOverlaySchema.optional(),
122
+ palette: booleanSchema.optional(),
94
123
  type: z.enum(['jpeg', 'png']).optional()
95
124
  })
96
125
  .strict()
@@ -113,7 +142,21 @@ const pdfMarginSchema = objectLikeSchema(
113
142
  export const pdfConfigSchema = objectLikeSchema(
114
143
  z
115
144
  .object({
116
- format: z.enum(['Letter', 'Legal', 'Tabloid', 'Ledger', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6']).optional(),
145
+ format: z
146
+ .enum([
147
+ 'Letter',
148
+ 'Legal',
149
+ 'Tabloid',
150
+ 'Ledger',
151
+ 'A0',
152
+ 'A1',
153
+ 'A2',
154
+ 'A3',
155
+ 'A4',
156
+ 'A5',
157
+ 'A6'
158
+ ])
159
+ .optional(),
117
160
  height: z.string().min(1).optional(),
118
161
  landscape: booleanSchema.optional(),
119
162
  margin: pdfMarginSchema.optional(),
@@ -125,7 +168,15 @@ export const pdfConfigSchema = objectLikeSchema(
125
168
  )
126
169
 
127
170
  const lighthouseOutputSchema = z.enum(['json', 'html', 'csv'])
128
- const lighthousePresetSchema = z.enum(['default', 'desktop', 'experimental', 'full', 'lr-desktop', 'lr-mobile', 'perf'])
171
+ const lighthousePresetSchema = z.enum([
172
+ 'default',
173
+ 'desktop',
174
+ 'experimental',
175
+ 'full',
176
+ 'lr-desktop',
177
+ 'lr-mobile',
178
+ 'perf'
179
+ ])
129
180
 
130
181
  const lighthouseConfigSchema = objectLikeSchema(
131
182
  z
@@ -186,7 +237,9 @@ const visualSchema = {
186
237
  filename: z.string().min(1).optional(),
187
238
  filter: z.string().min(1).optional(),
188
239
  force: booleanSchema.optional(),
189
- headers: objectLikeSchema(z.record(z.string(), z.union([z.string(), z.number(), booleanSchema]))).optional(),
240
+ headers: objectLikeSchema(
241
+ z.record(z.string(), z.union([z.string(), z.number(), booleanSchema]))
242
+ ).optional(),
190
243
  javascript: booleanSchema.optional(),
191
244
  mediaType: z.enum(['screen', 'print']).optional(),
192
245
  modules: stringOrStringArraySchema.optional(),
@@ -202,7 +255,9 @@ const visualSchema = {
202
255
  viewport: viewportSchema.optional(),
203
256
  waitForSelector: z.string().min(1).optional(),
204
257
  waitForTimeout: stringOrNumberSchema.optional(),
205
- waitUntil: z.union([waitUntilEventSchema, z.array(waitUntilEventSchema).min(1)]).optional()
258
+ waitUntil: z
259
+ .union([waitUntilEventSchema, z.array(waitUntilEventSchema).min(1)])
260
+ .optional()
206
261
  }
207
262
 
208
263
  export const extractInputSchema = baseSchema
@@ -231,12 +286,6 @@ export const pdfInputSchema = baseSchema
231
286
  })
232
287
  .strict()
233
288
 
234
- export const insightsInputSchema = baseSchema
235
- .extend({
236
- insights: z.union([booleanSchema, insightsConfigSchema]).optional()
237
- })
238
- .strict()
239
-
240
289
  export const audioInputSchema = baseSchema
241
290
  .extend({
242
291
  proxy: proxySchema.optional(),
@@ -253,19 +302,97 @@ export const videoInputSchema = baseSchema
253
302
  })
254
303
  .strict()
255
304
 
256
- export const paletteInputSchema = baseSchema
305
+ export const logoInputSchema = baseSchema
257
306
  .extend({
258
- meta: z.union([booleanSchema, metaConfigSchema]).optional(),
259
- palette: booleanSchema.optional()
307
+ square: booleanSchema.optional()
260
308
  })
261
309
  .strict()
262
310
 
263
- export const metaInputSchema = baseSchema
311
+ export const metadataInputSchema = baseSchema
264
312
  .extend({
265
313
  meta: z.union([booleanSchema, metaConfigSchema]).optional()
266
314
  })
267
315
  .strict()
268
316
 
269
- export const markdownInputSchema = baseSchema.strict()
317
+ // Content tools (markdown/html/text) accept a selector to scope the extraction.
318
+ const contentSchema = baseSchema
319
+ .extend({
320
+ selector: selectorSchema.optional(),
321
+ selectorAll: selectorSchema.optional(),
322
+ type: z.string().min(1).optional()
323
+ })
324
+ .strict()
325
+
326
+ // Collection tools accept selector/attr/type overrides for the data rule.
327
+ const collectionSchema = baseSchema
328
+ .extend({
329
+ selector: selectorSchema.optional(),
330
+ selectorAll: selectorSchema.optional(),
331
+ attr: z.string().min(1).optional(),
332
+ type: z.string().min(1).optional()
333
+ })
334
+ .strict()
335
+
336
+ export const markdownInputSchema = contentSchema
337
+
338
+ export const htmlInputSchema = contentSchema
339
+
340
+ export const textInputSchema = contentSchema
341
+
342
+ export const embedInputSchema = baseSchema
343
+ .extend({
344
+ maxWidth: z.coerce.number().int().positive().optional(),
345
+ maxHeight: z.coerce.number().int().positive().optional()
346
+ })
347
+ .strict()
348
+
349
+ export const linksInputSchema = collectionSchema
350
+
351
+ export const imagesInputSchema = collectionSchema
270
352
 
271
- export const textInputSchema = baseSchema.strict()
353
+ export const videosInputSchema = collectionSchema
354
+
355
+ export const audiosInputSchema = collectionSchema
356
+
357
+ export const emailsInputSchema = collectionSchema
358
+
359
+ export const technologiesInputSchema = baseSchema.strict()
360
+
361
+ export const lighthouseInputSchema = baseSchema
362
+ .extend({
363
+ onlyCategories: z.array(z.string().min(1)).optional(),
364
+ onlyAudits: z.array(z.string().min(1)).optional(),
365
+ skipAudits: z.array(z.string().min(1)).optional(),
366
+ output: z.union([z.string().min(1), z.array(z.string().min(1))]).optional()
367
+ })
368
+ .strict()
369
+
370
+ export const searchInputSchema = z
371
+ .object({
372
+ query: z.string().min(1),
373
+ apiKey: z.string().min(1).optional(),
374
+ type: z
375
+ .enum([
376
+ 'search',
377
+ 'news',
378
+ 'images',
379
+ 'videos',
380
+ 'places',
381
+ 'maps',
382
+ 'shopping',
383
+ 'scholar',
384
+ 'patents',
385
+ 'autocomplete'
386
+ ])
387
+ .optional(),
388
+ limit: z.coerce.number().int().positive().optional(),
389
+ location: z.string().min(1).optional(),
390
+ period: z.enum(['hour', 'day', 'week', 'month', 'year']).optional()
391
+ })
392
+ .strict()
393
+
394
+ export const functionInputSchema = baseSchema
395
+ .extend({
396
+ code: z.string().min(1)
397
+ })
398
+ .strict()
@@ -1,17 +1,17 @@
1
1
  import { audioInputSchema } from '../schemas.js'
2
- import { register } from './register.js'
2
+ import { register, urlMethod } from './register.js'
3
3
 
4
4
  export function audio (server) {
5
5
  register(
6
6
  server,
7
7
  'microlink_audio',
8
8
  [
9
- 'Detect and extract playable audio sources from any URL via Microlink.',
9
+ 'Detect the primary playable audio of any public URL via Microlink.',
10
10
  'Works with SoundCloud, Spotify, Mixcloud, and other audio platforms.',
11
- 'The audio URL is in `data.audio.url`. Also returns `type`, `duration`, `size`, `duration_pretty`, and `size_pretty`.',
12
- 'Combine with `proxy` for sites that require it, or with `meta` to suppress metadata extraction.'
11
+ 'Returns the audio asset: `url`, `type`, `duration`, `size`, `duration_pretty`, `size_pretty`.',
12
+ 'Mirrors the `microlink.audio(url)` library method. For every audio on the page, use `microlink_audios`.'
13
13
  ].join(' '),
14
14
  audioInputSchema,
15
- { audio: true, meta: false }
15
+ urlMethod('audio')
16
16
  )
17
17
  }
@@ -0,0 +1,16 @@
1
+ import { audiosInputSchema } from '../schemas.js'
2
+ import { register, urlMethod } from './register.js'
3
+
4
+ export function audios (server) {
5
+ register(
6
+ server,
7
+ 'microlink_audios',
8
+ [
9
+ 'Collect every audio source (`<audio>`, `<source>`) from any public URL via Microlink.',
10
+ 'Returns an array of absolute, deduped audio URLs.',
11
+ 'Mirrors the `microlink.audios(url)` library method. For the primary playable audio, use `microlink_audio`.'
12
+ ].join(' '),
13
+ audiosInputSchema,
14
+ urlMethod('audios')
15
+ )
16
+ }
@@ -0,0 +1,16 @@
1
+ import { emailsInputSchema } from '../schemas.js'
2
+ import { register, urlMethod } from './register.js'
3
+
4
+ export function emails (server) {
5
+ register(
6
+ server,
7
+ 'microlink_emails',
8
+ [
9
+ 'Collect every email address present on any public URL via Microlink.',
10
+ 'Detects addresses from `mailto:` links and plain text alike; returns a deduped array.',
11
+ 'Mirrors the `microlink.emails(url)` library method.'
12
+ ].join(' '),
13
+ emailsInputSchema,
14
+ urlMethod('emails')
15
+ )
16
+ }
@@ -0,0 +1,17 @@
1
+ import { embedInputSchema } from '../schemas.js'
2
+ import { register, urlMethod } from './register.js'
3
+
4
+ export function embed (server) {
5
+ register(
6
+ server,
7
+ 'microlink_embed',
8
+ [
9
+ 'Get the oEmbed-style embeddable iframe for any public URL via Microlink (YouTube, Tweet, CodePen, ...).',
10
+ 'Returns `{ html, scripts }` — the markup to embed plus the script URLs it needs.',
11
+ 'Pass `maxWidth` / `maxHeight` to constrain the embed size.',
12
+ 'Mirrors the `microlink.embed(url)` library method.'
13
+ ].join(' '),
14
+ embedInputSchema,
15
+ urlMethod('embed')
16
+ )
17
+ }
@@ -6,15 +6,13 @@ export function extract (server) {
6
6
  server,
7
7
  'microlink_extract',
8
8
  [
9
- 'Extract structured metadata from any public URL via Microlink.',
10
- 'Returns normalized fields: title, description, author, publisher, date, image, logo, lang, and url.',
11
- 'Use the `data` parameter to scrape custom fields via CSS selectors (selector/selectorAll, attr, type, evaluate).',
12
- 'Combine with `screenshot`, `pdf`, `video`, `audio`, `insights`, `palette`, `iframe` in a single request.',
13
- 'For `screenshot`, `pdf`, and `insights`, pass `true` to enable defaults or pass a config object with options; `{}` is treated as `true`.',
14
- 'Supports device emulation (`device`), custom headers, proxy, JavaScript injection (`scripts`, `modules`, `function`), interaction (`click`, `scroll`), and caching (`ttl`, `staleTtl`, `force`).',
15
- 'The CDN asset URL for any enabled media feature is in `data.<feature>.url` (e.g. `data.screenshot.url`).'
9
+ 'Scrape custom fields from any public URL via Microlink using MQL data rules.',
10
+ 'Pass `data` with the rules object (selector/selectorAll, attr, type, evaluate; nested rules and arrays supported); returns the extracted `data` object.',
11
+ 'Also supports combining capabilities (`screenshot`, `pdf`, `iframe`, `insights`) and browser controls in the same request.',
12
+ 'Mirrors the `microlink.extract(url, rules)` library method.'
16
13
  ].join(' '),
17
14
  extractInputSchema,
18
- {}
15
+ (client, { url, data, ...options }) =>
16
+ client.extract(url, data ?? {}, options)
19
17
  )
20
18
  }
@@ -0,0 +1,16 @@
1
+ import { functionInputSchema } from '../schemas.js'
2
+ import { register } from './register.js'
3
+
4
+ export function fn (server) {
5
+ register(
6
+ server,
7
+ 'microlink_function',
8
+ [
9
+ 'Run a JavaScript function against any public URL inside Microlink’s server-side browser sandbox.',
10
+ 'Pass `code` as the function source (e.g. "async ({ page }) => page.title()"); it receives `{ page, response, ...args }` and its return value comes back in `value`.',
11
+ 'Also returns `isFulfilled`, `profiling`, and `logging`. Mirrors the `microlink.function(url, code)` library method.'
12
+ ].join(' '),
13
+ functionInputSchema,
14
+ (client, { url, code, ...options }) => client.function(url, code, options)
15
+ )
16
+ }
@@ -0,0 +1,16 @@
1
+ import { htmlInputSchema } from '../schemas.js'
2
+ import { register, urlMethod } from './register.js'
3
+
4
+ export function html (server) {
5
+ register(
6
+ server,
7
+ 'microlink_html',
8
+ [
9
+ 'Extract the HTML content of any public URL via Microlink.',
10
+ 'Returns the page HTML as a string. Pass `selector` to scope it to part of the page.',
11
+ 'Mirrors the `microlink.html(url)` library method.'
12
+ ].join(' '),
13
+ htmlInputSchema,
14
+ urlMethod('html')
15
+ )
16
+ }