Use the Paragraph CLI and MCP server to manage posts, publications, subscribers, and coins on paragraph.com. Trigger when the user asks to publish, create, update, or manage newsletter content on Paragraph via CLI or MCP.
CLI for Paragraph — a web3 publishing and newsletter platform. Use it to manage posts, publications, subscribers, and coins.
For direct HTTP or SDK access without installing anything, see the paragraph-api skill instead.
If your client supports MCP, use the Paragraph MCP server instead of the CLI for a more integrated experience.
Use the hosted server at mcp.paragraph.com — no installation or API key management required. Authenticates via your Paragraph account in the browser.
claude mcp add paragraph --transport http https://mcp.paragraph.com/mcp
For other clients, add https://mcp.paragraph.com/mcp as a server URL in your MCP configuration.
If you prefer to run the server locally (requires Node.js 18+):
claude mcp add paragraph -- npx @paragraph-com/mcp
Local mode requires an API key via PARAGRAPH_API_KEY env var or paragraph login from the CLI.
The MCP server exposes 18 tools (posts, publications, subscribers, coins, search, feed, users) and shares authentication with the CLI. See full docs.
Install globally:
npm install -g @paragraph-com/cli
Authenticate — login persists the key to ~/.paragraph/config.json:
paragraph login --token <api-key>
echo "$PARAGRAPH_API_KEY" | paragraph login --with-token
Or skip login and pass the key per-command via env var:
PARAGRAPH_API_KEY=<api-key> paragraph post list --json
Verify: paragraph whoami --json
--json for parseable output. Data goes to stdout, status/errors to stderr.--yes on delete to skip confirmation prompts.--dry-run before delete, publish, and archive to preview what will happen.--id so you can chain commands.cat draft.md | paragraph post create --title "My Post"--limit and --cursor. The JSON response includes pagination.cursor and pagination.hasMore.--token or --with-token for non-interactive auth.paragraph whoami --json to verify credentials are valid.post create creates drafts. Only call post publish when the user asks.RATE_LIMITED, wait and retry. Avoid tight loops between paginated requests.# Create a draft
paragraph post create --title "My Post" --file ./post.md --tags "web3,defi" --json
paragraph post create --title "My Post" --text "# Hello World" --subtitle "A subtitle" --json
cat content.md | paragraph post create --title "My Post" --json
# List
paragraph post list --json
paragraph post list --status draft --limit 20 --json
paragraph post list --status scheduled --json
paragraph post list --publication <slug-or-id> --json
# Get (by ID, URL, or @pub/slug)
paragraph post get --id <post-id> --json
paragraph post get @my-blog/post-slug --json
# Extract a single field
paragraph post get --id <post-id> --field markdown > post.md
paragraph post get --id <post-id> --field title
# Update
paragraph post update --id <id-or-slug> --title "New Title" --json
paragraph post update --id <id-or-slug> --text "Updated content" --subtitle "New subtitle" --json
paragraph post update --id <id-or-slug> --file ./updated.md --tags "new,tags" --json
# Publish
paragraph post publish --id <id-or-slug> --json
paragraph post publish --id <id-or-slug> --newsletter --json
# Revert to draft
paragraph post draft --id <id-or-slug> --json
# Archive
paragraph post archive --id <id-or-slug> --json
# Schedule a post for future publication
paragraph post schedule --id <id-or-slug> --at "2026-05-01T09:00:00Z" --json
paragraph post schedule --id <id-or-slug> --at "2026-05-01T09:00:00Z" --newsletter --json
# Cancel a scheduled publication
paragraph post unschedule --id <id-or-slug> --json
# Create and schedule in one step
paragraph post create --title "Launch Day" --file ./post.md --schedule-at "2026-05-01T09:00:00Z" --newsletter --json
# Preview destructive actions
paragraph post delete --id <id-or-slug> --dry-run --json
paragraph post publish --id <id-or-slug> --dry-run --json
# Delete
paragraph post delete --id <id-or-slug> --yes --json
# Send test newsletter email
paragraph post test-email --id <post-id> --json
# Browse
paragraph post feed --limit 10 --json
paragraph post by-tag --tag web3 --limit 20 --json
paragraph publication get --id <slug-or-id-or-domain> --json
paragraph search post --query "ethereum" --json
paragraph search blog --query "web3" --json
paragraph subscriber list --limit 100 --json
paragraph subscriber count --publication <id> --json
paragraph subscriber add --email [email protected] --json
paragraph subscriber add --wallet 0x1234...abcd --json
paragraph subscriber import --csv ./subscribers.csv --json
paragraph coin get --id <id-or-address> --json
paragraph coin popular --limit 10 --json
paragraph coin search --query "ethereum" --json
paragraph coin holders --id <id-or-address> --limit 50 --json
paragraph coin quote --id <id-or-address> --amount <wei> --json
paragraph user get --id <user-id-or-wallet> --json
paragraph login --token <api-key>
echo "<api-key>" | paragraph login --with-token
paragraph whoami --json
paragraph logout
Paginated list (note: the CLI wraps items under data; the REST API and SDK use items instead):
{
"data": [{ "id": "...", "title": "..." }],
"pagination": { "cursor": "abc123", "hasMore": true }
}
Single item:
{ "id": "...", "title": "...", "markdown": "..." }
Mutation:
{ "id": "...", "status": "published" }
Error (on stderr):
{ "error": "Not found.", "code": "NOT_FOUND", "status": 404 }
Error codes: UNAUTHORIZED, FORBIDDEN, NOT_FOUND, RATE_LIMITED, SERVER_ERROR, REQUEST_FAILED, CLIENT_ERROR, UNKNOWN.
ID=$(paragraph post create --title "My Post" --file ./post.md --json | jq -r '.id')
paragraph post publish --id "$ID" --newsletter --json
paragraph post list --limit 100 --json | jq -r '.data[].id' | while read id; do
SLUG=$(paragraph post get --id "$id" --json | jq -r '.slug')
paragraph post get --id "$id" --field markdown > "${SLUG}.md"
done
CURSOR=""
while true; do
RESULT=$(paragraph subscriber list --limit 100 ${CURSOR:+--cursor "$CURSOR"} --json)
echo "$RESULT" | jq '.data[]'
CURSOR=$(echo "$RESULT" | jq -r '.pagination.cursor // empty')
HAS_MORE=$(echo "$RESULT" | jq '.pagination.hasMore')
[ "$HAS_MORE" = "true" ] || break
done
| Variable | Purpose |
|---|---|
| PARAGRAPH_API_KEY | API key (skip login) |
| PARAGRAPH_API_URL | Custom API base URL |
| PARAGRAPH_NON_INTERACTIVE | Set to 1 to force CLI mode |
| CI | Set to true to force CLI mode |
If commands fail with UNAUTHORIZED:
# Check if logged in
paragraph whoami --json
# Re-authenticate
paragraph login --token <api-key>
The CLI auto-clears stored credentials on 401. Re-login if credentials were revoked.
If you get RATE_LIMITED, wait and retry. The error includes a 429 status code. Avoid tight loops — add a delay between paginated requests.
If a command appears to hang, it may be waiting for stdin. Ensure you're passing content via --text, --file, or piping to stdin. The CLI times out after 30 seconds if stdin is piped but no data arrives.
npm install -g @paragraph-com/cli
# Verify
npx paragraph --version