(uri: string)
| 156 | ) |
| 157 | }, |
| 158 | async readResource(uri: string): Promise<ReadResourceResult> { |
| 159 | // Ownership isn't tracked, so try each client. A non-owning server may |
| 160 | // resolve an unrelated URI, so only accept a result whose `contents` |
| 161 | // actually include the requested `uri`; otherwise keep trying. A ui:// |
| 162 | // read must reach the server that owns it. |
| 163 | const errors: Array<unknown> = [] |
| 164 | const all = Object.values(clients) |
| 165 | for (const c of all) { |
| 166 | try { |
| 167 | const result = await (c as MCPClient<ServerDescriptor>).readResource( |
| 168 | uri, |
| 169 | ) |
| 170 | if (result.contents.some((entry) => entry.uri === uri)) { |
| 171 | return result |
| 172 | } |
| 173 | } catch (err) { |
| 174 | errors.push(err) |
| 175 | } |
| 176 | } |
| 177 | // Distinguish the two failure modes and never leave `cause` undefined: |
| 178 | // - at least one client threw → attach EVERY thrown error as an |
| 179 | // AggregateError cause. Keeping all of them matters in a multi-server |
| 180 | // pool: if the owning server fails first and an unrelated server fails |
| 181 | // after, a "last error wins" cause would bury the error you actually need. |
| 182 | // - every client responded but none owned the uri → there is no thrown |
| 183 | // error to attach, so explain that the uri was not found on any server. |
| 184 | if (errors.length > 0) { |
| 185 | throw new Error( |
| 186 | `Failed to read MCP resource "${uri}": no client could resolve it (${errors.length} error(s) attached)`, |
| 187 | { cause: new AggregateError(errors) }, |
| 188 | ) |
| 189 | } |
| 190 | throw new Error( |
| 191 | `Failed to read MCP resource "${uri}": no configured MCP server owns this uri`, |
| 192 | ) |
| 193 | }, |
| 194 | async close(): Promise<void> { |
| 195 | await Promise.all( |
| 196 | Object.values(clients).map((c) => |
nothing calls this directly
no test coverage detected