Skip to main content
Glama
README.md
# GrokBot ↔ Obsidian Bridge

A small, fail-closed OAuth reverse proxy that lets Grok Bot and other
compatible MCP clients reach a self-hosted Obsidian vault. The public
process terminates owner approval. Authenticated Streamable HTTP is
forwarded only to a loopback Optimike Obsidian MCP backend.

![GrokBot to Obsidian bridge overview](assets/bridge-overview.png)

This repository defaults to the `standard` tool profile and
`headless-readonly` runtime. It contains no real vault, hostname,
credential, private path, or note.

## How it works

1. Clone this repository and run the guided installer.
2. The installer pins and builds public Optimike Obsidian MCP `v3.0.0`,
   installs the bridge in a local venv, and writes a mode-600 environment
   file. It does not start the proxy, start Optimike, or touch a vault.
3. You start Optimike yourself on loopback, then start this proxy on
   loopback, then put HTTPS in front of the proxy.
4. Grok Bot discovers OAuth, registers a client, and waits for owner
   approval. After PKCE exchange it calls `/mcp`. The proxy strips
   client secrets and forwards the session to Optimike.

Optimike source is not vendored here. See `docs/UPSTREAM.md`.

## Requirements

- Python 3.11+
- Node.js 22.7.5+ and npm
- git
- A public HTTPS hostname that forwards to `127.0.0.1:8099`
- An Optimike checkout pinned by the installer to tag `v3.0.0`
  (`0a40387206a19deb6430f9b61b81fc7a07704717`)
- A vault copy you already manage. This installer never creates, opens,
  or mutates one.

## One command after clone

```bash
./scripts/install.sh --non-interactive
```

The script refuses root and sudo, never pipes a download into a shell,
and never prints the generated owner code. Then inspect health:

```bash
python3 scripts/doctor.py
```

Fill the remaining local vault placeholder in `.env.local`. Start Optimike
yourself with `headless-readonly`, `standard`, `readonly`, production JWT
authentication, and the generated internal secret:

```bash
set -a
. ./.env.local
set +a
cd .runtime/upstream
NODE_ENV=production MCP_TRANSPORT_TYPE=http MCP_HTTP_HOST=127.0.0.1 \
MCP_HTTP_PORT=3010 MCP_AUTH_MODE=jwt \
MCP_AUTH_SECRET_KEY="$OBSIDIAN_BRIDGE_UPSTREAM_JWT_SECRET" \
node dist/index.js
```

In a second terminal, start the public OAuth proxy:

```bash
.venv/bin/python -m obsidian_bridge serve
```

`GET /health` should return `{"status":"ok"}`. Do not expose the Python
port directly.

## Configure the plugin

```bash
python3 scripts/configure_plugin.py https://mcp.example.com/mcp
```

The generated `mcp.json` and `.mcp.json` contain only the public URL.
Do not add an `Authorization` header. Grok Bot, Codex, and Cursor use
OAuth discovery and PKCE.

## Add Composio beside this bridge

[Corey Ganim's Grok Bot tip](https://x.com/coreyganim/status/2092559429275447742)
uses [Composio Connect](https://docs.composio.dev/docs/composio-connect) to go
beyond a client's native connector catalog. Composio describes Connect as one
hosted MCP endpoint for 1000+ apps through 7 meta-tools. It is not literally
every MCP server, and it is not bundled with this repository.

Keep the two connections separate:

| Client | This repository | Optional Composio connection |
| --- | --- | --- |
| Grok Bot | Install the Grok plugin and connect `obsidian-bridge` | Add Composio Connect as a second MCP connector |
| Cursor | Install the Cursor plugin or add this MCP URL | Add Composio Connect as a second MCP server |
| Codex | Install the Codex plugin or add this MCP URL | Prefer Composio's native Codex plugin, or choose Connect MCP explicitly |

The `.grok-plugin`, `.cursor-plugin`, and `.codex-plugin` manifests here install
only the read-only Obsidian bridge. Composio traffic does not pass through the
vault and Composio does not replace this bridge. See the
[tutorial](docs/TUTORIAL.md#optional-add-composio-as-a-second-connector) for
the complete setup model and safety boundaries.

## Security model

- single-owner OAuth 2.1 with DCR, S256 PKCE, and rotating refresh tokens
- owner code is required on the approval page and is never a bearer token
- loopback bind; TLS stays outside this process
- the client bearer token is stripped and replaced with a short-lived,
  separately signed internal JWT before Optimike sees the request
- `/mcp/full` and non-readonly modes fail closed
- secrets stay in a mode-600 local file and out of git, argv, and stdout

Read `docs/SECURITY.md` and `docs/ARCHITECTURE.md` before exposing the
endpoint.

## Test and audit

```bash
python3 -m unittest discover -s tests -v
python3 src/privacy_scan.py --root .
python3 scripts/audit_git_history.py
```

## License

This bridge is MIT. Optimike Obsidian MCP remains a separate Apache-2.0
project and is not copied into this tree. See `docs/UPSTREAM.md`.