Skip to content
bart1208Public

About

MCP resource server over a personal knowledge vault: OAuth 2.1, RFC 9728, RFC 8707 audience-bound tokens, scopes derived from git-crypt classification

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

brain-mcp

An MCP server over a personal knowledge vault, built to answer one question properly: how does an AI agent prove who it is, and what is it allowed to read?

The vault is a git repository of Markdown pages. Some of them are ordinary. Some are encrypted on the remote with git-crypt because they are confidential. A few hold identity documents of real people. Until now all of them sat behind one shared bearer token on a private website — the same token for every reader, every device and every agent. That is OWASP NHI9 (identity reuse) and NHI10 (a human using a machine credential), and the repository's own notes had flagged it as accepted-for-now since August 2026.

This server replaces it. Every client is an OAuth 2.1 client with its own identity, every token is bound to this server as its audience, and every page is served or refused by scope.

What it serves

Two tools, both read-only:

Tool Input Output
search query, optional limit pages whose title or body match, with a snippet
read_page path one page with its content

Both filter by the caller's scopes. A page the caller may not see is indistinguishable from a page that does not exist; the server logs the real reason (path, class, missing scope, subject), the client does not learn it.

Classes and scopes

Confidentiality is derived from the vault's own .gitattributes, not configured here. Every path git-crypt encrypts is confidential; two rules refine it.

Class Rule Scope that unlocks it
ordinary not encrypted wiki:read
confidential git-crypt pattern matches wiki:confidential
people git-crypt and under wiki/people/ wiki:people
secret *.private.md (credential values, by vault convention) none — never served

Scopes are additive and none implies another. An agent that needs ordinary and confidential pages asks for both; access to wiki/people/ is a separate decision every time. The scope model and the classifier are in internal/authz/scope.go and internal/vault/classify.go, with tests against the real vault's rules.

Protocol

The server side of the MCP authorization specification, which is OAuth 2.1 with a fixed set of extensions:

  • RFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource/mcp, naming the authorization server and the scopes above.
  • 401 + WWW-Authenticate pointing at that document, so a client with no token, or the wrong one, discovers where to get a right one.
  • RFC 8414 authorization-server metadata, discovered from the issuer.
  • PKCE (S256), mandatory, public clients only.
  • RFC 8707 resource parameter on the authorization and token requests, so the access token's aud is this server's canonical URI. A token minted for any other resource is rejected. This is what makes token passthrough impossible by construction rather than by policy.
  • RFC 9068 JWT access tokens: typ=at+jwt, iss, aud, exp, scope, sub, verified against the issuer's JWKS on every request.

The HTTP middleware and the metadata handler come from the official Go SDK (auth and oauthex packages). The token verifier, the scope model, the classifier and the tools are this repository's.

Status — 2026-09-07, scaffold

Written on day one so that the tests describe the target before the code exists. What is real and what is not:

Piece State
Classifier from .gitattributes, four classes done, tested against the real vault rules
Scope model, Permit, secrets never served done, tested
Vault store: search, read, traversal and editor-cache guards done, tested
MCP tools, scope-filtered done, exercised only through the store tests
RFC 9728 metadata endpoint, 401 challenge done, verified with curl against the real vault
Token verifier (authz.NewVerifier) stub: rejects everything. Eight skipped tests in internal/authz/jwt_test.go say what it must do
Dev authorization server (cmd/devauth) stub: the package comment is the endpoint list
Confused-deputy test with an upstream API not started; a read-only vault has no upstream. Planned as a third tool that calls a separate service with the server's own credential, never the inbound token
Deployment on fenrir behind nginx, systemd unit in deploy/ unit written, not installed

The verifier and the dev AS are scheduled for 22–23 September 2026. Until then the server runs, publishes its metadata, and refuses every token — which is the correct behaviour for a resource server whose trust anchor is not configured yet.

Running

go build ./cmd/brain-mcp
BRAIN_MCP_VAULT=/path/to/vault \
BRAIN_MCP_LISTEN=127.0.0.1:8090 \
BRAIN_MCP_RESOURCE=https://mcp.example.org/mcp \
BRAIN_MCP_ISSUER=https://auth.example.org \
BRAIN_MCP_JWKS_URL=https://auth.example.org/jwks \
./brain-mcp
go test ./...        # skipped tests are the unbuilt verifier; see above

The vault path is runtime configuration. This repository contains no vault content and never will.

Decisions worth defending

  • Deny reads as not-found. Returning 403 for wiki/people/x.md confirms that x exists. The cost is a less helpful error for a misconfigured client; the operator log carries the reason instead.
  • No scope implies another. wiki:confidential does not include wiki:read. Verbose for clients, but every grant is explicit and readable in the token.
  • Secrets have no scope. *.private.md cannot be requested. Not "requires admin"; not served, full stop.
  • Classification lives in the vault, not in this server. If the repo starts encrypting a new directory, the server refuses it on the next request with no deploy. The rule that decides what is sensitive should be in exactly one place.
  • The SDK's middleware, not a hand-rolled one. The interesting code is the verifier and the policy, and the WWW-Authenticate format is not where a reviewer's time should go.

About

MCP resource server over a personal knowledge vault: OAuth 2.1, RFC 9728, RFC 8707 audience-bound tokens, scopes derived from git-crypt classification

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages