MCP server bridging local MCP clients (Claude Code, Cursor, Claude Desktop, ToolHive) to the Cullis federated agent-trust network.
Project description
cullis-connector
The user-side binary for the Cullis federated agent-trust network.
The Cullis Connector is a small MCP (Model Context Protocol) server that runs on your workstation, inside your MCP client of choice (Claude Code, Cursor, Claude Desktop, ToolHive, or any other MCP-speaking client), and bridges that client into the Cullis network. It handles:
- Enrollment — one-time device-code flow that provisions your identity against the Cullis Site.
- Session management — opening, routing, and closing agent-to-agent sessions over the Cullis broker.
- Discovery — browsing agents and services reachable from your org.
- Diagnostics — local troubleshooting commands, exposed as MCP tools so the host LLM can run them with your approval.
The connector is one of three Cullis components:
| Component | Runs where | Purpose |
|---|---|---|
| Cullis Site | Per-org control plane | Admin UI, enrollment approvals, CA |
| Cullis Broker | Per-org data plane | Routing, policy, audit |
| Cullis Connector | End-user workstation | Binds the user's MCP client into the net |
Install
PyPI (recommended)
pip install cullis-connector
cullis-connector --version
Python 3.10+ required. The package installs a single console script:
cullis-connector.
Homebrew (macOS and Linux)
brew tap cullis-security/tap
brew install cullis-connector
Docker
docker pull ghcr.io/cullis-security/cullis-connector:latest
docker run --rm ghcr.io/cullis-security/cullis-connector:latest --version
Persist the identity by mounting ~/.cullis:
docker run --rm -it \
-v "$HOME/.cullis:/home/cullis/.cullis" \
ghcr.io/cullis-security/cullis-connector:latest \
enroll --site-url https://cullis.acme.local \
--requester-name "Alice" --requester-email "alice@acme.example"
Standalone binary (GitHub Releases)
Each release on
https://github.com/cullis-security/cullis/releases ships pre-built
PyInstaller binaries for Linux amd64 and macOS. Download, chmod +x,
drop on your $PATH.
Enroll
The first run needs a one-time enrollment against your org's Cullis
Site. Your admin receives an approval prompt; once they click approve,
the connector persists a client certificate and key under
~/.cullis/identity/.
cullis-connector enroll \
--site-url https://cullis.acme.local \
--requester-name "Alice Example" \
--requester-email "alice@acme.example" \
--reason "New laptop, replacing the old MBP"
The command prints a URL you can share with your admin and polls until
approval or timeout. See cullis-connector enroll --help for all flags.
Multiple profiles on the same machine
Need more than one identity on a single workstation — say a north
and a south agent for testing, or a personal profile alongside a
work one? Pass --profile <name> (or set CULLIS_PROFILE) and each
profile lives in its own directory under ~/.cullis/profiles/<name>/
with a separate enrollment, config, and certificate.
# enroll two independent identities
cullis-connector enroll --profile north \
--site-url https://cullis.acme.local \
--requester-name "North Bot" --requester-email north@acme.example
cullis-connector enroll --profile south \
--site-url https://cullis.acme.local \
--requester-name "South Bot" --requester-email south@acme.example
# run them as two MCP stdio servers (different shell sessions or
# different IDE mcp.json entries)
cullis-connector serve --profile north
cullis-connector serve --profile south
Wiring both into the same MCP client is just two entries in its JSON:
{
"mcpServers": {
"cullis-north": {"command": "cullis-connector", "args": ["serve", "--profile", "north"]},
"cullis-south": {"command": "cullis-connector", "args": ["serve", "--profile", "south"]}
}
}
Legacy installs using the flat ~/.cullis/identity/ layout keep
working untouched — we don't auto-migrate so existing keys aren't
moved without the operator's consent. To convert an in-place install
to a named profile, stop the connector and mv ~/.cullis/identity ~/.cullis/profiles/default/identity (same for any other state you
keep alongside).
Explicit --config-dir /some/path still wins over --profile for
operators who need to pin the directory elsewhere.
Native desktop shell (no terminal)
pip install 'cullis-connector[dashboard,desktop]'
cullis-connector desktop
Launches a tray icon plus a native OS webview wrapping the local
dashboard — the process you'd usually see in a browser tab is now a
window on your taskbar. Closing the window hides it; quit from the
tray. The dashboard, enrollment flow, IDE autoconfig cards, and inbox
poller are identical to cullis-connector dashboard; only the shell
is native.
Platform prerequisites that pip cannot fetch:
- macOS / Windows 10+: nothing extra. The OS ships WebKit / WebView2.
- Linux:
gtk3andwebkit2gtkpackages — present on mainstream distros out of the box, installable via the distro's package manager on minimal images (e.g.apt install libgtk-3-0 libwebkit2gtk-4.1-0).
Combine with cullis-connector install-autostart to have the tray
appear at login.
Wire up your MCP client
Every example below assumes you have already enrolled successfully.
Claude Code CLI
If the claude binary is on your PATH, the dashboard /connected
page has a Claude Code CLI card — one click runs claude mcp add cullis --scope user -- cullis-connector serve for you (idempotent, so
re-clicking is a no-op). --scope user makes the registration persist
across projects on this machine.
Manual fallback, same thing by hand:
claude mcp add cullis cullis-connector \
-- --site-url https://cullis.acme.local
Or append to ~/.claude/settings.json manually:
{
"mcpServers": {
"cullis": {
"command": "cullis-connector",
"args": ["--site-url", "https://cullis.acme.local"]
}
}
}
Cursor
Add to ~/.cursor/mcp.json (or the project-local .cursor/mcp.json):
{
"mcpServers": {
"cullis": {
"command": "cullis-connector",
"args": ["--site-url", "https://cullis.acme.local"]
}
}
}
Cursor picks up the connector on next IDE restart.
Claude Desktop
Edit claude_desktop_config.json:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"cullis": {
"command": "cullis-connector",
"args": ["--site-url", "https://cullis.acme.local"]
}
}
}
Restart Claude Desktop — the Cullis tools appear in the MCP menu.
Zed
Add the Cullis server to Zed's settings.json (Zed uses
context_servers, not mcpServers):
{
"context_servers": {
"cullis": {
"command": "cullis-connector",
"args": ["serve"]
}
}
}
Or click the Zed card on the connector dashboard /connected
page to have it written for you.
Windsurf
Windsurf reads MCP servers from ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"cullis": {
"command": "cullis-connector",
"args": ["serve"]
}
}
}
Same one-click behaviour via the dashboard /connected page.
ToolHive
ToolHive pulls MCP servers from its registry. The Cullis Connector is planned to land there once the Docker image is public:
# (future, once listed in the ToolHive registry)
thv run cullis-connector -- --site-url https://cullis.acme.local
Until the registry entry is accepted, you can run the containerised connector manually:
thv run --docker ghcr.io/cullis-security/cullis-connector:latest \
-- --site-url https://cullis.acme.local
(Submission of the connector to the ToolHive registry is tracked as a separate rollout step.)
Talking to other agents
Once enrolled, the connector exposes a small set of natural-language MCP tools that your client (Claude Code, Cursor, …) calls on your behalf. You don't memorise SPIFFE handles — the LLM picks them up from the conversation.
Find someone and start a thread:
> contact mario
Active peer: Mario Rossi (chipfactory::mario).
> chat ciao, hai un attimo?
Sent to chipfactory::mario (correlation_id=…)
Disambiguation when the name is shared:
> contact mario
Found 3 matches for 'mario':
#1 Mario Rossi (chipfactory::mario) [intra-org]
#2 Mario Bianchi (acme::mario) [cross-org]
#3 Mariano Verdi (chipfactory::mariano) [intra-org]
→ pick one with contact('#1'), contact('#2'), …
> contact #2
Active peer: Mario Bianchi (acme::mario).
Read your inbox and reply in thread:
> receive_oneshot
1 one-shot message(s):
- [acme::alice] corr=4f12 reply_to=—: progetto pronto?
> reply sì, lo finisco oggi
Reply sent to acme::alice (threaded as reply to msg-…).
The intent-level tools (contact, chat, reply) wrap the lower-
level primitives (send_oneshot, receive_oneshot, discover_agents)
which remain exposed for power users and scripts. State is kept in
process memory only — no peer cache survives a server restart.
Notifications
When you also keep cullis-connector dashboard running (typically via
cullis-connector install-autostart), it polls the local Mastio every
~10 seconds in the background and pops a native OS notification
(libnotify on Linux, NSUserNotification on macOS, Toast on Windows)
when a new message arrives. Click the notification to open
http://127.0.0.1:7777/inbox. Works regardless of whether your MCP
client (Claude Code, Cursor, …) is open — the dashboard process is
the one that owns the poll loop.
Tweaks via env on the dashboard process:
| Env var | Default | Effect |
|---|---|---|
CULLIS_CONNECTOR_NOTIFICATIONS=off |
on |
Disables the poller entirely (no popups, no badge) |
CULLIS_CONNECTOR_POLL_S=30 |
10 |
Poll interval, in seconds |
Native notifications need the dashboard extra (which now pulls in
plyer):
pip install 'cullis-connector[dashboard]'
Headless boxes without a notification daemon fall back gracefully to
stderr — you'll see lines like [cullis-notify] Message from acme::mario: ciao! in the dashboard log.
Statusline badge in Claude Code
The dashboard exposes GET http://127.0.0.1:7777/status/inbox which
returns the unread count + last sender. Both this endpoint and
POST /status/inbox/seen require a bearer token generated at first
dashboard start and persisted at
~/.cullis/identity/statusline.token (chmod 0600). The loopback bind
alone doesn't stop other local processes under your user, so the token
is what actually authenticates the statusline poller.
Drop this snippet into your ~/.claude/settings.json to see
"📨 N from " in the Claude Code status bar:
{
"statusLine": {
"type": "command",
"command": "t=$(cat ~/.cullis/identity/statusline.token); s=$(curl -s -H \"Authorization: Bearer $t\" http://127.0.0.1:7777/status/inbox); u=$(echo $s | jq -r .unread); n=$(echo $s | jq -r .last_sender); [ \"$u\" != \"0\" ] && echo \"📨 $u from $n\" || true"
}
}
When you've read the messages,
curl -X POST -H "Authorization: Bearer $(cat ~/.cullis/identity/statusline.token)" http://127.0.0.1:7777/status/inbox/seen
clears the counter (the dashboard's /inbox view does this
automatically on load).
Configuration precedence
- CLI flags (
--site-url,--config-dir,--log-level, ...) - Environment variables (
CULLIS_SITE_URL,CULLIS_CONFIG_DIR, ...) ~/.cullis/config.yaml
Multi-org users can point each client at a distinct directory:
cullis-connector --config-dir ~/.cullis-orgA serve
cullis-connector --config-dir ~/.cullis-orgB serve
Troubleshooting
No identity found— runcullis-connector enroll ...first.- TLS failures against a dev Site — the admin must share the org
CA cert; import it into your trust store, or pass
--no-verify-tlsfor local development only. cullis-connectornot on$PATHafterpip install— your pip user bin directory is probably missing; addpython -m site --user-base/binto$PATH, or install in a venv.
License
cullis-connector is distributed under the permissive
Apache License 2.0. The Connector is the binary every
end user installs on their workstation to join the Cullis network, so
the terms match the other user-facing surfaces (cullis_sdk, sdk-ts,
enterprise-kit) — zero-friction review by any corporate legal team.
The Cullis server components (app/, mcp_proxy/) live in the same
monorepo under the Functional Source License 1.1 with Apache 2.0
future grant; see ../NOTICE for the full per-path map.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cullis_connector-0.3.6.tar.gz.
File metadata
- Download URL: cullis_connector-0.3.6.tar.gz
- Upload date:
- Size: 121.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
77c15c21af6a8f666e2db6285bcddae83f7320a58820f3f59cc94cbd858ec597
|
|
| MD5 |
4841108e006a0d16b93670abb832bdb6
|
|
| BLAKE2b-256 |
818a5b9c13ee66545df45601ec9d31cad54f96f59cee29946c22bb8ab06e1f39
|
Provenance
The following attestation bundles were made for cullis_connector-0.3.6.tar.gz:
Publisher:
release-connector.yml on cullis-security/cullis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cullis_connector-0.3.6.tar.gz -
Subject digest:
77c15c21af6a8f666e2db6285bcddae83f7320a58820f3f59cc94cbd858ec597 - Sigstore transparency entry: 1419452926
- Sigstore integration time:
-
Permalink:
cullis-security/cullis@e25362cc5273e22cdb881c32695b77478418785f -
Branch / Tag:
refs/tags/connector-v0.3.6 - Owner: https://github.com/cullis-security
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-connector.yml@e25362cc5273e22cdb881c32695b77478418785f -
Trigger Event:
push
-
Statement type:
File details
Details for the file cullis_connector-0.3.6-py3-none-any.whl.
File metadata
- Download URL: cullis_connector-0.3.6-py3-none-any.whl
- Upload date:
- Size: 142.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2bade0ef5bd02bdeeb6e25f21f089bebc0e9c88e20d57f7a7ddfa87e969bf27
|
|
| MD5 |
3d473f9228d544950fd2f497d1f02860
|
|
| BLAKE2b-256 |
94631b463e531472c9291a8de2c7216d30f14eb8e8a2e60c3130b836fbff76c7
|
Provenance
The following attestation bundles were made for cullis_connector-0.3.6-py3-none-any.whl:
Publisher:
release-connector.yml on cullis-security/cullis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cullis_connector-0.3.6-py3-none-any.whl -
Subject digest:
e2bade0ef5bd02bdeeb6e25f21f089bebc0e9c88e20d57f7a7ddfa87e969bf27 - Sigstore transparency entry: 1419453042
- Sigstore integration time:
-
Permalink:
cullis-security/cullis@e25362cc5273e22cdb881c32695b77478418785f -
Branch / Tag:
refs/tags/connector-v0.3.6 - Owner: https://github.com/cullis-security
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-connector.yml@e25362cc5273e22cdb881c32695b77478418785f -
Trigger Event:
push
-
Statement type: