@microlink/mcp 1.0.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.
@@ -0,0 +1,239 @@
1
+ import mql from '@microlink/mql'
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
+ const FREE_QUOTA_EXCEEDED_HINT =
20
+ '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
+
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
+ }
117
+
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
+ }
136
+
137
+ return FREE_QUOTA_EXCEEDED_HINT
138
+ }
139
+
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
168
+ }
169
+
170
+ return payload
171
+ }
172
+
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)
189
+
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
+ }
213
+ }
214
+ }
215
+
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
224
+ }
225
+
226
+ const isMicrolinkError = result.body?.status && result.body.status !== 'success'
227
+ const isError = !result.ok || isMicrolinkError
228
+
229
+ return {
230
+ isError,
231
+ structuredContent: output,
232
+ content: [
233
+ {
234
+ type: 'text',
235
+ text: JSON.stringify(output, null, 2)
236
+ }
237
+ ]
238
+ }
239
+ }
package/src/schemas.js ADDED
@@ -0,0 +1,271 @@
1
+ import { z } from 'zod'
2
+
3
+ const stringOrStringArraySchema = z.union([z.string(), z.array(z.string()).min(1)])
4
+ const stringOrNumberSchema = z.union([z.string(), z.number()])
5
+
6
+ function coerceJsonObjectString (value) {
7
+ if (typeof value !== 'string') {
8
+ return value
9
+ }
10
+
11
+ const input = value.trim()
12
+ if (input.length < 2 || !input.startsWith('{') || !input.endsWith('}')) {
13
+ return value
14
+ }
15
+
16
+ try {
17
+ const parsed = JSON.parse(input)
18
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
19
+ return parsed
20
+ }
21
+ } catch {
22
+ // Keep original value and let schema validation surface the error.
23
+ }
24
+
25
+ return value
26
+ }
27
+
28
+ function coerceBooleanString (value) {
29
+ if (typeof value === 'string') {
30
+ const normalized = value.trim().toLowerCase()
31
+ if (normalized === 'true') return true
32
+ if (normalized === 'false') return false
33
+ }
34
+
35
+ return value
36
+ }
37
+
38
+ const booleanSchema = z.preprocess(coerceBooleanString, z.boolean())
39
+ const objectLikeSchema = schema => z.preprocess(coerceJsonObjectString, schema)
40
+ const toggledObjectSchema = z.union([booleanSchema, objectLikeSchema(z.object({}).catchall(z.unknown()))])
41
+ const proxySchema = objectLikeSchema(
42
+ z.union([z.string().min(1), z.object({}).catchall(z.unknown())])
43
+ )
44
+
45
+ // A single data-extraction rule: CSS selector(s) + optional attr/type/evaluate/nested data.
46
+ const dataSingleRuleSchema = objectLikeSchema(
47
+ z
48
+ .object({
49
+ selector: z.string().min(1).optional(),
50
+ selectorAll: z.string().min(1).optional(),
51
+ attr: z.string().min(1).optional(),
52
+ type: z.string().min(1).optional(),
53
+ evaluate: z.string().min(1).optional(),
54
+ data: z.record(z.string(), z.unknown()).optional()
55
+ })
56
+ .catchall(z.unknown())
57
+ )
58
+
59
+ // 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'])
63
+
64
+ const viewportSchema = objectLikeSchema(
65
+ z
66
+ .object({
67
+ width: z.number().positive().optional(),
68
+ height: z.number().positive().optional(),
69
+ deviceScaleFactor: z.number().positive().optional(),
70
+ isMobile: booleanSchema.optional(),
71
+ hasTouch: booleanSchema.optional(),
72
+ isLandscape: booleanSchema.optional()
73
+ })
74
+ .strict()
75
+ )
76
+
77
+ const screenshotOverlaySchema = objectLikeSchema(
78
+ z
79
+ .object({
80
+ browser: z.enum(['light', 'dark']).optional(),
81
+ background: z.string().min(1).optional()
82
+ })
83
+ .strict()
84
+ )
85
+
86
+ export const screenshotConfigSchema = objectLikeSchema(
87
+ z
88
+ .object({
89
+ codeScheme: z.string().min(1).optional(),
90
+ element: z.string().min(1).optional(),
91
+ fullPage: booleanSchema.optional(),
92
+ omitBackground: booleanSchema.optional(),
93
+ overlay: screenshotOverlaySchema.optional(),
94
+ type: z.enum(['jpeg', 'png']).optional()
95
+ })
96
+ .strict()
97
+ )
98
+
99
+ const pdfMarginSchema = objectLikeSchema(
100
+ z.union([
101
+ z.string().min(1),
102
+ z
103
+ .object({
104
+ top: z.string().min(1).optional(),
105
+ bottom: z.string().min(1).optional(),
106
+ left: z.string().min(1).optional(),
107
+ right: z.string().min(1).optional()
108
+ })
109
+ .strict()
110
+ ])
111
+ )
112
+
113
+ export const pdfConfigSchema = objectLikeSchema(
114
+ z
115
+ .object({
116
+ format: z.enum(['Letter', 'Legal', 'Tabloid', 'Ledger', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6']).optional(),
117
+ height: z.string().min(1).optional(),
118
+ landscape: booleanSchema.optional(),
119
+ margin: pdfMarginSchema.optional(),
120
+ pageRanges: z.string().min(1).optional(),
121
+ scale: z.number().min(0.1).max(2).optional(),
122
+ width: z.string().min(1).optional()
123
+ })
124
+ .strict()
125
+ )
126
+
127
+ const lighthouseOutputSchema = z.enum(['json', 'html', 'csv'])
128
+ const lighthousePresetSchema = z.enum(['default', 'desktop', 'experimental', 'full', 'lr-desktop', 'lr-mobile', 'perf'])
129
+
130
+ const lighthouseConfigSchema = objectLikeSchema(
131
+ z
132
+ .object({
133
+ output: lighthouseOutputSchema.optional(),
134
+ onlyCategories: stringOrStringArraySchema.optional(),
135
+ preset: lighthousePresetSchema.optional()
136
+ })
137
+ .catchall(z.unknown())
138
+ )
139
+
140
+ export const insightsConfigSchema = objectLikeSchema(
141
+ z
142
+ .object({
143
+ lighthouse: z.union([booleanSchema, lighthouseConfigSchema]).optional(),
144
+ technologies: booleanSchema.optional()
145
+ })
146
+ .strict()
147
+ )
148
+
149
+ export const metaConfigSchema = objectLikeSchema(
150
+ z
151
+ .object({
152
+ author: booleanSchema.optional(),
153
+ date: booleanSchema.optional(),
154
+ description: booleanSchema.optional(),
155
+ image: booleanSchema.optional(),
156
+ lang: booleanSchema.optional(),
157
+ logo: booleanSchema.optional(),
158
+ publisher: booleanSchema.optional(),
159
+ title: booleanSchema.optional(),
160
+ url: booleanSchema.optional()
161
+ })
162
+ .strict()
163
+ )
164
+
165
+ const baseSchema = z.object({
166
+ url: z.string().url(),
167
+ apiKey: z.string().min(1).optional()
168
+ })
169
+
170
+ const fullShape = {
171
+ embed: z.string().min(1).optional(),
172
+ function: z.string().min(1).optional(),
173
+ iframe: toggledObjectSchema.optional(),
174
+ meta: z.union([booleanSchema, metaConfigSchema]).optional(),
175
+ palette: booleanSchema.optional(),
176
+ ping: toggledObjectSchema.optional()
177
+ }
178
+
179
+ const visualSchema = {
180
+ adblock: booleanSchema.optional(),
181
+ animations: booleanSchema.optional(),
182
+ click: stringOrStringArraySchema.optional(),
183
+ colorScheme: z.enum(['no-preference', 'light', 'dark']).optional(),
184
+ data: objectLikeSchema(z.record(z.string(), dataRuleSchema)).optional(),
185
+ device: z.string().min(1).optional(),
186
+ filename: z.string().min(1).optional(),
187
+ filter: z.string().min(1).optional(),
188
+ force: booleanSchema.optional(),
189
+ headers: objectLikeSchema(z.record(z.string(), z.union([z.string(), z.number(), booleanSchema]))).optional(),
190
+ javascript: booleanSchema.optional(),
191
+ mediaType: z.enum(['screen', 'print']).optional(),
192
+ modules: stringOrStringArraySchema.optional(),
193
+ prerender: z.union([z.literal('auto'), booleanSchema]).optional(),
194
+ proxy: proxySchema.optional(),
195
+ retry: z.number().int().nonnegative().optional(),
196
+ scripts: stringOrStringArraySchema.optional(),
197
+ scroll: z.string().min(1).optional(),
198
+ staleTtl: z.union([z.string(), z.number(), booleanSchema]).optional(),
199
+ styles: stringOrStringArraySchema.optional(),
200
+ timeout: stringOrNumberSchema.optional(),
201
+ ttl: stringOrNumberSchema.optional(),
202
+ viewport: viewportSchema.optional(),
203
+ waitForSelector: z.string().min(1).optional(),
204
+ waitForTimeout: stringOrNumberSchema.optional(),
205
+ waitUntil: z.union([waitUntilEventSchema, z.array(waitUntilEventSchema).min(1)]).optional()
206
+ }
207
+
208
+ export const extractInputSchema = baseSchema
209
+ .extend(fullShape)
210
+ .extend(visualSchema)
211
+ .extend({
212
+ audio: booleanSchema.optional(),
213
+ video: booleanSchema.optional(),
214
+ pdf: z.union([booleanSchema, pdfConfigSchema]).optional(),
215
+ screenshot: z.union([booleanSchema, screenshotConfigSchema]).optional(),
216
+ insights: z.union([booleanSchema, insightsConfigSchema]).optional()
217
+ })
218
+ .strict()
219
+
220
+ export const screenshotInputSchema = baseSchema
221
+ .extend(visualSchema)
222
+ .extend({
223
+ screenshot: z.union([booleanSchema, screenshotConfigSchema]).optional()
224
+ })
225
+ .strict()
226
+
227
+ export const pdfInputSchema = baseSchema
228
+ .extend(visualSchema)
229
+ .extend({
230
+ pdf: z.union([booleanSchema, pdfConfigSchema]).optional()
231
+ })
232
+ .strict()
233
+
234
+ export const insightsInputSchema = baseSchema
235
+ .extend({
236
+ insights: z.union([booleanSchema, insightsConfigSchema]).optional()
237
+ })
238
+ .strict()
239
+
240
+ export const audioInputSchema = baseSchema
241
+ .extend({
242
+ proxy: proxySchema.optional(),
243
+ meta: z.union([booleanSchema, metaConfigSchema]).optional(),
244
+ audio: booleanSchema.optional()
245
+ })
246
+ .strict()
247
+
248
+ export const videoInputSchema = baseSchema
249
+ .extend({
250
+ proxy: proxySchema.optional(),
251
+ meta: z.union([booleanSchema, metaConfigSchema]).optional(),
252
+ video: booleanSchema.optional()
253
+ })
254
+ .strict()
255
+
256
+ export const paletteInputSchema = baseSchema
257
+ .extend({
258
+ meta: z.union([booleanSchema, metaConfigSchema]).optional(),
259
+ palette: booleanSchema.optional()
260
+ })
261
+ .strict()
262
+
263
+ export const metaInputSchema = baseSchema
264
+ .extend({
265
+ meta: z.union([booleanSchema, metaConfigSchema]).optional()
266
+ })
267
+ .strict()
268
+
269
+ export const markdownInputSchema = baseSchema.strict()
270
+
271
+ export const textInputSchema = baseSchema.strict()
@@ -0,0 +1,17 @@
1
+ import { audioInputSchema } from '../schemas.js'
2
+ import { register } from './register.js'
3
+
4
+ export function audio (server) {
5
+ register(
6
+ server,
7
+ 'microlink_audio',
8
+ [
9
+ 'Detect and extract playable audio sources from any URL via Microlink.',
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.'
13
+ ].join(' '),
14
+ audioInputSchema,
15
+ { audio: true, meta: false }
16
+ )
17
+ }
@@ -0,0 +1,20 @@
1
+ import { extractInputSchema } from '../schemas.js'
2
+ import { register } from './register.js'
3
+
4
+ export function extract (server) {
5
+ register(
6
+ server,
7
+ 'microlink_extract',
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`).'
16
+ ].join(' '),
17
+ extractInputSchema,
18
+ {}
19
+ )
20
+ }
@@ -0,0 +1,23 @@
1
+ import { audio } from './audio.js'
2
+ import { extract } from './extract.js'
3
+ import { insights } from './insights.js'
4
+ import { markdown } from './markdown.js'
5
+ import { meta } from './meta.js'
6
+ import { palette } from './palette.js'
7
+ import { pdf } from './pdf.js'
8
+ import { screenshot } from './screenshot.js'
9
+ import { text } from './text.js'
10
+ import { video } from './video.js'
11
+
12
+ export function tools (server) {
13
+ extract(server)
14
+ screenshot(server)
15
+ pdf(server)
16
+ video(server)
17
+ audio(server)
18
+ insights(server)
19
+ meta(server)
20
+ palette(server)
21
+ markdown(server)
22
+ text(server)
23
+ }
@@ -0,0 +1,21 @@
1
+ import { insightsInputSchema } from '../schemas.js'
2
+ import { register } from './register.js'
3
+
4
+ export function insights (server) {
5
+ register(
6
+ server,
7
+ 'microlink_insights',
8
+ [
9
+ 'Get web performance and technology-stack insights for any URL via Microlink.',
10
+ 'Pass `insights: true` for defaults or `insights: { ... }` for options; `insights: {}` is treated as `true`.',
11
+ 'Use `insights.lighthouse` (true or config object) for a Lighthouse performance audit.',
12
+ ' - `insights.lighthouse.output`: report format — "json" (default), "html", or "csv".',
13
+ ' - `insights.lighthouse.preset`: "default", "desktop", "perf", "experimental", "full", "lr-desktop", "lr-mobile".',
14
+ ' - `insights.lighthouse.onlyCategories`: array of category IDs to include (e.g. ["performance", "accessibility"]).',
15
+ 'Use `insights.technologies` (true) to detect the technology stack (frameworks, CDNs, analytics, etc.) via Wappalyzer.',
16
+ 'Both can be combined in a single request.'
17
+ ].join(' '),
18
+ insightsInputSchema,
19
+ { insights: true, meta: false }
20
+ )
21
+ }
@@ -0,0 +1,16 @@
1
+ import { markdownInputSchema } from '../schemas.js'
2
+ import { register } from './register.js'
3
+
4
+ export function markdown (server) {
5
+ register(
6
+ server,
7
+ 'microlink_markdown',
8
+ [
9
+ 'Convert any public URL to Markdown via Microlink.',
10
+ 'Returns JSON output with Markdown content under `data.markdown`.',
11
+ 'Useful for extracting readable content from web pages, articles, and documentation.'
12
+ ].join(' '),
13
+ markdownInputSchema,
14
+ { data: { markdown: { attr: 'markdown' } }, meta: false }
15
+ )
16
+ }
@@ -0,0 +1,17 @@
1
+ import { metaInputSchema } from '../schemas.js'
2
+ import { register } from './register.js'
3
+
4
+ export function meta (server) {
5
+ register(
6
+ server,
7
+ 'microlink_meta',
8
+ [
9
+ 'Extract normalized metadata from any public URL via Microlink.',
10
+ 'Returns: `title`, `description`, `lang`, `author`, `publisher`, `date`, `url`, `image` (with dimensions and file info), and `logo` (publisher favicon).',
11
+ 'Pass `meta: false` to skip metadata extraction entirely — useful when you only need a screenshot or video and want a faster response.',
12
+ 'Pass a config object to include or exclude specific fields: `{ logo: true, title: true }` returns only those fields; `{ image: false }` returns everything except image.'
13
+ ].join(' '),
14
+ metaInputSchema,
15
+ { meta: true }
16
+ )
17
+ }
@@ -0,0 +1,17 @@
1
+ import { paletteInputSchema } from '../schemas.js'
2
+ import { register } from './register.js'
3
+
4
+ export function palette (server) {
5
+ register(
6
+ server,
7
+ 'microlink_palette',
8
+ [
9
+ 'Extract a color palette from images detected on any public URL via Microlink.',
10
+ 'For each image, returns: `palette` (hex colors from most to least dominant), `background_color` (optimal WCAG-contrast background), `color` (best overlay color for the background), and `alternative_color` (secondary overlay color).',
11
+ 'Color data is nested under each image field in the response (e.g. `data.image.palette`, `data.image.background_color`).',
12
+ 'Useful for generating design tokens, theming, or accessibility checks from real page images.'
13
+ ].join(' '),
14
+ paletteInputSchema,
15
+ { palette: true, meta: true }
16
+ )
17
+ }
@@ -0,0 +1,23 @@
1
+ import { pdfInputSchema } from '../schemas.js'
2
+ import { register } from './register.js'
3
+
4
+ export function pdf (server) {
5
+ register(
6
+ server,
7
+ 'microlink_pdf',
8
+ [
9
+ 'Generate a PDF of any public URL via Microlink and return a permanent CDN asset URL.',
10
+ 'The PDF URL is in `data.pdf.url`.',
11
+ 'Pass `pdf: true` for defaults or `pdf: { ... }` for options; `pdf: {}` is treated as `true`.',
12
+ 'Use `pdf.format` to set paper size: "A4" (default), "Letter", "Legal", "Tabloid", "Ledger", "A0"–"A6".',
13
+ 'Use `pdf.landscape` to switch to landscape orientation.',
14
+ 'Use `pdf.margin` to set page margins as a string ("0.35cm") or object with top/bottom/left/right.',
15
+ 'Use `pdf.scale` to scale the page (0.1–2.0).',
16
+ 'Use `pdf.pageRanges` to select specific pages (e.g. "1-5").',
17
+ 'Use `pdf.width` and `pdf.height` for custom dimensions (overrides format).',
18
+ 'Combine with `styles`, `scripts`, `modules`, `mediaType`, `waitForSelector`, and `waitUntil` for full control.'
19
+ ].join(' '),
20
+ pdfInputSchema,
21
+ { pdf: true, meta: false }
22
+ )
23
+ }