Skip to content

Commit 047710a

Browse files
zhocursoragent
andcommitted
fix: track cli, retry, and config-context modules for post-rebase tree
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 74ff525 commit 047710a

3 files changed

Lines changed: 258 additions & 0 deletions

File tree

src/cli.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* CLI argv parsing for `src/index.ts`. All flags map into {@link ConfigOverrides}
3+
* for {@link resolveConfig}; environment variables remain the fallback there.
4+
*/
5+
6+
import {
7+
DEFAULT_INDEX_NAME,
8+
DEFAULT_RERANK_MODEL,
9+
SERVER_VERSION,
10+
} from './constants.js';
11+
import type { ConfigOverrides } from './config.js';
12+
13+
export type ParseCliResult =
14+
| { kind: 'help' }
15+
| { kind: 'version' }
16+
| { kind: 'config'; overrides: ConfigOverrides };
17+
18+
function parsePositiveInt(raw: string | undefined): number | undefined {
19+
if (raw === undefined || raw === '') return undefined;
20+
const n = parseInt(raw, 10);
21+
return Number.isFinite(n) && n > 0 ? n : undefined;
22+
}
23+
24+
/** Parse `process.argv` (slice 2..) into help/version/config result. */
25+
export function parseCli(argv: string[] = process.argv.slice(2)): ParseCliResult {
26+
const overrides: ConfigOverrides = {};
27+
28+
for (let i = 0; i < argv.length; i++) {
29+
const arg = argv[i];
30+
const next = argv[i + 1];
31+
32+
switch (arg) {
33+
case '--help':
34+
case '-h':
35+
return { kind: 'help' };
36+
case '--version':
37+
case '-v':
38+
return { kind: 'version' };
39+
case '--api-key':
40+
if (next !== undefined) {
41+
overrides.apiKey = next;
42+
i++;
43+
}
44+
break;
45+
case '--index-name':
46+
if (next !== undefined) {
47+
overrides.indexName = next;
48+
i++;
49+
}
50+
break;
51+
case '--sparse-index-name':
52+
if (next !== undefined) {
53+
overrides.sparseIndexName = next;
54+
i++;
55+
}
56+
break;
57+
case '--rerank-model':
58+
if (next !== undefined) {
59+
overrides.rerankModel = next;
60+
i++;
61+
}
62+
break;
63+
case '--top-k': {
64+
const n = parsePositiveInt(next);
65+
if (n !== undefined) {
66+
overrides.defaultTopK = n;
67+
i++;
68+
}
69+
break;
70+
}
71+
case '--log-level':
72+
if (next !== undefined) {
73+
overrides.logLevel = next;
74+
i++;
75+
}
76+
break;
77+
case '--log-format':
78+
if (next !== undefined) {
79+
overrides.logFormat = next;
80+
i++;
81+
}
82+
break;
83+
case '--cache-ttl-seconds': {
84+
const n = parsePositiveInt(next);
85+
if (n !== undefined) {
86+
overrides.cacheTtlSeconds = n;
87+
i++;
88+
}
89+
break;
90+
}
91+
case '--request-timeout-ms': {
92+
const n = parsePositiveInt(next);
93+
if (n !== undefined) {
94+
overrides.requestTimeoutMs = n;
95+
i++;
96+
}
97+
break;
98+
}
99+
case '--disable-suggest-flow':
100+
overrides.disableSuggestFlow = true;
101+
break;
102+
case '--check-indexes':
103+
overrides.checkIndexes = true;
104+
break;
105+
default:
106+
break;
107+
}
108+
}
109+
110+
return { kind: 'config', overrides };
111+
}
112+
113+
/** Print CLI usage (stdout). */
114+
export function printHelp(): void {
115+
process.stdout.write(`
116+
Pinecone Read-Only MCP Server
117+
118+
Usage: pinecone-read-only-mcp [options]
119+
120+
Options:
121+
--api-key TEXT Pinecone API key (or PINECONE_API_KEY)
122+
--index-name TEXT Dense index [default: ${DEFAULT_INDEX_NAME}]
123+
--sparse-index-name TEXT Sparse index [default: {index-name}-sparse]
124+
--rerank-model TEXT Reranker model [default: ${DEFAULT_RERANK_MODEL}]
125+
--top-k N Default top-k for queries [env: PINECONE_TOP_K]
126+
--log-level LEVEL DEBUG | INFO | WARN | ERROR [default: INFO]
127+
--log-format FORMAT text | json [default: text]
128+
--cache-ttl-seconds N Namespace / suggest-flow cache TTL [env: PINECONE_CACHE_TTL_SECONDS]
129+
--request-timeout-ms N Per Pinecone call timeout [env: PINECONE_REQUEST_TIMEOUT_MS]
130+
--disable-suggest-flow Bypass suggest_query_params gate (PINECONE_DISABLE_SUGGEST_FLOW)
131+
--check-indexes Verify dense + sparse indexes then exit 0/1 (PINECONE_CHECK_INDEXES)
132+
--help, -h Show this message
133+
--version, -v Print package version
134+
135+
Environment variables are documented in README.md (CLI overrides win when both are set).
136+
137+
Examples:
138+
pinecone-read-only-mcp --api-key YOUR_KEY
139+
export PINECONE_API_KEY=YOUR_KEY && pinecone-read-only-mcp --index-name my-index
140+
`);
141+
}
142+
143+
/** Print package version (stdout). */
144+
export function printVersion(): void {
145+
process.stdout.write(`${SERVER_VERSION}\n`);
146+
}

