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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ jobs:
- name: Build
run: pnpm build

- name: Validate MCPB extension manifest
run: pnpm run validate:extension

trigger-learn-rebuild:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ npm-debug.log*
# Temporary files
tmp/
.tmp/

# MCPB staging (mcpb pack)
extension-pack/
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.2.2] - Unreleased

### Added

- Claude Desktop Extension (.mcpb) for one-click install. Packages Shield as a Desktop Extension that wraps existing MCP servers, enforces permissions via the Shield API, and logs all tool calls. Install by opening the .mcpb file in Claude Desktop.
- npx multicorn-shield restore command to recover original MCP server config after disabling the extension.

## [0.2.1] - 2026-03-23

### Security
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ That's it. Every tool call now goes through Shield's permission layer, and activ

See the [full MCP proxy guide](https://multicorn.ai/docs/mcp-proxy) for Claude Code, OpenClaw, and generic MCP client examples.

### Claude Desktop Extension (.mcpb)

Install Shield without the terminal: download the `.mcpb` bundle (or use **Install** from the Shield product page), open it in Claude Desktop, and enter your API key when prompted. The extension reads your existing MCP servers from `claude_desktop_config.json`, runs them as child processes, merges their tools, and checks every `tools/call` with the Shield API. Activity still shows up in your [Multicorn dashboard](https://app.multicorn.ai).

**Disable or uninstall recovery:** On each start the extension saves a copy of your `mcpServers` block to `~/.multicorn/extension-backup.json`. If you turn the extension off and need your original Claude Desktop MCP entries back, run:

```bash
npx multicorn-shield restore
```

Then restart Claude Desktop. That overwrites `mcpServers` in your config with the last backup.

**Duplicate tool names:** If two MCP servers expose the same tool name, the first server in your config file keeps the name. The duplicate is skipped and a warning is written to the extension logs (stderr). Rename tools on the server side if you need both.

**Extension icon:** The repo ships a minimal placeholder `icon.png` for packaging. TODO: replace with a proper PNG export from the Multicorn learn site favicon at `multicorn-learn/public/learn/favicon.svg` (relative to the monorepo root).

Build the bundle locally (requires a full `pnpm build` first):

```bash
pnpm run pack:extension
```

This runs `mcpb validate` and writes `dist/multicorn-shield.mcpb`.

### Option 2: OpenClaw Plugin (native integration)

If you're running [OpenClaw](https://openclaw.ai), Shield integrates directly as a plugin. No proxy layer, no code changes. The plugin intercepts every tool call at the infrastructure level before it executes.
Expand Down
38 changes: 38 additions & 0 deletions bin/multicorn-shield.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env node
/**
* Multicorn Shield CLI (restore and future subcommands).
*
* @module bin/multicorn-shield
*/

import { restoreClaudeDesktopMcpFromBackup } from "../src/extension/restore.js";

async function main(): Promise<void> {
const arg = process.argv[2];
if (arg === "restore") {
await restoreClaudeDesktopMcpFromBackup();
process.stderr.write(
"Restored MCP server entries from ~/.multicorn/extension-backup.json into Claude Desktop config.\n" +
"Restart Claude Desktop to apply changes.\n",
);
return;
}

process.stderr.write(
[
"multicorn-shield: Shield CLI",
"",
"Usage:",
" npx multicorn-shield restore",
" Restore MCP servers in claude_desktop_config.json from the Shield extension backup.",
"",
].join("\n"),
);
process.exit(arg === undefined || arg === "help" || arg === "--help" ? 0 : 1);
}

void main().catch((error: unknown) => {
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`Error: ${message}\n`);
process.exit(1);
});
14 changes: 14 additions & 0 deletions bin/shield-extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env node
/**
* Entry point for the Claude Desktop Extension MCP server (.mcpb bundle).
*
* @module bin/shield-extension
*/

import { runShieldExtension } from "../src/extension/server.js";

void runShieldExtension().catch((error: unknown) => {
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`multicorn-shield-extension: ${message}\n`);
process.exit(1);
});
37 changes: 37 additions & 0 deletions docs/desktop-extension-research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Desktop Extension (.mcpb) research spike

Answers the four questions for SR-04 before implementation. Sources: Anthropic Desktop Extensions article, open MCPB manifest spec (`anthropics/dxt` MANIFEST.md), and the multicorn-shield codebase.

## (1) Can a .mcpb extension act as a proxy or wrapper for other MCP servers?

Not as transport-layer middleware. A Desktop Extension is one MCP server process that Claude Desktop launches over stdio. The format does not define a way to sit in front of other MCP servers at the host level.

The extension can still behave like a proxy by:

- Reading the user's Claude Desktop MCP configuration from disk (paths already used in this repo for wizard flows).
- Spawning those MCP servers as child processes with stdio pipes.
- Implementing `tools/list` by merging child responses and `tools/call` by routing to the correct child after permission checks.

So the bundle is a standalone MCP server that multiplexes child servers, not a plugin that rewrites Claude Desktop's transport to existing servers.

