Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Commonality is a system for decentralized crowdfunding of public goods: people c
- [High-level project status](./workflow/project-status.md) AKA what milestone are we currently heading for: never deployed to mainnet yet, just did first testnet deployment, see also [MVP](./specs/product/mvp.md)
- Product boundaries between the eight sites: [product UI domains](./specs/product/ui-domains.md).
- Technical domain composition and live route ownership: [technical UI domains](./specs/tech/ui-domains.md) and the actual domain manifests under [`ui/src/domains/`](./ui/src/domains/).
- **CauseStarter** (SPA in [`ui/src/causestarter/`](./ui/src/causestarter/), `VITE_DOMAIN=causestarter`; glue/backlog [`causestarter/`](./causestarter/), [`causestarter/TODO.md`](./causestarter/TODO.md)): founder-first core domain (eventual primary entry; Tally / LazyGiving / etc. de-emphasized as tools). Included in `./scripts/services.sh --start` and `./scripts/deploy-causestarter.sh`. Local start currently publishes **only** the CauseStarter IPFS bundle (`LOCAL_UI_DOMAINS`; restore all UIs with `LOCAL_UI_DOMAINS=all` — see [local development](./workflow/local-development.md)). LLM helpers: [`cause-assist/`](./cause-assist/).
- **CauseStarter** (SPA in [`ui/src/causestarter/`](./ui/src/causestarter/), `VITE_DOMAIN=causestarter`; glue/backlog [`causestarter/`](./causestarter/), [`causestarter/TODO.md`](./causestarter/TODO.md)): founder-first core domain (eventual primary entry; Tally / LazyGiving / etc. de-emphasized as tools). Included in `./scripts/services.sh --start` and `./scripts/deploy-causestarter.sh`. Local start currently publishes **only** the CauseStarter IPFS bundle (`LOCAL_UI_DOMAINS`; restore all UIs with `LOCAL_UI_DOMAINS=all` — see [local development](./workflow/local-development.md)). LLM helpers: [`cause-assist/`](./cause-assist/). Protocol MCP: [`mcp/`](./mcp/). Agent job map: [`docs/end-user/causestarter/for-llms.md`](./docs/end-user/causestarter/for-llms.md).
- [Glossary](./specs/glossary.md) — the project's ubiquitous language. Read before naming anything; it also lists the known terminology drift.
- [Architecture Decision Records](./specs/decisions/README.md) — immutable log of *why* consequential decisions were made (and what was rejected). Grep before reversing something that looks wrong.
- [Role-based guidance](./workflow/roles/README.md) on what docs to read:
Expand Down
6 changes: 5 additions & 1 deletion causestarter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ See [`cause-assist/README.md`](../cause-assist/README.md). Bridge-cluster wordin
catalog. Do not restore Start → Grow → Deliver or “build a Movement.”
- **In-app docs** (`/docs/*`) bundle `docs/end-user/causestarter/`, `shared/`,
and `commonality/` via `endUserDocsPlugin`. Keep markdown links relative so
they resolve in that viewer.
they resolve in that viewer. Agent/protocol index: `/docs/for-llms`. Generated
SDK/contract docs: `/api-docs/sdk/` and `/api-docs/contracts/` (Vite serves
them in dev; `npm run build:docs` regenerates). Stdio MCP: `mcp/`.
- **CauseStarter is a lens, not a directory** ([ADR 0008](../specs/decisions/0008-operated-surfaces-are-lenses.md)).
It authors no discovery: no search, browse, ranking, featuring, or leaderboards.
A cause is reached at `/cause/:causeId` through a link its organizer circulates.
Expand Down Expand Up @@ -292,6 +294,8 @@ startup_timeout_sec = 90
Register once with: `grok mcp add playwright -- npx -y @playwright/mcp@latest`
Then **restart Grok** so MCP tools load.

Protocol reads (SDK / IPFS / attesters), not the browser: [`mcp/README.md`](../mcp/README.md) (`npm run mcp`).

3. Chromium for Playwright tests (repo root):
`npx playwright install chromium`

Expand Down
74 changes: 74 additions & 0 deletions docs/end-user/causestarter/for-llms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# CauseStarter API map (for LLMs)

Task-oriented index for agents. Signatures live in generated TypeDoc; this page only maps **jobs → imports / HTTP**. Do not treat CauseStarter screens as an API — they will change.

