(
defsOrOptions?: ReadonlyArray<AnyToolDefinition> | ToolsOptions,
maybeOptions: ToolsOptions = {},
)
| 120 | } |
| 121 | |
| 122 | async tools( |
| 123 | defsOrOptions?: ReadonlyArray<AnyToolDefinition> | ToolsOptions, |
| 124 | maybeOptions: ToolsOptions = {}, |
| 125 | ): Promise<Array<ServerTool>> { |
| 126 | if (this.#closed) throw new MCPConnectionError('MCP client is closed') |
| 127 | |
| 128 | const isDefs = Array.isArray(defsOrOptions) |
| 129 | const options: ToolsOptions = isDefs |
| 130 | ? maybeOptions |
| 131 | : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition |
| 132 | ((defsOrOptions as ToolsOptions) ?? {}) // SDK interop: defsOrOptions may be undefined at runtime even though TS types it as ToolsOptions here |
| 133 | |
| 134 | let tools: Array<ServerTool> |
| 135 | if (isDefs) { |
| 136 | // Explicit path: bind each TanStack toolDefinition to the server by name. |
| 137 | const available = new Map( |
| 138 | (await this.#client.listTools()).tools.map((t) => [t.name, t]), |
| 139 | ) |
| 140 | tools = (defsOrOptions as ReadonlyArray<AnyToolDefinition>).map((def) => { |
| 141 | const serverTool = available.get(def.name) |
| 142 | if (!serverTool) throw new MCPToolNotFoundError(def.name) |
| 143 | // Explicitly binding a task-required tool is an error (it would fail |
| 144 | // on every callTool with -32600) — unlike discovery, which skips them. |
| 145 | if (requiresTaskExecution(serverTool)) |
| 146 | throw new MCPTaskRequiredToolError(def.name) |
| 147 | const tool = def.server( |
| 148 | makeMcpExecute(this.#client, def.name, Boolean(def.outputSchema)), |
| 149 | ) as ServerTool |
| 150 | if (this.prefix) tool.name = `${this.prefix}_${def.name}` |
| 151 | if (options.lazy) tool.lazy = true |
| 152 | // Stamp MCP metadata so `serverToolNameOf` (and the call handler) can |
| 153 | // recover the UNPREFIXED native name + serverId — mirror toServerTools. |
| 154 | // `metadata.mcp` is `unknown`; only spread it when it's a plain object. |
| 155 | const existingMcp = tool.metadata?.mcp |
| 156 | const mcpBase = |
| 157 | existingMcp !== null && typeof existingMcp === 'object' |
| 158 | ? existingMcp |
| 159 | : {} |
| 160 | tool.metadata = { |
| 161 | ...tool.metadata, |
| 162 | mcp: { ...mcpBase, serverToolName: def.name, serverId: this.prefix }, |
| 163 | } |
| 164 | return tool |
| 165 | }) |
| 166 | } else { |
| 167 | // Auto-discovery path. |
| 168 | const defs = (await this.#client.listTools()).tools |
| 169 | tools = toServerTools(this.#client, defs, { |
| 170 | prefix: this.prefix, |
| 171 | lazy: options.lazy, |
| 172 | }) |
| 173 | } |
| 174 | |
| 175 | // Local duplicate guard (within one client's own list — applies to both branches). |
| 176 | const seen = new Set<string>() |
| 177 | for (const t of tools) { |
| 178 | if (seen.has(t.name)) throw new DuplicateToolNameError(t.name) |
| 179 | seen.add(t.name) |
no test coverage detected