infobroker 2026.8.23

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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +529 -0
  3. package/bin/infobroker.js +5 -0
  4. package/dist/chain.d.ts +14 -0
  5. package/dist/chain.js +63 -0
  6. package/dist/chain.js.map +1 -0
  7. package/dist/config.d.ts +9 -0
  8. package/dist/config.js +271 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/corroborate.d.ts +25 -0
  11. package/dist/corroborate.js +421 -0
  12. package/dist/corroborate.js.map +1 -0
  13. package/dist/http.d.ts +5 -0
  14. package/dist/http.js +25 -0
  15. package/dist/http.js.map +1 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +909 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/kb-crypto.d.ts +43 -0
  20. package/dist/kb-crypto.js +199 -0
  21. package/dist/kb-crypto.js.map +1 -0
  22. package/dist/kb.d.ts +100 -0
  23. package/dist/kb.js +1009 -0
  24. package/dist/kb.js.map +1 -0
  25. package/dist/lib/html.d.ts +2 -0
  26. package/dist/lib/html.js +13 -0
  27. package/dist/lib/html.js.map +1 -0
  28. package/dist/lib/url-guard.d.ts +14 -0
  29. package/dist/lib/url-guard.js +75 -0
  30. package/dist/lib/url-guard.js.map +1 -0
  31. package/dist/normalizer.d.ts +2 -0
  32. package/dist/normalizer.js +53 -0
  33. package/dist/normalizer.js.map +1 -0
  34. package/dist/providers/arxiv.d.ts +2 -0
  35. package/dist/providers/arxiv.js +78 -0
  36. package/dist/providers/arxiv.js.map +1 -0
  37. package/dist/providers/brave.d.ts +2 -0
  38. package/dist/providers/brave.js +79 -0
  39. package/dist/providers/brave.js.map +1 -0
  40. package/dist/providers/core.d.ts +2 -0
  41. package/dist/providers/core.js +62 -0
  42. package/dist/providers/core.js.map +1 -0
  43. package/dist/providers/duckduckgo.d.ts +2 -0
  44. package/dist/providers/duckduckgo.js +93 -0
  45. package/dist/providers/duckduckgo.js.map +1 -0
  46. package/dist/providers/exa.d.ts +2 -0
  47. package/dist/providers/exa.js +68 -0
  48. package/dist/providers/exa.js.map +1 -0
  49. package/dist/providers/generic-http.d.ts +2 -0
  50. package/dist/providers/generic-http.js +96 -0
  51. package/dist/providers/generic-http.js.map +1 -0
  52. package/dist/providers/github.d.ts +2 -0
  53. package/dist/providers/github.js +58 -0
  54. package/dist/providers/github.js.map +1 -0
  55. package/dist/providers/index.d.ts +3 -0
  56. package/dist/providers/index.js +59 -0
  57. package/dist/providers/index.js.map +1 -0
  58. package/dist/providers/internet_archive.d.ts +2 -0
  59. package/dist/providers/internet_archive.js +59 -0
  60. package/dist/providers/internet_archive.js.map +1 -0
  61. package/dist/providers/jina.d.ts +2 -0
  62. package/dist/providers/jina.js +34 -0
  63. package/dist/providers/jina.js.map +1 -0
  64. package/dist/providers/marginalia.d.ts +2 -0
  65. package/dist/providers/marginalia.js +47 -0
  66. package/dist/providers/marginalia.js.map +1 -0
  67. package/dist/providers/mojeek.d.ts +2 -0
  68. package/dist/providers/mojeek.js +47 -0
  69. package/dist/providers/mojeek.js.map +1 -0
  70. package/dist/providers/openstreetmap.d.ts +2 -0
  71. package/dist/providers/openstreetmap.js +43 -0
  72. package/dist/providers/openstreetmap.js.map +1 -0
  73. package/dist/providers/searxng.d.ts +2 -0
  74. package/dist/providers/searxng.js +52 -0
  75. package/dist/providers/searxng.js.map +1 -0
  76. package/dist/providers/semantic_scholar.d.ts +2 -0
  77. package/dist/providers/semantic_scholar.js +54 -0
  78. package/dist/providers/semantic_scholar.js.map +1 -0
  79. package/dist/providers/stack_exchange.d.ts +2 -0
  80. package/dist/providers/stack_exchange.js +83 -0
  81. package/dist/providers/stack_exchange.js.map +1 -0
  82. package/dist/providers/tavily.d.ts +2 -0
  83. package/dist/providers/tavily.js +66 -0
  84. package/dist/providers/tavily.js.map +1 -0
  85. package/dist/providers/wiby.d.ts +2 -0
  86. package/dist/providers/wiby.js +46 -0
  87. package/dist/providers/wiby.js.map +1 -0
  88. package/dist/providers/wikidata.d.ts +2 -0
  89. package/dist/providers/wikidata.js +46 -0
  90. package/dist/providers/wikidata.js.map +1 -0
  91. package/dist/providers/wikipedia.d.ts +2 -0
  92. package/dist/providers/wikipedia.js +71 -0
  93. package/dist/providers/wikipedia.js.map +1 -0
  94. package/dist/providers/wiktionary.d.ts +2 -0
  95. package/dist/providers/wiktionary.js +48 -0
  96. package/dist/providers/wiktionary.js.map +1 -0
  97. package/dist/providers/yep.d.ts +2 -0
  98. package/dist/providers/yep.js +104 -0
  99. package/dist/providers/yep.js.map +1 -0
  100. package/dist/quota.d.ts +37 -0
  101. package/dist/quota.js +116 -0
  102. package/dist/quota.js.map +1 -0
  103. package/dist/rate-limiter.d.ts +7 -0
  104. package/dist/rate-limiter.js +37 -0
  105. package/dist/rate-limiter.js.map +1 -0
  106. package/dist/retry.d.ts +11 -0
  107. package/dist/retry.js +51 -0
  108. package/dist/retry.js.map +1 -0
  109. package/dist/types.d.ts +231 -0
  110. package/dist/types.js +2 -0
  111. package/dist/types.js.map +1 -0
  112. package/package.json +58 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jose A. Troche
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,529 @@
1
+ <!--
2
+ README DESIGN:
3
+
4
+ Product principle.
5
+ The README is the product — it answers three questions in under
6
+ 60 seconds: what this does, why you should care, how to use it.
7
+ Readme-driven development: changes that affect the README's claims
8
+ SHALL update the README before or alongside the code change. A README
9
+ that promises something the server does not deliver is a defect.
10
+ Every numeric claim (tool count, provider count, zero-config count)
11
+ reconciles against src/index.ts and config.json — the validator
12
+ enforces this (Surface reconciliation).
13
+
14
+ Voice: Professional, confident, benefit-first. Direct address ("you").
15
+ No first-person ("we", "I", "our"). Short declarative fragments in the
16
+ tagline. Every sentence survives a reader who knows nothing about
17
+ Infobroker.
18
+
19
+ Demo: Natural-language prompts in blockquotes ("Search for..."), never
20
+ full tool names (`infobroker_web_search`). Show the reader how to
21
+ express what they want — the AI maps intent to tools. Every demo
22
+ prompt SHALL be a valid natural-language command the reader could
23
+ actually run; broken prompts are a README defect.
24
+
25
+ Structure: Hero → North Star → Quick Start → MCP Server (§3 features) →
26
+ Skills → Providers → Configuration → How It Compares → Contribute →
27
+ License → Spec. No other ordering. Canonical h2 headings are enforced
28
+ by validate-readme.
29
+
30
+ Audience split: §2 is for developers who want to start the server.
31
+ §3 describes what users can do with it. The Skills section (§3.5)
32
+ describes the bundled client skills and instructions. §4-5 are
33
+ configuration. §6 is competitive context. §7-9 are
34
+ contributor/license/spec.
35
+
36
+ Skills section: Prose only — no tables, no blockquotes, no feature
37
+ bullet lists. Lists the six bundled skills and the workflow shapes the
38
+ orchestrator routes to, with analysis-loop as the escalation shape.
39
+ Cross-links to the skill references (pipeline-map.md, workflows.md).
40
+
41
+ No repetition. One story vector per section. Don't explain the same
42
+ concept in two places — the validator flags near-duplicate sentences.
43
+ No feature bullet lists in prose. No tables for feature descriptions.
44
+
45
+ Feature blurbs: Each h3 under §3 follows a four-beat cadence —
46
+ benefit hook, mechanics, competitive proof, closer. The
47
+ competitive-proof sentence contrasts Infobroker against the
48
+ current tool landscape without naming individual competitors; it
49
+ answers "why this beats what you're used to."
50
+
51
+ MCP server order: Features under §3 follow a research workflow —
52
+ Search → Extract → Verify → Write → Manage. New features
53
+ insert at the workflow point they serve; reorder the section
54
+ to restore the workflow after every addition or removal.
55
+
56
+ Comparison table: Three columns (Tool name | What you're used to |
57
+ How Infobroker differs). One row per competitor category, never
58
+ individual products. Prose paragraph below synthesizes the table;
59
+ it never repeats a row's content verbatim.
60
+
61
+ Hero: Exactly three elements — h1 heading, bold tagline, one prose
62
+ paragraph. No sub-headings, bullet lists, or preamble paragraphs.
63
+ The tagline uses short declarative fragments separated by periods
64
+ — never a sentence or question. Enforced maximum 200 words
65
+ (validate-readme). The tagline "One server. Every source. Research
66
+ that delivers." is the repeated refrain — it appears exactly twice,
67
+ in the Hero tagline and the comparison closing prose, and no more.
68
+ The hero paragraph closes with "Free first. Privacy always." —
69
+ distinct from the tagline. Updating any repeated line requires
70
+ updating its echo.
71
+
72
+ North Star: Single paragraph stating the Bothan Spynet metaphor and
73
+ intelligence-cycle framing. No sub-headings, lists, or blockquotes.
74
+ Maximum 100 words (enforced by validate-readme).
75
+
76
+ Tables. Exactly two tables: the Providers table (§4) and the
77
+ Comparison table (§6). No other tables. The Providers table lists
78
+ every configured provider (excluding the `native_fetch` fallback
79
+ renderer) — one row per provider, matching config.json.
80
+
81
+ Word budget. Hero ≤ 200 words. North Star ≤ 100 words. Each §3 feature
82
+ h3 ≤ 350 words. The validator's section-length check enforces these.
83
+
84
+ Non-goals. The README is not an API reference, a tool catalog, a spec
85
+ document, or a changelog. The complete tool inventory lives in the
86
+ feature taxonomy (§D of infobroker.md), which the README links to.
87
+ Tool names appear in prose only as shorthand in backticks where a
88
+ feature is introduced (e.g. `kb`), never as a bare list.
89
+
90
+ Validator. Rules marked "(validate-readme)" SHALL be checked by
91
+ scripts/validate-readme.ts. Other rules are enforced by author/AI
92
+ discipline. Adding an enforceable rule requires a corresponding
93
+ validator check. Tool and provider names are derived from
94
+ src/index.ts and config.json at validate time — never hardcoded.
95
+
96
+ Binary style checklist (applies to every AI edit of this file):
97
+ 1. Second person ("you"), never first-person.
98
+ 2. Tool names in backticks, shorthand form; full `infobroker_`
99
+ prefixes only in the design comment's "never do this" example.
100
+ 3. Blockquotes only in §3 feature subsections, 2-5 natural-language
101
+ prompts each, no tool names.
102
+ 4. Tagline refrain "One server. Every source. Research that
103
+ delivers." appears exactly twice (Hero + comparison closing).
104
+ 5. No bullet list of features in prose.
105
+ 6. Exactly two tables (Providers, Comparison), no others.
106
+ 7. Every numeric claim reconciles to src/index.ts + config.json.
107
+ 8. ATX headings only, no setext.
108
+ 9. One story vector per section, no near-duplicate sentences.
109
+ 10. "Last updated: YYYY-MM-DD." matches package.json version date.
110
+ -->
111
+
112
+ # Infobroker
113
+
114
+ **One server. Every source. Research that delivers.**
115
+
116
+ Infobroker is a multi-provider MCP server that unifies web search,
117
+ structured knowledge, academic, archive, and content-extraction APIs
118
+ behind a single tool surface. Fifteen zero-config providers ship in the
119
+ box — search the web, look up facts, fetch articles — with nothing to
120
+ configure. Five more providers unlock with API keys or self-hosting. A built-in corroboration engine cross-references independent
121
+ sources to separate established facts from contested claims. Bundled
122
+ client skills transform raw research into polished writing. Free first.
123
+ Privacy always.
124
+
125
+ ## North Star
126
+
127
+ Infobroker is the [Bothan Spynet](https://starwars.fandom.com/wiki/Bothan_Spynet/Legends) as a tool — a decentralized intelligence
128
+ network that queries independent sources and routes results through a
129
+ single, impartial interface. In intelligence-cycle terms, you supply the
130
+ direction and get the dissemination; the server handles the collection and
131
+ processing.
132
+
133
+ ## Quick Start
134
+
135
+ ```sh
136
+ cd Infobroker && npm install && npm run start
137
+ ```
138
+
139
+ Add this to your OpenCode config (`~/.config/opencode/opencode.json`):
140
+
141
+ ```json
142
+ {
143
+ "instructions": [
144
+ "<path-to-Infobroker>/instructions/search-preferences.md"
145
+ ],
146
+ "skills": {
147
+ "paths": [
148
+ "<path-to-Infobroker>/skills",
149
+ "<path-to-opencode-config>/skills"
150
+ ]
151
+ },
152
+ "mcp": {
153
+ "infobroker": {
154
+ "type": "local",
155
+ "command": ["node_modules/.bin/tsx", "src/index.ts"],
156
+ "cwd": "<path-to-Infobroker>",
157
+ "environment": {
158
+ "INFOBROKER_CONFIG": "<path-to-Infobroker>/config.json"
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ The `mcp` block starts the server; the `instructions` and `skills` blocks
166
+ are what activate the bundled client skills. Without them the skills ship
167
+ in the repository but stay inert.
168
+
169
+ Free providers work immediately. API-keyed providers — Brave, Exa,
170
+ Tavily, SearXNG — unlock higher throughput and specialized search:
171
+
172
+ ```bash
173
+ export INFOBROKER_BRAVE_API_KEY="your-key"
174
+ export INFOBROKER_EXA_API_KEY="your-key"
175
+ ```
176
+
177
+ Requirements: Node.js 20+.
178
+
179
+ ## MCP Server
180
+
181
+ Your research backend. Six tools, twenty providers, one
182
+ corroboration engine. The complete feature inventory is documented in the
183
+ [feature taxonomy](infobroker.md#d-appendix-feature-taxonomy) in the
184
+ spec.
185
+
186
+ ### Unified Search
187
+
188
+ > "Search for the location of the second Death Star."
189
+ > "Find scholarly papers on hyperspace travel theories."
190
+
191
+ `web_search` sends one query to every provider that can answer it. Search
192
+ across DuckDuckGo, Wikipedia, academic databases, news, code repositories —
193
+ or describe your task and the server picks the best source. Failed providers fall
194
+ back silently through a configurable chain so you get results, not
195
+ error messages. Other search tools lock you to one engine; Infobroker
196
+ routes every query to the right provider and keeps going when one
197
+ fails.
198
+
199
+ ### Content Extraction
200
+
201
+ > "Fetch the article on the Battle of Yavin and summarize it."
202
+ > "Get the text of that page about the Death Star plans."
203
+
204
+ `fetch_page` hands any URL to Jina Reader, which renders it as clean
205
+ Markdown optimized for LLM consumption. Falls back to native HTTP when
206
+ Jina is throttled. Wikipedia and Internet Archive have dedicated
207
+ renderers for source-specific extraction. Built-in web fetchers return
208
+ raw HTML; Infobroker gives you clean, readable content from any source —
209
+ ready for summarization or analysis.
210
+
211
+ ### Provider Intelligence
212
+
213
+ > "Which source should I use to research the Death Star's weakness?"
214
+ > "Show me all available sources and their quota status."
215
+
216
+ The server knows its own capabilities. `web_search` auto-selects the
217
+ best backend for your task, weighing capability, quota, and latency —
218
+ or routes by your intent when you ask for privacy, speed, or free-only
219
+ sources. `providers` surfaces every configured source and drills into a
220
+ single provider's uptime and error history. No other search MCP server
221
+ gives you operational visibility into every backend.
222
+
223
+ ### Multi-Source Verification
224
+
225
+ > "Verify whether the Empire really destroyed Alderaan."
226
+ > "Find the consensus on who fired first — Han or Greedo."
227
+
228
+ `corroborate` runs a multi-pass truth-finding loop: broad
229
+ search across active providers, claim extraction, cross-source
230
+ reconciliation, and targeted follow-up for gaps. Claims corroborated
231
+ across independent sources score high confidence, weighted by each
232
+ source's authority; every source is bound to the claim it supports.
233
+ Contradictions are surfaced with all perspectives. Gaps trigger refined
234
+ queries. You get a structured report — confirmed, contested, and
235
+ unverified findings — with source provenance, per-source claims, and
236
+ confidence scores. Every other search tool returns a list of links;
237
+ Infobroker finds the truth and tells you how sure it is.
238
+
239
+ ### Knowledge Base
240
+
241
+ > "Search what you already found about the Rebel Alliance fleet."
242
+ > "Ingest this article so it's cached for next time."
243
+
244
+ Every search, fetch, and corroboration run is cached in a local knowledge
245
+ base. `kb` checks the cache before hitting external providers — only
246
+ falling back to the network when the cached results aren't fresh enough
247
+ or relevant enough. Its actions ingest new text or a URL by hand, report
248
+ what's cached, and remove content. Content is age-scored, expired on a
249
+ freshness schedule, and deduplicated by source. Beyond the cache, `kb`
250
+ archives the reports you generate: ingest with `source_type: "report"`
251
+ (and default to the knowledge base) and revisit them with `kb` list and
252
+ `kb` get, or write them to a local directory instead. Other search MCP
253
+ servers re-fetch the same facts every session; Infobroker remembers and
254
+ reuses what it already found.
255
+
256
+ ### Research Pipeline
257
+
258
+ > "Research the construction of the Death Star, then draft a summary."
259
+ > "Fact-check these claims about Darth Vader's origin."
260
+
261
+ Infobroker doesn't stop at search results. Bundled client skills chain
262
+ its tools into writing pipelines, routing every request through a solved
263
+ workflow shape and the writing sub-skills until a finished document comes
264
+ out the other end. Everything lives in the repository — no external paths
265
+ or separate install. The full pipeline — the six skills, the workflow
266
+ shapes, and the escalation path — is detailed in the [Skills
267
+ section](#skills). Other search MCP servers produce search results;
268
+ Infobroker produces finished work.
269
+
270
+ ### Operational Visibility
271
+
272
+ > "Show server health."
273
+ > "Hot-reload my config without restarting."
274
+
275
+ Quota counters persist to disk and survive restarts. Rate limits are
276
+ enforced per-provider, not globally. Configuration is hot-reloadable
277
+ via `reload_config` — change providers, adjust chains, or tweak
278
+ thresholds without dropping connections. `web_search` doubles as
279
+ DuckDuckGo query autocomplete. `providers` reports the server's build
280
+ health and request stats. You always know what your search server is
281
+ doing and how much capacity remains.
282
+
283
+ ## Skills
284
+
285
+ The MCP server is one half of the product. The bundled skills are the
286
+ other. Six client skills ship in the repository — no external dependency,
287
+ no separate install — and they turn raw research into finished work.
288
+
289
+ The orchestrator skill (`infobroker`) opens with a classify gate that
290
+ maps your request to a workflow shape: research-and-write, fact-check,
291
+ deep-dive, competitive evaluation, literature review, monitoring,
292
+ red-team, vetting, or gated analysis. Each shape composes the same
293
+ primitives — recall from the knowledge base, search, extract, verify,
294
+ write, and cite — into its own sequence and ends with a grep-able
295
+ completion token so you can confirm the outcome. Four writing sub-skills
296
+ execute the writing phases: `summarization` condenses findings before
297
+ writing, `technical-writing` drafts reports and docs, `proofreading`
298
+ polishes language, and `translation` produces multilingual output.
299
+
300
+ Gated analysis is the escalation shape. When a question is high-stakes or
301
+ decision-driving, the classify gate routes to the `analysis-loop` skill —
302
+ a disciplined path with confidence-scored findings, source-reliability
303
+ grading, and structured analytic techniques — rather than the lighter
304
+ research-and-write route. It shares the same primitives and Infobroker
305
+ tools but runs its own gated workflow, so you get the rigor without
306
+ leaving the pipeline.
307
+
308
+ A single instruction file, `search-preferences.md`, routes your client
309
+ toward these tools: the knowledge base first, external providers only
310
+ when the cache falls short. Wire it and the skills directory into your
311
+ OpenCode config once — the Quick Start above shows the exact snippet —
312
+ and every research request follows the pipeline automatically.
313
+
314
+ Write your own skill into `skills/` to add a workflow shape of your own.
315
+ The pipeline diagram lives in `references/pipeline-map.md` and the
316
+ workflow-shape definitions in `references/workflows.md`. Other search MCP
317
+ servers return links; Infobroker ships the writers that turn them into
318
+ documented answers.
319
+
320
+ ## Providers
321
+
322
+ Twenty providers. Fifteen work with zero configuration.
323
+
324
+ | Provider | Tier | Type | Key Required |
325
+ |----------|------|------|-------------|
326
+ | DuckDuckGo | Built-in | Web search | No |
327
+ | Jina Reader | Free HTTP | Content extraction | No |
328
+ | Wikipedia | Free HTTP | Encyclopedia | No |
329
+ | Wiktionary | Free HTTP | Dictionary | No |
330
+ | Wikidata | Free HTTP | Structured facts | No |
331
+ | OpenStreetMap | Free HTTP | Geocoding | No |
332
+ | Internet Archive | Free HTTP | Historical | No |
333
+ | arXiv | Free HTTP | Academic | No |
334
+ | Semantic Scholar | Free HTTP | Academic | Optional |
335
+ | Stack Exchange | Free HTTP | Code Q&A | Optional |
336
+ | GitHub | Free HTTP | Code search | Optional |
337
+ | CORE | Free HTTP | Open access | Optional |
338
+ | Marginalia | Built-in | Small web | No |
339
+ | Mojeek | Built-in | Independent index | No |
340
+ | Wiby | Built-in | Small web | No |
341
+ | Brave Search | Keyed HTTP | Web, News | Yes |
342
+ | Exa | Keyed HTTP | Semantic | Yes |
343
+ | Tavily | Keyed HTTP | Synthesis | Yes |
344
+ | Yep | Keyed HTTP | Web, Semantic | Yes |
345
+ | SearXNG | Self-hosted | Full privacy | Yes (self) |
346
+
347
+ Built-in and free-HTTP providers are active out of the box. Keyed
348
+ providers enable with an API key. Self-hosted providers point at a server
349
+ you run yourself:
350
+
351
+ ```bash
352
+ export INFOBROKER_BRAVE_API_KEY="BSA-..."
353
+ export INFOBROKER_SEARXNG_URL="http://localhost:8080"
354
+ ```
355
+
356
+ Then set `"enabled": true` in `config.json` for the provider.
357
+
358
+ SearXNG is the only shipped self-hosted provider, and it is optional
359
+ through and through. Nothing in the server requires it, and nothing is
360
+ bundled or installed on its behalf — SearXNG runs as a container you
361
+ operate, and Infobroker queries its JSON endpoint like any other backend.
362
+ Leave it disabled (the default) and you lose nothing: the
363
+ privacy-critical chain still serves via DuckDuckGo and Mojeek. Enable it
364
+ only when you want full query privacy, in which case only your own
365
+ SearXNG instance sees your queries.
366
+
367
+ ## Configuration
368
+
369
+ | Variable | Purpose |
370
+ |----------|---------|
371
+ | `INFOBROKER_CONFIG` | Path to config.json (default: `./config.json`) |
372
+ | `INFOBROKER_CONFIG_LOCAL` | Optional path to a user config layer (default: `config.local.json`) |
373
+ | `INFOBROKER_<NAME>_API_KEY` | API key for keyed providers |
374
+ | `INFOBROKER_<NAME>_URL` | URL for self-hosted providers |
375
+
376
+ `config.json` ships with the repository and holds the defaults: which
377
+ providers are enabled, their priority in fallback chains, rate limits,
378
+ corroboration parameters, and the task-to-provider dispatch table.
379
+ Hot-reloadable via `reload_config` — edit the file, call the tool, and
380
+ changes take effect without a restart.
381
+
382
+ Your own overrides live in a separate user layer — `config.local.json`
383
+ in the project directory (or a path you set via `INFOBROKER_CONFIG_LOCAL`).
384
+ This file is git-ignored, so pulling updates from the repository never
385
+ overwrites your settings. Values in the user layer take precedence over
386
+ the shipped defaults; anything left out falls back to `config.json`.
387
+
388
+ The knowledge base ships empty. By default it writes to a user-scoped
389
+ path (`~/.local/share/infobroker/knowledge-base`) outside the repository,
390
+ so the content you research and cache stays on your machine and is never
391
+ committed. Each deployed instance accumulates its own store.
392
+
393
+ ### Knowledge base encryption
394
+
395
+ Research reports and cached pages can be sensitive, and the knowledge
396
+ base stores them in a single file in your home directory. Enable optional
397
+ at-rest encryption by adding a `kb.encryption` block and supplying a key:
398
+
399
+ ```json
400
+ {
401
+ "kb": {
402
+ "encryption": { "enabled": true, "key_file": "~/.config/infobroker/kb.key" }
403
+ }
404
+ }
405
+ ```
406
+
407
+ The key file (plain, 0600) is the most reliable source across MCP clients
408
+ and operating systems; `INFOBROKER_KB_KEY` (a 32-byte key) or
409
+ `INFOBROKER_KB_PASSPHRASE` (a passphrase) also work. Generate a key with
410
+ `openssl rand -base64 32`. Encryption protects the store and disk-saved
411
+ reports from anyone who obtains the files without the key — device theft,
412
+ backup or cloud-sync leaks, other local accounts. It does not protect
413
+ against a malicious MCP client on the same machine, or malware, which
414
+ full-disk encryption covers.
415
+
416
+ Two rules keep this safe. First, encryption is your opt-in: if the key is
417
+ missing or wrong, the knowledge base locks and reports an error rather
418
+ than touching your data — so back up the key (a forgotten key or
419
+ passphrase means the store is unrecoverable by design). Second, the server
420
+ never writes a partial file: every save is atomic, and an unrecognized or
421
+ newer store format is never overwritten.
422
+
423
+ The `kb` tool's `encryption` action is the day-to-day surface for this
424
+ journey, and it never echoes secret material — `generate_key` and `backup`
425
+ return file paths, and `rekey` reads a key file rather than a raw key.
426
+
427
+ **Enable** by generating a key, backing it up, adding the `kb.encryption`
428
+ block, and reloading; the store is encrypted in place immediately.
429
+ **Disable** by removing the block and reloading; the store is decrypted to
430
+ plaintext immediately (keep the key available during the transition so the
431
+ server can read the store to decrypt it). **Recover** a locked store with
432
+ `status` to see the state, `verify` to confirm a candidate key before
433
+ committing it, `backup` to restore a copy of your key file, and `rekey` to
434
+ move to a new key without losing content. After re-keying, point
435
+ `kb.encryption.key_file` at the new key, reload, then run `verify` again to
436
+ confirm the new key opens the store.
437
+
438
+ ```
439
+ infobroker_kb action=encryption operation=generate_key key_file=~/.config/infobroker/kb.key
440
+ infobroker_kb action=encryption operation=backup key_file=~/.backup/kb.key.bak
441
+ infobroker_reload_config
442
+ ```
443
+
444
+ Add the `kb.encryption` block to `config.local.json` before reloading to
445
+ enable, or remove it before reloading to disable. When the store is locked,
446
+ `status`, `verify`, and `rekey` remain reachable so you can recover without
447
+ first unlocking.
448
+
449
+ ### Bring your own endpoint
450
+
451
+ Any HTTP search endpoint can become an Infobroker provider without
452
+ touching the source tree. Declare it in `config.local.json` as a
453
+ `generic_http` provider, then reference it from a dispatch chain:
454
+
455
+ ```json
456
+ {
457
+ "providers": {
458
+ "my_search": {
459
+ "tier": "generic_http",
460
+ "capabilities": ["web_search"],
461
+ "enabled": true,
462
+ "priority": 20,
463
+ "endpoint": "https://api.example.com/search",
464
+ "query_param": "q",
465
+ "results_path": "data.items",
466
+ "field_map": { "title": "name", "url": "link", "snippet": "summary" }
467
+ }
468
+ },
469
+ "dispatch": { "general_web": ["my_search", "duckduckgo"] }
470
+ }
471
+ ```
472
+
473
+ The server GETs `endpoint?query_param=<query>`, walks `results_path`
474
+ (dot-separated into the response JSON), and maps each result to the
475
+ common shape using `field_map`. Add the slug to your `config.local.json`
476
+ override and call `reload_config` to use it immediately.
477
+
478
+ ## How It Compares
479
+
480
+ | Tool name | What you're used to | How Infobroker differs |
481
+ |-----------|--------------------|-----------------------|
482
+ | Built-in `websearch` / `webfetch` | One search engine, one fetch mode, no configuration, no visibility into what backend is used | Fifteen zero-config providers with a unified tool surface. Choose the right source for each task. Fall back automatically on failure. See every provider's status and quota. |
483
+ | Raw API calls | Manual HTTP requests, per-provider auth, per-provider response parsing, no fallback, no quota tracking | One interface for every provider. API keys configured once. Results normalized to a common shape. Rate limits and quota tracked automatically. |
484
+ | Dedicated search APIs | Pay-per-query, vendor lock-in, opaque routing | Free-first design. DuckDuckGo, Wikipedia, and thirteen other providers work with zero configuration. Upgrade paths for Brave, Exa, and Tavily. Self-hosted SearXNG for full privacy. |
485
+ | Other search MCP servers | Single-provider focus, no fallback, no corroboration, no writing pipeline | Multi-provider with automatic fallback. Corroboration engine cross-references independent sources. Bundled writing skills transform research into finished documents. |
486
+ | AI with built-in search | The model picks the search engine, serves stale cache, no reproducibility | You control the provider chain. Queries are reproducible. Fallback behavior is visible. The corroboration engine verifies facts across independent sources. |
487
+
488
+ Every other search MCP server asks you to pick a provider and trust it.
489
+ Infobroker gives you a fleet — and picks the right one for each task.
490
+ When a provider fails, the next one takes over without you noticing.
491
+ When a claim matters, the corroboration engine finds agreement,
492
+ contradiction, and gaps. The bundled skills close the loop from raw
493
+ research to finished writing. One server. Every source. Research that
494
+ delivers.
495
+
496
+ Last updated: 2026-08-23.
497
+
498
+ ## Contribute
499
+
500
+ - **Node.js 20+.** `node --version`. Get it at
501
+ [nodejs.org](https://nodejs.org).
502
+ - `npm install && npm run typecheck`
503
+ - Bundle your own skill in `skills/` to extend the research pipeline.
504
+ - Validate README structure: `npm run validate-readme`
505
+ - **Versioning:** CalVer (`YYYY.MM.DD`). `npm run version-bump` stamps
506
+ today's date into all version references. Pre-commit hooks verify
507
+ consistency. `npm run push` checks, tags, and pushes.
508
+ - MCP protocol: [modelcontextprotocol.io](https://modelcontextprotocol.io)
509
+ - Providers: [DuckDuckGo](https://duckduckgo.com) ·
510
+ [Jina Reader](https://jina.ai/reader) ·
511
+ [Wikipedia API](https://en.wikipedia.org/w/api.php)
512
+
513
+ Canonical origin: [git.gay/flukeatzerocool/Infobroker](https://git.gay/flukeatzerocool/Infobroker). This GitHub repository is a read-only mirror.
514
+
515
+ ## License
516
+
517
+ MIT. Free to use, modify, and redistribute. The bundled client skills and
518
+ instruction files ship under the same license, so the full research pipeline
519
+ — server, skills, and documentation — is freely reusable in commercial and
520
+ open-source work alike. Third-party providers remain subject to their own
521
+ terms and API keys.
522
+
523
+ ## Spec
524
+
525
+ The server is built from a single source specification, `infobroker.md`
526
+ (v2026.08.23), which defines every requirement and the gates that verify it.
527
+ Each requirement traces to an implementation file, and `npm run check`
528
+ reconciles the code, the spec, and this README so what is documented is what
529
+ the server actually delivers.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import("../dist/index.js").catch((e) => {
3
+ console.error("infobroker failed to start:", e);
4
+ process.exit(1);
5
+ });
@@ -0,0 +1,14 @@
1
+ export declare const SUPPORTED_OPTIONS: Record<string, {
2
+ time_range?: boolean;
3
+ page?: boolean;
4
+ safe_search?: boolean;
5
+ region?: boolean;
6
+ }>;
7
+ export declare function ignoredParams(provider: string, opts: {
8
+ safe_search?: string;
9
+ time_range?: string;
10
+ page?: number;
11
+ content_type?: string;
12
+ region?: string;
13
+ }): string[];
14
+ export declare function selectChain(chain: string[], priority: string | undefined, getLatency: (slug: string) => number): string[];
package/dist/chain.js ADDED
@@ -0,0 +1,63 @@
1
+ // @implements REQ-020c REQ-020d
2
+ import { getDispatchChain, getConfig } from "./config.js";
3
+ // Which of the filter-style options a provider actually honors. Used by
4
+ // REQ-020d to report parameters silently dropped by the serving provider.
5
+ // `max_results` is deliberately absent: it is enforced server-side.
6
+ // `content_type` is a server-side post-filter (never honored by a provider).
7
+ export const SUPPORTED_OPTIONS = {
8
+ duckduckgo: { time_range: true, safe_search: true, region: true },
9
+ brave: { time_range: true, region: true },
10
+ };
11
+ export function ignoredParams(provider, opts) {
12
+ const supported = SUPPORTED_OPTIONS[provider] ?? {};
13
+ const ignored = [];
14
+ if (opts.time_range && !supported.time_range)
15
+ ignored.push("time_range");
16
+ if (opts.page !== undefined && opts.page > 1 && !supported.page)
17
+ ignored.push("page");
18
+ if (opts.safe_search === "off" && !supported.safe_search)
19
+ ignored.push("safe_search");
20
+ if (opts.region && !supported.region)
21
+ ignored.push("region");
22
+ if (opts.content_type && opts.content_type !== "all")
23
+ ignored.push("content_type");
24
+ return ignored;
25
+ }
26
+ export function selectChain(chain, priority, getLatency) {
27
+ const config = getConfig();
28
+ let selected = chain;
29
+ if (priority === "privacy") {
30
+ const privacyChain = getDispatchChain("privacy_critical");
31
+ if (privacyChain.length > 0) {
32
+ selected = privacyChain;
33
+ }
34
+ }
35
+ else if (priority === "free_only") {
36
+ const filtered = chain.filter((slug) => {
37
+ const p = config.providers[slug];
38
+ return p && p.tier !== "keyed_http" && p.tier !== "self_hosted_http";
39
+ });
40
+ if (filtered.length > 0) {
41
+ selected = filtered;
42
+ }
43
+ else {
44
+ selected = getDispatchChain("general_web").filter((slug) => {
45
+ const p = config.providers[slug];
46
+ return p && p.tier !== "keyed_http" && p.tier !== "self_hosted_http";
47
+ });
48
+ }
49
+ }
50
+ else if (priority === "speed") {
51
+ selected = [...chain].sort((a, b) => {
52
+ const la = getLatency(a);
53
+ const lb = getLatency(b);
54
+ if (la === 0)
55
+ return 1;
56
+ if (lb === 0)
57
+ return -1;
58
+ return la - lb;
59
+ });
60
+ }
61
+ return selected.slice(0, config.output.fallback_depth);
62
+ }
63
+ //# sourceMappingURL=chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain.js","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Config, ProviderConfig } from "./types.js";
2
+ export declare function getConfigPath(): string;
3
+ export declare function getUserConfigPath(): string | undefined;
4
+ export declare function loadConfig(): Config;
5
+ export declare function reloadConfig(): Config;
6
+ export declare function getConfig(): Config;
7
+ export declare function getEnvVar(providerSlug: string, suffix: "_API_KEY" | "_URL"): string | undefined;
8
+ export declare function getActiveProviders(): [string, ProviderConfig][];
9
+ export declare function getDispatchChain(taskType: string): string[];