## (2) How does the extension discover which other MCP servers the user has installed?

The MCPB manifest and Claude Desktop do not expose an API for "list other extensions" or "read merged MCP config" to the running server.

Practical discovery is reading `claude_desktop_config.json` (or equivalent) from the known OS paths at runtime. The Node-based server can use `fs` like any local process.

Optional future path: users register targets in the Multicorn dashboard (hosted proxy config). That is extra setup and not required for the local config file approach.

## (3) Can the manifest collect the Shield API key via `user_config`?

Yes. Declare a `user_config` field with `type: "string"`, `sensitive: true`, and `required: true`. Claude Desktop prompts on first enable, stores the value in the OS secret store, and substitutes `${user_config.<key>}` into `mcp_config` env or args. Sensitive values should not be written to project JSON on disk by the extension itself.

## (4) What happens when the extension is disabled?

Claude Desktop stops starting that MCP server. Tools exposed only through the extension disappear from the client.

Other MCP entries that still exist in `claude_desktop_config.json` are unchanged on disk unless the user edited them during onboarding. To make disable and uninstall safe, Shield backs up the `mcpServers` object before relying on wrapped behaviour and ships a `restore` CLI that writes the backup back into `claude_desktop_config.json`. See the README Desktop Extension section.

## Tooling note

Run `npx @anthropic-ai/mcpb init` to generate a manifest baseline. The CLI currently emits `manifest_version` **0.2** (verify on upgrade). Use `mcpb validate` and `mcpb pack` for the bundle; do not hand-roll the zip format.
Binary file added icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"manifest_version": "0.2",
"name": "multicorn-shield",
"display_name": "Multicorn Shield",
"version": "0.2.1",
"description": "Permission controls and audit logging for your MCP servers. Wraps servers from Claude Desktop config and checks every tool call with Shield.",
"author": {
"name": "Multicorn AI",
"url": "https://multicorn.ai"
},
"homepage": "https://multicorn.ai",
"repository": {
"type": "git",
"url": "https://github.com/Multicorn-AI/multicorn-shield.git"
},
"documentation": "https://multicorn.ai/docs/mcp-proxy",
"icon": "icon.png",
"license": "MIT",
"privacy_policies": ["https://multicorn.ai/privacy"],
"tools_generated": true,
"keywords": ["permissions", "security", "audit", "mcp", "shield"],
"server": {
"type": "node",
"entry_point": "server/index.js",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/server/index.js"],
"env": {
"MULTICORN_API_KEY": "${user_config.api_key}",
"MULTICORN_BASE_URL": "https://api.multicorn.ai",
"MULTICORN_SHIELD_EXTENSION": "1"
}
}
},
"user_config": {
"api_key": {
"type": "string",
"title": "Shield API key",
"description": "Your Multicorn Shield API key. Create one at https://app.multicorn.ai/settings/api-keys",
"sensitive": true,
"required": true
}
},
"compatibility": {
"platforms": ["darwin", "win32"],
"runtimes": {
"node": ">=20.0.0"
}
}
}
18 changes: 13 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
}
},
"bin": {
"multicorn-proxy": "./dist/multicorn-proxy.js"
"multicorn-proxy": "./dist/multicorn-proxy.js",
"multicorn-shield": "./dist/multicorn-shield.js"
},
"files": [
"dist",
Expand All @@ -41,7 +42,11 @@
"test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit",
"docs": "typedoc",
"clean": "rm -rf dist coverage docs/api",
"clean": "rm -rf dist coverage docs/api extension-pack",
"stage-extension-pack": "rm -rf extension-pack && mkdir -p extension-pack/server && cp manifest.json extension-pack/ && cp icon.png extension-pack/ && cp dist/shield-extension.js extension-pack/server/index.js",
"validate:extension": "pnpm run stage-extension-pack && mcpb validate extension-pack/manifest.json",
"build:extension": "tsup",
"pack:extension": "pnpm run build && pnpm run stage-extension-pack && mcpb validate extension-pack/manifest.json && mcpb pack extension-pack dist/multicorn-shield.mcpb",
"size": "size-limit",
"prepublishOnly": "pnpm run clean && pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build",
"prepare": "husky",
Expand All @@ -59,12 +64,16 @@
]
},
"dependencies": {
"lit": "^3.2.0"
"@modelcontextprotocol/sdk": "^1.27.1",
"lit": "^3.2.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@anthropic-ai/mcpb": "^2.1.2",
"@eslint/js": "^9.19.0",
"@open-wc/testing-helpers": "^3.0.1",
"@size-limit/file": "^11.1.6",
"@types/node": "^22.0.0",
"@vitest/coverage-istanbul": "^3.0.5",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
Expand All @@ -75,7 +84,6 @@
"jsdom": "^25.0.1",
"lint-staged": "^16.2.7",
"prettier": "^3.4.2",
"@size-limit/file": "^11.1.6",
"size-limit": "^11.1.6",
"tsup": "^8.3.6",
"typedoc": "^0.28.17",
Expand Down
Loading
Loading