Human briefing: [CauseStarter](./index.md). Concept orientation: [tldr-for-llms.md](../tldr-for-llms.md).

## Generated references (regenerate, don’t hand-write)

On a deployed site these are static files (not React routes):

- **SDK TypeDoc:** [/api-docs/sdk/](/api-docs/sdk/)
- **Solidity contract docs:** [/api-docs/contracts/index.md](/api-docs/contracts/index.md)

Regenerate with `npm run build:docs` at the repo root (`typedoc` on `sdk/`, solc docs on `hardhat/`). Local Vite (`causestarter:dev` on :5174) serves the same trees at those paths when the generated folders exist.

There is **no CauseStarter REST API**. The UI is a lens over the SDK plus a few helper HTTP services.

## What is already in the SDK (including IPFS)

Import subpaths of `@commonality/sdk` (no flat barrel). Construct machinery with `createSDKMachinery` from `@commonality/sdk/machinery`. Node env helpers: `@commonality/sdk/node` (`createIPFSConfigInNodeJSFromTheUsualEnvVars`).

| Job | Import | Functions |
| --- | --- | --- |
| Fetch / upload IPFS | `@commonality/sdk/utils` | `fetchFromIPFS`, `uploadToIPFS`, `uploadBlobToIPFS`. Upload needs `ipfsConfig.apiUrl` (Kubo `/api/v0/add`). Fetch needs `gatewayUrl`. |
| Publish statement bytes on-chain | `@commonality/sdk/published-data` | `publishData`, resolvers. Content-addressed documents also go through displayable-documents. |
| Create a signable statement and sign it | `@commonality/sdk/conceptspace` | `createAndSignStatement` (upload + PublishedData + `believeStatement`). Also `believeStatement` / `disbelieveStatement` / `clearOpinion`. |
| Read a statement | `@commonality/sdk/conceptspace` | `getStatement`, `getStatementWithContent`, `getUserBelief`, believer-set helpers. |
| Implication graph | `@commonality/sdk/conceptspace` | `getImplicationsFrom`, `getImplicationsTo`, `getImplication`. On-chain attest is `attestImplication` (usually an attester service, not a donor). |
| Cause board as a named ref | `@commonality/sdk/mutable-refs` | `getUserRef`, `getUserRefs`. Organizers publish a roster document and point a named ref at it. |
| Projects / pledges | `@commonality/sdk/lazy-giving`, `@commonality/sdk/fundingportals`, `@commonality/sdk/delegation` | `getProject`; alignment/success vouches; notes and standing pledges. |
| Indexer | `@commonality/sdk/utils` event-cache client; `@commonality/sdk/indexer-sync` | Raw events only. Fold in the SDK. `GET {EVENT_CACHE_URL}/status`. |

Local defaults after a stack start: `IPFS_API` → localhost:5001, `IPFS_GATEWAY` → localhost:8080/ipfs, `EVENT_CACHE_URL` → localhost:42069, plus the usual `*_CONTRACT_ADDRESS` env vars.

## HTTP services that are *not* the SDK

These are optional operators. Anyone can run their own. Users trust *identities* (attester / nudger addresses), not “the platform.”

| Service | Typical local URL | What an agent calls | Notes |
| --- | --- | --- | --- |
| **Cause assist** | localhost:3002 | `POST /atomize`, `/sharpen-plank`, `/draft-anchor`, `/check-implications`, `/safety-check`, `/check-coherence`, bridge-cluster draft verbs | Wording help only. **No chain writes.** See `cause-assist/README.md`. |
| **Implication attester** | localhost:3006/implication-attester (compose path) | `POST /evaluate-implication`, `POST /evaluate-implications-batch`, `GET /health`, `/quote`, `/attester-status` | x402 payment on evaluate. Positive evaluations publish on-chain. |
| **Content attester** | content-attester package | `POST /evaluate-content` (batch too) | Same attester-core HTTP shape; different prompt/domain. |
| **Finders** | implication-finder, content-finder | Usually **no public “please find” API** | Poll event cache, POST candidates to an attester. |
| **Nudgers** | implication-graph-nudger, explorer-curator, bridge-creator | `GET /.well-known/nudger.json`, `POST /suggest` (curator), `POST /propose-bridge` (bridge-creator) | Suggestions are signed publications; users opt in. |
| **Platform API** | localhost:3001 | onramp, sponsored gas, policy content | Ops for the operated UIs, not the protocol. |
| **Coherence badge worker** | worker package | operator-only chain write | CauseStarter site operator mints badges; cause-assist HTTP must not hold that key. |

