@juspay/neurolink 12.49.16 → 12.49.18

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.
@@ -106,15 +106,21 @@ export class ToolDiscoveryService extends EventEmitter {
106
106
  }
107
107
  /**
108
108
  * Discover tools from an external MCP server
109
+ * Startup probes use the fresh connection directly under the same operation
110
+ * deadline, so an ordinary discovery cooldown cannot exhaust reconnects.
109
111
  */
110
- async discoverTools(serverId, client, timeout = DEFAULT_TOOL_TIMEOUT) {
112
+ async discoverTools(serverId, client, timeout = DEFAULT_TOOL_TIMEOUT, isCurrentDiscovery = () => true, startupDiscovery = false) {
111
113
  return withSpan({
112
114
  name: "neurolink.mcp.discoverTools",
113
115
  tracer: tracers.mcp,
114
116
  attributes: { "mcp.server_id": serverId },
115
117
  }, async (span) => {
116
118
  const startTime = Date.now();
119
+ let ownsDiscovery = false;
117
120
  try {
121
+ if (!isCurrentDiscovery()) {
122
+ throw new Error(`Discovery owner for '${serverId}' is no longer current`);
123
+ }
118
124
  // Prevent concurrent discovery for same server
119
125
  if (this.discoveryInProgress.has(serverId)) {
120
126
  return {
@@ -127,19 +133,23 @@ export class ToolDiscoveryService extends EventEmitter {
127
133
  };
128
134
  }
129
135
  this.discoveryInProgress.add(serverId);
136
+ ownsDiscovery = true;
130
137
  mcpLogger.info(`[ToolDiscoveryService] Starting tool discovery for server: ${serverId}`);
131
- // Create circuit breaker for tool discovery
132
- const circuitBreaker = globalCircuitBreakerManager.getBreaker(`tool-discovery-${serverId}`, {
133
- failureThreshold: 2,
134
- resetTimeout: 60000,
135
- operationTimeout: timeout,
136
- });
137
- // Discover tools with circuit breaker protection
138
- const tools = await circuitBreaker.execute(async () => {
139
- return await this.performToolDiscovery(serverId, client, timeout);
140
- });
138
+ const discover = () => this.performToolDiscovery(serverId, client, timeout);
139
+ // A new connection must get its bounded readiness probe even while
140
+ // the ordinary discovery breaker is cooling down. Its transport and
141
+ // operation deadlines still apply; real failures still reject startup.
142
+ const tools = startupDiscovery
143
+ ? await discover()
144
+ : await globalCircuitBreakerManager
145
+ .getBreaker(`tool-discovery-${serverId}`, {
146
+ failureThreshold: 2,
147
+ resetTimeout: 60000,
148
+ operationTimeout: timeout,
149
+ })
150
+ .execute(discover);
141
151
  // Register discovered tools
142
- const registeredTools = await this.registerDiscoveredTools(serverId, tools);
152
+ const registeredTools = await this.registerDiscoveredTools(serverId, tools, isCurrentDiscovery);
143
153
  span.setAttribute("mcp.tools_discovered", registeredTools.length);
144
154
  const result = {
145
155
  success: true,
@@ -182,7 +192,9 @@ export class ToolDiscoveryService extends EventEmitter {
182
192
  };
183
193
  }
184
194
  finally {
185
- this.discoveryInProgress.delete(serverId);
195
+ if (ownsDiscovery) {
196
+ this.discoveryInProgress.delete(serverId);
197
+ }
186
198
  }
187
199
  });
188
200
  }
@@ -201,13 +213,19 @@ export class ToolDiscoveryService extends EventEmitter {
201
213
  /**
202
214
  * Register discovered tools
203
215
  */
204
- async registerDiscoveredTools(serverId, tools) {
216
+ async registerDiscoveredTools(serverId, tools, isCurrentDiscovery) {
205
217
  const registeredTools = [];
218
+ if (!isCurrentDiscovery()) {
219
+ throw new Error(`Discovery owner for '${serverId}' is no longer current`);
220
+ }
206
221
  // Clear existing tools for this server
207
222
  this.clearServerTools(serverId);
208
223
  for (const tool of tools) {
209
224
  try {
210
225
  const toolInfo = await this.createToolInfo(serverId, tool);
226
+ if (!isCurrentDiscovery()) {
227
+ throw new Error(`Discovery owner for '${serverId}' is no longer current`);
228
+ }
211
229
  const validation = this.validateTool(toolInfo);
212
230
  if (!validation.isValid) {
213
231
  mcpLogger.warn(`[ToolDiscoveryService] Skipping invalid tool ${tool.name} from ${serverId}:`, validation.errors);
@@ -232,11 +250,7 @@ export class ToolDiscoveryService extends EventEmitter {
232
250
  }
233
251
  // Add tool if not already present
234
252
  if (!serverTools.find((t) => t.name === tool.name)) {
235
- serverTools.push({
236
- name: tool.name,
237
- description: tool.description || "",
238
- inputSchema: tool.inputSchema,
239
- });
253
+ serverTools.push(toolInfo);
240
254
  }
241
255
  // Track server tools (legacy)
242
256
  if (!this.serverTools.has(serverId)) {
@@ -257,9 +271,15 @@ export class ToolDiscoveryService extends EventEmitter {
257
271
  mcpLogger.debug(`[ToolDiscoveryService] Registered tool: ${tool.name} from ${serverId}`);
258
272
  }
259
273
  catch (error) {
274
+ if (!isCurrentDiscovery()) {
275
+ throw error;
276
+ }
260
277
  mcpLogger.error(`[ToolDiscoveryService] Failed to register tool ${tool.name} from ${serverId}:`, error);
261
278
  }
262
279
  }
280
+ if (!isCurrentDiscovery()) {
281
+ throw new Error(`Discovery owner for '${serverId}' is no longer current`);
282
+ }
263
283
  return registeredTools;
264
284
  }
265
285
  /**
@@ -271,6 +291,7 @@ export class ToolDiscoveryService extends EventEmitter {
271
291
  description: tool.description || "No description provided",
272
292
  serverId,
273
293
  inputSchema: tool.inputSchema,
294
+ ...(tool.annotations ? { annotations: { ...tool.annotations } } : {}),
274
295
  isAvailable: true,
275
296
  stats: {
276
297
  totalCalls: 0,
@@ -782,6 +803,7 @@ export class ToolDiscoveryService extends EventEmitter {
782
803
  description: tool.description,
783
804
  serverId,
784
805
  inputSchema: tool.inputSchema,
806
+ ...(tool.annotations ? { annotations: { ...tool.annotations } } : {}),
785
807
  isAvailable: true,
786
808
  stats: {
787
809
  totalCalls: 0,
@@ -819,6 +841,7 @@ export class ToolDiscoveryService extends EventEmitter {
819
841
  description: tool.description,
820
842
  serverId,
821
843
  inputSchema: tool.inputSchema,
844
+ ...(tool.annotations ? { annotations: { ...tool.annotations } } : {}),
822
845
  isAvailable: true,
823
846
  stats: {
824
847
  totalCalls: 0,
@@ -6,7 +6,7 @@
6
6
  * Uses real MCP infrastructure for tool discovery and execution.
7
7
  */
8
8
  import type { DecisionCallerOptions, DecisionLimitsQuery, DecisionLimitsReading, DecisionOptions, DecisionResult, AgentDefinition, AgentNetworkConfig, AgentRunOptions, AgentRunOutcome, AgentToolRegistrationOptions, IsolatedAgentDefinition, NetworkExecutionInput, NetworkExecutionOptions, NetworkExecutionResult, NetworkStreamChunk, WorkerInstanceOptions } from "./types/index.js";
9
- import type { CompactionConfig, CompactionResult, ArtifactStore, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult, StreamGenerationEndContext, ToolRoutingServerDescriptor, ToolDedupConfig, ToolConfig, KnowledgeEngineStatus, ChecklistState, ArtifactPageRequest, BankArtifactOptions, BankedArtifactRef, DelegateCollectRequest, DelegateCollectResult, DelegateHandle, DelegateRegistrationOptions, DelegateSpawnOptions, BackgroundCommandHandle, BackgroundCommandOptions, BackgroundCommandOutputPage, BackgroundCommandPageRequest, BackgroundCommandPolicy, BackgroundCommandStatus, GitToolResult, GitToolsetOptions } from "./types/index.js";
9
+ import type { CompactionConfig, CompactionResult, ArtifactStore, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPServerHealth, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult, StreamGenerationEndContext, ToolRoutingServerDescriptor, ToolDedupConfig, ToolConfig, KnowledgeEngineStatus, ChecklistState, ArtifactPageRequest, BankArtifactOptions, BankedArtifactRef, DelegateCollectRequest, DelegateCollectResult, DelegateHandle, DelegateRegistrationOptions, DelegateSpawnOptions, BackgroundCommandHandle, BackgroundCommandOptions, BackgroundCommandOutputPage, BackgroundCommandPageRequest, BackgroundCommandPolicy, BackgroundCommandStatus, GitToolResult, GitToolsetOptions } from "./types/index.js";
10
10
  import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
11
11
  import type { RedisConversationMemoryManager } from "./core/redisConversationMemoryManager.js";
12
12
  import { ExternalServerManager } from "./mcp/externalServerManager.js";
@@ -2150,6 +2150,7 @@ export declare class NeuroLink {
2150
2150
  * List all external MCP servers
2151
2151
  * @returns Array of server health information
2152
2152
  */
2153
+ getExternalMCPServerHealth(): ExternalMCPServerHealth[];
2153
2154
  listExternalMCPServers(): Array<{
2154
2155
  serverId: string;
2155
2156
  status: string;
package/dist/neurolink.js CHANGED
@@ -2306,6 +2306,9 @@ Current user's request: ${currentInput}`;
2306
2306
  * Setup event handlers for external server manager
2307
2307
  */
2308
2308
  setupExternalServerEventHandlers(constructorId) {
2309
+ this.externalServerManager.on("healthCheck", (event) => {
2310
+ this.emitter.emit("externalMCP:serverHealth", event);
2311
+ });
2309
2312
  this.externalServerManager.on("connected", (event) => {
2310
2313
  logger.debug(`[NeuroLink] 🔗 EXTERNAL_SERVER_EVENT_CONNECTED`, {
2311
2314
  constructorId,
@@ -12812,6 +12815,9 @@ Current user's request: ${currentInput}`;
12812
12815
  * List all external MCP servers
12813
12816
  * @returns Array of server health information
12814
12817
  */
12818
+ getExternalMCPServerHealth() {
12819
+ return this.externalServerManager.getServerStatuses();
12820
+ }
12815
12821
  listExternalMCPServers() {
12816
12822
  const serverStatuses = this.externalServerManager.getServerStatuses();
12817
12823
  const allServers = this.externalServerManager.listServers();
@@ -49,6 +49,8 @@ export type AgentInput = string | Record<string, unknown>;
49
49
  * Result of agent execution
50
50
  */
51
51
  export type AgentResult = {
52
+ /** SDK estimate in USD; undefined when the model has no known pricing. */
53
+ cost?: number;
52
54
  /** Generated content */
53
55
  content: string;
54
56
  /** Structured output if schema was provided */
@@ -80,6 +82,10 @@ export type AgentResult = {
80
82
  * Options for agent execution
81
83
  */
82
84
  export type AgentExecutionOptions = {
85
+ /** Enable SDK usage and cost analytics for this execution. */
86
+ enableAnalytics?: boolean;
87
+ /** Cumulative budget for the NeuroLink instance, not a per-run cap. */
88
+ maxBudgetUsd?: number;
83
89
  /** Additional context for the agent */
84
90
  context?: Record<string, unknown>;
85
91
  /** Override max steps for this execution */
@@ -469,6 +475,8 @@ export type AgentStreamChunkType = "agent-start" | "agent-thinking" | "agent-tex
469
475
  * Agent stream chunk
470
476
  */
471
477
  export type AgentStreamChunk = {
478
+ /** SDK cost estimate in USD (for complete chunks), when priced. */
479
+ cost?: number;
472
480
  /** Chunk type */
473
481
  type: AgentStreamChunkType;
474
482
  /** Agent ID */
@@ -28,6 +28,12 @@ export type ProviderModelConfig = {
28
28
  /**
29
29
  * Real-time provider performance tracking metrics
30
30
  */
31
+ export type ProviderPerformanceSample = {
32
+ responseTime: number;
33
+ tokensGenerated: number;
34
+ cost?: number;
35
+ success: boolean;
36
+ };
31
37
  export type ProviderPerformanceMetrics = {
32
38
  responseTime: number[];
33
39
  successRate: number;
@@ -10,7 +10,7 @@ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
10
10
  /**
11
11
  * Supported MCP transport protocols - imported from mcpTypes.js (canonical definition)
12
12
  */
13
- import type { MCPTransportType, MCPServerInfo, MCPServerReadiness } from "./mcp.js";
13
+ import type { MCPTransportType, MCPServerInfo, MCPServerReadiness, MCPToolAnnotations } from "./mcp.js";
14
14
  export type { MCPTransportType } from "./mcp.js";
15
15
  /**
16
16
  * External MCP server configuration for process spawning
@@ -40,6 +40,8 @@ export type ExternalMCPServerConfig = {
40
40
  url?: string;
41
41
  /** HTTP headers for authentication and configuration (HTTP/SSE/WebSocket) */
42
42
  headers?: Record<string, string>;
43
+ /** Trusted HTTP/SSE egress hook, retained for every connection and request. */
44
+ fetch?: typeof globalThis.fetch;
43
45
  /** List of tool names to block/blacklist from this server */
44
46
  blockedTools?: string[];
45
47
  /** Additional metadata */
@@ -114,6 +116,8 @@ export type ExternalMCPToolInfo = {
114
116
  serverId: string;
115
117
  /** Input schema (JSON Schema) */
116
118
  inputSchema?: JsonObject;
119
+ /** Server-supplied behavior hints; absent annotations remain absent. */
120
+ annotations?: MCPToolAnnotations;
117
121
  /** Whether the tool is currently available */
118
122
  isAvailable: boolean;
119
123
  /** Tool metadata */
@@ -147,6 +151,12 @@ export type ExternalMCPServerHealth = {
147
151
  toolCount: number;
148
152
  /** Any health issues detected */
149
153
  issues: string[];
154
+ /** Consecutive failed connections or live health checks, reset on recovery. */
155
+ consecutiveFailures?: number;
156
+ /** Most recent connection/health error, absent after successful recovery. */
157
+ lastError?: string;
158
+ /** Next automatic retry deadline, absent when no retry is scheduled. */
159
+ nextRetryAt?: Date;
150
160
  /** Performance metrics */
151
161
  performance: {
152
162
  uptime: number;
@@ -54,6 +54,12 @@ export type MCPServerInfo = {
54
54
  env?: Record<string, string>;
55
55
  url?: string;
56
56
  headers?: Record<string, string>;
57
+ /**
58
+ * Trusted host-provided fetch for HTTP and SSE, including reconnects and tool
59
+ * calls. Use a guarded, DNS-pinned dispatcher for untrusted server URLs.
60
+ * Programmatic only: functions cannot be persisted in JSON configuration.
61
+ */
62
+ fetch?: typeof globalThis.fetch;
57
63
  /** HTTP transport-specific options */
58
64
  httpOptions?: MCPHTTPTransportOptions;
59
65
  timeout?: number;
@@ -132,12 +138,12 @@ export type MCPServerInfo = {
132
138
  };
133
139
  };
134
140
  /**
135
- * HTTP Transport Options for fine-grained control
141
+ * Transport deadlines (all transports), plus HTTP connection-pool options
136
142
  */
137
143
  export type MCPHTTPTransportOptions = {
138
- /** Connection timeout in milliseconds (default: 30000) */
144
+ /** Explicit connection cap in milliseconds for every transport; absent retains the server/client startup budget. */
139
145
  connectionTimeout?: number;
140
- /** Request timeout in milliseconds (default: 60000) */
146
+ /** Explicit whole-request cap in milliseconds for every transport; absent retains caller/server RPC limits (HTTP fetch defaults are separate). */
141
147
  requestTimeout?: number;
142
148
  /** Idle timeout for connection pool (default: 120000) */
143
149
  idleTimeout?: number;
@@ -283,6 +283,15 @@ const PRICING = {
283
283
  },
284
284
  // Google Vertex AI — Claude models on Vertex (same pricing, @ date suffix)
285
285
  vertex: {
286
+ // Standard Sonnet 4 rates; the lookup also recognizes its @ date suffix.
287
+ // https://platform.claude.com/docs/en/about-claude/pricing
288
+ "claude-sonnet-4": {
289
+ input: 3.0 / 1_000_000,
290
+ output: 15.0 / 1_000_000,
291
+ cacheRead: 0.3 / 1_000_000,
292
+ cacheCreation: 3.75 / 1_000_000,
293
+ cacheCreation1h: 6.0 / 1_000_000,
294
+ },
286
295
  // A 1-hour cache write is 2x base input, as on the direct API.
287
296
  // Claude 5 family. Absent here until now, so a Vertex leg serving any of
288
297
  // them resolved to no rate at all and booked the turn at $0 — on the one
@@ -527,7 +536,13 @@ const PRICING = {
527
536
  },
528
537
  // Google (Gemini) — updated March 2026
529
538
  google: {
530
- // Gemini 3.1 family (all require -preview suffix)
539
+ // GA text/image/video rates: https://ai.google.dev/gemini-api/docs/pricing
540
+ "gemini-3.1-flash-lite": {
541
+ input: 0.25 / 1_000_000,
542
+ output: 1.5 / 1_000_000,
543
+ cacheRead: 0.025 / 1_000_000,
544
+ },
545
+ // Gemini 3.1 preview family
531
546
  // cacheRead = 0.25x input (cached content tokens; explicit-cache storage is
532
547
  // billed separately by TTL/time, so no per-token cacheCreation rate here).
533
548
  "gemini-3.1-pro-preview": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.49.16",
3
+ "version": "12.49.18",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
6
6
  "author": {
@@ -83,6 +83,9 @@
83
83
  "test:harness-offline-timeout": "pnpm exec tsx test/continuous-test-suite-harness-offline-timeout.ts",
84
84
  "test:resolve-request-kind": "pnpm exec tsx test/continuous-test-suite-resolve-request-kind.ts",
85
85
  "test:mcp": "pnpm exec tsx test/continuous-test-suite-mcp-infra.ts",
86
+ "test:mcp:egress": "pnpm exec tsx test/continuous-test-suite-mcp-egress.ts",
87
+ "test:mcp:deadline-health": "pnpm exec tsx test/continuous-test-suite-mcp-deadline-health.ts",
88
+ "test:mcp:timeout-compat": "pnpm exec tsx test/continuous-test-suite-mcp-timeout-compat.ts",
86
89
  "test:tool-resolution": "pnpm exec tsx test/continuous-test-suite-tool-resolution.ts",
87
90
  "test:mcp:http": "pnpm exec tsx test/continuous-test-suite-mcp-http.ts",
88
91
  "test:mcp:sdk": "pnpm exec tsx test/continuous-test-suite-mcp-sdk.ts",
@@ -104,6 +107,7 @@
104
107
  "test:anthropic-silent-truncation": "pnpm exec tsx test/continuous-test-suite-anthropic-silent-truncation.ts",
105
108
  "test:anthropic-thinking-disabled": "pnpm exec tsx test/continuous-test-suite-anthropic-thinking-disabled.ts",
106
109
  "test:cache-cost-accounting": "pnpm exec tsx test/continuous-test-suite-cache-cost-accounting.ts",
110
+ "test:agent:cost": "pnpm exec tsx test/continuous-test-suite-agent-cost.ts",
107
111
  "test:adjust-body-after-400": "pnpm exec tsx test/continuous-test-suite-adjust-body-after-400.ts",
108
112
  "test:zod3-schema-native-path": "pnpm exec tsx test/continuous-test-suite-zod3-schema-native-path.ts",
109
113
  "test:error-classification-e2e": "pnpm exec tsx test/continuous-test-suite-error-classification-e2e.ts",
@@ -302,7 +306,8 @@
302
306
  "test:native-audio-model-aware": "pnpm exec tsx test/continuous-test-suite-native-audio-model-aware.ts",
303
307
  "test:cli-json-output": "pnpm exec tsx test/continuous-test-suite-cli-json-output.ts",
304
308
  "test:logging-guidelines": "pnpm exec tsx test/continuous-test-suite-logging-guidelines.ts",
305
- "test:proxy-polyfills": "pnpm exec tsx test/continuous-test-suite-proxy-polyfills.ts"
309
+ "test:proxy-polyfills": "pnpm exec tsx test/continuous-test-suite-proxy-polyfills.ts",
310
+ "test:mcp:startup-readiness": "pnpm exec tsx test/continuous-test-suite-mcp-startup-readiness.ts"
306
311
  },
307
312
  "files": [
308
313
  "dist",