@clearlaunch/mcp
v0.4.1
Published
Instrument any MCP server — tool usage, errors, access times, and per-tool enable/disable — managed from your ClearLaunch project.
Downloads
1,010
Readme
@clearlaunch/mcp
Guard and observability for your MCP server. Not an MCP itself — you wrap the server you already have, and ClearLaunch stores the tool permissions and the usage reporting.
- Guard — tools you disable stop running, without touching your handlers.
- Observability — per-tool calls, errors, latency and access times.
- Embeddable — expose the config UI inside your own product, for your own tenants.
Install
npm i @clearlaunch/mcpInstrument your server
Wrap the server once; every tool registered afterwards is covered. Both
tool() and registerTool() are instrumented, so it does not matter which
registration API your server uses.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { instrument } from '@clearlaunch/mcp';
const server = instrument(new McpServer({ name: 'my-mcp', version: '1.0.0' }), {
projectKey: 'pk_…', // from ClearLaunch → project → MCP
serverName: 'my-mcp', // shown in the portal
// tenantId: 'acme-corp', // see multi-tenant below
});
server.tool('cards', 'Manage cards', schema, handler); // traced + guardedTelemetry failures never propagate into your handlers, and a permission fetch that fails falls back to allow-all — the guard must not take your server down.
Register tools after calling instrument(). Anything registered before is
not wrapped. If no tool is registered through a wrapped method, the SDK prints a
one-line warning to stderr rather than leaving you with an empty portal and no
explanation.
How failures are counted
A tool can fail two ways, and both are recorded as errors:
- Throwing — the exception is recorded and re-thrown, so your server's own error handling is unchanged.
- Returning
{ isError: true }— MCP's idiomatic failure. The message from the first content block is captured.
If you are upgrading from 0.3.0 or earlier, expect your error rate to rise:
isError results were previously counted as successes, so a server reporting
failures that way showed a permanent zero.
Options: projectKey, apiUrl, serverId, serverName, flushIntervalMs,
enforcePermissions (default true), telemetry (default true), tenantId.
The two keys
| | pk_… public | sk_… secret |
|---|---|---|
| Purpose | Identifies the project | Authorises writes |
| Safe in a browser | Yes | No |
| Gate | Origin allowlist | Verified against a stored hash |
| Can change config | No | Yes |
The public key is readable by anyone who views source on a page embedding a widget, so it is deliberately not accepted as authorisation for writes. Sending it as a bearer token is rejected like any other wrong credential.
Create the secret in ClearLaunch → project → MCP → Server-to-server key. It is shown once; store it as you would a database password.
Embedding config in your own product
Your users manage MCP settings inside your UI. Your frontend never sees a ClearLaunch credential — it talks to your backend, which holds the secret.
Your UI ──► Your backend ──► ClearLaunch
(holds sk_…)Read (public, origin-gated — safe from your server or your frontend):
GET /api/v1/public/sdk/<pk_key>/mcp/permissions
GET /api/v1/public/sdk/<pk_key>/mcp/permissions?tenantId=acme-corpWrite (server-to-server only):
// your-backend/routes/mcp-settings.ts
app.patch('/settings/mcp', async (req, res) => {
// YOUR auth decides who may change what — ClearLaunch does not know your users.
const tenantId = req.user.tenantId;
const r = await fetch(
`https://api.clearlaunch.ai/api/v1/public/sdk/${process.env.CL_PUBLIC_KEY}/mcp/permissions`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.CL_SECRET_KEY}`, // sk_… — server only
},
body: JSON.stringify({
tenantId, // omit to edit the project-wide default
disabled: req.body.disabled, // e.g. ['vault', 'deploy']
allowlistMode: false,
}),
},
);
if (!r.ok) return res.status(502).json({ error: 'Could not update MCP settings' });
res.json(await r.json());
});Storing the secret: an environment variable or your existing secret manager.
Never NEXT_PUBLIC_*, VITE_*, or any bundled config — those reach the browser.
Multi-tenant
One ClearLaunch project can serve every tenant of your platform.
- No
tenantId→ the project-wide default. - With
tenantId→ that tenant's override.
Resolution is override-then-default, never a merge: a tenant with an override gets exactly that config, and a tenant without one inherits the default. A merge would silently grant tools a tenant was meant not to have.
tenantId is opaque to ClearLaunch — use whatever id your platform already has.
const server = instrument(new McpServer(…), {
projectKey: 'pk_…',
tenantId: currentTenant.id, // this instance serves one tenant
});Letting an agent configure it
A coding agent with the ClearLaunch MCP connected can read your registered tools and populate the guard config, instead of you typing tool names into a UI:
sdk(action: "mcp_permissions") # read current config
sdk(action: "set_mcp_permissions", disabled: […]) # write it
sdk(action: "set_mcp_permissions", tenantId: "acme-corp", enabled: […], allowlistMode: true)Permission modes
- Deny-list (default): everything runs except
disabled. - Allow-list (
allowlistMode: true): onlyenabledruns.
Instrumented servers pick up changes within ~60s (permission cache TTL). A
blocked call returns a clear denial to the agent and is recorded as denied in
the portal, so you can see what was attempted.
