Skip to main content
Glama
bewellit

bewell-catalyst-mcp

by bewellit

bewell-catalyst-mcp

An MCP (Model Context Protocol) server for managing an organization's activity library on the BeWell Catalyst platform. From any MCP-capable Claude session you can create, update, list, fetch and delete library activities of every type, including uploading their media (images, video, audio).

The server contains no business logic: every tool call is a thin RPC to a Catalyst cloud function, where authentication, authorization and domain rules live. The one thing it does locally is read media files from disk and upload them through a signed URL.

What publishing means: a published activity lands in the organization's library and appears immediately in the teachers' Manage Activities screen. Participants see it only once a teacher places it in a course. Publishing stocks the library; it does not push content to participants.

Install

Requires Node.js 20 or newer.

Add to your .mcp.json (Claude Code) or MCP client configuration:

{
  "mcpServers": {
    "bewell-catalyst": {
      "command": "npx",
      "args": ["-y", "bewell-catalyst-mcp"],
      "env": {
        "BEWELL_CATALYST_API_KEY": "bwc_...",
        "BEWELL_CATALYST_FUNCTIONS_BASE_URL": "https://europe-west1-bewellit-prod-eu.cloudfunctions.net"
      }
    }
  }
}

Related MCP server: rustici-mcp-server

Environment variables

Variable

Purpose

BEWELL_CATALYST_API_KEY

API key (bwc_...) minted by a platform admin. Sent in the x-bewell-catalyst-api-key header on every call. One key can manage one or more organizations.

BEWELL_CATALYST_FUNCTIONS_BASE_URL

Cloud Functions origin of the target environment (selects the region/project).

BEWELL_CATALYST_ORG_LOCK (optional)

Pin this workspace to a single organizationId. When set, every org-scoped tool refuses any other organizationId before making a network call. list_organizations stays available. See Working with multiple organizations.

Environment

Base URL

EU

https://europe-west1-bewellit-prod-eu.cloudfunctions.net

US

https://us-central1-bewellit-prod-us.cloudfunctions.net

API keys and storage are per-environment: a key minted for the EU environment only works against the EU base URL, and media uploaded in one environment cannot be referenced from the other.

Getting an API key

Keys are minted by a platform administrator in the Catalyst admin UI (there is no self-serve issuance). A key manages one or more organizations and carries the publishing scope, which covers every tool in this server. Keys can expire and can be revoked at any time; treat them as secrets. If you don't know which organizationId values a key manages, call list_organizations.

Tools (15)

Tool

Purpose

upload_media

Upload a local file, get back the finalUrl for media fields

create_text_activity / update_text_activity

Markdown articles, optional hero image and journal prompts

create_video_activity / update_video_activity

Video activities (uploaded file + duration + thumbnail)

create_audio_activity / update_audio_activity

Audio activities such as guided meditations

create_image_activity / update_image_activity

Full-screen image activities

create_simple_activity / update_simple_activity

Config-only cards: mindful_timer, breathing, cold_plunge, copyright

get_activity

Fetch one activity by slug (returns activity: null when absent)

list_activities

Paginated summaries, optional type filter, no content bodies

delete_activity

Delete an MCP-created activity plus (best-effort) its media

list_organizations

List the organizations this key manages ({id, name}); takes no inputs

Identity and idempotency

Activities are addressed by (organizationId, contentSlug). The slug namespace is shared across all types: one slug names exactly one activity per organization, and its type can never change after creation. Creating with an existing slug (same type, MCP-created) acts as an update; retries are safe.

Only activities created through this MCP can be updated or deleted. Activities authored in-app are readable via get_activity / list_activities but never writable here.

Working with multiple organizations

A single key can manage several organizations. There is still one key and one config entry — you choose which organization a call targets purely through the organizationId argument on each tool. Switching organizations means passing a different organizationId; there is no config edit and no restart.

  • Discover what a key manages with list_organizations (no inputs). It returns { "organizations": [{ "id": "...", "name": "..." }, ...] } (name is the display name, or null if the record is unavailable). Start here whenever you don't already know the organizationId to use.

  • Target an organization by passing one of those ids as organizationId to any other tool.

The server enforces membership on every call: a key may only act on the organizations attached to it. A call for an unattached organization is refused with PERMISSION_DENIED — call list_organizations to see the valid ids.

When a workspace should only ever touch one organization, set BEWELL_CATALYST_ORG_LOCK to that organization's id. Every org-scoped tool then refuses any other organizationId client-side, before any network call, naming both the locked and the attempted organization. list_organizations remains available (read-only discovery is harmless).

The recommended pattern is one .mcp.json entry per project, each locked to that project's organization — so a Claude session working in that project physically cannot drift to another organization even though the key could reach several:

