intempt-mcp-server
v1.0.0
Published
MCP server for Intempt — CRM, meetings, analytics, AI chat, and marketing automation
Maintainers
Readme
intempt-mcp-server
MCP server for Intempt — AI content generation, brand management, knowledge base, and user/account lookup through natural language.
Works with Claude Desktop, Claude Code, Cursor, Windsurf, VS Code (Copilot), Zed, Cline, and any MCP-compatible client.
Documentation
| Document | What it answers |
|---|---|
| docs/TEST_AND_EXECUTION_PROTOCOL.md | How to build, test, restart and prove the server works against production — including the checks that pass without proving anything |
| docs/PROTOCOL_CONFORMANCE.md | Row-by-row MCP + OAuth conformance with file-level evidence, and the two gaps that remain open |
| Limitations | The capability ceiling: what it will not do, and what is broken upstream |
Status
| Area | Status |
|------|--------|
| MCP server (stdio + HTTP transport) | Working — Node.js, MCP SDK, 124 tools registered (measured 2026-08-10) |
| Auth — browser login (stdio) | Working — authorization_code + PKCE over a loopback redirect (RFC 8252) as the registered intempt-desktop client. /oauth/authorize hands the browser to the social provider's own login, so no Intempt-hosted page is involved. Token refresh, shared ~/.intempt/auth.json |
| Auth — device code flow (HTTP transport only) | Working, pending a console deploy — the flow itself is fine; it sends the user to intempt.oauth.device.verification-uri = https://intempt.com/device. That page is built and working on console-v2 branch ved-device-route, but is not yet pushed or deployed, so production does not serve it. Kept for the HTTP transport, where the browser is on a different machine and loopback cannot work. Not used on a normal desktop — stdio uses the loopback flow above, which is why desktop login needs no device page |
| Content generation (7 tools) | Working — routes through BluChatClient's WebSocket (wss://api.intempt.com/v1/{org}/projects/{project}/chat) |
| Registry-backed (111 tools) | Working — real REST endpoints via @intempt/commands, extracted from console-v2's own working API client code (204 registry entries, 202 real / 2 confirmed stub; 48 excluded from the MCP surface by design) |
| Docker + CI/CD | Working — Dockerfile, compose, GitHub Actions for test + publish |
| Test suite | Working — 382 tests, 11 files; 381 pass, 1 fails on Windows only (symlinkSync needs Administrator; see the test protocol) |
| MCP Resources | Working — 8 brand kit/knowledge-base resources registered, dual-exposed alongside the equivalent tools. A client sees 7 in resources/list + 1 in resources/templates/list (intempt://brand/attributes/{name} is parameterised, and the spec lists templates separately) — so "7" from a client is correct, not a missing resource |
| Skill self-registration via npm install -g | Working, and verified from a tarball install — the postinstall hook copies the 9 intempt/claude-plugin skills into ~/.claude/skills/, same mechanism as @chatcut/skill. This row previously said "inert until published", which was wrong: it does not need a registry, only an install |
| Payments/billing integration | Not started — no per-tool metering yet (works after auth since all API-driven) |
| npm publish | Pipeline ready, not yet published — .github/workflows/mcp-publish.yml builds, typechecks, tests and publishes on a mcp@* tag. @intempt/commands is a devDependency that esbuild inlines into the bundle, so this package publishes on its own with no unpublished dependencies. Blocked only on an NPM_TOKEN in repo/org secrets and on the branch being pushed (CI builds from the remote) |
Quick Start
Requires Node.js >= 18.
npm install -g intempt-mcp-server
claude mcp add intempt -- intempt-mcp-serverOr run it without installing:
claude mcp add intempt -- npx -y intempt-mcp-serverThen, in the chat, run the login tool. A browser opens on your normal Intempt
sign-in page; when it says you're logged in, every tool works. If your account
belongs to more than one org or project, run list_orgs and then use_project
— no restart is needed.
Check whether the package is published yet — this answers for itself, so it cannot go stale the way a hardcoded "not published" notice does:
npm view intempt-mcp-server versionIf that returns a version, ignore this section and use the registry commands above.
If it errors with E404, the first release has not landed; build and hand over a
tarball instead. It is the identical artifact the registry will serve.
cd packages/mcp
npm run build
npm pack # -> intempt-mcp-server-1.0.0.tgzOn the installing machine:
npm install -g ./intempt-mcp-server-1.0.0.tgz
claude mcp add intempt -- intempt-mcp-server⚠️ A tarball install and a registry install both provide the same
intempt-mcp-server binary, so they collide. Run npm uninstall -g
intempt-mcp-server before switching from one to the other.
Setup by Platform
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"intempt": {
"command": "npx",
"args": ["-y", "intempt-mcp-server"],
"env": {
"INTEMPT_ORG": "your-org",
"INTEMPT_PROJECT": "your-project"
}
}
}
}Claude Code
Recommended: the Intempt plugin. It registers the MCP server automatically and bundles a setup skill that handles CLI install, login, and org/project selection in one step (intempt/claude-plugin):
/plugin marketplace add intempt/claude-plugin
/plugin install intempt@intempt-pluginsRestart Claude Code (exit and relaunch), then say:
run intempt setupManual alternative, if you don't want the plugin:
claude mcp add intempt -- npx -y intempt-mcp-serverSkills without the plugin marketplace: npm install -g intempt-mcp-server also self-registers the same 9 skills directly into ~/.claude/skills/, via a postinstall hook -- the same mechanism ChatCut's Claude Code integration uses. No /plugin marketplace add step needed. Set INTEMPT_SKIP_SKILL_INSTALL=1 to opt out. This works from a tarball install too -- it needs an install, not a registry.
If your account belongs to more than one org/project, set the default explicitly (skip this if you only belong to one of each -- login picks it automatically):
intempt use --org your-org --project your-project # or omit both flags for an interactive pickerOr set env vars, which always take precedence:
export INTEMPT_ORG=your-org
export INTEMPT_PROJECT=your-projectCursor
Open Cursor Settings > MCP Servers > Add:
{
"mcpServers": {
"intempt": {
"command": "npx",
"args": ["-y", "intempt-mcp-server"],
"env": {
"INTEMPT_ORG": "your-org",
"INTEMPT_PROJECT": "your-project"
}
}
}
}Windsurf
Edit ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"intempt": {
"command": "npx",
"args": ["-y", "intempt-mcp-server"],
"env": {
"INTEMPT_ORG": "your-org",
"INTEMPT_PROJECT": "your-project"
}
}
}
}VS Code (GitHub Copilot)
Add to your workspace .vscode/mcp.json:
{
"servers": {
"intempt": {
"command": "npx",
"args": ["-y", "intempt-mcp-server"],
"env": {
"INTEMPT_ORG": "your-org",
"INTEMPT_PROJECT": "your-project"
}
}
}
}Zed
Edit ~/.config/zed/settings.json:
{
"context_servers": {
"intempt": {
"command": {
"path": "npx",
"args": ["-y", "intempt-mcp-server"],
"env": {
"INTEMPT_ORG": "your-org",
"INTEMPT_PROJECT": "your-project"
}
}
}
}
}Cline
Open Cline Settings > MCP Servers > Add Custom Server:
{
"mcpServers": {
"intempt": {
"command": "npx",
"args": ["-y", "intempt-mcp-server"],
"env": {
"INTEMPT_ORG": "your-org",
"INTEMPT_PROJECT": "your-project"
}
}
}
}Authentication
Auth is fully implemented against the Intempt auth service (not Auth0) at api.intempt.com -- there is no separate auth.intempt.com host; that string is only the JWT iss claim value, never an HTTP target. Uses device code flow (RFC 8628).
Interactive Login (Device Flow)
Say "log me in to Intempt" in your AI assistant. The flow:
- MCP calls
POST /oauth/deviceonapi.intempt.com(client_id: intempt-cli, a real pre-registered public client) - Returns a verification URL + user code -- MCP opens it in your browser automatically (falls back to a clickable link + code in chat if that fails)
- User authenticates via Google/Microsoft/Apple in browser
- MCP polls
POST /oauth/tokenuntil approved - Calls
GET /v1/myselfto fetch identity (email, org/project memberships with roles) -- the token response itself carries onlyaccess_token/refresh_token/token_type/expires_in - Access token + refresh token + identity stored at
~/.intempt/auth.json(mode 0600) -- shared with theintemptCLI, so one login covers both - If the account belongs to exactly one org and one project, both are saved as the default automatically -- no
INTEMPT_ORG/INTEMPT_PROJECTneeded. A multi-org/multi-project account resolves this withintempt use(see above) - Tokens auto-refresh on expiry (single-use rotation)
No passwords in chat. Credentials persist across sessions.
Static Token (CI / Automation)
Set INTEMPT_AUTH_TOKEN to skip interactive login:
{
"env": {
"INTEMPT_AUTH_TOKEN": "your-jwt-token",
"INTEMPT_ORG": "your-org",
"INTEMPT_PROJECT": "your-project"
}
}Tools (124)
124 = 111 registry-backed + 7 content-gen + 3 auth + 3 org/project switching
(use_project, list_orgs, list_projects). Counted 2026-08-10 by
instrumenting registerTools, not by adding up this page.
The 111 registry-backed tools are derived mechanically from @intempt/commands'
204-entry registry (13 domains, extracted from console-v2's own working API
client code) — ask your MCP client to list tools for the exact, current names
and schemas. They're generated from the registry, not hand-maintained here, so
this section can't drift, but it also won't enumerate all 111 one by one.
Content Generation (7)
Routes through BluChatClient's WebSocket (wss://api.intempt.com/v1/{org}/projects/{project}/chat), never POST /chat/message (dead, no gateway route).
| Tool | Example prompt |
|------|----------------|
| generate_email | "Write a cold outreach email for enterprise SaaS buyers" |
| generate_sms | "Flash sale SMS with 20% off code SPRING20, ends Friday" |
| generate_push_notification | "Abandoned cart reminder for running shoes" |
| generate_landing_page | "Landing page for our spring sale with hero, features, and CTA" |
| generate_slack_message | "Team announcement about hitting 10k active users" |
| generate_text | "Product description for our analytics dashboard" |
| generate_image | "Hero image for a summer sale campaign" |
Auth (3)
| Tool | Example prompt |
|------|----------------|
| login | "Log me in to Intempt" |
| logout | "Log me out" |
| whoami | "Which account am I connected to?" |
Org / project switching (3)
Every data call runs against one organization + project. A user in several orgs has none selected by default, and every data tool then fails with "Organization and project are required" until one is chosen.
| Tool | Example prompt |
|------|----------------|
| list_orgs | "Which Intempt orgs can I see?" |
| list_projects | "What projects are in intempt_external_use?" |
| use_project | "Switch to intempt_on_intempt" |
use_project takes effect on the next tool call — no restart. Precedence is
use_project selection > INTEMPT_ORG/INTEMPT_PROJECT env vars > the stored
default, and whoami says when a session selection is outranking an env var.
Registry-backed (111), by domain
| Domain | Tools | Example |
|--------|-------|---------|
| accounts | Account overview/summary/activity, tagging, owner assignment | "Show me the Acme Corp account" (get_account_detail, view-parameterized) |
| analytics | Funnels, retention, insights, dashboards, reports | "Analyze the signup → activation → purchase funnel" (analyze_funnel_overall) |
| blu-chat | Knowledge base, brand kit/personas/tone, snippets, outreach | "Draft an email for [email protected] about our new feature" (draft_outreach, real REST) |
| deals | Deal CRUD, tags, deal analytics | "Give me deal analytics for this quarter" (get_deals_analytics) |
| designer | Brand designs, studios, characters, poses | "List our brand design templates" (list_brand_designs) |
| events | Event CRUD, attributes, event metrics | "What events are we tracking?" (list_events) |
| experiences | Experiment/variant CRUD, experience metrics | "How is the Pricing Page test performing?" (get_experience_metrics, view-parameterized) |
| journeys | Journey detail views, lifecycle actions (start/stop/pause/...), drafts, transformers | "What's the config for the Welcome Series journey?" (get_journey_detail, view-parameterized) |
| meetings | Meeting summaries, meeting types | "Summarize my last meeting with Acme Corp" (summarize_meeting) |
| segments | Segment lists, counts, saved views | "How many users are in the Power Users segment?" (get_segment_match_count) |
| users | User overview/summary/activity, tagging, owner assignment, outreach | "Tell me everything about [email protected]" (get_user_detail, view-parameterized, auto-resolves the email) |
| workflows | list_active_workflows | Confirmed stub — console-v2 has no backend for this yet |
| (no domain) | search_project | Confirmed stub — no cross-entity search backend exists yet |
Natural-language identifiers work everywhere they apply -- e.g. "[email protected]" or "Acme Corp" resolve to an internal ID automatically (POST /profile-lists/{users\|accounts}, first match) before the detail/summary call; there's no separate "lookup" tool.
Resolved 2026-08-02: 12 of the 202 registry entries needed bespoke request-body construction (KNOWN_STRUCTURAL_BODYKEY_GAPS, @intempt/commands) -- each now has a buildBody function producing the real payload shape instead of throwing.
Not available: recipe list/search/get/preview/run. The recipe execution backend is confirmed absent across every platform service searched -- these tools don't exist in the 121-tool list at all, not as stubs.
Limitations — what this server will not do
Written so an evaluator learns the ceiling here rather than by hitting it. All figures measured 2026-08-10.
It creates nothing, and it acts on one record at a time. Of the 204 registry
entries, 48 are excluded from the MCP surface by design: the 46 marked
create-or-bulk-or-destructive are filtered out before registration
(mcpConsolidation.ts:254) along with the 2 stubs (:255). Most of the
remaining difference between 156 and the 111 registry-backed tools is
consolidation, not exclusion — several entries fold into one tool with a
selector argument, so they stay reachable. The served catalogue is 56 reads +
44 single-record edits plus the session tools. Asked to create a segment or
bulk-delete users, the honest answer is "use the console" — there is no tool to
approximate it with.
It does delete, one record at a time. 11 delete_*/remove_* tools are
served (snippets, personas, studios, characters, poses, brand designs,
knowledge, meeting types, journey drafts/transformers, experience metrics).
There is no undo. They carry destructiveHint: true, including
delete_journey_draft, which is served over POST and which method-sniffing
alone would misclassify.
Collections are capped, and say so. Responses cap at 50 items and shed bulky
per-item fields to fit a size budget the client enforces — list_events returns
511 items / 359 KB raw, which the client refuses outright. Truncation is
announced in _truncated (with the exact next offset) and field-shedding in
_shaped. A capped count is not a total.
3 tools are broken upstream, in the Intempt backend rather than here:
get_meetings_summary, list_snippets, get_journey_list_count.
Retrying does not help. They are named in the server instructions so an agent
does not burn a retry loop on them. Each was re-checked on 2026-08-11 against
both the project-scoped and the root path and fails on both.
This list said 6 until 2026-08-11.
list_brand_personalities,list_sales_agentsandlist_brand_methodologieswere never broken -- they are platform-wide endpoints at/v1/<path>that we were calling under/v1/{org}/projects/{project}/. They now return real data. A "known broken" list is uniquely self-confirming: it tells agents not to call the very tools that would disprove it, so entries have to be re-measured, not inherited.
list_users search cannot match an email address. Any searchTerm
containing @ matches nothing — a backend encoding bug that console-v2's own
e2e helper documents and works around. An empty result does not mean the
user is absent.
Known unfixed, tracked: 71 declared arguments across the registry are still
never forwarded by the dispatcher (down from 95 on 2026-08-10). The set is
pinned as a shrink-only baseline in registry.test.ts, which fails in both
directions — a new one is a build failure, and so is a fixed one left in the
list. The MCP-served subset of that class has been cleared; what remains is
predominantly CLI-only, including a genuine one: delete_users /
delete_accounts / delete_meetings declare a required id list that
apiDelete(path) never sends.
Authorization: see docs/PROTOCOL_CONFORMANCE.md.
The audience claim is validated but cannot bind to this server, because the
platform issues one shared audience for the whole API — closing that needs an
auth-service change, and it is written up there rather than papered over.
Environment Variables
Core
| Variable | Default | Description |
|----------|---------|-------------|
| INTEMPT_ORG | — | Organization slug (required) |
| INTEMPT_PROJECT | — | Project slug (required) |
| INTEMPT_AUTH_TOKEN | — | Static JWT token (skips interactive login) |
| INTEMPT_API_URL | https://api.intempt.com | API gateway URL |
| INTEMPT_AUTH_URL | same as INTEMPT_API_URL | Auth service URL -- same origin as the API, there is no separate auth.intempt.com host |
HTTP Transport
| Variable | Default | Description |
|----------|---------|-------------|
| MCP_TRANSPORT | stdio | Set to http for remote mode |
| MCP_PORT | 3010 | HTTP server port |
| MCP_SERVER_URL | http://localhost:3010 | Public URL for OAuth callbacks |
| MCP_CLIENT_ID | intempt-mcp | OAuth client ID |
| MCP_DEFAULT_PROVIDER | google | Default social login provider |
HTTP Transport (Remote Mode)
For shared infrastructure, multi-user environments, or Claude Connectors:
MCP_TRANSPORT=http MCP_PORT=3010 npx intempt-mcp-serverThis starts an HTTP server with OAuth 2.1 (PKCE) at http://localhost:3010.
Endpoints:
| Endpoint | Auth | Description |
|----------|------|-------------|
| POST /mcp | Bearer | MCP JSON-RPC |
| GET /mcp | Bearer | SSE streaming |
| DELETE /mcp | Bearer | Session teardown |
| GET /health | None | Health check ({"status":"ok","sessions":N}) |
| GET /.well-known/oauth-authorization-server | None | OAuth discovery |
Docker
Build and run the HTTP transport in a container:
docker build -t intempt-mcp .
docker run -p 3010:3010 \
-e INTEMPT_ORG=your-org \
-e INTEMPT_PROJECT=your-project \
intempt-mcpOr with docker compose:
INTEMPT_ORG=your-org INTEMPT_PROJECT=your-project docker compose upPull from GHCR (after CI publishes):
docker pull ghcr.io/intempt/mcp-server:latestCI/CD
Two GitHub Actions workflows:
.github/workflows/ci.yml — Build & Test
Runs on every push to main and on pull requests. Tests against Node.js 18, 20, and 22.
.github/workflows/publish.yml — Publish on Tag
Triggered by version tags (v*). Publishes to three registries:
- npm —
npm publish --access public(requiresNPM_TOKENsecret) - GitHub Packages — scoped
@intemptpackage (usesGITHUB_TOKEN) - GHCR — Docker image with semver tags (uses
GITHUB_TOKEN)
Release workflow:
npm version patch # or minor, major
git push && git push --tagsThe tag push triggers the publish workflow which builds, tests, and publishes to all three registries.
Required secrets:
| Secret | Where to set | Purpose |
|--------|-------------|---------|
| NPM_TOKEN | GitHub repo > Settings > Secrets | npm publish |
| GITHUB_TOKEN | Automatic | GitHub Packages + GHCR |
Development
git clone https://github.com/intempt/cli.git
cd cli/packages/commands && npm install && npm run build
cd ../mcp
npm install
npm run build
npm test
npm run dev # TypeScript watch modePublishing Manually
npm
npm publish --access publicThe prepublishOnly hook runs tsc automatically.
GitHub Packages
echo "@intempt:registry=https://npm.pkg.github.com" >> .npmrc
npm publishDocker (GHCR)
docker build -t ghcr.io/intempt/mcp-server:latest .
docker push ghcr.io/intempt/mcp-server:latestHow It Works
Architecture
User (in Claude/Cursor/VS Code/etc.)
↓ natural language
MCP Server (packages/mcp, cli repo monorepo)
↓ resolves names → IDs internally
Intempt Platform REST API (api.intempt.com)
├── /oauth/*, /v1/myself → auth service (login, identity)
├── /v1/{org}/projects/{proj}/chat → AI gateway, WebSocket (7 content-gen tools, via BluChatClient)
└── 111 registry-backed tools → real endpoints per @intempt/commands, extracted
from console-v2's own working API client code
(13 domains, 202 total entries, 200 real / 2 stub)There is no /mcp/* stub namespace -- that was an earlier, abandoned model. @intempt/commands (shared with the intempt CLI) is the single source of truth for every registry-backed tool's endpoint, args, and write-safety class.
Content Generation
Content tools (generate_email, generate_sms, etc.) route through BluChatClient's WebSocket protocol (wss://api.intempt.com/v1/{org}/projects/{project}/chat) -- first-message {type:"auth",token} frame, then a message frame with a generated session_id and the active_skill, resolving on the server's {type:"messageEnd"} frame. Never POST /chat/message -- that path exists in llm-wrapper but has no gateway route and is unreachable.
User/Account Resolution
All tools accept natural language identifiers (names, emails, descriptions) — never internal IDs. When you say "look up [email protected]", the MCP server:
- Calls
POST /profile-lists/userswith{searchTerm: "[email protected]", perPage: 1} - Gets the internal ID from the first result (no disambiguation list yet if there are multiple matches -- an open gap)
- Calls the real detail endpoint (e.g.
get_user_overview, one of the views inside the consolidatedget_user_detailtool)
Same pattern for accounts (via /profile-lists/accounts).
Tool Inventory Detail
All 121 tools are registered and routed against real endpoints or an explicit, documented gap -- there are no sample-data stubs anywhere in the shipped server.
| Group | Count | Notes |
|-------|-------|-------|
| Content generation | 7 | Real, via BluChatClient |
| Auth | 3 | Real, self-contained device-code flow |
| Registry-backed | 111 | Real REST endpoints via @intempt/commands (156 MCP-eligible entries, 61 narrow reads/lifecycle actions consolidated into 16 view-parameterized tools) |
Confirmed stub, no backend anywhere (2 of the 202 registry entries): search_project (no cross-entity search endpoint in console-v2), list_active_workflows (console-v2's workflows page is a bare placeholder with zero API calls).
Not implemented, backend confirmed absent (recipe list/search/get/preview/run): two independent source searches across gateway, single-metadata, llm-wrapper, audience-service, and metric found zero recipe backend anywhere. These tools don't exist in the 121-tool list -- not stubbed, simply absent, matching the "no fabricated data" rule below.
12 registry entries needed bespoke request-body construction -- RESOLVED 2026-08-02: get_account_event_overview, get_account_activity, enrich_accounts, get_user_activity, list_user_meetings, enrich_users, create_group, create_segment, list_deals, create_deal, get_deal_activity, list_meetings. Each now has a buildBody function (@intempt/commands' bodyBuilders.ts) producing the real console-v2 payload shape.
Known Gaps
| Gap | Notes |
|-----|-------|
| Recipe execution | Platform/backend team dependency, not something this package can unblock by itself |
| Payments/billing metering | No per-tool credit metering yet; platform APIs still enforce org-level entitlements at the gateway (403 if the org lacks access) |
| Rate limiting | No client-side rate limiting — relies on gateway 429 responses |
| npm publish | intempt-mcp-server is not on the registry yet, so npx / npm install -g from npm 404 until the first release. The publish pipeline itself is ready (mcp-publish.yml, on a mcp@* tag or manual dispatch) and needs only an NPM_TOKEN secret plus the branch pushed, since CI builds from the remote. Note this does not block the postinstall skill self-registration, which works from any install including a tarball |
RESOLVED 2026-08-02: Console URLs -- 15 single-entity detail/activity reads in accounts/users/deals/meetings now return a consoleUrl field via RegistryEntry.consoleUrl + the shared attachConsoleUrl helper (@intempt/commands).
RESOLVED 2026-08-02: Disambiguation on multiple entity matches -- the shared resolver (resolveUserId/resolveAccountId, @intempt/commands) now returns an explicit ambiguous-candidates list instead of silently picking the first match; callers ask for id explicitly.
Project Structure
skills/ Vendored copies of intempt/claude-plugin's 9 SKILL.md files -- package
ROOT, a sibling of src/ and dist/, not nested under either (postinstall.ts's
dist/postinstall.js finds it via ../skills relative to its own dist/ location)
src/
index.ts Entry point — creates MCP server (stdio or HTTP)
tools.ts 121 tool registrations — 111 generated from mcpToolList() (@intempt/commands)
+ 7 content-gen + 3 auth, with Zod schemas
handlers.ts Tool dispatch — registry-backed dispatch via @intempt/commands,
BluChatClient for content-gen, custom auth handlers
client.ts IntemptClient — HTTP client for platform API
auth.ts AuthManager — device-code flow, GET /v1/myself identity,
token refresh, shared ~/.intempt/auth.json storage
oauth.ts OAuth 2.1 server for HTTP transport mode
http.ts HTTP/SSE transport server
resources.ts MCP Resources for brand kit/knowledge base — dual-exposed
alongside the equivalent tools, same dispatch underneath
postinstall.ts Self-registers skills/ into ~/.claude/skills/ on npm install -g
(same mechanism as @chatcut/skill)
__tests__/
handlers.test.ts 32 tests — tool routing, user/account resolution
auth.test.ts 39 tests — device flow, GET /v1/myself, refresh, shared storage
oauth.test.ts 29 tests — OAuth 2.1 server
client.test.ts 18 tests — HTTP client
tools.test.ts 14 tests — tool registration, annotation hints
resources.test.ts 9 tests — MCP Resources
http.test.ts 7 tests — HTTP transport bearer-auth gating
postinstall.test.ts 16 tests — skill self-registration, chownToRealUser, per-skill isolationLicense
MIT
