boxadm-mcp
English | 日本語
MCP (Model Context Protocol) server that surfaces external file flow froma Box admin's point of view. It reads Box's enterprise event log(admin_logs) to highlight "who shares a lot with the outside" and "whichfiles get accessed from outside" — an early-warning signal for leakage, not ageneral-purpose file browser.
Read-only: it never revokes shares, deletes files, or otherwise mutatesanything — it only surfaces risk. This is a different tool from ageneral-purpose Box file MCP (the official Box MCP, or the claude.ai Boxconnector): those operate on a user's own files and cannot see enterpriseevents, which is exactly what this server is for.
Named after the admin-console viewpoint (boxadm = Box admin), sibling ofgwsadm-mcp.
Features
| Tool | Category | Description |
|---|---|---|
health_check |
— | version + auth_mode + Box auth + admin_logs scope probe + configured domain allowlist. Reports needs-login when not yet authenticated (OAuth mode) |
recent_admin_events |
Diagnostic | Raw recent enterprise events (for checking event types/fields). Supports manual pagination via stream_position |
external_access_events |
Access (events, enterprise-wide) | Aggregates external DOWNLOAD/PREVIEW within a window: top external accessors, top externally-accessed files, share-link count. Pass created_by_logins for DLP tracing of a specific account |
external_collaborators |
Exposure (enumeration) | Lists external collaborators (outside-org login or external invite email) |
public_shared_links |
Exposure (enumeration) | Lists items shared with an open (anyone-with-the-link) share link |
top_external_sharers |
Exposure (enumeration) | Ranks internal owners by external exposure (external collabs + public links) |
daily_brief |
Combined | Morning summary combining access (events) and exposure (enumeration) |
Auth model
Two modes, selected via BOX_AUTH_MODE:
oauth— OAuth 2.0 (user auth). An admin authorizes once in a browser; therefresh token keeps it running unattended after that.ccg— Client Credentials Grant (server-to-server). Simpler to rununattended if your Box tenant has an available server-authentication appslot.
admin_logs (enterprise events) is readable in either mode, provided theauthorizing/impersonated user is an admin and the app has the Manageenterprise properties scope.
OAuth setup (one-time, by a Box admin)
- Developer Console → Create Platform App → Custom App → UserAuthentication (OAuth 2.0)
- Redirect URI:
http://localhost:8787/callback - Application Scopes: check Manage enterprise properties (requiredfor
admin_logs). Add Read all files and folders too if you also wantcollaboration/share-link enumeration (requires re-consent) - Enable the app in the Admin Console (unpublished apps are disabled bydefault under most tenant policies)
- Note the Client ID / Client Secret
- First login: set
BOX_AUTH_MODE=oauthetc., then runboxadm-mcp auth→ authorize in the browser → a token cache is written to~/.config/boxadm-mcp/token.json(chmod 600)
Setup
# uv
uv pip install boxadm-mcp
# pip
pip install boxadm-mcp
Or from source:
git clone https://github.com/shigechika/boxadm-mcp.git
cd boxadm-mcp
# uv
uv sync
# pip
pip install -e .
Configuration
| Variable | Required | Description |
|---|---|---|
BOX_AUTH_MODE |
oauth / ccg (default ccg) |
|
BOX_CLIENT_ID |
✓ | App Client ID |
BOX_CLIENT_SECRET |
✓ | App Client Secret |
BOX_ENTERPRISE_ID |
ccg mode | Enterprise ID (CCG subject; not needed for oauth) |
BOX_OAUTH_REDIRECT_URI |
oauth redirect. Default http://localhost:8787/callback |
|
BOX_TOKEN_CACHE |
oauth token cache path. Default ~/.config/boxadm-mcp/token.json |
|
BOX_API_BASE |
Default https://api.box.com |
|
BOX_ALLOWED_DOMAINS |
✓ | Internal email domains (comma-separated). No default — every address counts as external until you set this |
Keep secrets out of .mcp.json (e.g. in a local env file sourced beforelaunch); .mcp.json itself can reference ${BOX_CLIENT_ID}-style variablesand be safely committed.
Scope and limits
- Access tools (
external_access_events, and the access half ofdaily_brief) read the enterprise-wide events stream. Hittingmax_eventssetscapped: true(oldest-first scan). - Exposure (enumeration) tools only see folders visible to theco-admin account (not a guaranteed 100% of the enterprise), plus
max_folders/max_depthlimits (surfaced viacapped). Requires theRead all files and folders scope. - Enumeration tools share a short-TTL scan memo across calls;
public_shared_linksskips collaboration calls entirely (optimization).
DLP tracing (reverse-lookup by accessor)
To answer "what did this external account download": passcreated_by_logins (comma-separated logins) to external_access_events. Itkeeps only that accessor's events and returns per-file detail(matched_events: item id/name, owner, size in bytes+GB, timestamp,event_type, whether it was via a share link).
external_access_events(since_hours=26, created_by_logins="[email protected]")
- Since the accessor could appear anywhere in the window, a filtered callauto-extends the scan cap to up to 50,000 events (oldest-first) — butonly matching events are kept, so memory stays bounded.
- In this mode the response carries
events_matched(match count) instead ofevents_scanned(no running total is kept; usecappedto judge coverage).capped: truemeans the window wasn't fully scanned — raisemax_events. - Box's
admin_logsAPI has nocreated_byquery parameter, so this is aclient-side filter (fetch_admin_events(created_by_logins=...)).
Usage
Claude Code
Add to .mcp.json:
{
"mcpServers": {
"boxadm-mcp": {
"type": "stdio",
"command": "boxadm-mcp",
"env": {
"BOX_AUTH_MODE": "oauth",
"BOX_CLIENT_ID": "${BOX_CLIENT_ID}",
"BOX_CLIENT_SECRET": "${BOX_CLIENT_SECRET}",
"BOX_ALLOWED_DOMAINS": "example.com"
}
}
}
}
CLI Options
boxadm-mcp auth # OAuth first-time login (opens a browser)
boxadm-mcp --version # Print version and exit
boxadm-mcp # Start MCP server (STDIO, default)
Development
git clone https://github.com/shigechika/boxadm-mcp.git
cd boxadm-mcp
# uv
uv sync --dev
uv run pytest -v
uv run ruff check .
# pip
python3 -m venv .venv
.venv/bin/pip install -e . && .venv/bin/pip install pytest respx ruff
.venv/bin/pytest -v
.venv/bin/ruff check .
Tests never touch Box — respx mocks the CCG/OAuth token endpoint and theadmin_logs/enumeration APIs.
Releasing
Releases are automated with release-please.Merging Conventional Commits (feat:, fix:, …)to main keeps a release PR open with the next version and changelog. Mergingthat PR tags vX.Y.Z and publishes a GitHub Release, whose release: publishedevent triggers the release workflow to build and publish to PyPI and the MCPRegistry. release-please owns the version in boxadm_mcp/__init__.py andserver.json (do not bump them by hand).
[!IMPORTANT]The release-please workflow should be given a repository secret
RELEASE_PLEASE_TOKEN(a PAT withcontents: write+pull-requests: write).The defaultGITHUB_TOKENcannot create the Release that triggers thedownstreamreleaseworkflow (GitHub blocks workflow runs triggered byGITHUB_TOKEN), so without the PAT nothing gets published. The workflow fallsback toGITHUB_TOKENwhen the secret is unset so PR CI keeps working on forks.
Governance
Because this surfaces what users share, run it as authorized information-securitymonitoring with a clear purpose, a defined set of viewers, and a retentionpolicy. Most external sharing is legitimate (collaborators, vendors), so treatfindings as a risk ranking, not an alert queue — build an allowlist ofknown-OK sharers over time.
License
MIT