Skip to content
Open
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
4 changes: 4 additions & 0 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:

jobs:
test-github-action-workflow:
# Secrets are not available to pull_request runs from forks, so the
# SCREENLY_API_TOKEN would be empty and `screen list` would fail auth.
# Only run this integration check for same-repository pull requests.
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
name: List screens
steps:
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,20 @@ jobs:
os: macos-15
rust: stable
target: x86_64-apple-darwin
mcpb_platform: darwin
mcpb_alias: macos-x64
- build: macos-aarch64
os: macos-15
rust: stable
target: aarch64-apple-darwin
mcpb_platform: darwin
mcpb_alias: macos-arm64
- build: windows
os: ubuntu-22.04
rust: stable
target: x86_64-pc-windows-gnu
mcpb_platform: win32
mcpb_alias: windows-x64
- build: windows-32
os: ubuntu-22.04
rust: stable
Expand Down Expand Up @@ -105,6 +111,42 @@ jobs:
fi
cd -

# Only desktop targets are bundled: Claude Desktop runs on macOS and Windows.
- name: Package MCP Bundle
if: matrix.mcpb_platform != ''
shell: bash
run: |
set -euo pipefail
mkdir -p mcpb-build/server
cp mcpb/README.md mcpb/icon.png mcpb-build/
if [[ "${{ matrix.build }}" == windows* ]]; then
cp "target/${{ matrix.target }}/release/screenly.exe" mcpb-build/server/
else
cp "target/${{ matrix.target }}/release/screenly" mcpb-build/server/
fi
# The committed manifest carries a 0.0.0 placeholder so the version cannot
# drift from Cargo.toml; the tag is the single source of truth.
# Windows packs screenly.exe, so entry_point/command must match the artifact.
if [[ "${{ matrix.build }}" == windows* ]]; then
entry_point="server/screenly.exe"
else
entry_point="server/screenly"
fi
jq --arg version "${GITHUB_REF_NAME#v}" \
--arg platform "${{ matrix.mcpb_platform }}" \
--arg entry_point "$entry_point" \
'.version = $version
| .compatibility.platforms = [$platform]
| .server.entry_point = $entry_point
| .server.mcp_config.command = ("${__dirname}/" + $entry_point)
| del(.server.mcp_config.platform_overrides)' \
mcpb/manifest.json > mcpb-build/manifest.json
npx --yes @anthropic-ai/mcpb@2.1.2 pack \
mcpb-build "screenly-cli-${{ matrix.target }}.mcpb"
# Friendlier alias next to the rustc-target name (both are published).
cp "screenly-cli-${{ matrix.target }}.mcpb" \
"screenly-${{ matrix.mcpb_alias }}.mcpb"

- name: Publish
uses: softprops/action-gh-release@v1
# TODO: if any of the build step fails, the release should be deleted.
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@

# Build output for nix
result*

# Local MCP Bundle builds (release artifacts)
*.mcpb
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ serde_with = "3.8.3"
serde_yaml = "0.9.17"
sha1 = "0.10.5"
sha2 = "0.10.7"
simple_logger = { version = "5", features = ["colors"] }
# The "stderr" feature keeps stdout free of log output, which the `mcp`
# subcommand needs for its JSON-RPC stream.
simple_logger = { version = "5", features = ["colors", "stderr"] }
strum = "0.27"
strum_macros = "0.27"
temp-env = "0.3.6"
Expand Down
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,8 @@ $ screenly --output json screen list > screens.json
```

> [!NOTE]
> In debug builds, the CLI outputs log messages to stdout. Use `RUST_LOG=off` to suppress them
> when redirecting output to a file:
> ```bash
> $ RUST_LOG=off screenly --output csv screen list > screens.csv
> $ RUST_LOG=off screenly --output json screen list > screens.json
> ```
> Log messages go to stderr, so redirecting stdout to a file captures only command output.
> Use `RUST_LOG` to change the log level, or `RUST_LOG=off` to silence logging entirely.

## MCP Server (AI Assistant Integration)

Expand All @@ -124,9 +120,26 @@ The server communicates over stdio and exposes the full Screenly API as tools.
| **Shared Playlists** | `shared_playlist_list`, `shared_playlist_create`, `shared_playlist_delete` |
| **Edge Apps** | `edge_app_list`, `edge_app_list_settings`, `edge_app_list_instances` |

Every tool is annotated with behaviour hints (`readOnlyHint`, `destructiveHint`, `idempotentHint`), so MCP clients can tell read-only tools apart from ones that modify or delete data and prompt for confirmation before destructive actions.

### Configuration Examples

#### Cursor / Claude Desktop
#### Claude Desktop Extension (`.mcpb`)

For [Claude Desktop](https://claude.ai/download), the expected install path is
**Desktop Extensions** (Settings → Extensions) — the same idea as installing
the CLI with Homebrew. Once Screenly is listed, install it there and paste your
API token when prompted. No manual JSON editing required.

For testing before the listing is live, you can sideload a `.mcpb` from the
[latest release](https://github.com/Screenly/cli/releases/latest). macOS release bundles are
not Developer ID–signed yet (same as the CLI `.tar.gz` artifacts); a browser download may be
blocked by Gatekeeper. If that happens, use **System Settings → Privacy & Security → Open Anyway**.
Details: [`mcpb/README.md`](mcpb/README.md).

The token is stored in your operating system's keychain rather than a plaintext config file.

#### Cursor / other clients

Add to your MCP configuration file:

Expand Down
128 changes: 128 additions & 0 deletions mcpb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Screenly for Claude Desktop

Manage your [Screenly](https://www.screenly.io) digital signage network from Claude.

This is the packaging directory for the Screenly MCP Bundle (`.mcpb`). The bundle wraps
the MCP server that ships inside the [Screenly CLI](https://github.com/Screenly/cli) so it
can be installed into Claude Desktop with a single click, with no terminal setup required.

## Installation

### Recommended: Claude Desktop Extensions

Once listed, install Screenly from **Desktop Extensions** in Claude Desktop
(Settings → Extensions) — the same idea as installing the CLI with Homebrew.
Claude handles download and setup; you only need to paste your Screenly API token
when prompted.

You can generate a token at `https://[your-workspace].screenlyapp.com` under
**Settings → Security → API tokens**.