{
  "mcpServers": {
    "bewell-catalyst": {
      "command": "npx",
      "args": ["-y", "bewell-catalyst-mcp"],
      "env": {
        "BEWELL_CATALYST_API_KEY": "bwc_...",
        "BEWELL_CATALYST_FUNCTIONS_BASE_URL": "https://europe-west1-bewellit-prod-eu.cloudfunctions.net",
        "BEWELL_CATALYST_ORG_LOCK": "your-org-id"
      }
    }
  }
}

Multi-organization sessions (rare, operator-driven) simply omit the lock. The lock is a convenience guard against accidental cross-organization writes; the server-side membership check remains the hard boundary either way.

Per-type contracts

Common required fields: organizationId, contentSlug, name. Common optional fields: description, tags, author, analyticsId, isPractice, includeInAiChat.

Type

Additionally required

Optional extras

Notes

text

contentMarkdown (max 50k chars)

imageUrl (hero), imagePosition (top/bottom), journalPrompts

video

contentUrl, durationSeconds

thumbnailUrl (strongly recommended), journalPrompts

Directly streamable file (mp4/webm); a YouTube page URL will not play

audio

contentUrl, durationSeconds

thumbnailUrl, journalPrompts

The card shows "(0 seconds)" without a real duration

image

imageUrl

markdown description

Full-screen viewer

copyright

description (the notice text)

none

Served by the *_simple_activity tools

mindful_timer

none

none

Name-only config card

breathing

none

none

Breathing pattern is a user choice at runtime

cold_plunge

none

none

Name-only config card

durationSeconds is a positive integer (max 86400) and is not probed server-side: measure the real duration locally (for example with ffprobe) and pass it.

analyticsId enables completion tracking and is write-once: it can be set on create (recommended value: the contentSlug) or added later, but never changed or cleared once set. Without it the activity is untracked.

Media workflow

Always upload_media first, then pass the returned finalUrl into the create/update call. Media URL fields only accept finalUrl values minted for your own organization and environment: external URLs are rejected.

Role

Feeds

Formats

Max size

video

contentUrl (video)

.mp4 .mov .webm

500 MB

audio

contentUrl (audio)

.mp3 .m4a .aac .wav .ogg

50 MB

thumbnail

thumbnailUrl (video/audio)

.jpg .png .webp

5 MB

image

imageUrl (image activity, text hero)

.jpg .png .webp .gif

2 MB

inline

images inside contentMarkdown

.jpg .png .webp .gif

2 MB

filePath must be a local path on the machine running the MCP server. The MIME type is inferred from the extension (pass contentType explicitly to override). Re-uploading with the same role and the same file extension overwrites the previous file; the same role with a different extension creates a sibling file instead. That is harmless: only the finalUrl you pass to create/update is referenced, and delete_activity's folder cleanup collects any orphans. inline creates a new file per call.

Update semantics (PATCH)

update_* tools patch, they do not replace:

  • Omitted optional field: the stored value is preserved.

  • Explicit null: the stored value is cleared (tags: [] clears tags).

  • name and the type's required fields (for example contentUrl + durationSeconds for video) must be re-sent on every update.

  • analyticsId: omit to preserve. Changing or clearing an existing value is refused.

  • type is immutable; the update tools re-assert it and the server verifies it matches.

Deleting

delete_activity removes the activity document and best-effort deletes its uploaded media. Two caveats:

  1. Placements are not checked. If a teacher has placed the activity in a course template or course, deleting it leaves a dangling reference (the app renders nothing for it). Check with the teacher before deleting anything that may be in use.

  2. Only MCP-created activities can be deleted.

Deletion is idempotent: a missing slug returns {"deleted": false, "reason": "not-found"} without error. In a successful response, mediaCleanedUp: false means the document was deleted but the media folder could not be cleaned up.

Error glossary

Status

Meaning

What to do

INVALID_ARGUMENT

A field failed validation; the message names it

Fix the named field. For "first-party media URL" errors, use upload_media and pass its finalUrl

FAILED_PRECONDITION "not created by MCP"

The activity was authored in-app

Leave it alone; create a new slug for your own version

FAILED_PRECONDITION "type is immutable"

The slug is taken by another type

Choose a new contentSlug

FAILED_PRECONDITION "analyticsId is immutable"

Attempt to change/clear a set analyticsId

Omit analyticsId from the update

UNAUTHENTICATED

Key missing, malformed, revoked or expired

Check BEWELL_CATALYST_API_KEY; ask a platform admin for a new key

PERMISSION_DENIED

The organizationId is not one this key manages, or the key is missing scope

Call list_organizations and pass a listed id

RESOURCE_EXHAUSTED

Rate limit (100/min per key, 30/min per IP)

Wait a minute, retry

INTERNAL

Server-side failure

Retry once; report if persistent

Development

npm install
npm run build   # tsc -> dist/
npm run lint    # eslint
npm test        # vitest

License

MIT

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bewellit/bewell-catalyst-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server