@microlink/mcp 2.4.0 → 2.5.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/README.md +3 -1
- package/package.json +5 -4
- package/src/index.js +16 -5
- package/src/microlink-client.js +25 -15
- package/src/schemas.js +9 -14
- package/src/tools/extract.js +1 -1
- package/src/tools/register.js +28 -8
package/README.md
CHANGED
|
@@ -161,9 +161,11 @@ Each tool is a thin wrapper over a [`microlink.io`](https://github.com/microlink
|
|
|
161
161
|
### Response shape
|
|
162
162
|
|
|
163
163
|
- Each tool returns the library's **direct result** under `structuredContent.data` (and the same value as pretty-printed JSON text). For example `microlink_markdown` → `{ data: "# Title\n..." }`, `microlink_screenshot` → `{ data: { url, type, width, height, size } }`, `microlink_links` → `{ data: ["https://...", ...] }`.
|
|
164
|
+
- Every tool also declares an MCP `outputSchema` describing its `structuredContent.data`, mirroring the TypeScript types shipped by the library (`Asset`, `Metadata`, `Embed`, `FunctionResult`, ...), so MCP clients get machine-readable result contracts. Error results are exempt from output validation. Fields that can legitimately be absent are nullable (for example `logo` when no brand logo is detected, or `markdown` when the selector matches nothing).
|
|
165
|
+
- Tools are annotated `readOnlyHint: true` since they only fetch and transform public URLs. The exception is `microlink_function`, which executes user-supplied code and is not annotated read-only.
|
|
164
166
|
- On failure the tool sets MCP `isError` and returns `{ error: { message, code?, status?, statusCode?, url?, more?, details? } }`, where `message` carries the specific cause reported by the API. Capability errors that retrying cannot fix (for example `EPROXYNEEDED` or `EINTEGRATION`) also include machine-readable `reason` (`upgrade_required`), `capability`, an `upgrade` object with the plan and pricing URL, and an agent-facing `hint` with the next step. A `429` includes `reason: "quota_exceeded"` and a free-quota `hint`.
|
|
165
167
|
|
|
166
|
-
Parameters labeled `PRO` in the official Microlink docs
|
|
168
|
+
Parameters that require a paid plan are labeled `PRO` in their own schema descriptions, mirroring the official Microlink docs.
|
|
167
169
|
For compatibility with some MCP clients:
|
|
168
170
|
- boolean parameters also accept the strings `"true"` and `"false"` and are normalized before validation.
|
|
169
171
|
- parameters that accept objects also accept JSON stringified objects (for example, `screenshot: "{\"overlay\":{\"browser\":\"dark\"}}"`).
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@microlink/mcp",
|
|
3
3
|
"description": "MCP server for Microlink API",
|
|
4
4
|
"homepage": "https://github.com/microlinkhq/microlink",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.5.0",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@microlink/mql": "0.19.0",
|
|
47
47
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
48
|
-
"microlink.io": "0.10.
|
|
48
|
+
"microlink.io": "0.10.4",
|
|
49
49
|
"zod": "~4.4.3"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
@@ -59,12 +59,13 @@
|
|
|
59
59
|
"scripts": {
|
|
60
60
|
"postinstall": "node scripts/postinstall.js",
|
|
61
61
|
"start": "node src/index.js",
|
|
62
|
-
"test": "node --test test/*.test.js"
|
|
62
|
+
"test": "node --test test/*.test.js",
|
|
63
|
+
"test:live": "LIVE=1 node --test test/live.test.js"
|
|
63
64
|
},
|
|
64
65
|
"license": "MIT",
|
|
65
66
|
"publishConfig": {
|
|
66
67
|
"access": "public"
|
|
67
68
|
},
|
|
68
69
|
"type": "module",
|
|
69
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "69d1f76ff2dcbfb56cccd86a17da9c719e398301"
|
|
70
71
|
}
|
package/src/index.js
CHANGED
|
@@ -8,14 +8,25 @@ import { tools } from './tools/index.js'
|
|
|
8
8
|
const require = createRequire(import.meta.url)
|
|
9
9
|
const { version: pkgVersion } = require('../package.json')
|
|
10
10
|
|
|
11
|
+
const DEFAULT_INSTRUCTIONS = [
|
|
12
|
+
'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.',
|
|
13
|
+
'Always pass full URLs including the protocol.',
|
|
14
|
+
'Without an API key, requests use the free endpoint (50 requests/day); pass apiKey or set MICROLINK_API_KEY for PRO.',
|
|
15
|
+
'On failure, read the error message and the hint/reason fields and adjust the request instead of retrying blindly.'
|
|
16
|
+
].join(' ')
|
|
17
|
+
|
|
11
18
|
export function createMicrolinkServer ({
|
|
12
19
|
name = 'microlink-mcp-server',
|
|
13
|
-
version = pkgVersion
|
|
20
|
+
version = pkgVersion,
|
|
21
|
+
instructions = DEFAULT_INSTRUCTIONS
|
|
14
22
|
} = {}) {
|
|
15
|
-
const server = new McpServer(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
const server = new McpServer(
|
|
24
|
+
{
|
|
25
|
+
name,
|
|
26
|
+
version
|
|
27
|
+
},
|
|
28
|
+
{ instructions }
|
|
29
|
+
)
|
|
19
30
|
|
|
20
31
|
tools(server)
|
|
21
32
|
|
package/src/microlink-client.js
CHANGED
|
@@ -32,19 +32,22 @@ export function resolveApiKey (inputApiKey, headerApiKey) {
|
|
|
32
32
|
)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
const isPlainObject = value =>
|
|
36
|
+
value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
37
|
+
|
|
38
|
+
function toToolResponse (isError, field, value) {
|
|
39
39
|
return {
|
|
40
|
-
isError
|
|
41
|
-
structuredContent: {
|
|
42
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
40
|
+
isError,
|
|
41
|
+
structuredContent: { [field]: value },
|
|
42
|
+
content: [{ type: 'text', text: JSON.stringify(value, null, 2) }]
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// Every tool returns the library's direct result (a string, array, or object).
|
|
47
|
+
// MCP `structuredContent` must be an object, so wrap the value under `data`.
|
|
48
|
+
export function asToolResult (value) {
|
|
49
|
+
return toToolResponse(false, 'data', value ?? null)
|
|
50
|
+
}
|
|
48
51
|
|
|
49
52
|
// The API wraps some failures in a generic top-level message ("The request
|
|
50
53
|
// has been not processed…"), while the specific cause travels in `data`
|
|
@@ -53,7 +56,7 @@ const isPlainObject = value =>
|
|
|
53
56
|
// auxiliary strings in `data`.
|
|
54
57
|
const GENERIC_API_MESSAGE = 'The request has been not processed.'
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
function toErrorPayload (error) {
|
|
57
60
|
const isMql = error instanceof MicrolinkError
|
|
58
61
|
const statusCode = isMql ? error.statusCode : undefined
|
|
59
62
|
const description = isMql ? error.description : undefined
|
|
@@ -99,9 +102,16 @@ export function asErrorResult (error) {
|
|
|
99
102
|
payload.hint = FREE_QUOTA_EXCEEDED_HINT
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
return payload
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function asErrorResult (error) {
|
|
109
|
+
const payload =
|
|
110
|
+
isPlainObject(error) &&
|
|
111
|
+
!(error instanceof Error) &&
|
|
112
|
+
typeof error.message === 'string'
|
|
113
|
+
? error
|
|
114
|
+
: toErrorPayload(error)
|
|
115
|
+
|
|
116
|
+
return toToolResponse(true, 'error', payload)
|
|
107
117
|
}
|
package/src/schemas.js
CHANGED
|
@@ -214,6 +214,9 @@ export const metaConfigSchema = objectLikeSchema(
|
|
|
214
214
|
.strict()
|
|
215
215
|
)
|
|
216
216
|
|
|
217
|
+
const optionalApiKey = description =>
|
|
218
|
+
z.string().min(1).optional().describe(description)
|
|
219
|
+
|
|
217
220
|
const baseSchema = z.object({
|
|
218
221
|
url: z
|
|
219
222
|
.string()
|
|
@@ -221,13 +224,9 @@ const baseSchema = z.object({
|
|
|
221
224
|
.describe(
|
|
222
225
|
'Public URL of the page to process. Include the protocol, for example https://example.com.'
|
|
223
226
|
),
|
|
224
|
-
apiKey:
|
|
225
|
-
.
|
|
226
|
-
|
|
227
|
-
.optional()
|
|
228
|
-
.describe(
|
|
229
|
-
'Microlink PRO API key. Omit it unless you have one: requests then use the MICROLINK_API_KEY environment variable or the free endpoint.'
|
|
230
|
-
)
|
|
227
|
+
apiKey: optionalApiKey(
|
|
228
|
+
'Microlink PRO API key. Omit it unless you have one: requests then use the MICROLINK_API_KEY environment variable or the free endpoint.'
|
|
229
|
+
)
|
|
231
230
|
})
|
|
232
231
|
|
|
233
232
|
const fullShape = {
|
|
@@ -426,13 +425,9 @@ export const searchInputSchema = z
|
|
|
426
425
|
.describe(
|
|
427
426
|
'Google search query. Operators like site:, filetype: or quotes work as-is.'
|
|
428
427
|
),
|
|
429
|
-
apiKey:
|
|
430
|
-
.
|
|
431
|
-
|
|
432
|
-
.optional()
|
|
433
|
-
.describe(
|
|
434
|
-
'Microlink API key. Required for this tool: Google search runs on the PRO endpoint.'
|
|
435
|
-
),
|
|
428
|
+
apiKey: optionalApiKey(
|
|
429
|
+
'Microlink API key. Required for this tool: Google search runs on the PRO endpoint.'
|
|
430
|
+
),
|
|
436
431
|
type: z
|
|
437
432
|
.enum([
|
|
438
433
|
'search',
|
package/src/tools/extract.js
CHANGED
|
@@ -7,7 +7,7 @@ export function extract (server) {
|
|
|
7
7
|
'microlink_extract',
|
|
8
8
|
[
|
|
9
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.',
|
|
10
|
+
'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.',
|
|
11
11
|
'Also supports combining capabilities (`screenshot`, `pdf`, `iframe`, `insights`) and browser controls in the same request.',
|
|
12
12
|
'Mirrors the `microlink.extract(url, rules)` library method.'
|
|
13
13
|
].join(' '),
|
package/src/tools/register.js
CHANGED
|
@@ -54,6 +54,30 @@ export const INTERACTIVE_ANNOTATIONS = {
|
|
|
54
54
|
openWorldHint: true
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Human-friendly titles shown by MCP clients for each tool.
|
|
58
|
+
const TITLES = {
|
|
59
|
+
metadata: 'Metadata',
|
|
60
|
+
logo: 'Logo',
|
|
61
|
+
markdown: 'Markdown',
|
|
62
|
+
html: 'HTML',
|
|
63
|
+
text: 'Plain text',
|
|
64
|
+
screenshot: 'Screenshot',
|
|
65
|
+
pdf: 'PDF',
|
|
66
|
+
embed: 'Embed',
|
|
67
|
+
video: 'Video',
|
|
68
|
+
audio: 'Audio',
|
|
69
|
+
links: 'Links',
|
|
70
|
+
images: 'Images',
|
|
71
|
+
videos: 'Videos',
|
|
72
|
+
audios: 'Audios',
|
|
73
|
+
emails: 'Emails',
|
|
74
|
+
technologies: 'Technologies',
|
|
75
|
+
lighthouse: 'Lighthouse',
|
|
76
|
+
search: 'Google search',
|
|
77
|
+
function: 'Function',
|
|
78
|
+
extract: 'Extract'
|
|
79
|
+
}
|
|
80
|
+
|
|
57
81
|
// Common shape: a tool that maps to `client.<method>(url, options)`.
|
|
58
82
|
export function urlMethod (method) {
|
|
59
83
|
return (client, { url, ...options }) => client[method](url, options)
|
|
@@ -86,23 +110,19 @@ export function register (
|
|
|
86
110
|
const key = name.replace(/^microlink_/, '')
|
|
87
111
|
const dataSchema = outputSchemas[key]
|
|
88
112
|
const outputSchema = dataSchema ? { data: dataSchema } : undefined
|
|
113
|
+
const title = TITLES[key]
|
|
89
114
|
|
|
90
115
|
server.registerTool(
|
|
91
116
|
name,
|
|
92
|
-
{ description, inputSchema, outputSchema, annotations },
|
|
117
|
+
{ title, description, inputSchema, outputSchema, annotations },
|
|
93
118
|
async (args, extra) => {
|
|
94
119
|
const parsed = inputSchema.safeParse(args)
|
|
95
120
|
|
|
96
121
|
if (!parsed.success) {
|
|
97
|
-
|
|
122
|
+
return asErrorResult({
|
|
98
123
|
message: 'Input validation failed.',
|
|
99
124
|
issues: parsed.error.issues
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
isError: true,
|
|
103
|
-
structuredContent: { error: payload },
|
|
104
|
-
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }]
|
|
105
|
-
}
|
|
125
|
+
})
|
|
106
126
|
}
|
|
107
127
|
|
|
108
128
|
try {
|