Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/mcp/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ const require = createRequire(import.meta.url)
const { version: pkgVersion } = require('../package.json')

const DEFAULT_INSTRUCTIONS = [
'Turn any public URL into screenshots, PDFs, metadata, readable content (Markdown, HTML or plain text), media sources, technology stacks, Lighthouse audits, Google search results or custom-scraped fields.',
'Always pass full URLs including the protocol.',
"If you are unsure of a product tool's parameters, call microlink_docs first.",
'Product tools turn a public URL into a Microlink result. Always pass full URLs including the protocol.',
"Call microlink_docs({ product }) for a product tool's parameters; do not guess nested options.",
'video/audio return the primary playable asset; videos/audios/links/images collect every match on the page.',
'extract is custom MQL rules, not full-page markdown/html/text. search takes a query. Checkout tools start and track the purchase of an API key.',
'Without an API key, requests use the free endpoint (50 requests/day); pass apiKey or set MICROLINK_API_KEY for PRO.',
'On failure, read the error message and the hint/reason fields and adjust the request instead of retrying blindly.'
].join(' ')
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function audio (server) {
register(
server,
'microlink_audio',
[
'Detect the primary playable audio of any public URL via Microlink.',
'Works with SoundCloud, Spotify, Mixcloud, and other audio platforms.',
'Returns the audio asset: `url`, `type`, `duration`, `size`, `duration_pretty`, `size_pretty`.',
'Mirrors the `microlink.audio(url)` library method. For every audio on the page, use `microlink_audios`.'
].join(' '),
'Primary playable audio asset. For every audio URL on the page, use microlink_audios.',
audioInputSchema,
urlMethod('audio')
)
Expand Down
6 changes: 1 addition & 5 deletions packages/mcp/src/tools/audios.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export function audios (server) {
register(
server,
'microlink_audios',
[
'Collect every audio source (`<audio>`, `<source>`) from any public URL via Microlink.',
'Returns an array of absolute, deduped audio URLs.',
'Mirrors the `microlink.audios(url)` library method. For the primary playable audio, use `microlink_audio`.'
].join(' '),
'Every audio source URL on the page. For the primary playable audio, use microlink_audio.',
audiosInputSchema,
urlMethod('audios')
)
Expand Down
6 changes: 1 addition & 5 deletions packages/mcp/src/tools/docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ export function docs (server) {
register(
server,
'microlink_docs',
[
'Fetch the canonical, complete parameter documentation for a Microlink product.',
'Call this before using a product tool whose parameters you do not know well.',
'Returns the product markdown directly from microlink.io, the same source used by `microlink <product> docs`.'
].join(' '),
'Canonical parameter docs for a Microlink product. Same markdown as `microlink <product> docs`.',
docsInputSchema,
(_client, { product }) => loadProductDocs(product)
)
Expand Down
6 changes: 1 addition & 5 deletions packages/mcp/src/tools/emails.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export function emails (server) {
register(
server,
'microlink_emails',
[
'Collect every email address present on any public URL via Microlink.',
'Detects addresses from `mailto:` links and plain text alike; returns a deduped array.',
'Mirrors the `microlink.emails(url)` library method.'
].join(' '),
'Every email address on the page.',
emailsInputSchema,
urlMethod('emails')
)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function embed (server) {
register(
server,
'microlink_embed',
[
'Get the oEmbed-style embeddable iframe for any public URL via Microlink (YouTube, Tweet, CodePen, ...).',
'Returns `{ html, scripts }` — the markup to embed plus the script URLs it needs.',
'Pass `maxWidth` / `maxHeight` to constrain the embed size.',
'Mirrors the `microlink.embed(url)` library method.'
].join(' '),
'oEmbed iframe for a URL (`{ html, scripts }`). Null when none is found.',
embedInputSchema,
urlMethod('embed')
)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function extract (server) {
register(
server,
'microlink_extract',
[
'Scrape custom fields from any public URL via Microlink using MQL data rules.',
'Pass `data` with the rules object (selector/selectorAll, attr, type, evaluate; nested rules and arrays supported); returns the extracted `data` object. By default no page metadata is included; pass `meta: true` to also get the default metadata.',
'Also supports combining capabilities (`screenshot`, `pdf`, `iframe`, `insights`) and browser controls in the same request.',
'Mirrors the `microlink.extract(url, rules)` library method.'
].join(' '),
'Custom MQL data rules on a URL. Not full-page markdown/html/text. Can attach screenshot/pdf/insights on the same request.',
extractInputSchema,
(client, { url, data, ...options }) =>
client.extract(url, data ?? {}, options)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function fn (server) {
register(
server,
'microlink_function',
[
'Run a JavaScript function against any public URL inside Microlink’s server-side browser sandbox.',
'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`.',
'Combine with browser options such as `javascript`, `waitUntil`, `waitForSelector`, `click`, `scroll`, `headers`, and `proxy`.',
'Also returns `isFulfilled`, `profiling`, and `logging`. Mirrors the `microlink.function(url, code)` library method.'
].join(' '),
"Run JavaScript against a URL in Microlink's browser sandbox. Pass `code`; returns `{ isFulfilled, value }`.",
functionInputSchema,
(client, { url, code, ...options }) => client.function(url, code, options),
INTERACTIVE_ANNOTATIONS
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function html (server) {
register(
server,
'microlink_html',
[
'Extract the HTML content of any public URL via Microlink.',
'Returns the page HTML as a string. Pass `selector` to scope it to part of the page.',
'Combine with browser options such as `javascript`, `waitUntil`, `waitForSelector`, `headers`, and `proxy`.',
'Mirrors the `microlink.html(url)` library method.'
].join(' '),
'Page HTML as a string. For custom CSS/MQL fields, use microlink_extract.',
htmlInputSchema,
urlMethod('html')
)
Expand Down
6 changes: 1 addition & 5 deletions packages/mcp/src/tools/images.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export function images (server) {
register(
server,
'microlink_images',
[
'Collect every image (`<img src>`) from any public URL via Microlink.',
'Returns an array of absolute, deduped image URLs. Pass `selectorAll` to scope the collection.',
'Mirrors the `microlink.images(url)` library method.'
].join(' '),
'Every absolute image URL on the page.',
imagesInputSchema,
urlMethod('images')
)
Expand Down
6 changes: 1 addition & 5 deletions packages/mcp/src/tools/lighthouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export function lighthouse (server) {
register(
server,
'microlink_lighthouse',
[
'Run a Google Lighthouse audit (performance, accessibility, best-practices, SEO) for any public URL via Microlink.',
'Returns the Lighthouse report.',
'Mirrors the `microlink.lighthouse(url)` library method.'
].join(' '),
'Google Lighthouse report for a public URL.',
lighthouseInputSchema,
urlMethod('lighthouse')
)
Expand Down
6 changes: 1 addition & 5 deletions packages/mcp/src/tools/links.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export function links (server) {
register(
server,
'microlink_links',
[
'Collect every link (`<a href>`) from any public URL via Microlink.',
'Returns an array of absolute, deduped URLs. Pass `selectorAll` to scope the collection.',
'Mirrors the `microlink.links(url)` library method.'
].join(' '),
'Every absolute link URL on the page.',
linksInputSchema,
urlMethod('links')
)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/logo.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function logo (server) {
register(
server,
'microlink_logo',
[
'Extract the brand logo of any public URL via Microlink.',
'Returns the logo asset: `url`, `type`, `width`, `height`, `size`, and `size_pretty`.',
'Pass `square: true` to prefer a square (icon-shaped) variant.',
'Mirrors the `microlink.logo(url, { square })` library method.'
].join(' '),
'Brand logo of a public URL as an asset. Null when none is detected.',
logoInputSchema,
urlMethod('logo')
)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function markdown (server) {
register(
server,
'microlink_markdown',
[
'Convert any public URL to Markdown via Microlink.',
'Returns the page content as a Markdown string. Pass `selector` to scope it to part of the page.',
'Combine with browser options such as `javascript`, `waitUntil`, `waitForSelector`, `headers`, and `proxy`.',
'Mirrors the `microlink.markdown(url)` library method.'
].join(' '),
'Page content as Markdown. For custom CSS/MQL fields, use microlink_extract.',
markdownInputSchema,
urlMethod('markdown')
)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function metadata (server) {
register(
server,
'microlink_metadata',
[
'Extract normalized metadata from any public URL via Microlink.',
'Returns the metadata object: `title`, `description`, `lang`, `author`, `publisher`, `date`, `url`, `image` (with dimensions and file info), and `logo`.',
'Pass `meta: false` to skip extraction, or a config object to include/exclude fields (e.g. `{ logo: true, title: true }`).',
'Mirrors the `microlink.metadata(url)` library method.'
].join(' '),
'Normalized metadata from a public URL (`title`, `description`, `image`, `logo`, ...).',
metadataInputSchema,
urlMethod('metadata')
)
Expand Down
8 changes: 1 addition & 7 deletions packages/mcp/src/tools/pdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ export function pdf (server) {
register(
server,
'microlink_pdf',
[
'Generate a PDF of any public URL via Microlink and return the asset object (`url`, `type`, `size`, ...) with a permanent CDN URL.',
'Pass `pdf: true` for defaults or `pdf: { ... }` for options; `pdf: {}` is treated as `true`.',
'Use `pdf.format` ("A4" default, "Letter", "Legal", ...), `pdf.landscape`, `pdf.margin` (string or top/bottom/left/right object), `pdf.scale` (0.1-2.0), `pdf.pageRanges` ("1-5"), or `pdf.width`/`pdf.height`.',
'Combine with `styles`, `scripts`, `modules`, `mediaType`, `waitForSelector`, and `waitUntil` for full control.',
'Mirrors the `microlink.pdf(url, options)` library method.'
].join(' '),
'Generate a PDF of a public URL. Returns a CDN asset (`url`, `type`, `size`).',
pdfInputSchema,
capabilityMethod('pdf', 'pdf')
)
Expand Down
10 changes: 1 addition & 9 deletions packages/mcp/src/tools/screenshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@ export function screenshot (server) {
register(
server,
'microlink_screenshot',
[
'Capture a screenshot of any public URL via Microlink and return the asset object (`url`, `type`, `width`, `height`, `size`, ...) with a permanent CDN URL.',
'Pass `screenshot: true` for defaults or `screenshot: { ... }` for options; `screenshot: {}` is treated as `true`.',
'Use `screenshot.fullPage` to capture the whole scrollable page.',
'Use `screenshot.animated` to capture an animated screenshot (GIF/MP4) instead of a still image.',
'Use `screenshot.element` (CSS selector) to capture a specific element, `screenshot.type` for format ("jpeg", default "png"), `screenshot.omitBackground` for transparency, `screenshot.overlay` for browser chrome, `screenshot.palette` to also extract dominant colors, or `screenshot.codeScheme` to theme code pages.',
'Combine with `device`, `viewport`, `click`, `scroll`, `styles`, `scripts`, `modules`, `waitForSelector`, `waitForTimeout`, `waitUntil`, `colorScheme`, and `mediaType`.',
'Mirrors the `microlink.screenshot(url, options)` library method.'
].join(' '),
'Capture a screenshot of a public URL. Returns a CDN asset (`url`, `type`, `width`, `height`, `size`).',
screenshotInputSchema,
capabilityMethod('screenshot', 'screenshot')
)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function search (server) {
register(
server,
'microlink_search',
[
'Search Google and get structured results via Microlink (requires an API key).',
'Returns `results` (title, url, description) plus `knowledgeGraph`, `peopleAlsoAsk`, and `relatedSearches` when available.',
'Use `type` for a vertical ("search" default, "news", "images", "videos", "places", "maps", "shopping", "scholar", "patents", "autocomplete"), and `limit` / `page` / `location` / `period` to refine.',
'Google search operators (`site:`, `filetype:`, quotes, ...) work as-is. Mirrors the `microlink.search(query)` library method.'
].join(' '),
'Google as structured data. Takes `query` (not a URL). Requires an API key.',
searchInputSchema,
(client, { query, ...options }) => client.search(query, options)
)
Expand Down
6 changes: 1 addition & 5 deletions packages/mcp/src/tools/technologies.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export function technologies (server) {
register(
server,
'microlink_technologies',
[
'Detect the technology stack behind any public URL via Microlink (Wappalyzer).',
'Returns an array of detected technologies (frameworks, CDNs, analytics, e-commerce, etc.).',
'Mirrors the `microlink.technologies(url)` library method.'
].join(' '),
'Detected tech stack (Wappalyzer) for a public URL.',
technologiesInputSchema,
urlMethod('technologies')
)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function text (server) {
register(
server,
'microlink_text',
[
'Extract the plain text of any public URL via Microlink.',
'Returns the readable page text as a string. Pass `selector` to scope it to part of the page.',
'Combine with browser options such as `javascript`, `waitUntil`, `waitForSelector`, `headers`, and `proxy`.',
'Mirrors the `microlink.text(url)` library method.'
].join(' '),
'Readable page text as a string. For custom CSS/MQL fields, use microlink_extract.',
textInputSchema,
urlMethod('text')
)
Expand Down
7 changes: 1 addition & 6 deletions packages/mcp/src/tools/video.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ export function video (server) {
register(
server,
'microlink_video',
[
'Detect the primary playable video of any public URL via Microlink.',
'Works with YouTube, Vimeo, Twitter/X, TikTok, Instagram, and hundreds of other platforms.',
'Returns the video asset: `url`, `type`, `duration`, `size`, `width`, `height`, `duration_pretty`, `size_pretty`.',
'Mirrors the `microlink.video(url)` library method. For every video on the page, use `microlink_videos`.'
].join(' '),
'Primary playable video asset. For every video URL on the page, use microlink_videos.',
videoInputSchema,
urlMethod('video')
)
Expand Down
6 changes: 1 addition & 5 deletions packages/mcp/src/tools/videos.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export function videos (server) {
register(
server,
'microlink_videos',
[
'Collect every video source (`<video>`, `<source>`) from any public URL via Microlink.',
'Returns an array of absolute, deduped video URLs.',
'Mirrors the `microlink.videos(url)` library method. For the primary playable video, use `microlink_video`.'
].join(' '),
'Every video source URL on the page. For the primary playable video, use microlink_video.',
videosInputSchema,
urlMethod('videos')
)
Expand Down
1 change: 1 addition & 0 deletions packages/mcp/test/stdio-server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ test('createMicrolinkServer returns MCP server instance', () => {
test('createMicrolinkServer sets default instructions and honors overrides', () => {
const withDefaults = createMicrolinkServer()
assert.match(withDefaults.server._instructions, /microlink_docs/)
assert.match(withDefaults.server._instructions, /Checkout tools/)

const custom = createMicrolinkServer({ instructions: 'Custom.' })
assert.equal(custom.server._instructions, 'Custom.')
Expand Down
16 changes: 16 additions & 0 deletions packages/mcp/test/tools.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,22 @@ test('every tool declares a human-friendly title', () => {
}
})

test('product tool descriptions name the job, not every parameter', () => {
const configs = {}
tools({
registerTool: (name, config) => {
configs[name] = config
}
})

assert.doesNotMatch(configs.microlink_screenshot.description, /fullPage/)
assert.doesNotMatch(configs.microlink_pdf.description, /pageRanges/)
assert.doesNotMatch(configs.microlink_extract.description, /selectorAll/)
assert.match(configs.microlink_video.description, /microlink_videos/)
assert.match(configs.microlink_videos.description, /microlink_video/)
assert.match(configs.microlink_markdown.description, /microlink_extract/)
})

test('errors are surfaced as MCP isError with code/message', async t => {
const handlers = captureTool(metadata)
await withStubbedRequest(
Expand Down