Skip to content
Open
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -56,4 +57,4 @@
"@types/yargs": "^17.0.32",
"typescript": "^5.3.3"
}
}
}
26 changes: 24 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -85,7 +106,7 @@ class TavilyClient {
this.server = new Server(
{
name: "tavily-mcp",
version: "0.2.20",
version: PACKAGE_VERSION,
},
{
capabilities: {
Expand Down Expand Up @@ -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',
Expand All @@ -913,4 +935,4 @@ if (argv['list-tools']) {

// Otherwise start the server
const server = new TavilyClient();
server.run().catch(console.error);
server.run().catch(console.error);
57 changes: 57 additions & 0 deletions tests/cli-metadata.test.mjs
Original file line number Diff line number Diff line change
@@ -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/);
});