From d35a6fc2dd76923c82ca7f26b4503260f25e944f Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 24 Sep 2025 13:59:17 +0300 Subject: [PATCH 1/4] feat: centralize constants and add timeout support to apply command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create centralized constants structure in src/constants/ - MCP-related constants (DEFAULT_CONNECTION_TIMEOUT_MS=30000, TimeoutError) - Token threshold constants (LOW=5000, MEDIUM=15000) - Add --timeout flag support to apply command's --verify-mcp feature - Update all hardcoded timeouts to use centralized constants - Add timeout documentation to README with examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 25 +++++++++++-------------- src/commands/apply.ts | 20 ++++++++++++++------ src/commands/verifyMcp.ts | 3 ++- src/constants/index.ts | 2 ++ src/constants/mcp.ts | 13 +++++++++++++ src/constants/tokens.ts | 6 ++++++ src/core/mcpClient.ts | 17 ++++++----------- src/core/rulesParser.ts | 3 ++- 8 files changed, 56 insertions(+), 33 deletions(-) create mode 100644 src/constants/index.ts create mode 100644 src/constants/mcp.ts create mode 100644 src/constants/tokens.ts diff --git a/README.md b/README.md index 148a599..03eb86e 100644 --- a/README.md +++ b/README.md @@ -94,25 +94,16 @@ agentinit mcp --install # Install specific MCP ### `agentinit verify_mcp` -Verify MCP server installations and list their capabilities. - -```bash -agentinit verify_mcp --all # Verify all configured MCP servers -agentinit verify_mcp --mcp-name # Verify specific MCP server -``` +Verify MCP server installations and get their tools with token usage. **Examples:** ```bash # Verify all MCPs in project agentinit verify_mcp --all - -# Verify specific server -agentinit verify_mcp --mcp-name everything - -# Test MCP configuration directly +# Verify STDIO server agentinit verify_mcp --mcp-stdio everything "npx -y @modelcontextprotocol/server-everything" - -agentinit verify_mcp --mcp-http +# Verify HTTP server +agentinit verify_mcp --mcp-http notion_api "https://mcp.notion.com/mcp" --timeout 30000 ``` Shows connection status, response time, and available tools/resources/prompts for each MCP server. @@ -181,7 +172,13 @@ This generates `.agentinit/agentinit.toml` with your MCP configurations. - `--header "KEY:VALUE"` - Adds custom headers in KEY:VALUE format (can be used multiple times) - Both flags can be combined for APIs requiring multiple authentication methods -**MCP Verification**: Use the `--verify-mcp` flag to test MCP servers immediately after configuration. This ensures servers are reachable and shows their available tools, resources, and prompts. +**MCP Verification**: Use the `--verify-mcp` flag to test MCP servers immediately after configuration. This ensures servers are reachable and shows their available tools, resources, and prompts. Use `--timeout ` to set a custom connection timeout (default: 30000ms). + +```bash +# Verify with custom timeout +npx agentinit apply --verify-mcp --timeout 30000 \ + --mcp-stdio chrome-mcp "bunx -y chrome-devtools-mcp@latest" +``` #### Rules Configuration diff --git a/src/commands/apply.ts b/src/commands/apply.ts index 5f533e4..46eb400 100644 --- a/src/commands/apply.ts +++ b/src/commands/apply.ts @@ -2,6 +2,7 @@ import ora from 'ora'; import { green, yellow, red, cyan } from 'kleur/colors'; import { logger } from '../utils/logger.js'; import { MCPParser, MCPParseError } from '../core/mcpParser.js'; +import { TOKEN_COUNT_THRESHOLDS, DEFAULT_CONNECTION_TIMEOUT_MS } from '../constants/index.js'; import { RulesParser, RulesParseError } from '../core/rulesParser.js'; import { TOMLGenerator } from '../core/tomlGenerator.js'; import { readFileIfExists, writeFile, getAgentInitTomlPath } from '../utils/fs.js'; @@ -32,8 +33,8 @@ interface ApplyResult { // Color utility functions for token display function colorizeTokenCount(tokenCount: number): string { - if (tokenCount <= 5000) return green(tokenCount.toString()); - if (tokenCount <= 15000) return yellow(tokenCount.toString()); + if (tokenCount <= TOKEN_COUNT_THRESHOLDS.LOW) return green(tokenCount.toString()); + if (tokenCount <= TOKEN_COUNT_THRESHOLDS.MEDIUM) return yellow(tokenCount.toString()); if (tokenCount <= 30000) return red(tokenCount.toString()); // Light red approximated with red return red(tokenCount.toString()); // Red for >30k } @@ -65,6 +66,12 @@ export async function applyCommand(args: string[]): Promise { // Check if --verify-mcp flag is specified const verifyMcp = args.includes('--verify-mcp'); + // Parse timeout argument + const timeoutIndex = args.findIndex(arg => arg === '--timeout'); + const timeoutArg = timeoutIndex >= 0 && timeoutIndex + 1 < args.length ? args[timeoutIndex + 1] : null; + const parsedTimeout = timeoutArg ? parseInt(timeoutArg, 10) : NaN; + const timeout = timeoutArg && !Number.isNaN(parsedTimeout) && Number.isFinite(parsedTimeout) ? parsedTimeout : undefined; + if (!hasMcpArgs && !hasRulesArgs) { logger.info('Usage: agentinit apply [options]'); logger.info(''); @@ -74,6 +81,7 @@ export async function applyCommand(args: string[]): Promise { logger.info(' --global Apply configuration globally (requires --agent)'); logger.info(' If not specified, auto-detects agents in the project'); logger.info(' --verify-mcp Verify MCP servers after configuration'); + logger.info(` --timeout Connection timeout in milliseconds for MCP verification (default: ${DEFAULT_CONNECTION_TIMEOUT_MS})`); logger.info(''); logger.info('Rules Configuration Options:'); logger.info(' --rules Apply rule templates (comma-separated)'); @@ -134,9 +142,9 @@ export async function applyCommand(args: string[]): Promise { // Parse the MCP arguments (filter out --client, --agent, --global and rules args) const mcpArgs = args.filter((arg, index) => { // Filter out non-MCP arguments - if (arg === '--client' || arg === '--agent' || arg === '--global' || arg === '--verify-mcp') return false; + if (arg === '--client' || arg === '--agent' || arg === '--global' || arg === '--verify-mcp' || arg === '--timeout') return false; if (arg === '--rules' || arg === '--rule-raw' || arg === '--rules-file' || arg === '--rules-remote') return false; - if (index > 0 && (args[index - 1] === '--client' || args[index - 1] === '--agent')) return false; + if (index > 0 && (args[index - 1] === '--client' || args[index - 1] === '--agent' || args[index - 1] === '--timeout')) return false; if (index > 0 && (args[index - 1] === '--rules' || args[index - 1] === '--rule-raw' || args[index - 1] === '--rules-file' || args[index - 1] === '--rules-remote')) return false; return true; }); @@ -334,8 +342,8 @@ export async function applyCommand(args: string[]): Promise { const verifySpinner = ora(`Verifying ${mcpParsed.servers.length} MCP server(s)...`).start(); try { - const verifier = new MCPVerifier(); - const verificationResults = await verifier.verifyServers(mcpParsed.servers); + const verifier = new MCPVerifier(timeout); + const verificationResults = await verifier.verifyServers(mcpParsed.servers, timeout); const successCount = verificationResults.filter(r => r.status === 'success').length; const errorCount = verificationResults.filter(r => r.status === 'error').length; diff --git a/src/commands/verifyMcp.ts b/src/commands/verifyMcp.ts index 5ed33e3..ea186aa 100644 --- a/src/commands/verifyMcp.ts +++ b/src/commands/verifyMcp.ts @@ -1,6 +1,7 @@ import ora from 'ora'; import { logger } from '../utils/logger.js'; import { MCPVerifier } from '../core/mcpClient.js'; +import { DEFAULT_CONNECTION_TIMEOUT_MS } from '../constants/index.js'; import { MCPParser, MCPParseError } from '../core/mcpParser.js'; import { AgentManager } from '../core/agentManager.js'; import type { MCPServerConfig } from '../types/index.js'; @@ -45,7 +46,7 @@ export async function verifyMcpCommand(args: string[]): Promise { logger.info('Verify existing configurations:'); logger.info(' --mcp-name Verify specific MCP server by name'); logger.info(' --all Verify all configured MCP servers'); - logger.info(' --timeout Connection timeout in milliseconds (default: 10000)'); + logger.info(` --timeout Connection timeout in milliseconds (default: ${DEFAULT_CONNECTION_TIMEOUT_MS})`); logger.info(''); logger.info('Verify direct MCP configuration:'); logger.info(' --mcp-stdio Verify STDIO MCP server'); diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..e9a5cfa --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,2 @@ +export { DEFAULT_CONNECTION_TIMEOUT_MS, MCP_VERIFIER_CONFIG, TimeoutError } from './mcp.js'; +export { TOKEN_COUNT_THRESHOLDS, type TokenCountThreshold } from './tokens.js'; \ No newline at end of file diff --git a/src/constants/mcp.ts b/src/constants/mcp.ts new file mode 100644 index 0000000..5bc8a1c --- /dev/null +++ b/src/constants/mcp.ts @@ -0,0 +1,13 @@ +export const DEFAULT_CONNECTION_TIMEOUT_MS = 30000; + +export const MCP_VERIFIER_CONFIG = { + name: "agentinit-verifier", + version: "1.0.0" +} as const; + +export class TimeoutError extends Error { + constructor(message: string) { + super(message); + this.name = 'TimeoutError'; + } +} \ No newline at end of file diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts new file mode 100644 index 0000000..ab3f21b --- /dev/null +++ b/src/constants/tokens.ts @@ -0,0 +1,6 @@ +export const TOKEN_COUNT_THRESHOLDS = { + LOW: 5000, + MEDIUM: 15000 +} as const; + +export type TokenCountThreshold = typeof TOKEN_COUNT_THRESHOLDS[keyof typeof TOKEN_COUNT_THRESHOLDS]; \ No newline at end of file diff --git a/src/core/mcpClient.ts b/src/core/mcpClient.ts index 73c024a..b58d09a 100644 --- a/src/core/mcpClient.ts +++ b/src/core/mcpClient.ts @@ -5,6 +5,7 @@ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; import { countTokens } from 'contextcalc'; import { green, yellow, red } from 'kleur/colors'; import { MCPServerType } from '../types/index.js'; +import { DEFAULT_CONNECTION_TIMEOUT_MS, MCP_VERIFIER_CONFIG, TimeoutError, TOKEN_COUNT_THRESHOLDS } from '../constants/index.js'; import type { MCPServerConfig, MCPVerificationResult, @@ -21,17 +22,11 @@ export class MCPVerificationError extends Error { } } -export class TimeoutError extends Error { - constructor(message: string) { - super(message); - this.name = 'TimeoutError'; - } -} export class MCPVerifier { private defaultTimeout: number; - constructor(defaultTimeout: number = 10000) { + constructor(defaultTimeout: number = DEFAULT_CONNECTION_TIMEOUT_MS) { this.defaultTimeout = defaultTimeout; } @@ -39,8 +34,8 @@ export class MCPVerifier { * Color utility function for token display */ private colorizeTokenCount(tokenCount: number): string { - if (tokenCount <= 5000) return green(tokenCount.toString()); - if (tokenCount <= 15000) return yellow(tokenCount.toString()); + if (tokenCount <= TOKEN_COUNT_THRESHOLDS.LOW) return green(tokenCount.toString()); + if (tokenCount <= TOKEN_COUNT_THRESHOLDS.MEDIUM) return yellow(tokenCount.toString()); return red(tokenCount.toString()); } @@ -92,8 +87,8 @@ export class MCPVerifier { // Create the MCP client client = new Client({ - name: "agentinit-verifier", - version: "1.0.0" + name: MCP_VERIFIER_CONFIG.name, + version: MCP_VERIFIER_CONFIG.version }); // Set up timeout promise that properly cancels resources diff --git a/src/core/rulesParser.ts b/src/core/rulesParser.ts index 7671ac6..6b10fcb 100644 --- a/src/core/rulesParser.ts +++ b/src/core/rulesParser.ts @@ -2,6 +2,7 @@ import { readFileSync } from 'fs'; import { fileExists } from '../utils/fs.js'; import { RulesTemplateLoader } from './rulesTemplateLoader.js'; import type { RulesConfig, AppliedRules, RemoteRulesOptions } from '../types/rules.js'; +import { DEFAULT_CONNECTION_TIMEOUT_MS } from '../constants/index.js'; export class RulesParseError extends Error { constructor(message: string) { @@ -191,7 +192,7 @@ export class RulesParser { } const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), options.timeout || 10000); + const timeoutId = setTimeout(() => controller.abort(), options.timeout || DEFAULT_CONNECTION_TIMEOUT_MS); const response = await fetch(options.url, { headers, From 6be5b937ec6fcbf924ea93f378156ba44a46b9d5 Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 24 Sep 2025 14:23:46 +0300 Subject: [PATCH 2/4] fix: address review comments --- src/commands/apply.ts | 5 ++--- src/commands/verifyMcp.ts | 2 +- src/constants/mcp.ts | 19 ++++++++++++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/commands/apply.ts b/src/commands/apply.ts index 46eb400..ed3e17d 100644 --- a/src/commands/apply.ts +++ b/src/commands/apply.ts @@ -35,8 +35,7 @@ interface ApplyResult { function colorizeTokenCount(tokenCount: number): string { if (tokenCount <= TOKEN_COUNT_THRESHOLDS.LOW) return green(tokenCount.toString()); if (tokenCount <= TOKEN_COUNT_THRESHOLDS.MEDIUM) return yellow(tokenCount.toString()); - if (tokenCount <= 30000) return red(tokenCount.toString()); // Light red approximated with red - return red(tokenCount.toString()); // Red for >30k + return red(tokenCount.toString()); } function colorizeTokenDiff(diff: number): string { @@ -70,7 +69,7 @@ export async function applyCommand(args: string[]): Promise { const timeoutIndex = args.findIndex(arg => arg === '--timeout'); const timeoutArg = timeoutIndex >= 0 && timeoutIndex + 1 < args.length ? args[timeoutIndex + 1] : null; const parsedTimeout = timeoutArg ? parseInt(timeoutArg, 10) : NaN; - const timeout = timeoutArg && !Number.isNaN(parsedTimeout) && Number.isFinite(parsedTimeout) ? parsedTimeout : undefined; + const timeout = timeoutArg && Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? parsedTimeout : undefined; if (!hasMcpArgs && !hasRulesArgs) { logger.info('Usage: agentinit apply [options]'); diff --git a/src/commands/verifyMcp.ts b/src/commands/verifyMcp.ts index ea186aa..412178c 100644 --- a/src/commands/verifyMcp.ts +++ b/src/commands/verifyMcp.ts @@ -24,7 +24,7 @@ export async function verifyMcpCommand(args: string[]): Promise { const timeoutIndex = args.findIndex(arg => arg === '--timeout'); const timeoutArg = timeoutIndex >= 0 && timeoutIndex + 1 < args.length ? args[timeoutIndex + 1] : null; const parsedTimeout = timeoutArg ? parseInt(timeoutArg, 10) : NaN; - const timeout = timeoutArg && !Number.isNaN(parsedTimeout) && Number.isFinite(parsedTimeout) ? parsedTimeout : undefined; + const timeout = timeoutArg && Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? parsedTimeout : undefined; // Check if MCP configuration arguments are present const hasMcpArgs = args.some(arg => arg.startsWith('--mcp-')); diff --git a/src/constants/mcp.ts b/src/constants/mcp.ts index 5bc8a1c..f4d6905 100644 --- a/src/constants/mcp.ts +++ b/src/constants/mcp.ts @@ -1,8 +1,25 @@ +import { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + export const DEFAULT_CONNECTION_TIMEOUT_MS = 30000; +// Dynamic version from package.json +function getPackageVersion(): string { + try { + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + const packageJsonPath = join(__dirname, '../../package.json'); + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + return packageJson.version || '1.0.0'; + } catch { + return '1.0.0'; + } +} + export const MCP_VERIFIER_CONFIG = { name: "agentinit-verifier", - version: "1.0.0" + version: getPackageVersion() } as const; export class TimeoutError extends Error { From e337f072bd41794756c72a3cdba96ebaff033e5f Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 24 Sep 2025 14:40:17 +0300 Subject: [PATCH 3/4] fix: address review comments --- src/commands/apply.ts | 12 +++++++++++- src/core/rulesParser.ts | 25 ++++++++++++++----------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/commands/apply.ts b/src/commands/apply.ts index ed3e17d..3b59e59 100644 --- a/src/commands/apply.ts +++ b/src/commands/apply.ts @@ -387,7 +387,17 @@ export async function applyCommand(args: string[]): Promise { if (server.type === 'stdio' && server.command) { logger.info(` Command: ${server.command} ${server.args?.join(' ') || ''}`); } else if (server.url) { - logger.info(` URL: ${server.url}`); + let sanitizedUrl: string; + try { + const parsedUrl = new URL(server.url); + parsedUrl.username = ''; + parsedUrl.password = ''; + parsedUrl.search = ''; + sanitizedUrl = parsedUrl.toString(); + } catch { + sanitizedUrl = server.url.split('?')[0] || 'invalid-url'; + } + logger.info(` URL: ${sanitizedUrl}`); } }); } diff --git a/src/core/rulesParser.ts b/src/core/rulesParser.ts index 6b10fcb..5c42cbd 100644 --- a/src/core/rulesParser.ts +++ b/src/core/rulesParser.ts @@ -192,14 +192,17 @@ export class RulesParser { } const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), options.timeout || DEFAULT_CONNECTION_TIMEOUT_MS); - - const response = await fetch(options.url, { - headers, - signal: controller.signal - }); - - clearTimeout(timeoutId); + const timeoutMs = options.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS; + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + let response: any; + try { + response = await fetch(options.url, { + headers, + signal: controller.signal + }); + } finally { + clearTimeout(timeoutId); + } if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); @@ -215,9 +218,9 @@ export class RulesParser { // Treat as plain text return content .split('\n') - .map(line => line.trim()) - .filter(line => line && !line.startsWith('#')) - .map(line => line.replace(/^[-*]\s*/, '')); + .map((line: string) => line.trim()) + .filter((line: string) => line && !line.startsWith('#')) + .map((line: string) => line.replace(/^[-*]\s*/, '')); } } catch (error) { throw new RulesParseError(`Failed to fetch remote rules from ${options.url}: ${error instanceof Error ? error.message : 'Unknown error'}`); From 9b3927742f564f3e4d6b0d2ecf28330a688a7c75 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 24 Sep 2025 11:42:56 +0000 Subject: [PATCH 4/4] chore(release): 1.3.0 [skip ci] # [1.3.0](https://github.com/agentinit/agentinit/compare/v1.2.1...v1.3.0) (2025-09-24) ### Bug Fixes * address review comments ([e337f07](https://github.com/agentinit/agentinit/commit/e337f072bd41794756c72a3cdba96ebaff033e5f)) * address review comments ([6be5b93](https://github.com/agentinit/agentinit/commit/6be5b937ec6fcbf924ea93f378156ba44a46b9d5)) ### Features * centralize constants and add timeout support to apply command ([d35a6fc](https://github.com/agentinit/agentinit/commit/d35a6fc2dd76923c82ca7f26b4503260f25e944f)) --- CHANGELOG.md | 13 +++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c07049..f9f3ffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [1.3.0](https://github.com/agentinit/agentinit/compare/v1.2.1...v1.3.0) (2025-09-24) + + +### Bug Fixes + +* address review comments ([e337f07](https://github.com/agentinit/agentinit/commit/e337f072bd41794756c72a3cdba96ebaff033e5f)) +* address review comments ([6be5b93](https://github.com/agentinit/agentinit/commit/6be5b937ec6fcbf924ea93f378156ba44a46b9d5)) + + +### Features + +* centralize constants and add timeout support to apply command ([d35a6fc](https://github.com/agentinit/agentinit/commit/d35a6fc2dd76923c82ca7f26b4503260f25e944f)) + ## [1.2.1](https://github.com/agentinit/agentinit/compare/v1.2.0...v1.2.1) (2025-09-20) diff --git a/package-lock.json b/package-lock.json index f36a050..b6f2543 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agentinit", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agentinit", - "version": "1.2.1", + "version": "1.3.0", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index d674c0e..86ee124 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agentinit", - "version": "1.2.1", + "version": "1.3.0", "description": "A CLI tool for managing and configuring AI coding agents", "main": "dist/index.js", "type": "module",