Attester-core shared routes: `GET /health`, `GET /quote`, plus a per-service status path.

## MCP in this repo

Package `@commonality/mcp` (`mcp/`). Stdio server wrapping the SDK reads (and optional writes) plus the HTTP helpers above.

```bash
npm run mcp --workspace=@commonality/mcp
```

Grok:

```toml
[mcp_servers.commonality]
command = "npm"
args = ["run", "mcp", "--workspace=@commonality/mcp"]
```

Writes (`upload_ipfs`, `believe_statement`) stay off unless `COMMONALITY_MCP_WRITES=1` and a key is set. Implication evaluate may still return HTTP 402 (x402).

## Rules of thumb

- Prefer SDK queries over scraping CauseStarter.
- Prefer `getStatementWithContent` over raw gateway fetches when you want displayable JSON.
- Do not invent a cause directory; you need an owner address + slug/ref name, or a statement CID.
- Do not treat an attester HTTP 200 as “the protocol agrees.” It is one identity’s attestation, filtered by the viewer’s trust graph.
4 changes: 4 additions & 0 deletions docs/end-user/causestarter/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ Most products with that pitch ask you to join something, trust a black box, or b

You do not need the longer [vision and strategy](../commonality/vision-and-strategy/README.md) notes to use a cause board. They are there if you want the civilizational argument.

## For assistants and developers

The UI is a lens, not the protocol. **[CauseStarter API map (for LLMs)](./for-llms.md)** maps jobs (sign, publish, pledge, vouch, IPFS, attesters) onto SDK imports and helper HTTP services. Generated **[SDK API docs](/api-docs/sdk/)** and **[contract docs](/api-docs/contracts/index.md)** are the signature reference (`npm run build:docs`). A stdio MCP server lives in the `mcp/` package.

## See it as a story

- [A neighborhood throws a block party](../shared/use-case-walkthroughs/block-party.md)
Expand Down
5 changes: 3 additions & 2 deletions docs/end-user/shared/for-crypto-natives.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ The SDK also fetches IPFS content directly from a gateway, so statement content

The SDK and smart contract documentation is auto-generated:

