Day 8

One connector for every tool server (and the bugs you only find by running it)

It started as "how do we ingest our wiki?" and ended as a generic, reusable connector. The unit tests passed against a fake server. Then I ran it against a real one, and reality had notes.

It started as a narrow question — "how do we open our team wiki in the RAG system?" — and ended as a generic, reusable connector. The platform can now ingest from any tool server that speaks the protocol, and that wiki is just one line of config.

From one source to any source

The honest first answer to the wiki question was unglamorous: export to PDF and use the local-files connector we already have. Zero code. Works today.

But the follow-up sharpened the ask — not just this one source. An internal tool. Make sure it's reusable, that we can take it and reuse it.

That's a different, better feature. Don't hand-write a wiki connector, then a Confluence one, then a Notion one. Write one connector that speaks the protocol, and let every source become a config entry.

Sources are config, not code. That's been the standing rule on this project, and this is the feature that finally makes it true.

Two layers, one reusability boundary

The design splits so the valuable part is portable:

The tool-calling side stays generic on purpose — the same call is the substrate for the live half of this work later, where the assistant queries a server mid-conversation. Ingest now, live later, one client.

The bugs you only find by running it

The unit tests passed against a fake server. Then I actually ran it, and reality had notes.

Bug 1 — Windows can't find the command

First real ingest died with a "cannot find the file specified" error. The culprit: on Windows the launcher is really a .cmd file, and the async subprocess API doesn't consult the path-extension list the way a shell does.

Fix: resolve the command explicitly before spawning. A genuine cross-platform bug that no amount of mocked tests would have surfaced.

Bug 2 — my test server had no resources

I'd planned to validate resources-mode against the official filesystem server. Ran it: tools-only. It serves files through tool calls, not through the resources primitive at all.

That's not a bug in my code. It's the whole reason the connector has two modes. Switched the demo to the reference server, which does expose resources, and it lit up — seven markdown documents ingested end-to-end, embedded, browsable, retrievable in chat.

A connector isn't done when its tests pass. It's done when you've watched real bytes move. Kailangan mo talagang patakbuhin (you really have to run it) — both findings came from the first five minutes of actually doing so.

"Let me see the tools we have"

Once it worked, the next ask was to see it. So there's a new tab that lists the configured servers and, on demand, spawns each one and shows the tools and resources it exposes. Inspecting the demo server reports 13 tools and 7 resources, live.

It doubles as a debugging tool — if a server is misconfigured, the tab tells you it's unreachable, instead of failing silently at ingest time three steps later.

What I learned

Generalizing early was the right call here, and the reusability constraint actually improved the design. Forcing the client to have no dependencies on the rest of the app left it clean enough to lift out entirely.

And the no-lock-in rule held up under pressure. A read-only, opt-in snapshot ingest is the same shape as the existing code-host connector — not the inbound-tracker dependency the rule was written to prevent. The rule survived a case it wasn't written for, which is how you know it's a good rule.

The two layers, and both bugs precisely

The endpoints, the code, and the specific tooling. Same password as the other Knowledge Platform posts — or request access below.

Build log entry from a daily journal. Written the evening of the work, lightly edited.