### Sideload from a GitHub release (testing / pre-listing)

1. Download the bundle for your machine from the
[latest release](https://github.com/Screenly/cli/releases/latest), for example
`screenly-macos-arm64.mcpb` on an Apple Silicon Mac (aliases:
`screenly-macos-x64.mcpb`, `screenly-windows-x64.mcpb`).
2. Open the file. Claude Desktop shows an installation dialog.
3. Paste your Screenly API token when prompted.

#### macOS Gatekeeper note

Release `.mcpb` bundles currently ship the same unsigned macOS binary as the CLI
`.tar.gz` artifacts. A bundle downloaded in a browser may be blocked by Gatekeeper.

If Claude Desktop cannot start the extension after a sideload install, open
**System Settings → Privacy & Security**, look for the blocked `screenly`
message, and click **Open Anyway**. Then try the extension again.

Prefer the Desktop Extensions install once it is available. Developer ID signing and
notarization for release binaries is tracked separately and is not unique to the MCP bundle.

## What you can do

Once installed, you can ask Claude to:

- Review your screens and check which ones are offline or out of sync
- Add a web page, image, or video as an asset
- Build a playlist and schedule it, for example "only during business hours on weekdays"
- Organise content with asset groups and labels
- Share a playlist with another team
- Inspect Edge Apps, their settings, and their instances

## Capabilities

The bundle exposes 33 tools. Every tool is annotated so Claude knows whether it only reads
data or modifies your account, which means Claude will ask for confirmation before doing
anything destructive.

| Category | Tools |
| --- | --- |
| Screens | `screen_list`, `screen_get` |
| Assets | `asset_list`, `asset_get`, `asset_create`, `asset_update`, `asset_delete` |
| Asset Groups | `asset_group_list`, `asset_group_create`, `asset_group_update`, `asset_group_delete` |
| Playlists | `playlist_list`, `playlist_create`, `playlist_update`, `playlist_delete` |
| Playlist Items | `playlist_item_list`, `playlist_item_create`, `playlist_item_update`, `playlist_item_delete` |
| Labels | `label_list`, `label_create`, `label_update`, `label_delete`, `label_link_screen`, `label_unlink_screen`, `label_link_playlist`, `label_unlink_playlist` |
| Shared Playlists | `shared_playlist_list`, `shared_playlist_create`, `shared_playlist_delete` |
| Edge Apps | `edge_app_list`, `edge_app_list_settings`, `edge_app_list_instances` |

Twelve of these tools are read-only. Thirteen are marked destructive (deletes, unlinks,
and updates that overwrite existing fields) so clients can prompt before running them.

## Authentication

The bundle authenticates with a Screenly API token, which you provide during installation.
The token is marked as sensitive in the bundle manifest, so Claude Desktop stores it in your
operating system's keychain rather than in a plaintext configuration file.

Tokens are scoped to a single Screenly team. To limit what the extension can reach, use a
token for a team that contains only the screens you want Claude to manage. You can revoke a
token at any time from the Screenly console.

## Privacy Policy

Screenly's privacy policy is available at
<https://www.screenly.io/privacy-policy/>.

This extension connects to the Screenly API at `api.screenlyapp.com` over HTTPS (or another
Screenly API host if configured via `API_BASE_URL`). Requests are made directly from your
machine using the API token you supply.

The data returned to Claude is the data you ask for: your screens, assets, playlists,
labels, and Edge Apps, along with their metadata. Your API token is sent only to Screenly,
as the credential for those API requests.

Separately, the Screenly CLI initializes [Sentry](https://sentry.io) crash reporting on
startup (including when run as this extension). If the process panics, diagnostic details
such as the stack trace and device/OS context may be sent to Sentry's ingest endpoint
(`*.ingest.sentry.io`). This is used for reliability debugging, not product analytics.
Screenly's handling of that data is covered by the privacy policy linked above.

## Support

- Documentation: <https://developer.screenly.io/mcp>
- Issues: <https://github.com/Screenly/cli/issues>

## Building the bundle

Bundles are built automatically for each tagged release by
[`.github/workflows/release.yml`](../.github/workflows/release.yml), which injects the
release version into `manifest.json` and packs it together with the compiled `screenly`
binary.

To build one locally:

```bash
npm install -g @anthropic-ai/mcpb

cargo build --release

mkdir -p build/server
cp mcpb/manifest.json mcpb/README.md mcpb/icon.png build/
cp target/release/screenly build/server/
mcpb pack build screenly.mcpb
```

The `version` field in the committed `manifest.json` is a `0.0.0` placeholder. The release
workflow replaces it with the Git tag so it cannot drift from `Cargo.toml`.
Binary file added mcpb/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading