( filePath: string, serverName: string )
| 124 | * if the file or section is missing. |
| 125 | */ |
| 126 | export async function readTomlServerEntry( |
| 127 | filePath: string, |
| 128 | serverName: string |
| 129 | ): Promise<Record<string, unknown> | undefined> { |
| 130 | let raw: string; |
| 131 | try { |
| 132 | raw = await readFile(filePath, "utf-8"); |
| 133 | } catch { |
| 134 | return undefined; |
| 135 | } |
| 136 | |
| 137 | const sectionHeader = `[mcp_servers.${serverName}]`; |
| 138 | const startIdx = raw.indexOf(sectionHeader); |
| 139 | if (startIdx === -1) return undefined; |
| 140 | |
| 141 | // The top-level table's values live between its header and the next `[...]` |
| 142 | // header (whether that's a sub-table like `[mcp_servers.foo.http_headers]` |
| 143 | // or an unrelated section). Sub-table values belong to the sub-table, not |
| 144 | // here, so excluding them is correct. |
| 145 | const rest = raw.slice(startIdx + sectionHeader.length); |
| 146 | const nextHeader = /^\[/m.exec(rest); |
| 147 | const block = nextHeader ? rest.slice(0, nextHeader.index) : rest; |
| 148 | |
| 149 | const entry: Record<string, unknown> = {}; |
| 150 | const lineRe = /^([A-Za-z_][\w-]*)\s*=\s*(.+?)\s*$/gm; |
| 151 | let lineMatch: RegExpExecArray | null; |
| 152 | while ((lineMatch = lineRe.exec(block)) !== null) { |
| 153 | const [, key, valueText] = lineMatch; |
| 154 | try { |
| 155 | entry[key] = JSON.parse(valueText); |
| 156 | } catch { |
| 157 | // Skip values we can't parse as JSON (e.g., bare TOML numbers like 20) |
| 158 | } |
| 159 | } |
| 160 | return Object.keys(entry).length > 0 ? entry : undefined; |
| 161 | } |
| 162 | |
| 163 | /** |
| 164 | * True when `entry` looks like a stdio invocation of `@upstash/context7-mcp` |
no test coverage detected