Skip to main content
Glama
CaptainCurso

Music and Madness Sync Bridge

by CaptainCurso
README.md
# Music and Madness Sync Bridge

This project implements Stage 1 of your plan:

- FoundryVTT -> Notion Story Bible journal mirroring
- Manual-run sync commands
- MCP tool surface for Codex/LLM orchestration
- Full media copy to durable local storage
- Conflict tracking with manual resolution
- Non-destructive writes under `Music and Madness Story Bible` with a top-level `Journals` wiki tree

## Project layout

- `/Users/nicholasmcdowell/Documents/Codex Projects/Music and Madness/music-madness-sync-bridge/src`
  - Bridge service, CLI, MCP server, Foundry/Notion adapters.
- `/Users/nicholasmcdowell/Documents/Codex Projects/Music and Madness/music-madness-sync-bridge/foundry-module/music-madness-bridge`
  - Foundry module scaffold exposing authenticated journal read events.
- `/Users/nicholasmcdowell/Documents/Codex Projects/Music and Madness/music-madness-sync-bridge/data`
  - Runtime state (`state/bridge.sqlite`), media copies (`media/`), logs (`logs/audit.log`).

## Definitions

- **MCP**: Model Context Protocol. Lets Codex call bridge tools like `sync.foundry_to_notion.apply`.
- **Mirror block**: Generated section under `## Foundry Mirror` in a Notion page.
- **Journals wiki tree**: A top-level Story Bible child page named `Journals`, with sub-pages matching Foundry folders.
- **Conflict**: Source and target changed since last sync, requiring manual decision.

## Setup

1. Copy env template.

```bash
cd '/Users/nicholasmcdowell/Documents/Codex Projects/Music and Madness/music-madness-sync-bridge'
cp .env.example .env
```

2. Fill `.env` values:
- Foundry URL/tokens
- Notion key
- Story Bible root page ID (`NOTION_STORY_BIBLE_PAGE_ID`)

3. Typecheck.

```bash
npm run typecheck
```

## Operator commands

Preview changes (no writes):

```bash
npm run sync:preview
```

`sync:preview` auto-starts the local proxy when `FOUNDRY_BASE_URL` is local (`127.0.0.1` / `localhost` / `::1`) and `FOUNDRY_AUTO_PROXY=1`.
Preview/write targets are wiki pages under `Journals`, not Notion databases.

Apply mirror updates:

```bash
npm run sync:apply
```

`sync:apply` has the same auto-proxy behavior and stops the proxy afterward only if this command started it.
When creating a page, it mirrors Foundry folder structure under the `Journals` wiki tree.
Media download/linking is enabled by default; disable with `--no-include-media`.

List conflicts:

```bash
npm run sync:conflicts
```

Resolve one conflict:

```bash
npm run sync:resolve -- --conflict-id <id> --resolution manual_merge --notes "Reviewed and merged"
```

Health check:

```bash
tsx src/cli.ts health
```

## MCP server

Run stdio MCP server:

```bash
npm run mcp:start
```

Exposed tools:

- `foundry.health_check`
- `foundry.list_journals`
- `foundry.get_journal`
- `foundry.export_journal_media`
- `sync.foundry_to_notion.preview`
- `sync.foundry_to_notion.apply`
- `sync.diff`
- `sync.conflicts.list`
- `sync.conflicts.resolve`

## Guardrails enforced

- Only writes to Notion pages in `NOTION_ALLOWED_DATABASE_IDS`.
- Existing prose preserved by appending/updating generated mirror block.
- Legacy `Music and Madness` section is untouched by default because database scope is explicit.

## Known limitations in this initial implementation

- Foundry HTTP routes are expected at `/health`, `/journals`, `/journals/:id`, etc.
- The Foundry module included here is an authenticated socket/event scaffold. If you need direct HTTP routes from Foundry itself, run a small local proxy (or use an API module) that maps those routes to module events.
- Media is copied to local durable files. To render media directly inside cloud Notion reliably, set `MEDIA_PUBLIC_BASE_URL` to a reachable static host.

## Local test bridge API (optional)

If your Foundry module routes are not live yet, run the mock API that implements:

- `GET /health`
- `GET /journals`
- `GET /journals/:id`
- `GET /journals/:id/media`
- `GET /assets/:assetId`
- `GET /changes?since=`

Command:

```bash
npm run foundry:mock-api
```

Fixture data file:

- `/Users/nicholasmcdowell/Documents/Codex Projects/Music and Madness/music-madness-sync-bridge/fixtures/foundry-fixture.json`

## Real Foundry proxy (Socket.IO -> HTTP routes)

Use this when your Foundry world and `music-madness-bridge` module are running.

### Why this exists

Your sync client expects HTTP routes (`/journals`, `/health`, etc.).
The Foundry module communicates via socket events in-world.
This proxy translates HTTP calls to socket event calls.

### Start order

1. Start Foundry and open the target world.
2. Ensure module `music-madness-bridge` is enabled.
3. In Foundry world settings, set `Bridge Token` to the same value as `.env` `FOUNDRY_BRIDGE_TOKEN`.
4. Populate in `.env`:
   - `FOUNDRY_SITE_URL`
   - `FOUNDRY_WORLD`
   - `FOUNDRY_SESSION_COOKIE`
   - `FOUNDRY_BRIDGE_TOKEN`
5. Start proxy:

```bash
npm run foundry:proxy
```

6. In another terminal, run bridge operations:

```bash
npm run sync:preview
npm run sync:apply
```

### Session cookie note

`FOUNDRY_SESSION_COOKIE` is the value of the `session` cookie from a browser already logged into your Foundry world.
Treat it like a secret token.

### Current limitation

`GET /assets/:assetId` returns `501` in socket-proxy mode unless your module adds binary asset streaming.
Media still syncs from journal-linked `sourceUrl` values where accessible.

## Remote Foundry host (LAN)

If Foundry runs on another machine on your network, set:

```bash
FOUNDRY_SITE_URL=http://192.168.1.105:30000/
```

Keep the sync client pointed at your **local proxy**:

```bash
FOUNDRY_BASE_URL=http://127.0.0.1:8788
```

So the flow is:

- this laptop sync client -> local proxy (`127.0.0.1:8788`)
- local proxy -> remote Foundry (`192.168.1.105:30000`)

You still need:
- `FOUNDRY_WORLD` for that remote Foundry world
- `FOUNDRY_SESSION_COOKIE` from a browser logged into that remote Foundry
- matching `FOUNDRY_BRIDGE_TOKEN` in Foundry module world settings

## Remote diagnostic command

Run this before sync to verify remote Foundry + proxy health.
This command does **not** auto-start proxy; start it explicitly first with `npm run foundry:proxy`.

```bash
npm run health:remote
```

What it checks:
- `FOUNDRY_SITE_URL` HTTP reachability
- local proxy `/health` response and socket connection state
- local proxy `/journals` route response

Exit code is non-zero if any check fails.

Verbose diagnostics with remediation hints:

```bash
npm run health:remote -- --verbose
```

## Doctor command

Validate your `.env` before running proxy or sync:

```bash
npm run doctor
```

What it validates:
- required env variables are present
- URL formats (`FOUNDRY_BASE_URL`, `FOUNDRY_SITE_URL`)
- Notion DB scope consistency (`NOTION_DEFAULT_TARGET_DB_ID` is in `NOTION_ALLOWED_DATABASE_IDS`)
- proxy port sanity
- basic shape checks for world slug and session cookie

Exit code is non-zero when required checks fail.


## Remote module install (manifest URL)

For one-click Foundry install/update via URL, use the release workflow guide:

- `/Users/nicholasmcdowell/Documents/Codex Projects/Music and Madness/music-madness-sync-bridge/docs/foundry_remote_install.md`

Quick command (example):

```bash
MODULE_VERSION=0.1.0 MODULE_URL=https://github.com/<you>/<repo> MODULE_MANIFEST_URL=https://raw.githubusercontent.com/<you>/<repo>/main/foundry-module/music-madness-bridge/module.json MODULE_DOWNLOAD_URL=https://github.com/<you>/<repo>/releases/download/v0.1.0/music-madness-bridge-v0.1.0.zip node scripts/release/prepare-foundry-module.mjs
```