src/server/config-context.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ServerConfig } from '../config.js';
2+
import { resolveConfig } from '../config.js';
3+
4+
let activeConfig: ServerConfig | null = null;
5+
6+
/** Replace the process-global server config (called from `setupServer` with CLI/env-derived config). */
7+
export function setServerConfig(config: ServerConfig): void {
8+
activeConfig = config;
9+
}
10+
11+
/**
12+
* Active server config for modules that cannot receive `ServerConfig` through parameters
13+
* (namespace cache TTL, suggest-flow gate, etc.).
14+
*
15+
* When `setupServer()` runs without an explicit config, falls back to `resolveConfig({})`
16+
* so env defaults still apply.
17+
*/
18+
export function getServerConfig(): ServerConfig {
19+
if (!activeConfig) {
20+
activeConfig = resolveConfig({});
21+
}
22+
return activeConfig;
23+
}

src/server/retry.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Bounded retry + timeout helpers used by `PineconeClient` and re-exported
3+
* from `server.ts` for library consumers.
4+
*/
5+
6+
/** Retry policy. */
7+
export interface RetryOptions {
8+
/** Total number of attempts after the first try. Default 2. */
9+
retries?: number;
10+
/** Base backoff in ms (doubled per attempt). Default 250. */
11+
backoffMs?: number;
12+
/** Predicate that decides whether an error is retryable. */
13+
shouldRetry?: (error: unknown) => boolean;
14+
/** Logger called once per retry with attempt number and error. */
15+
onRetry?: (attempt: number, error: unknown) => void;
16+
}
17+
18+
/** Timeout policy applied around any async call. */
19+
export interface TimeoutOptions {
20+
/** Hard timeout in ms. Default 15000. */
21+
timeoutMs?: number;
22+
/** Label included in the timeout error message. */
23+
label?: string;
24+
}
25+
26+
/** Default predicate: retry on common transient HTTP statuses (429/5xx) and network-ish messages. */
27+
export function defaultShouldRetry(error: unknown): boolean {
28+
if (error instanceof Error) {
29+
const msg = error.message;
30+
if (/\b(429|502|503|504)\b/.test(msg)) return true;
31+
if (/timeout|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND/i.test(msg)) return true;
32+
}
33+
const status =
34+
(error as { status?: number; statusCode?: number })?.status ??
35+
(error as { statusCode?: number })?.statusCode;
36+
if (typeof status === 'number' && (status === 429 || (status >= 500 && status < 600))) {
37+
return true;
38+
}
39+
return false;
40+
}
41+
42+
/**
43+
* Run `fn` and retry on transient failures.
44+
*/
45+
export async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
46+
const retries = options.retries ?? 2;
47+
const baseBackoff = options.backoffMs ?? 250;
48+
const shouldRetry = options.shouldRetry ?? defaultShouldRetry;
49+
50+
let lastError: unknown;
51+
for (let attempt = 0; attempt <= retries; attempt++) {
52+
try {
53+
return await fn();
54+
} catch (error) {
55+
lastError = error;
56+
if (attempt === retries || !shouldRetry(error)) {
57+
throw error;
58+
}
59+
options.onRetry?.(attempt + 1, error);
60+
const wait = baseBackoff * Math.pow(2, attempt);
61+
await new Promise<void>((resolve) => setTimeout(resolve, wait));
62+
}
63+
}
64+
throw lastError;
65+
}
66+
67+
/**
68+
* Race `fn()` against a timeout. Throws a descriptive error when the timeout fires.
69+
*/
70+
export async function withTimeout<T>(
71+
fn: () => Promise<T>,
72+
options: TimeoutOptions = {}
73+
): Promise<T> {
74+
const timeoutMs = options.timeoutMs ?? 15_000;
75+
const label = options.label ?? 'pinecone';
76+
77+
let timer: ReturnType<typeof setTimeout> | undefined;
78+
const timeoutPromise = new Promise<never>((_, reject) => {
79+
timer = setTimeout(() => {
80+
reject(new Error(`Timeout after ${timeoutMs}ms while waiting for ${label}`));
81+
}, timeoutMs);
82+
});
83+
84+
try {
85+
return await Promise.race([fn(), timeoutPromise]);
86+
} finally {
87+
if (timer) clearTimeout(timer);
88+
}
89+
}

0 commit comments

Comments
 (0)