- **SDK API docs** ([sdk/docs/api/](https://github.com/AdamSpitz/commonality/tree/master/sdk/docs/api)) — TypeScript SDK reference, auto-generated via typedoc from the SDK source.
- **Contract docs** ([hardhat/docs/](https://github.com/AdamSpitz/commonality/tree/master/hardhat/docs)) — Solidity contract reference, auto-generated via solc doc.
- **SDK API docs** ([sdk/docs/api/](https://github.com/AdamSpitz/commonality/tree/master/sdk/docs/api) or [/api-docs/sdk/](/api-docs/sdk/) on a deployed UI) — TypeScript SDK reference, auto-generated via typedoc from the SDK source. IPFS fetch/upload is in `@commonality/sdk/utils` (`fetchFromIPFS`, `uploadToIPFS`).
- **Contract docs** ([hardhat/docs/](https://github.com/AdamSpitz/commonality/tree/master/hardhat/docs) or [/api-docs/contracts/index.md](/api-docs/contracts/index.md)) — Solidity contract reference, auto-generated via solc doc.
- **CauseStarter job map for agents** ([causestarter/for-llms.md](../causestarter/for-llms.md)) — which SDK subpath or HTTP service (attesters, cause-assist, nudgers) to use; not a second TypeDoc.

Run `npm run build:docs` to regenerate these.

Expand Down
8 changes: 8 additions & 0 deletions docs/end-user/tldr-for-llms.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ Each entry follows the same shape:
- **When a user encounters it** — UI surfaces or moments in the user's flow where this concept becomes load-bearing.
- **What they might want help with** — typical assistant tasks around this concept.

## CauseStarter API map (jobs → SDK / HTTP)

[Full page](/docs/end-user/causestarter/for-llms.md)

- **What it is:** Task index for agents: which `@commonality/sdk` subpath (including IPFS upload/fetch) and which HTTP services (cause-assist, implication attester, nudgers) implement each job. Points at generated TypeDoc under `/api-docs/sdk/` rather than restating signatures. Documents the repo MCP (`mcp/`).
- **When a user encounters it:** `/docs/for-llms`, or when an assistant is about to call the protocol instead of driving the UI.
- **What they might want help with:** Uploading a statement to IPFS; signing; reading a cause ref; talking to an attester without treating it as platform truth.

## CauseStarter (you landed on a cause page)

[Full page](/docs/end-user/causestarter/index.md)
Expand Down
47 changes: 47 additions & 0 deletions mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Commonality MCP

Stdio [MCP](https://modelcontextprotocol.io) server for agents that should talk to the protocol instead of scraping CauseStarter.

Reads go through `@commonality/sdk` (event cache + IPFS + folds). Optional HTTP helpers reach **cause-assist** and the **implication attester**. Chain/IPFS writes stay off until `COMMONALITY_MCP_WRITES=1`.

Job map: [`docs/end-user/causestarter/for-llms.md`](../docs/end-user/causestarter/for-llms.md). Generated SDK docs: `sdk/docs/api/` (`npm run build:docs`).

## Run

From the repo root, with the same env as local stack (`.env` after `./scripts/setup-env.sh localhost`):

```bash
npm run mcp --workspace=@commonality/mcp
```

Grok (`~/.grok/config.toml` or `grok mcp add`):

```toml
[mcp_servers.commonality]
command = "npm"
args = ["run", "mcp", "--workspace=@commonality/mcp"]
```

Run the process with cwd = this repository so workspace resolution works.

## Tools

| Tool | Side effect |
| --- | --- |
| `get_statement`, `fetch_ipfs`, `get_implications_*`, `get_user_belief`, `get_user_ref`, `get_project`, `indexer_status` | Read |
| `cause_assist` | HTTP to cause-assist (wording only) |
| `implication_attester_status`, `evaluate_implication` | HTTP; evaluate may 402 (x402) and, if paid, the *attester* publishes on-chain |
| `upload_ipfs`, `believe_statement` | Writes; require `COMMONALITY_MCP_WRITES=1` |

## Env

Same contract/IPFS/indexer names as integration tests (`EVENT_CACHE_URL`, `IPFS_API`, `IPFS_GATEWAY`, `BELIEFS_CONTRACT_ADDRESS`, …). Extra:

| Var | Default |
| --- | --- |
| `CAUSE_ASSIST_URL` | `http://127.0.0.1:3002` |
| `IMPLICATION_ATTESTER_URL` | `http://localhost:3006/implication-attester` |
| `COMMONALITY_MCP_WRITES` | unset (reads only) |
| `MCP_PRIVATE_KEY` or `ETHEREUM_PRIVATE_KEY` | required for `believe_statement` |

Do not log secrets. Stdio is the MCP transport — keep `console.log` off `stdout`.
33 changes: 33 additions & 0 deletions mcp/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import js from '@eslint/js'
import codeMetrics from '../eslint.metrics.mjs'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
...codeMetrics,
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
],
languageOptions: {
ecmaVersion: 2020,
globals: {
node: true,
mocha: true,
},
parserOptions: {
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'@typescript-eslint/no-explicit-any': 'off',
},
},
])
35 changes: 35 additions & 0 deletions mcp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@commonality/mcp",
"version": "0.1.0",
"description": "Stdio MCP server for Commonality SDK reads, IPFS, and helper HTTP services",
"type": "module",
"bin": {
"commonality-mcp": "./dist/index.js"
},
"scripts": {
"prebuild": "npm run build --workspace=@commonality/sdk",
"build": "tsc",
"clean": "rm -rf dist",
"pretypecheck": "npm run build --workspace=@commonality/sdk",
"typecheck": "tsc --noEmit",
"mcp": "tsx src/index.ts",
"start": "node dist/index.js",
"test": "npm run build && mocha \"dist/**/*.test.js\"",
"lint": "eslint ."
},
"dependencies": {
"@commonality/sdk": "1.0.0",
"@modelcontextprotocol/sdk": "^1.25.1",
"viem": "2.54.3",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "^20.10.0",
"eslint": "^9.39.1",
"mocha": "^10.8.2",
"tsx": "^4.21.0",
"typescript": "^5.3.2",
"typescript-eslint": "^8.46.4"
}
}
65 changes: 65 additions & 0 deletions mcp/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { Address } from 'viem'
import type { ContractAddresses } from '@commonality/sdk/machinery'

function env(name: string, fallback?: string): string | undefined {
const value = process.env[name]
if (value && value.length > 0) return value
return fallback
}

function address(name: string): Address | undefined {
const value = env(name)
return value ? (value as Address) : undefined
}

export const CAUSE_ASSIST_PATHS = [
'/atomize',
'/sharpen-plank',
'/draft-anchor',
'/suggest-statements',
'/suggest-mediator-scaffold',
'/draft-modified-plank',
'/draft-stand-in-sliver',
'/draft-bridge-plank',
'/critique-triple',
'/check-implications',
'/safety-check',
'/check-coherence',
'/health',
] as const

export type CauseAssistPath = (typeof CAUSE_ASSIST_PATHS)[number]

export function isCauseAssistPath(path: string): path is CauseAssistPath {
return (CAUSE_ASSIST_PATHS as readonly string[]).includes(path)
}

const ZERO: Address = '0x0000000000000000000000000000000000000000'

function loadContractAddresses(): ContractAddresses {
return {
beliefs: address('BELIEFS_CONTRACT_ADDRESS') ?? ZERO,
implications: address('IMPLICATIONS_CONTRACT_ADDRESS') ?? ZERO,
assuranceContractFactory: address('ASSURANCE_CONTRACT_FACTORY_ADDRESS') ?? ZERO,
erc1155Factory: address('ERC1155_FACTORY_ADDRESS') ?? ZERO,
delegatableNotes: address('DELEGATABLE_NOTES_CONTRACT_ADDRESS') ?? address('DELEGATABLE_NOTES_ADDRESS') ?? ZERO,
noteIntent: address('NOTE_INTENT_ADDRESS') ?? ZERO,
alignmentAttestations: address('PROJECT_ALIGNMENT_CONTRACT_ADDRESS') ?? address('ALIGNMENT_ATTESTATIONS_CONTRACT_ADDRESS') ?? ZERO,
mutableRefUpdater: address('MUTABLE_REF_UPDATER_CONTRACT_ADDRESS') ?? address('MUTABLE_REF_UPDATER_ADDRESS') ?? ZERO,
trustRegistry: address('TRUST_REGISTRY_ADDRESS') ?? ZERO,
nudgePublications: address('NUDGE_PUBLICATIONS_CONTRACT_ADDRESS'),
publishedData: address('PUBLISHED_DATA_CONTRACT_ADDRESS'),
}
}

export function loadMcpConfig() {
return {
writesEnabled: process.env.COMMONALITY_MCP_WRITES === '1',
privateKey: (env('MCP_PRIVATE_KEY') ?? env('ETHEREUM_PRIVATE_KEY') ?? env('PRIVATE_KEY')) as `0x${string}` | undefined,
rpcUrl: env('ETH_RPC_URL') ?? env('RPC_URL') ?? 'http://127.0.0.1:8545',
eventCacheUrl: env('EVENT_CACHE_URL') ?? 'http://localhost:42069',
causeAssistUrl: (env('CAUSE_ASSIST_URL') ?? 'http://127.0.0.1:3002').replace(/\/$/, ''),
implicationAttesterUrl: (env('IMPLICATION_ATTESTER_URL') ?? 'http://localhost:3006/implication-attester').replace(/\/$/, ''),
contractAddresses: loadContractAddresses(),
}
}
13 changes: 13 additions & 0 deletions mcp/src/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export async function fetchJson(url: string, init?: RequestInit): Promise<{ status: number; body: unknown }> {
const response = await fetch(url, init)
const text = await response.text()
let body: unknown = text
if (text.length > 0) {
try {
body = JSON.parse(text)
} catch {
body = text
}
}
return { status: response.status, body }
}
14 changes: 14 additions & 0 deletions mcp/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { registerCommonalityTools } from './tools.js'

const server = new McpServer({
name: 'commonality',
version: '0.1.0',
})

registerCommonalityTools(server)

const transport = new StdioServerTransport()
await server.connect(transport)
Loading
Loading