From 569b85ed6b50b5c478b0f2ea65cc1c61073aa817 Mon Sep 17 00:00:00 2001 From: wowsofine Date: Mon, 8 Jun 2026 12:52:38 +0800 Subject: [PATCH] Fix tavily-mcp version metadata --- package.json | 3 +- src/index.ts | 26 +++++++++++++++-- tests/cli-metadata.test.mjs | 57 +++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 tests/cli-metadata.test.mjs diff --git a/package.json b/package.json index d6a2589..0ae676b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", + "test": "npm run build && node --test tests/cli-metadata.test.mjs", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js", @@ -56,4 +57,4 @@ "@types/yargs": "^17.0.32", "typescript": "^5.3.3" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index cb92416..a5dc23b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,8 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import {CallToolRequestSchema, ListToolsRequestSchema, Tool} from "@modelcontextprotocol/sdk/types.js"; import axios from "axios"; +import fs from "fs"; +import path from "path"; import { randomUUID } from "crypto"; import dotenv from "dotenv"; import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; @@ -17,6 +19,25 @@ const IS_KEYLESS = !API_KEY; const HUMAN_ID = process.env.TAVILY_HUMAN_ID; const SESSION_ID = randomUUID(); +function readPackageVersion(): string { + const entrypoint = process.argv[1] ? fs.realpathSync(process.argv[1]) : process.cwd(); + let dir = path.dirname(path.resolve(entrypoint)); + for (let i = 0; i < 5; i++) { + const packagePath = path.join(dir, "package.json"); + if (fs.existsSync(packagePath)) { + const packageJSON = JSON.parse(fs.readFileSync(packagePath, "utf8")) as { version?: unknown }; + if (typeof packageJSON.version === "string") return packageJSON.version; + } + + const parent = path.dirname(dir); + if (parent === dir) break; + dir = parent; + } + + return "unknown"; +} + +const PACKAGE_VERSION = readPackageVersion(); interface TavilyResponse { // Response structure from Tavily API @@ -85,7 +106,7 @@ class TavilyClient { this.server = new Server( { name: "tavily-mcp", - version: "0.2.20", + version: PACKAGE_VERSION, }, { capabilities: { @@ -898,6 +919,7 @@ interface Arguments { // Modify the command line parsing section to use proper typing const argv = yargs(hideBin(process.argv)) + .version(PACKAGE_VERSION) .option('list-tools', { type: 'boolean', description: 'List all available tools and exit', @@ -913,4 +935,4 @@ if (argv['list-tools']) { // Otherwise start the server const server = new TavilyClient(); -server.run().catch(console.error); \ No newline at end of file +server.run().catch(console.error); diff --git a/tests/cli-metadata.test.mjs b/tests/cli-metadata.test.mjs new file mode 100644 index 0000000..98d5cc6 --- /dev/null +++ b/tests/cli-metadata.test.mjs @@ -0,0 +1,57 @@ +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const pkg = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8')); +const packDir = await mkdtemp(path.join(os.tmpdir(), 'tavily-mcp-pack-')); +const packResult = await execFileAsync('npm', ['pack', '--pack-destination', packDir], { cwd: root }); +const tarballName = packResult.stdout + .trim() + .split('\n') + .find((line) => line.endsWith('.tgz')); +assert.ok(tarballName, 'npm pack did not print a tarball path'); +const tarball = path.join(packDir, tarballName); +const npmEnv = { ...process.env }; +delete npmEnv.npm_config_npm_globalconfig; +delete npmEnv.npm_config_verify_deps_before_run; +delete npmEnv.npm_config__jsr_registry; + +test.after(async () => { + await rm(packDir, { recursive: true, force: true }); +}); + +async function runTavilyMcp(args) { + try { + const { stdout, stderr } = await execFileAsync( + 'npm', + ['exec', '--yes', `--package=${tarball}`, '--', 'tavily-mcp', ...args], + { cwd: path.dirname(packDir), env: npmEnv } + ); + return { code: 0, stdout, stderr }; + } catch (error) { + return { + code: error.code, + stdout: error.stdout ?? '', + stderr: error.stderr ?? '' + }; + } +} + +test('top-level version flag prints the package version', async () => { + const result = await runTavilyMcp(['--version']); + assert.equal(result.code, 0); + assert.equal(result.stdout.trim(), pkg.version); +}); + +test('top-level help remains successful', async () => { + const result = await runTavilyMcp(['--help']); + assert.equal(result.code, 0); + assert.match(result.stdout, /List all available tools and exit/); +});