# MCP Server in Go: How Zekra Is Built on ToGO Plugins

> A case study of an open-source MCP memory server written in Go: which ToGO modules Zekra uses, real code, counts from the repo, and what was hard.

*2026-10-19T12:49:02Z*

# MCP Server in Go: How Zekra Is Built on ToGO Plugins

If you are looking for how to build an **MCP server in Go** that is more than a demo, this is a worked example. [Zekra](https://zekra.dev) is an open-source memory service that AI agents reach over MCP, and it is a [ToGO](https://github.com/togo-framework/togo) app. This case study shows which ToGO pieces it uses, with code and counts taken from the repository, plus the parts that were hard.

All numbers below were counted from `github.com/fadymondy/zekra` on 19 October 2026. I say how each was counted.

![Zekra case study cover](/uploads/sites/c797a67b-97b7-4ed5-8459-baafb6981307/media/zekra-togo-case-study-og.png)

## 1. The problem

I run agents in Claude Code, Codex and Cursor, and each new session started from zero. I wanted one memory service that any of them could call. It had to:

- speak MCP, so no agent needs custom glue;
- retain facts and update them instead of piling up duplicates;
- recall with a mix of vectors, BM25 and a knowledge graph;
- work in Arabic and English;
- be self-hostable.

That is a database-heavy backend with auth, a cache, background work, an HTTP API, a web console and an MCP surface. A lot of plumbing, none of it the interesting part.

## 2. Why ToGO

ToGO is a Go plus React framework where features come as plugins that register themselves with a kernel. For Zekra that meant:

- the database, cache, queue and storage drivers already existed as plugins;
- the memory logic could be its own plugin with its own module and tests;
- the harness (API server, migrations, console) stayed thin;
- one binary serves REST, MCP and the console.

The alternative was assembling chi, a migration tool, an auth library and a job runner by hand. ToGO already does that wiring.

## 3. Architecture

From `go.mod`, the ToGO modules Zekra requires directly are:

| Module | Role |
|---|---|
| `togo` v0.21.0 | the kernel |
| `db-postgres` v0.1.0 | Postgres driver plugin |
| `cache` v0.3.0, `cache-redis` | cache interface and the Redis L1 tier |
| `queue` v0.3.0 | background work |
| `storage` v0.2.0 | object storage (cold tier) |
| `brain` | the memory organ: schema, retain/recall, graph, connectors, chat, REST |
| `brain-tei` | embeddings and reranking through Text Embeddings Inference |
| `brain-cognee` | entity and graph extraction |
| `brain-ocr` | text from photos and documents |

The `auth` plugin is wired separately for the console login. The memory service is decoupled from it on purpose: the brain plugin depends on auth through a one-method interface, not an import.

The harness lives in `cmd/api`, `cmd/migrate`, `internal/` and `web/`. A separate `cmd/zekra-mcp` is a stdio MCP server that is a thin adapter over the REST API.

For MCP, the tool definitions and JSON-RPC dispatch live in one package, `plugins/brain/mcptools`. Its package comment says the surface is shared by the stdio binary and the remote streamable-HTTP endpoint (`/api/mcp`); each transport supplies a backend that performs the REST call. All scoping and validation stays server side in the REST handlers.

Storage is PostgreSQL with VectorChord, BM25 and pgvector. Access control, per the README, is one namespace per brain with per-agent grants and access tokens enforced server side.

## 4. Real code

**A plugin registers itself.** From `plugins/brain/plugin.go`:

```go
func init() {
	togo.RegisterProviderFunc(Name, togo.PriorityLate+10, func(k *togo.Kernel) error {
		svc := brain.New(k)
		secured := func(h http.HandlerFunc) http.HandlerFunc { return secure(k, svc, h) }
		svc.RegisterRoutes(k.Router, secured)
		k.Set(Name, svc)
		return nil
	})
}
```

The priority number matters. The code comment explains it: the brain routes must mount after the auth plugin has installed its chi middleware, because chi forbids `Use()` once any route exists.

**The app opts in with blank imports.** From `internal/plugins/local.go`:

```go
import (
	_ "github.com/togo-framework/brain"
	_ "github.com/togo-framework/brain-cognee"
	_ "github.com/togo-framework/brain-ocr"
	_ "github.com/togo-framework/brain-tei"
	_ "github.com/togo-framework/cache-redis"
)
```

**A plugin describes itself.** From `plugins/brain/togo.plugin.yaml`:

```yaml
name: brain
priority: 50
backend:
  package: github.com/togo-framework/brain
frontend:
  dir: web
```

**Product logic stays plain Go.** From `plugins/brain/internal/brain/writedecision.go`, the thresholds that decide whether a write is a duplicate or replaces an older memory:

```go
const (
	simDuplicate = 0.97
	simSupersede = 0.88
)
```

The function that uses them depends only on the nearest neighbour, so it is unit-testable without a database or an embedder.

## 5. Numbers, and how I counted

| What | Count | How |
|---|---|---|
| Commits on `main` | 350 | `git rev-list --count origin/main` |
| First commit | 16 July 2026 | `git log --reverse` |
| In-repo plugin modules | 5 (`brain`, `brain-tei`, `brain-cognee`, `brain-ocr`, `cache-redis`) | directories under `plugins/`, each with its own `go.mod` |
| MCP tools declared | 69 distinct names | `"name":` entries in the six tool-definition files of `plugins/brain/mcptools` |
| Tables in the brain schema | 34 | `CREATE TABLE` in `plugins/brain/internal/brain/schema.sql` |
| Indexes | 46 | `CREATE INDEX` in the same file |
| Go files in the memory service | 88 | `*.go` in `plugins/brain/internal/brain` |
| Test files in the repo | 51 | `*_test.go`, excluding a scratch worktree |
| Plugins imported by the showcase | 207 | import lines in `internal/plugins/showcase.go` |

Two caveats. The 69 is what the definition files declare; the published docs describe smaller sets per client binary (for example 31 for `zekra-mcp`), so treat 69 as the code count, not what every client sees. And the showcase file exists so Zekra exercises the wider ToGO ecosystem; it does not mean all 207 plugins are core to memory.

There are no latency or load numbers here because the repo has no benchmark I can cite.

## 6. What was hard, and where ToGO helped

**Helped:** database, cache, queue and storage came as plugins; the priority system gave a defined boot order; the plugin pattern let the memory organ live in its own module with its own tests; one binary serves everything.

**Hard, honestly:**

- **Boot order.** The chi `Use()` rule above cost real debugging time. The fix is a priority number plus a comment, which is fine, but you need to know it.
- **In-repo plugins.** The `brain` family is developed inside the Zekra repo, so `go.mod` pins them at `v0.0.0` with `replace` directives pointing at `./plugins/...`. The comment in `go.mod` says this goes away once the harness consumes the published plugins. Until then it is more manual than `togo install`.
- **Split wiring.** `plugins.gen.go` is owned by `togo install`, so local plugins and auth live in separate files (`local.go`, `auth.go`) to stop codegen overwriting them. `togo.yaml` itself lists `plugins: []`.
- **Generated code is not committed.** You must run `togo generate` (sqlc, gqlgen, atlas, OpenAPI) before building, as the self-hosting guide says.
- **Not ToGO's job:** retrieval quality, the Postgres extensions and the model servers. ToGO gave structure; it did not do the retrieval for me.

## FAQ

**Is Zekra a ToGO app?** Yes. `go.mod` requires `github.com/togo-framework/togo` v0.21.0, and the memory service is itself a ToGO plugin.

**Does it use the official MCP Go SDK?** The repository's own `mcptools` package defines the tools and the JSON-RPC dispatch, shared by the stdio binary and the HTTP endpoint.

**Can I self-host it?** Yes, under AGPL-3.0. It needs PostgreSQL with the VectorChord extensions and a TEI embedding server; see the self-hosting guide in the repo.

**Why plugins instead of one app?** Each concern (memory, embeddings, graph extraction, OCR, cache) has its own module, tests and failure surface, and can be swapped.

## Links

- Product article: [Self-hosted AI agent memory with Zekra](https://zekra.dev/en/blog/self-hosted-ai-agent-memory-server)
- Founder post: [Why I built Zekra](https://fadymondy.com/en/blog/why-i-built-zekra)
- Product site: [zekra.dev](https://zekra.dev)
- GitHub: [fadymondy/zekra](https://github.com/fadymondy/zekra) (AGPL-3.0)
- Product Hunt: [Zekra](https://www.producthunt.com/products/zekra)
- Discord: [join](https://discord.gg/99JBJd6FsR)
- ToGO: [github.com/togo-framework/togo](https://github.com/togo-framework/togo)
- More ToGO apps: [ToGO showcase](https://to-go.dev/en/showcase)

---

Source: https://to-go.dev/en/blog/zekra-togo-case-study
