ninerouter-mcp 1.0.1 → 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.
package/dist/tools/web.js CHANGED
@@ -1,29 +1,32 @@
1
- import { z } from "zod/v4";
2
- import { requestJson } from "../ninerouter-client.js";
3
- import { toPrettyJson, tryModelsWithFallback } from "./common.js";
1
+ import { z } from 'zod/v4';
2
+ import { requestJson } from '../ninerouter-client.js';
3
+ import { tryModelsWithFallback, toPrettyJson } from './common.js';
4
4
  export function registerWebTools(server, config) {
5
- server.registerTool("web_search", {
6
- description: "Search the web through 9Router and return the raw search payload.",
5
+ server.registerTool('web_search', {
6
+ description: 'Search the web and return the raw upstream search payload as JSON. Use when the user needs current information, links, or news. Pass `model` (or its alias `provider`) to pick a specific search backend (e.g. `tavily/search`, `brave-search/search`); otherwise the configured `default_models.web_search` fallback chain is tried in order.',
7
7
  inputSchema: z.object({
8
- query: z.string().min(1).describe("Search query to send to 9Router."),
9
- model: z.string().optional().describe("9Router web-search model id, for example tavily/search or brave-search/search."),
10
- provider: z.string().optional().describe("Alias for model; accepted for compatibility with 9Router docs."),
8
+ query: z.string().min(1).describe('Search query to send to the backend.'),
9
+ model: z
10
+ .string()
11
+ .optional()
12
+ .describe('Web-search model id, for example tavily/search or brave-search/search.'),
13
+ provider: z.string().optional().describe('Alias for model.'),
11
14
  maxResults: z.number().int().positive().max(20).optional().default(5),
12
- searchType: z.enum(["web", "news"]).optional().default("web"),
15
+ searchType: z.enum(['web', 'news']).optional().default('web'),
13
16
  country: z.string().optional(),
14
17
  language: z.string().optional(),
15
18
  timeRange: z.string().optional(),
16
19
  domainFilter: z.string().optional(),
17
20
  }),
18
- }, async ({ query, model, provider, maxResults, searchType, country, language, timeRange, domainFilter }) => {
21
+ }, async ({ query, model, provider, maxResults, searchType, country, language, timeRange, domainFilter, }) => {
19
22
  const userModel = model ?? provider;
20
23
  const defaultModels = config.defaultModels?.webSearch ?? [];
21
24
  const modelsToTry = userModel ? [userModel] : defaultModels;
22
25
  if (modelsToTry.length === 0) {
23
- throw new Error("No model specified and no default_models.web_search configured.");
26
+ throw new Error('No model specified and no default_models.web_search configured.');
24
27
  }
25
28
  const payload = await tryModelsWithFallback(modelsToTry, async (selectedModel) => {
26
- return await requestJson(config, "/v1/search", {
29
+ return await requestJson(config, '/v1/search', {
27
30
  model: selectedModel,
28
31
  query,
29
32
  max_results: maxResults,
@@ -35,16 +38,19 @@ export function registerWebTools(server, config) {
35
38
  });
36
39
  });
37
40
  return {
38
- content: [{ type: "text", text: toPrettyJson(payload) }],
41
+ content: [{ type: 'text', text: toPrettyJson(payload) }],
39
42
  };
40
43
  });
41
- server.registerTool("web_fetch", {
42
- description: "Fetch a URL through 9Router and return markdown, text, or HTML extraction output.",
44
+ server.registerTool('web_fetch', {
45
+ description: 'Fetch a URL and return its content as markdown (default), plain text, or HTML. Use when the user gives a link and you need its readable body. Output is truncated to `maxCharacters` (default 8000). Pass `model` (alias `provider`) to choose a fetcher (e.g. `jina-reader/fetch`, `firecrawl/fetch`); otherwise the configured `default_models.web_fetch` fallback chain is tried in order.',
43
46
  inputSchema: z.object({
44
- url: z.string().url().describe("URL to fetch and extract."),
45
- model: z.string().optional().describe("9Router web-fetch model id, for example jina-reader/fetch or firecrawl/fetch."),
46
- provider: z.string().optional().describe("Alias for model; accepted for compatibility with 9Router docs."),
47
- format: z.enum(["markdown", "text", "html"]).optional().default("markdown"),
47
+ url: z.string().url().describe('URL to fetch and extract.'),
48
+ model: z
49
+ .string()
50
+ .optional()
51
+ .describe('Web-fetch model id, for example jina-reader/fetch or firecrawl/fetch.'),
52
+ provider: z.string().optional().describe('Alias for model.'),
53
+ format: z.enum(['markdown', 'text', 'html']).optional().default('markdown'),
48
54
  maxCharacters: z.number().int().nonnegative().optional().default(8000),
49
55
  }),
50
56
  }, async ({ url, model, provider, format, maxCharacters }) => {
@@ -52,10 +58,10 @@ export function registerWebTools(server, config) {
52
58
  const defaultModels = config.defaultModels?.webFetch ?? [];
53
59
  const modelsToTry = userModel ? [userModel] : defaultModels;
54
60
  if (modelsToTry.length === 0) {
55
- throw new Error("No model specified and no default_models.web_fetch configured.");
61
+ throw new Error('No model specified and no default_models.web_fetch configured.');
56
62
  }
57
63
  const payload = await tryModelsWithFallback(modelsToTry, async (selectedModel) => {
58
- return await requestJson(config, "/v1/web/fetch", {
64
+ return await requestJson(config, '/v1/web/fetch', {
59
65
  model: selectedModel,
60
66
  url,
61
67
  format,
@@ -63,7 +69,7 @@ export function registerWebTools(server, config) {
63
69
  });
64
70
  });
65
71
  return {
66
- content: [{ type: "text", text: toPrettyJson(payload) }],
72
+ content: [{ type: 'text', text: toPrettyJson(payload) }],
67
73
  };
68
74
  });
69
75
  }
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "ninerouter-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for 9Router with model discovery, automatic fallback, and type-safe validation",
6
6
  "license": "Apache-2.0",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
7
10
  "author": "Farhan Brillan Widyakirana <https://tojoo.dev>",
8
11
  "repository": {
9
12
  "type": "git",
@@ -39,16 +42,42 @@
39
42
  "prepack": "npm run build",
40
43
  "start": "node dist/index.js",
41
44
  "dev": "tsx watch src/index.ts",
42
- "create-config": "tsx src/create-config.ts"
45
+ "create-config": "tsx src/create-config.ts",
46
+ "release": "semantic-release",
47
+ "lint": "eslint .",
48
+ "lint:fix": "eslint . --fix",
49
+ "format": "prettier --write .",
50
+ "format:check": "prettier --check .",
51
+ "typecheck": "tsc --noEmit",
52
+ "check": "npm run typecheck && npm run lint && npm run format:check",
53
+ "prepare": "husky"
43
54
  },
44
55
  "dependencies": {
45
- "@modelcontextprotocol/server": "^2.0.0-beta.2",
46
- "@iarna/toml": "^2.2.5",
47
- "zod": "^4.0.0"
56
+ "@modelcontextprotocol/server": "^2.0.0-beta.4",
57
+ "toml": "^5.0.0",
58
+ "zod": "^4.4.3"
48
59
  },
49
60
  "devDependencies": {
50
- "@types/node": "^22.0.0",
51
- "tsx": "^4.19.0",
52
- "typescript": "^5.6.0"
61
+ "@eslint/js": "^10.0.1",
62
+ "@semantic-release/git": "^10.0.1",
63
+ "@semantic-release/npm": "^13.1.5",
64
+ "@types/node": "^22.20.0",
65
+ "eslint": "^10.7.0",
66
+ "husky": "^9.1.7",
67
+ "lint-staged": "^16.0.0",
68
+ "prettier": "^3.9.5",
69
+ "semantic-release": "^25.0.7",
70
+ "tsx": "^4.23.1",
71
+ "typescript": "^6.0.3",
72
+ "typescript-eslint": "^8.64.0"
73
+ },
74
+ "lint-staged": {
75
+ "*.{ts,tsx,js,cjs,mjs}": [
76
+ "eslint --fix",
77
+ "prettier --write"
78
+ ],
79
+ "*.{json,md,yml,yaml}": [
80
+ "prettier --write"
81
+ ]
53
82
  }
54
83
  }