├── .gitignore ├── README.md ├── package.json ├── pnpm-lock.yaml ├── src └── server.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | *.log 4 | .env* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jina Reader MCP Server 2 | 3 | Fetch the content of a remote URL as Markdown with Jina Reader 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcp-jina-reader", 3 | "version": "0.1.0", 4 | "description": "Fetch the content of a remote URL as Markdown with Jina Reader", 5 | "private": true, 6 | "type": "module", 7 | "files": [], 8 | "scripts": {}, 9 | "dependencies": { 10 | "litemcp": "^0.7.0", 11 | "zod": "^3.23.8" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^20.11.24", 15 | "prettier": "^3.4.1", 16 | "typescript": "^5.3.3" 17 | }, 18 | "packageManager": "pnpm@9.14.4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab" 19 | } 20 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | litemcp: 12 | specifier: ^0.7.0 13 | version: 0.7.0(zod@3.23.8) 14 | zod: 15 | specifier: ^3.23.8 16 | version: 3.23.8 17 | devDependencies: 18 | '@types/node': 19 | specifier: ^20.11.24 20 | version: 20.17.9 21 | prettier: 22 | specifier: ^3.4.1 23 | version: 3.4.1 24 | typescript: 25 | specifier: ^5.3.3 26 | version: 5.7.2 27 | 28 | packages: 29 | 30 | '@modelcontextprotocol/sdk@1.0.3': 31 | resolution: {integrity: sha512-2as3cX/VJ0YBHGmdv3GFyTpoM8q2gqE98zh3Vf1NwnsSY0h3mvoO07MUzfygCKkWsFjcZm4otIiqD6Xh7kiSBQ==} 32 | 33 | '@sec-ant/readable-stream@0.4.1': 34 | resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} 35 | 36 | '@sindresorhus/merge-streams@4.0.0': 37 | resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} 38 | engines: {node: '>=18'} 39 | 40 | '@types/node@20.17.9': 41 | resolution: {integrity: sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==} 42 | 43 | bytes@3.1.2: 44 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} 45 | engines: {node: '>= 0.8'} 46 | 47 | citty@0.1.6: 48 | resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} 49 | 50 | consola@3.2.3: 51 | resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} 52 | engines: {node: ^14.18.0 || >=16.10.0} 53 | 54 | content-type@1.0.5: 55 | resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} 56 | engines: {node: '>= 0.6'} 57 | 58 | cross-spawn@7.0.6: 59 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 60 | engines: {node: '>= 8'} 61 | 62 | depd@2.0.0: 63 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 64 | engines: {node: '>= 0.8'} 65 | 66 | execa@9.5.2: 67 | resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} 68 | engines: {node: ^18.19.0 || >=20.5.0} 69 | 70 | figures@6.1.0: 71 | resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} 72 | engines: {node: '>=18'} 73 | 74 | get-stream@9.0.1: 75 | resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} 76 | engines: {node: '>=18'} 77 | 78 | http-errors@2.0.0: 79 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 80 | engines: {node: '>= 0.8'} 81 | 82 | human-signals@8.0.0: 83 | resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==} 84 | engines: {node: '>=18.18.0'} 85 | 86 | iconv-lite@0.6.3: 87 | resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} 88 | engines: {node: '>=0.10.0'} 89 | 90 | inherits@2.0.4: 91 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 92 | 93 | is-plain-obj@4.1.0: 94 | resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} 95 | engines: {node: '>=12'} 96 | 97 | is-stream@4.0.1: 98 | resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} 99 | engines: {node: '>=18'} 100 | 101 | is-unicode-supported@2.1.0: 102 | resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} 103 | engines: {node: '>=18'} 104 | 105 | isexe@2.0.0: 106 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 107 | 108 | litemcp@0.7.0: 109 | resolution: {integrity: sha512-UjAt0Q2GHVxY4RvUi4BL32IP1oofQVdUfrawE+TRzYf5Aj3YDVug0jzNonFg/e48Lqnl4tzwMjEeG98Zfti8/w==} 110 | hasBin: true 111 | peerDependencies: 112 | zod: ^3.23.8 113 | 114 | npm-run-path@6.0.0: 115 | resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} 116 | engines: {node: '>=18'} 117 | 118 | parse-ms@4.0.0: 119 | resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} 120 | engines: {node: '>=18'} 121 | 122 | path-key@3.1.1: 123 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 124 | engines: {node: '>=8'} 125 | 126 | path-key@4.0.0: 127 | resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} 128 | engines: {node: '>=12'} 129 | 130 | prettier@3.4.1: 131 | resolution: {integrity: sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==} 132 | engines: {node: '>=14'} 133 | hasBin: true 134 | 135 | pretty-ms@9.2.0: 136 | resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} 137 | engines: {node: '>=18'} 138 | 139 | raw-body@3.0.0: 140 | resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} 141 | engines: {node: '>= 0.8'} 142 | 143 | safer-buffer@2.1.2: 144 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 145 | 146 | setprototypeof@1.2.0: 147 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 148 | 149 | shebang-command@2.0.0: 150 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 151 | engines: {node: '>=8'} 152 | 153 | shebang-regex@3.0.0: 154 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 155 | engines: {node: '>=8'} 156 | 157 | signal-exit@4.1.0: 158 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 159 | engines: {node: '>=14'} 160 | 161 | statuses@2.0.1: 162 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} 163 | engines: {node: '>= 0.8'} 164 | 165 | strip-final-newline@4.0.0: 166 | resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} 167 | engines: {node: '>=18'} 168 | 169 | toidentifier@1.0.1: 170 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} 171 | engines: {node: '>=0.6'} 172 | 173 | typescript@5.7.2: 174 | resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} 175 | engines: {node: '>=14.17'} 176 | hasBin: true 177 | 178 | undici-types@6.19.8: 179 | resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} 180 | 181 | unicorn-magic@0.3.0: 182 | resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} 183 | engines: {node: '>=18'} 184 | 185 | unpipe@1.0.0: 186 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 187 | engines: {node: '>= 0.8'} 188 | 189 | which@2.0.2: 190 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 191 | engines: {node: '>= 8'} 192 | hasBin: true 193 | 194 | yoctocolors@2.1.1: 195 | resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} 196 | engines: {node: '>=18'} 197 | 198 | zod-to-json-schema@3.23.5: 199 | resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==} 200 | peerDependencies: 201 | zod: ^3.23.3 202 | 203 | zod@3.23.8: 204 | resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} 205 | 206 | snapshots: 207 | 208 | '@modelcontextprotocol/sdk@1.0.3': 209 | dependencies: 210 | content-type: 1.0.5 211 | raw-body: 3.0.0 212 | zod: 3.23.8 213 | 214 | '@sec-ant/readable-stream@0.4.1': {} 215 | 216 | '@sindresorhus/merge-streams@4.0.0': {} 217 | 218 | '@types/node@20.17.9': 219 | dependencies: 220 | undici-types: 6.19.8 221 | 222 | bytes@3.1.2: {} 223 | 224 | citty@0.1.6: 225 | dependencies: 226 | consola: 3.2.3 227 | 228 | consola@3.2.3: {} 229 | 230 | content-type@1.0.5: {} 231 | 232 | cross-spawn@7.0.6: 233 | dependencies: 234 | path-key: 3.1.1 235 | shebang-command: 2.0.0 236 | which: 2.0.2 237 | 238 | depd@2.0.0: {} 239 | 240 | execa@9.5.2: 241 | dependencies: 242 | '@sindresorhus/merge-streams': 4.0.0 243 | cross-spawn: 7.0.6 244 | figures: 6.1.0 245 | get-stream: 9.0.1 246 | human-signals: 8.0.0 247 | is-plain-obj: 4.1.0 248 | is-stream: 4.0.1 249 | npm-run-path: 6.0.0 250 | pretty-ms: 9.2.0 251 | signal-exit: 4.1.0 252 | strip-final-newline: 4.0.0 253 | yoctocolors: 2.1.1 254 | 255 | figures@6.1.0: 256 | dependencies: 257 | is-unicode-supported: 2.1.0 258 | 259 | get-stream@9.0.1: 260 | dependencies: 261 | '@sec-ant/readable-stream': 0.4.1 262 | is-stream: 4.0.1 263 | 264 | http-errors@2.0.0: 265 | dependencies: 266 | depd: 2.0.0 267 | inherits: 2.0.4 268 | setprototypeof: 1.2.0 269 | statuses: 2.0.1 270 | toidentifier: 1.0.1 271 | 272 | human-signals@8.0.0: {} 273 | 274 | iconv-lite@0.6.3: 275 | dependencies: 276 | safer-buffer: 2.1.2 277 | 278 | inherits@2.0.4: {} 279 | 280 | is-plain-obj@4.1.0: {} 281 | 282 | is-stream@4.0.1: {} 283 | 284 | is-unicode-supported@2.1.0: {} 285 | 286 | isexe@2.0.0: {} 287 | 288 | litemcp@0.7.0(zod@3.23.8): 289 | dependencies: 290 | '@modelcontextprotocol/sdk': 1.0.3 291 | citty: 0.1.6 292 | execa: 9.5.2 293 | zod: 3.23.8 294 | zod-to-json-schema: 3.23.5(zod@3.23.8) 295 | 296 | npm-run-path@6.0.0: 297 | dependencies: 298 | path-key: 4.0.0 299 | unicorn-magic: 0.3.0 300 | 301 | parse-ms@4.0.0: {} 302 | 303 | path-key@3.1.1: {} 304 | 305 | path-key@4.0.0: {} 306 | 307 | prettier@3.4.1: {} 308 | 309 | pretty-ms@9.2.0: 310 | dependencies: 311 | parse-ms: 4.0.0 312 | 313 | raw-body@3.0.0: 314 | dependencies: 315 | bytes: 3.1.2 316 | http-errors: 2.0.0 317 | iconv-lite: 0.6.3 318 | unpipe: 1.0.0 319 | 320 | safer-buffer@2.1.2: {} 321 | 322 | setprototypeof@1.2.0: {} 323 | 324 | shebang-command@2.0.0: 325 | dependencies: 326 | shebang-regex: 3.0.0 327 | 328 | shebang-regex@3.0.0: {} 329 | 330 | signal-exit@4.1.0: {} 331 | 332 | statuses@2.0.1: {} 333 | 334 | strip-final-newline@4.0.0: {} 335 | 336 | toidentifier@1.0.1: {} 337 | 338 | typescript@5.7.2: {} 339 | 340 | undici-types@6.19.8: {} 341 | 342 | unicorn-magic@0.3.0: {} 343 | 344 | unpipe@1.0.0: {} 345 | 346 | which@2.0.2: 347 | dependencies: 348 | isexe: 2.0.0 349 | 350 | yoctocolors@2.1.1: {} 351 | 352 | zod-to-json-schema@3.23.5(zod@3.23.8): 353 | dependencies: 354 | zod: 3.23.8 355 | 356 | zod@3.23.8: {} 357 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import { LiteMCP } from "litemcp"; 2 | import { z } from "zod"; 3 | 4 | async function fetchWithJinaReader(url: string): Promise { 5 | const apiKey = process.env.JINA_API_KEY; 6 | console.error(`Fetching ${url}`, apiKey ? "with API key" : "without API key"); 7 | const response = await fetch(`https://r.jina.ai/${url}`, { 8 | headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined, 9 | }); 10 | if (!response.ok) { 11 | throw new Error(`Failed to fetch ${url}: ${response.statusText}`); 12 | } 13 | return response.text(); 14 | } 15 | 16 | const server = new LiteMCP("mcp-jina-reader", "0.1.0"); 17 | 18 | server.addTool({ 19 | name: "fetch_url_content", 20 | description: "Fetch the content of a URL as Markdown.", 21 | parameters: z.object({ 22 | url: z.string().url(), 23 | }), 24 | execute(args) { 25 | return fetchWithJinaReader(args.url); 26 | }, 27 | }); 28 | 29 | server.addPrompt({ 30 | name: "fetch_url_content", 31 | description: "Fetch the content of a URL as Markdown.", 32 | arguments: [ 33 | { 34 | name: "url", 35 | description: "The URL to fetch the content of.", 36 | required: true, 37 | }, 38 | ], 39 | async load(args) { 40 | const content = await fetchWithJinaReader(args.url); 41 | return `Content of ${args.url}:\n${content}`; 42 | }, 43 | }); 44 | 45 | server.start(); 46 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | --------------------------------------------------------------------------------