mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-05-10 05:42:40 +00:00
Compare commits
13 Commits
add-netsui
...
tobin/mcp-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
351dfafced | ||
|
|
cf62a6c02d | ||
|
|
3bd94cc810 | ||
|
|
a8be018317 | ||
|
|
33e62b9bd6 | ||
|
|
9f103c621d | ||
|
|
caa8c1a539 | ||
|
|
33fd73c8b9 | ||
|
|
777db5c30b | ||
|
|
aeecad8f43 | ||
|
|
bb7730114d | ||
|
|
3df5394ee9 | ||
|
|
db52e65c44 |
@@ -24,7 +24,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/amekala/adspirer-mcp-plugin.git",
|
||||
"sha": "aa70dbdbbbb843e94a794c10c2b13f5dd66b5e40"
|
||||
"sha": "c40623f1aa7b568e960d3f2e2558a6fcf10e6c18"
|
||||
},
|
||||
"homepage": "https://www.adspirer.com"
|
||||
},
|
||||
@@ -47,7 +47,7 @@
|
||||
"url": "https://github.com/techwolf-ai/ai-first-toolkit.git",
|
||||
"path": "plugins/ai-firstify",
|
||||
"ref": "main",
|
||||
"sha": "7f18e11d694b9ae62ea3009fbbc175f08ae913df"
|
||||
"sha": "852272ec21cebab98202df967dffee127209b6bc"
|
||||
},
|
||||
"homepage": "https://ai-first.techwolf.ai"
|
||||
},
|
||||
@@ -57,7 +57,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/endorlabs/ai-plugins.git",
|
||||
"sha": "a0f1d5632b6f9e6c26eaa9806f5d8d454ca5b06f"
|
||||
"sha": "975f0ce422b1f2677681ffd085aef34ea1826b70"
|
||||
},
|
||||
"homepage": "https://www.endorlabs.com"
|
||||
},
|
||||
@@ -67,7 +67,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/AikidoSec/aikido-claude-plugin.git",
|
||||
"sha": "d7fa8b8e192680d9a26c1a5dcaead7cf5cdb7139"
|
||||
"sha": "5d9c13d367218e9b43a11d4502f623ab98859225"
|
||||
},
|
||||
"homepage": "https://github.com/AikidoSec/aikido-claude-plugin"
|
||||
},
|
||||
@@ -86,9 +86,10 @@
|
||||
{
|
||||
"name": "amplitude",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"source": "git-subdir",
|
||||
"url": "https://github.com/amplitude/mcp-marketplace.git",
|
||||
"sha": "be54ccb66b10593721dd3a31e47b2db20ea02d2f"
|
||||
"path": "plugins/amplitude",
|
||||
"ref": "main"
|
||||
},
|
||||
"description": "Use Amplitude as an expert analyst — instrument Amplitude, discover product opportunities, analyze charts, create dashboards, manage experiments, and understand users and accounts.",
|
||||
"category": "monitoring",
|
||||
@@ -108,7 +109,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/astronomer/agents.git",
|
||||
"sha": "7ef022b02f5296b5ecc52ba0db3ba9345ec03c9e"
|
||||
"sha": "5935c4330dea4dfb8e93568956b10a543ecdb3d1"
|
||||
},
|
||||
"homepage": "https://github.com/astronomer/agents"
|
||||
},
|
||||
@@ -185,7 +186,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/AzureCosmosDB/cosmosdb-claude-code-plugin.git",
|
||||
"sha": "56e6da0cae93cdee8bcfa5e624ecdd9a0a483181"
|
||||
"sha": "23c168856e4435793bd27a72d4714f022a3a1e90"
|
||||
},
|
||||
"description": "Expert assistant for Azure Cosmos DB — data modeling, query optimization, performance tuning, and best practices.",
|
||||
"category": "database",
|
||||
@@ -234,7 +235,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/box/box-for-ai.git",
|
||||
"sha": "6f4ec3549f3e869b115628403555b1c9220b2b34"
|
||||
"sha": "0fb23244e3c35cd562206c80eff1e22c456046ea"
|
||||
},
|
||||
"homepage": "https://github.com/box/box-for-ai"
|
||||
},
|
||||
@@ -244,7 +245,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/brightdata/skills.git",
|
||||
"sha": "e671da495f7ec0ed6be5e9fa71e260f886a1dc36"
|
||||
"sha": "44b24797d82cfd535c5b97831d5c6ba86c9d60df"
|
||||
},
|
||||
"homepage": "https://docs.brightdata.com"
|
||||
},
|
||||
@@ -266,7 +267,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/ChromeDevTools/chrome-devtools-mcp.git",
|
||||
"sha": "c2d8009ff75f76bce1ec4cf79c2467b50d81725e"
|
||||
"sha": "a1612be8e01401cf1711c64bc2ef5da5763ba956"
|
||||
},
|
||||
"homepage": "https://github.com/ChromeDevTools/chrome-devtools-mcp"
|
||||
},
|
||||
@@ -338,7 +339,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/cloudflare/skills.git",
|
||||
"sha": "5ec03da67e230df52b698255c8e5979dc9b124b6"
|
||||
"sha": "0397d7d88fa6ac7517a88389622eb0799e86ded2"
|
||||
},
|
||||
"description": "Skills for the Cloudflare developer platform: Workers, Durable Objects, Agents SDK, MCP servers, Wrangler CLI, and web performance.",
|
||||
"category": "deployment",
|
||||
@@ -350,17 +351,20 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/cloudinary-devs/cloudinary-plugin.git",
|
||||
"sha": "137c5d7acd9c3f10e80cd2a400486971e1664f31"
|
||||
"sha": "7b443d7dbd607bfe4850d8cfcab6ba4cbf1a57c3"
|
||||
},
|
||||
"homepage": "https://cloudinary.com/documentation"
|
||||
},
|
||||
{
|
||||
"name": "cockroachdb",
|
||||
"description": "CockroachDB plugin for Claude Code — explore schemas, write optimized SQL, debug queries, and manage distributed database clusters directly from your AI coding agent.",
|
||||
"description": "Connect Claude Code directly to your CockroachDB clusters for hands-on database work — explore schemas, write optimized SQL, debug queries, and manage distributed database clusters. This plugin provides 14 tools across two active MCP backends (self-hosted MCP Toolbox and managed CockroachDB Cloud MCP Server), three specialized agents (DBA, Developer, Operator), 32 skills across 6 operational domains, and built-in safety hooks.",
|
||||
"author": {
|
||||
"name": "Cockroach Labs"
|
||||
},
|
||||
"category": "database",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/cockroachdb/claude-plugin.git",
|
||||
"sha": "a54566e03c852567589ef85bb449d1e4de229667"
|
||||
"url": "https://github.com/cockroachdb/claude-plugin.git"
|
||||
},
|
||||
"homepage": "https://github.com/cockroachdb/claude-plugin"
|
||||
},
|
||||
@@ -444,7 +448,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/astronomer/agents.git",
|
||||
"sha": "7ef022b02f5296b5ecc52ba0db3ba9345ec03c9e"
|
||||
"sha": "5935c4330dea4dfb8e93568956b10a543ecdb3d1"
|
||||
},
|
||||
"homepage": "https://github.com/astronomer/agents"
|
||||
},
|
||||
@@ -454,7 +458,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/astronomer/agents.git",
|
||||
"sha": "85d6053b1e21724f9cefb1e3f5219bd54fc77224"
|
||||
"sha": "5935c4330dea4dfb8e93568956b10a543ecdb3d1"
|
||||
},
|
||||
"homepage": "https://github.com/astronomer/agents"
|
||||
},
|
||||
@@ -470,6 +474,19 @@
|
||||
},
|
||||
"homepage": "https://github.com/awslabs/agent-plugins"
|
||||
},
|
||||
{
|
||||
"name": "datadog",
|
||||
"description": "Use Datadog directly in Claude Code through a preconfigured Datadog MCP server. Query logs, metrics, traces, dashboards, and more through natural conversation. This plugin is in preview.",
|
||||
"author": {
|
||||
"name": "Datadog"
|
||||
},
|
||||
"category": "monitoring",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/datadog-labs/claude-code-plugin.git"
|
||||
},
|
||||
"homepage": "https://www.datadoghq.com/"
|
||||
},
|
||||
{
|
||||
"name": "dataverse",
|
||||
"description": "Agent skills for building on, analyzing, and managing Microsoft Dataverse — with Dataverse MCP, PAC CLI, and Python SDK.",
|
||||
@@ -545,7 +562,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/fastly/fastly-agent-toolkit.git",
|
||||
"sha": "d9ba949011e725be55cae11acc741aa1f1f393d3"
|
||||
"sha": "329331c887512850f13e481b45c4298c0387a4d2"
|
||||
},
|
||||
"homepage": "https://github.com/fastly/fastly-agent-toolkit/blob/main/README.md"
|
||||
},
|
||||
@@ -566,7 +583,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/voxel51/fiftyone-skills.git",
|
||||
"sha": "593e0553fc9fd94db52386ada2c9e2074a6ecf89"
|
||||
"sha": "02bd4ea170ca01a751c2d2dd6bf2df8f62e65626"
|
||||
},
|
||||
"homepage": "https://docs.voxel51.com/"
|
||||
},
|
||||
@@ -623,7 +640,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/followrabbit-ai/awesome-rabbit.git",
|
||||
"sha": "f59ec3d1f6337a6ed825ef06836a221ed3d2ffb0"
|
||||
"sha": "6926154501300d348a7b50d47479648fe87985b6"
|
||||
},
|
||||
"homepage": "https://subscriptions.agentic.followrabbit.ai/"
|
||||
},
|
||||
@@ -658,7 +675,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/PAIR-Systems-Inc/goodmem-claude-code-plugin.git",
|
||||
"sha": "215568baf203887b5d7f8245e0503dd4a81336c2"
|
||||
"sha": "4e23ab2b3bc7cb4167c99e10d9640ad7089744d7"
|
||||
},
|
||||
"homepage": "https://github.com/PAIR-Systems-Inc/goodmem-claude-code-plugin"
|
||||
},
|
||||
@@ -697,7 +714,7 @@
|
||||
"url": "https://github.com/helius-labs/core-ai.git",
|
||||
"path": "helius-plugin",
|
||||
"ref": "main",
|
||||
"sha": "05ea4d1128d46618266bbcc23a5e7019c57be0d6"
|
||||
"sha": "d9d252497bcf1e4bd5073a76715cd50a8353f9c3"
|
||||
},
|
||||
"homepage": "https://www.helius.dev/docs"
|
||||
},
|
||||
@@ -735,7 +752,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/intercom/claude-plugin-external.git",
|
||||
"sha": "eeef353eead2e3dc5f33f64dbaae54e1309e0d45"
|
||||
"sha": "52653572c47700443eb61154c4e4334a355e755e"
|
||||
},
|
||||
"homepage": "https://github.com/intercom/claude-plugin-external"
|
||||
},
|
||||
@@ -823,6 +840,38 @@
|
||||
"source": "./external_plugins/linear",
|
||||
"homepage": "https://github.com/anthropics/claude-plugins-public/tree/main/external_plugins/linear"
|
||||
},
|
||||
{
|
||||
"name": "liquid-lsp",
|
||||
"description": "LSP integration for Shopify Liquid templates via the Shopify CLI theme language server.",
|
||||
"author": {
|
||||
"name": "Shopify"
|
||||
},
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "https://github.com/Shopify/liquid-skills.git",
|
||||
"path": "plugins/liquid-lsp",
|
||||
"ref": "main",
|
||||
"sha": "a00ca039d82114a7af1b4cbc3025b16c624a42fa"
|
||||
},
|
||||
"homepage": "https://github.com/Shopify/liquid-skills/tree/main/plugins/liquid-lsp"
|
||||
},
|
||||
{
|
||||
"name": "liquid-skills",
|
||||
"description": "Liquid language fundamentals, CSS/JS/HTML coding standards, and WCAG accessibility patterns for Shopify themes",
|
||||
"author": {
|
||||
"name": "Shopify"
|
||||
},
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "https://github.com/Shopify/liquid-skills.git",
|
||||
"path": "plugins/liquid-skills",
|
||||
"ref": "main",
|
||||
"sha": "bf7a7aa9f9809b0dcd80cb5f7fd2795a7208a7a3"
|
||||
},
|
||||
"homepage": "https://github.com/Shopify/liquid-skills/tree/main/plugins/liquid-skills"
|
||||
},
|
||||
{
|
||||
"name": "lua-lsp",
|
||||
"description": "Lua language server for code intelligence",
|
||||
@@ -882,7 +931,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/mintlify/mintlify-claude-plugin.git",
|
||||
"sha": "ce435be18a700dc849d6a63a80da4816d1e2128c"
|
||||
"sha": "acd6d2e0128c4f235d55cfb8d8c91ecbdd5df8cc"
|
||||
},
|
||||
"homepage": "https://www.mintlify.com/"
|
||||
},
|
||||
@@ -908,7 +957,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/mongodb/agent-skills.git",
|
||||
"sha": "c47079f65e88a113c52d1ce0618684cef300246c"
|
||||
"sha": "24529d9540b962d57f30e75d25071bebea5809ad"
|
||||
},
|
||||
"homepage": "https://www.mongodb.com/docs/mcp-server/overview/"
|
||||
},
|
||||
@@ -921,7 +970,7 @@
|
||||
"url": "https://github.com/neondatabase/agent-skills.git",
|
||||
"path": "plugins/neon-postgres",
|
||||
"ref": "main",
|
||||
"sha": "54d7a9db2ddd476f84d5d1fd7bac323907858a8b"
|
||||
"sha": "1438d7db4560a649d62eba99e9d5008b77ac5758"
|
||||
},
|
||||
"homepage": "https://github.com/neondatabase/agent-skills/tree/main/plugins/neon-postgres"
|
||||
},
|
||||
@@ -936,28 +985,8 @@
|
||||
"homepage": "https://github.com/netlify/context-and-tools"
|
||||
},
|
||||
{
|
||||
"name": "netsuite-aiconnector-service-skill",
|
||||
"description": "NetSuite Intelligence skill — teaches AI the correct tool-selection order, output formatting, domain knowledge, multi-subsidiary and currency handling, and SuiteQL safety checklist for any session using the NetSuite AI Service Connector.",
|
||||
"author": {
|
||||
"name": "Oracle NetSuite"
|
||||
},
|
||||
"category": "productivity",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "https://github.com/oracle/netsuite-suitecloud-sdk.git",
|
||||
"path": "packages/agent-skills",
|
||||
"ref": "master",
|
||||
"sha": "43bacf43763e1eedd0892b4652be3d45df94f0e7"
|
||||
},
|
||||
"homepage": "https://github.com/oracle/netsuite-suitecloud-sdk",
|
||||
"strict": false,
|
||||
"skills": [
|
||||
"./netsuite-ai-connector-instructions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "netsuite-sdf-roles-and-permissions",
|
||||
"description": "Use when generating or reviewing NetSuite SDF permission configurations — customrole XML, script deployment permissions, permkey values, permlevel choices, run-as role design, and least-privilege access. Confirms exact ADMI_/LIST_/REGT_/REPO_/TRAN_ permission IDs and validates permissions against bundled NetSuite reference data.",
|
||||
"name": "netsuite-suitecloud",
|
||||
"description": "NetSuite agent skills from Oracle — authoring guidance for SuiteCloud Development Framework (SDF) objects and UIF single-page-app components, plus runtime guidance for the NetSuite AI Service Connector.",
|
||||
"author": {
|
||||
"name": "Oracle NetSuite"
|
||||
},
|
||||
@@ -969,31 +998,13 @@
|
||||
"ref": "master",
|
||||
"sha": "43bacf43763e1eedd0892b4652be3d45df94f0e7"
|
||||
},
|
||||
"homepage": "https://github.com/oracle/netsuite-suitecloud-sdk",
|
||||
"strict": false,
|
||||
"skills": [
|
||||
"./netsuite-sdf-roles-and-permissions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "netsuite-uif-spa-reference",
|
||||
"description": "Use when building, modifying, or debugging NetSuite UIF SPA components. Provides API/type lookup for @uif-js/core and @uif-js/component — constructors, methods, props, enums, hooks, and component options.",
|
||||
"author": {
|
||||
"name": "Oracle NetSuite"
|
||||
},
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "https://github.com/oracle/netsuite-suitecloud-sdk.git",
|
||||
"path": "packages/agent-skills",
|
||||
"ref": "master",
|
||||
"sha": "43bacf43763e1eedd0892b4652be3d45df94f0e7"
|
||||
},
|
||||
"homepage": "https://github.com/oracle/netsuite-suitecloud-sdk",
|
||||
"strict": false,
|
||||
"skills": [
|
||||
"./netsuite-ai-connector-instructions",
|
||||
"./netsuite-sdf-roles-and-permissions",
|
||||
"./netsuite-uif-spa-reference"
|
||||
]
|
||||
],
|
||||
"homepage": "https://github.com/oracle/netsuite-suitecloud-sdk"
|
||||
},
|
||||
{
|
||||
"name": "nightvision",
|
||||
@@ -1040,7 +1051,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/Optimal-AI/optibot-skill.git",
|
||||
"sha": "981db1f630c3116d7df0a71e5967af55b08e813c"
|
||||
"sha": "ce2be448ee713606aa653fc93ef2f98a200fe327"
|
||||
},
|
||||
"homepage": "https://getoptimal.ai"
|
||||
},
|
||||
@@ -1144,7 +1155,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/gitroomhq/postiz-agent.git",
|
||||
"sha": "c5d1bf5f7e95a71e230fc19ae2150ddd9c549854"
|
||||
"sha": "37d627244c53a4b3a7ca94c52cc2db13aaaf468e"
|
||||
},
|
||||
"homepage": "https://postiz.com/agent"
|
||||
},
|
||||
@@ -1155,7 +1166,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/Postman-Devrel/postman-claude-code-plugin.git",
|
||||
"sha": "40b11ac3466c500cf4625ac016d5c01cd00046f4"
|
||||
"sha": "416e40da03a237df7bf03f4362cf6fc7b989b567"
|
||||
},
|
||||
"homepage": "https://learning.postman.com/docs/developer/postman-mcp-server/"
|
||||
},
|
||||
@@ -1245,7 +1256,7 @@
|
||||
"url": "https://github.com/railwayapp/railway-skills.git",
|
||||
"path": "plugins/railway",
|
||||
"ref": "main",
|
||||
"sha": "d52f3741a6a33a3191d6138eb3d6c3355cb970d1"
|
||||
"sha": "eaa89d8f594412b0b837b6531241e7d166e12202"
|
||||
},
|
||||
"homepage": "https://docs.railway.com/ai/claude-code-plugin"
|
||||
},
|
||||
@@ -1277,7 +1288,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/Digital-Process-Tools/claude-remember.git",
|
||||
"sha": "779ab61d8d412230eeec1840b8ca104bebea4358"
|
||||
"sha": "914445ac5f06a164800ea90ba4db41a0486321ae"
|
||||
},
|
||||
"homepage": "https://github.com/Digital-Process-Tools/claude-remember"
|
||||
},
|
||||
@@ -1358,7 +1369,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/sanity-io/agent-toolkit.git",
|
||||
"sha": "4b1fb10bd707a22cf0cdfad5374ffc885f2ffa8d"
|
||||
"sha": "bc09fa9854507c538a856648aafbd4e1a775a95c"
|
||||
},
|
||||
"homepage": "https://www.sanity.io"
|
||||
},
|
||||
@@ -1502,7 +1513,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/sourcegraph-community/sourcegraph-claudecode-plugin.git",
|
||||
"sha": "cfe3d44476957b16d1575261bef6b2dc7cb1e0b7"
|
||||
"sha": "332ee0ca9a409ccd791abee43c7abf2606469017"
|
||||
},
|
||||
"homepage": "https://sourcegraph.com"
|
||||
},
|
||||
@@ -1513,7 +1524,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/spotify/ads-claude-plugin.git",
|
||||
"sha": "a4bce9912db071d47dfb410086a48004e0539efa"
|
||||
"sha": "63585cc919da51dd24fab594d829869595301922"
|
||||
},
|
||||
"homepage": "https://github.com/spotify/ads-claude-plugin"
|
||||
},
|
||||
@@ -1560,7 +1571,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/sumup/sumup-skills.git",
|
||||
"sha": "802476c39a0422d3277e37288b03968ad731bc30"
|
||||
"sha": "0fd0a911ecaffd7187fe35e914d8ead6de584ffd"
|
||||
},
|
||||
"homepage": "https://www.sumup.com/"
|
||||
},
|
||||
@@ -1660,7 +1671,7 @@
|
||||
"url": "https://github.com/UI5/plugins-claude.git",
|
||||
"path": "plugins/ui5",
|
||||
"ref": "main",
|
||||
"sha": "5070dfc1cef711d6efad40beb43750027039d71f"
|
||||
"sha": "cec940abd4b7b6866de8e7e4522f3dba0449379d"
|
||||
},
|
||||
"homepage": "https://github.com/UI5/plugins-claude"
|
||||
},
|
||||
@@ -1673,7 +1684,7 @@
|
||||
"url": "https://github.com/UI5/plugins-claude.git",
|
||||
"path": "plugins/ui5-typescript-conversion",
|
||||
"ref": "main",
|
||||
"sha": "5070dfc1cef711d6efad40beb43750027039d71f"
|
||||
"sha": "cec940abd4b7b6866de8e7e4522f3dba0449379d"
|
||||
},
|
||||
"homepage": "https://github.com/UI5/plugins-claude"
|
||||
},
|
||||
@@ -1693,7 +1704,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/TSedmanDC/Voila-API-Skill.git",
|
||||
"sha": "b9cfcb860cb5ae4ece57d67422a6cdd92ef96739"
|
||||
"sha": "422c7beb772a0de4592a204584e0e990fc5dc139"
|
||||
},
|
||||
"homepage": "https://github.com/TSedmanDC/Voila-API-Skill"
|
||||
},
|
||||
@@ -1704,7 +1715,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/wix/skills.git",
|
||||
"sha": "15dda227e34959b1340e33bb9aede7e23a273f42"
|
||||
"sha": "bf25b5a45b2413b3581f3dcbcd63f3737791a051"
|
||||
},
|
||||
"homepage": "https://dev.wix.com/docs/wix-cli/guides/development/about-wix-skills"
|
||||
},
|
||||
@@ -1714,7 +1725,7 @@
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/Automattic/claude-code-wordpress.com.git",
|
||||
"sha": "e4d23c3bffdcdb7f70134ab6a1a110258ff75cfd"
|
||||
"sha": "052ca970df2c577d7c651e784935186ff93e6779"
|
||||
},
|
||||
"homepage": "https://developer.wordpress.com/wordpress-com-claude-code-plugin/"
|
||||
},
|
||||
@@ -1727,7 +1738,7 @@
|
||||
"url": "https://github.com/zapier/zapier-mcp.git",
|
||||
"path": "plugins/zapier",
|
||||
"ref": "main",
|
||||
"sha": "b93007e9a726c6ee93c57a949e732744ef5acbfd"
|
||||
"sha": "76c4669321847c8f72a6e0462c17f29fd437519a"
|
||||
},
|
||||
"homepage": "https://github.com/zapier/zapier-mcp/tree/main/plugins/zapier"
|
||||
},
|
||||
|
||||
@@ -14,10 +14,15 @@ The UI layer is **additive**. Under the hood it's still tools, resources, and th
|
||||
|
||||
## Claude host specifics
|
||||
|
||||
- `_meta.ui.prefersBorder: false` on a `ui://` resource removes the outer card border (mobile).
|
||||
| `_meta.ui.*` key | Where | Effect |
|
||||
|---|---|---|
|
||||
| `resourceUri` | tool | Which `ui://` resource the host renders for this tool's results. |
|
||||
| `visibility: ["app"]` | tool | Hide a widget-only helper tool (e.g. geometry/image fetcher called via `callServerTool`) from Claude's tool list. |
|
||||
| `prefersBorder: false` | resource | Drop the host's outer card border (mobile). |
|
||||
| `csp.{connectDomains, resourceDomains, baseUriDomains}` | resource | Declare external origins; default is block-all. `frameDomains` is currently restricted in Claude. |
|
||||
|
||||
- `hostContext.safeAreaInsets: {top, right, bottom, left}` (px) — honor these for notches and the composer overlay.
|
||||
- `_meta.ui.csp.{connectDomains, resourceDomains, baseUriDomains}` — declare external origins per resource; default is block-all. `frameDomains` is currently restricted in Claude.
|
||||
- Directory submission for MCP Apps requires 3–5 PNG screenshots, ≥1000px wide, cropped to the app response only (no prompt in the image). See https://claude.com/docs/connectors/building/submission#asset-specifications.
|
||||
- Directory submission requires OAuth or **authless** (`none`) — static bearer is private-deploy only and blocks listing — plus tool `annotations` and 3–5 PNG screenshots; see `references/directory-checklist.md`.
|
||||
|
||||
---
|
||||
|
||||
@@ -104,6 +109,7 @@ const server = new McpServer({ name: "contacts", version: "1.0.0" });
|
||||
// 1. The tool — returns DATA, declares which UI to show
|
||||
registerAppTool(server, "pick_contact", {
|
||||
description: "Open an interactive contact picker",
|
||||
annotations: { title: "Pick Contact", readOnlyHint: true },
|
||||
inputSchema: { filter: z.string().optional() },
|
||||
_meta: { ui: { resourceUri: "ui://widgets/contact-picker.html" } },
|
||||
}, async ({ filter }) => {
|
||||
@@ -172,7 +178,10 @@ The `/*__EXT_APPS_BUNDLE__*/` placeholder gets replaced by the server at startup
|
||||
| `app.updateModelContext({...})` | Widget → host | Update context silently (no visible message) |
|
||||
| `app.callServerTool({name, arguments})` | Widget → server | Call another tool on your server |
|
||||
| `app.openLink({url})` | Widget → host | Open a URL in a new tab (sandbox blocks `window.open`) |
|
||||
| `app.getHostContext()` / `app.onhostcontextchanged` | Host → widget | Theme (`light`/`dark`), locale, etc. |
|
||||
| `app.getHostContext()` / `app.onhostcontextchanged` | Host → widget | Theme, host CSS vars, `containerDimensions`, `displayMode`, `deviceCapabilities` |
|
||||
| `app.requestDisplayMode({mode})` | Widget → host | Ask for `inline` / `pip` / `fullscreen` |
|
||||
| `app.downloadFile({name, mimeType, content})` | Widget → host | Host-mediated download (base64 content) |
|
||||
| `new App(info, caps, {autoResize: true})` | — | Iframe height tracks rendered content |
|
||||
|
||||
`sendMessage` is the typical "user picked something, tell Claude" path. `updateModelContext` is for state that Claude should know about but shouldn't clutter the chat. `openLink` is **required** for any outbound navigation — `window.open` and `<a target="_blank">` are blocked by the sandbox attribute.
|
||||
|
||||
@@ -225,6 +234,7 @@ const pickerHtml = readFileSync("./widgets/picker.html", "utf8")
|
||||
|
||||
registerAppTool(server, "pick_contact", {
|
||||
description: "Open an interactive contact picker. User selects one contact.",
|
||||
annotations: { title: "Pick Contact", readOnlyHint: true },
|
||||
inputSchema: { filter: z.string().optional().describe("Name/email prefix filter") },
|
||||
_meta: { ui: { resourceUri: "ui://widgets/picker.html" } },
|
||||
}, async ({ filter }) => {
|
||||
@@ -348,6 +358,24 @@ Desktop caches UI resources aggressively. After editing widget HTML, **fully qui
|
||||
|
||||
The `sleep` keeps stdin open long enough to collect all responses. Parse the jsonl output with `jq` or a Python one-liner.
|
||||
|
||||
**Widget dev loop** — avoid the ⌘Q-relaunch cycle entirely by serving the inlined widget HTML at a plain GET route with a fake `ExtApps` shim that fires `ontoolresult` from a query param:
|
||||
|
||||
```ts
|
||||
app.get("/widget-preview", (_req, res) => {
|
||||
const shim = `globalThis.ExtApps={applyHostStyleVariables:()=>{},App:class{
|
||||
constructor(){this.h={}} ontoolresult;onhostcontextchanged;
|
||||
async connect(){const p=new URLSearchParams(location.search).get("payload");
|
||||
if(p)this.ontoolresult?.({content:[{type:"text",text:p}]});}
|
||||
getHostContext(){return{theme:"light"}}
|
||||
sendMessage(m){console.log("sendMessage",m)} updateModelContext(){}
|
||||
callServerTool(){return Promise.resolve({content:[]})} openLink(){} downloadFile(){}
|
||||
}};`;
|
||||
res.type("html").send(widgetHtml.replace("/*__EXT_APPS_BUNDLE__*/", shim));
|
||||
});
|
||||
```
|
||||
|
||||
Open `http://localhost:3000/widget-preview?payload={"rows":[...]}` in a normal browser tab and iterate with ordinary devtools.
|
||||
|
||||
**Host fallback** — use a host without the apps surface (or MCP Inspector) and confirm the tool's text content degrades gracefully.
|
||||
|
||||
**CSP debugging** — open the iframe's own devtools console. CSP violations are the #1 reason widgets silently fail (blank rectangle, no error in the main console). See `references/iframe-sandbox.md`.
|
||||
@@ -356,6 +384,9 @@ The `sleep` keeps stdin open long enough to collect all responses. Parse the jso
|
||||
|
||||
## Reference files
|
||||
|
||||
- `references/iframe-sandbox.md` — CSP/sandbox constraints, the bundle-inlining pattern, image handling
|
||||
- `references/iframe-sandbox.md` — CSP/sandbox constraints, the bundle-inlining pattern, image handling, host theming
|
||||
- `references/widget-templates.md` — reusable HTML scaffolds for picker / confirm / progress / display
|
||||
- `references/apps-sdk-messages.md` — the `App` class API: widget ↔ host ↔ server messaging
|
||||
- `references/apps-sdk-messages.md` — the `App` class API: widget ↔ host ↔ server messaging, lifecycle & supersession
|
||||
- `references/payload-budgeting.md` — host tool-result size caps, prune-then-truncate, heavy assets via `callServerTool`
|
||||
- `references/abuse-protection.md` — Anthropic egress CIDRs, tiered rate limiting, `trust proxy`, response caching
|
||||
- `references/directory-checklist.md` — pre-flight for connector-directory submission
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Abuse protection for authless hosted servers
|
||||
|
||||
An authless StreamableHTTP server is reachable by anything on the internet.
|
||||
There are three resources to protect: your compute, any upstream API quota
|
||||
your tools consume, and egress bandwidth for large `callServerTool` payloads.
|
||||
|
||||
## You don't get a per-user identity
|
||||
|
||||
In authless mode there is no token and stateless transport gives no session
|
||||
ID. Traffic from claude.ai is proxied through Anthropic's egress — every web
|
||||
user arrives from the same small set of IPs:
|
||||
|
||||
```
|
||||
160.79.104.0/21
|
||||
2607:6bc0::/48
|
||||
```
|
||||
|
||||
(See https://platform.claude.com/docs/en/api/ip-addresses.)
|
||||
|
||||
Claude Desktop, Claude Code, and other hosts connect **directly from the
|
||||
user's machine**, so those *do* have distinct per-user IPs. Per-IP limiting
|
||||
therefore works for direct-connect clients; for claude.ai you can only limit
|
||||
the aggregate Anthropic pool. If true per-user limits matter, that's the
|
||||
trigger to add OAuth.
|
||||
|
||||
## Tiered token-bucket (per-replica backstop)
|
||||
|
||||
```ts
|
||||
const ANTHROPIC_CIDRS = ["160.79.104.0/21", "2607:6bc0::/48"];
|
||||
const TIERS = {
|
||||
anthropic: { capacity: 600, refillPerSec: 100 }, // shared pool
|
||||
other: { capacity: 30, refillPerSec: 2 }, // per-IP
|
||||
};
|
||||
```
|
||||
|
||||
Match `req.ip` against the CIDRs, pick a bucket (`"anthropic"` or
|
||||
`"ip:<addr>"`), 429 + `Retry-After` on exhaust. This is a per-replica
|
||||
backstop — cross-replica enforcement belongs at the edge (Cloudflare, Cloud
|
||||
Armor), which keeps the containers stateless.
|
||||
|
||||
## `trust proxy` must match your topology
|
||||
|
||||
`req.ip` only honours `X-Forwarded-For` if `app.set('trust proxy', N)` is
|
||||
set. `true` trusts every hop, which lets a direct client send
|
||||
`X-Forwarded-For: 160.79.108.42` and claim the Anthropic tier. Set it to the
|
||||
exact number of trusted hops (e.g. `1` behind a single LB, `2` behind
|
||||
Cloudflare → origin LB) and **never `true` in production**.
|
||||
|
||||
## Hard-allowlisting Anthropic IPs is a product decision
|
||||
|
||||
Blocking everything outside `160.79.104.0/21` locks out Desktop, Claude Code,
|
||||
and every other MCP host. Use the CIDRs to **tier** rate limits, not to gate
|
||||
access, unless claude.ai-only is an explicit goal.
|
||||
|
||||
## Cache upstream responses
|
||||
|
||||
For tools that wrap a third-party API, an in-process LRU keyed on the
|
||||
normalized query (TTL hours, no secrets in the key) is the primary cost
|
||||
control — repeat queries become free and absorb thundering-herd. Rate limits
|
||||
are the safety net, not the first line.
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
The `@modelcontextprotocol/ext-apps` package provides the `App` class (browser side) and `registerAppTool`/`registerAppResource` helpers (server side). Messaging is bidirectional and persistent.
|
||||
|
||||
## Construction
|
||||
|
||||
```js
|
||||
const app = new App(
|
||||
{ name: "MyWidget", version: "1.0.0" },
|
||||
{}, // capabilities
|
||||
{ autoResize: true }, // options
|
||||
);
|
||||
```
|
||||
|
||||
`autoResize: true` wires a `ResizeObserver` that emits `ui/notifications/size-changed` so the host iframe height tracks your rendered content. Without it the frame is fixed-height and tall renders get clipped — set it for any widget whose height depends on data.
|
||||
|
||||
---
|
||||
|
||||
## Widget → Host
|
||||
@@ -63,6 +75,26 @@ card.querySelector("a").addEventListener("click", (e) => {
|
||||
|
||||
Host-mediated download (sandbox blocks direct `<a download>`). `content` is a base64 string.
|
||||
|
||||
```js
|
||||
const csv = rows.map((r) => Object.values(r).join(",")).join("\n");
|
||||
app.downloadFile({
|
||||
name: "export.csv",
|
||||
mimeType: "text/csv",
|
||||
content: btoa(unescape(encodeURIComponent(csv))),
|
||||
});
|
||||
```
|
||||
|
||||
### `app.requestDisplayMode({ mode })`
|
||||
|
||||
Ask the host to switch the widget between `"inline"`, `"pip"`, or `"fullscreen"`. Check `getHostContext().availableDisplayModes` first; hide the control if the mode isn't offered. The host responds by firing `onhostcontextchanged` with new `displayMode` and `containerDimensions` — re-render at the new size.
|
||||
|
||||
```js
|
||||
if (app.getHostContext()?.availableDisplayModes?.includes("fullscreen")) {
|
||||
expandBtn.hidden = false;
|
||||
expandBtn.onclick = () => app.requestDisplayMode({ mode: "fullscreen" });
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Host → Widget
|
||||
@@ -84,9 +116,22 @@ app.ontoolresult = ({ content }) => {
|
||||
|
||||
Fires with the arguments Claude passed to the tool. Useful if the widget needs to know what was asked for (e.g., highlight the search term).
|
||||
|
||||
### `app.ontoolinputpartial = ({ arguments }) => {...}` / `app.ontoolcancelled = () => {...}`
|
||||
|
||||
`ontoolinputpartial` fires while Claude is still streaming arguments — use it to show a skeleton ("Preparing: <title>…") before the result lands. `ontoolcancelled` fires if the call is aborted; clear the skeleton.
|
||||
|
||||
### `app.getHostContext()` / `app.onhostcontextchanged = (ctx) => {...}`
|
||||
|
||||
Read and subscribe to host context — `theme` (`"light"` / `"dark"`), locale, etc. Call `getHostContext()` **after** `connect()`. Subscribe for live updates (user toggles dark mode mid-conversation).
|
||||
Read and subscribe to host context. Call `getHostContext()` **after** `connect()`. Subscribe for live updates (user toggles dark mode, expands to fullscreen).
|
||||
|
||||
| `ctx.` field | Use |
|
||||
|---|---|
|
||||
| `theme` | `"light"` / `"dark"` — toggle a `.dark` class |
|
||||
| `styles.variables` | Host CSS tokens — pass to `applyHostStyleVariables()` so colors/fonts match host chrome |
|
||||
| `displayMode` / `availableDisplayModes` | Current mode and which `requestDisplayMode` targets are valid |
|
||||
| `containerDimensions.{maxHeight,width}` | Size your render to this instead of hard-coded px |
|
||||
| `deviceCapabilities.touch` | Switch hover-only affordances to tap (`pointerdown`) |
|
||||
| `safeAreaInsets` | Padding for notches / composer overlay |
|
||||
|
||||
```js
|
||||
const applyTheme = (t) =>
|
||||
@@ -129,14 +174,36 @@ No `{ notify }` destructure — `extra` is `RequestHandlerExtra`; progress goes
|
||||
## Lifecycle
|
||||
|
||||
1. Claude calls a tool with `_meta.ui.resourceUri` declared
|
||||
2. Host fetches the resource (your HTML) and renders it in an iframe
|
||||
2. Host fetches the resource (your HTML) and mounts a **fresh iframe** for this call
|
||||
3. Widget script runs, sets handlers, calls `await app.connect()`
|
||||
4. Host pipes the tool's return value → `ontoolresult` fires
|
||||
5. Widget renders, user interacts
|
||||
6. Widget calls `sendMessage` / `updateModelContext` / `callServerTool` as needed
|
||||
7. Widget persists until conversation context moves on — subsequent calls to the same tool reuse the iframe and fire `ontoolresult` again
|
||||
7. Iframe persists in the transcript; **the next call to the same tool mounts another iframe** alongside it
|
||||
|
||||
There's no explicit "submit and close" — the widget is a long-lived surface.
|
||||
There's no explicit "submit and close" — each instance is long-lived, but instances are not reused across calls.
|
||||
|
||||
### Supersession
|
||||
|
||||
Because earlier instances stay mounted, a click on a stale widget can `sendMessage` after a newer one has rendered. Detect this with a `BroadcastChannel` and make older instances inert:
|
||||
|
||||
```js
|
||||
let superseded = false;
|
||||
const seq = Date.now() + Math.random();
|
||||
const bc = new BroadcastChannel("my-widget");
|
||||
bc.onmessage = (e) => {
|
||||
if (e.data?.seq > seq) {
|
||||
superseded = true;
|
||||
document.body.classList.add("superseded"); // opacity:.45; pointer-events:none
|
||||
}
|
||||
};
|
||||
bc.postMessage({ seq });
|
||||
|
||||
// Guard outbound calls:
|
||||
function safeSend(msg) {
|
||||
if (!superseded) app.sendMessage(msg);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Connector-directory submission checklist
|
||||
|
||||
Pre-flight before submitting a remote MCP app to the Claude connector
|
||||
directory. Each item is a hard review criterion.
|
||||
|
||||
| Area | Requirement |
|
||||
|---|---|
|
||||
| **Auth** | OAuth (DCR or CIMD) or **`none`** (authless). Static bearer tokens are private-deploy only and block listing. Authless is valid for public-data servers — the server holds any upstream API keys. |
|
||||
| **Tool annotations** | Every tool sets `annotations.title` plus the relevant hints: `readOnlyHint: true` for fetch/search tools, `destructiveHint` / `idempotentHint` for writes, `openWorldHint: true` if the tool reaches an external system. |
|
||||
| **Tool names** | ≤ 64 characters, snake/kebab case. |
|
||||
| **Widget layout** | Inline height ≤ 500px, no nested scroll containers, 44pt minimum touch targets, WCAG-AA contrast in both themes. |
|
||||
| **Theming** | `html, body { background: transparent }`, `<meta name="color-scheme" content="light dark">`, adopt host CSS tokens via `applyHostStyleVariables`. |
|
||||
| **External links** | Use `app.openLink`. Declare each origin (e.g. `https://api.example.com`) in the connector's *Allowed link URIs* so the link skips the confirm modal. |
|
||||
| **Helper tools** | Widget-only tools (geometry/image fetchers) carry `_meta.ui.visibility: ["app"]` so they don't appear in Claude's tool list. |
|
||||
| **Screenshots** | 3–5 PNGs, ≥ 1000px wide, cropped to the app response only — no prompt text in frame. |
|
||||
|
||||
See `abuse-protection.md` for rate-limit and IP-tiering guidance once the
|
||||
authless endpoint is public.
|
||||
@@ -122,23 +122,38 @@ that survives un-inlined.
|
||||
|
||||
---
|
||||
|
||||
## Dark mode
|
||||
## Theme & host styles
|
||||
|
||||
```js
|
||||
const applyTheme = (theme) =>
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
The host renders the iframe inside its own card chrome — paint a **transparent** background and adopt host CSS tokens so the widget blends in across light/dark and across hosts.
|
||||
|
||||
app.onhostcontextchanged = (ctx) => applyTheme(ctx.theme);
|
||||
await app.connect();
|
||||
applyTheme(app.getHostContext()?.theme);
|
||||
```html
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
```
|
||||
|
||||
```css
|
||||
:root { --ink:#0f1111; --bg:#fff; color-scheme:light; }
|
||||
:root.dark { --ink:#e6e6e6; --bg:#1f2428; color-scheme:dark; }
|
||||
:root {
|
||||
--ink: var(--color-text-primary, #0f1111);
|
||||
--sub: var(--color-text-secondary, #5a6270);
|
||||
--line: var(--color-border-default, #e3e6ea);
|
||||
}
|
||||
html, body { background: transparent; color: var(--ink); }
|
||||
:root.dark .thumb { mix-blend-mode: normal; } /* multiply → images vanish in dark */
|
||||
```
|
||||
|
||||
```js
|
||||
const { App, applyHostStyleVariables } = globalThis.ExtApps;
|
||||
|
||||
function applyHostContext(ctx) {
|
||||
document.documentElement.classList.toggle("dark", ctx?.theme === "dark");
|
||||
if (ctx?.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
|
||||
}
|
||||
app.onhostcontextchanged = applyHostContext;
|
||||
await app.connect();
|
||||
applyHostContext(app.getHostContext());
|
||||
```
|
||||
|
||||
`applyHostStyleVariables` writes the host's `--color-*` / `--font-*` / `--border-radius-*` tokens onto `:root`; the hex values above are fallbacks for hosts that don't supply them.
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Payload budgeting
|
||||
|
||||
Hosts cap tool-result text. claude.ai and Claude Desktop truncate at roughly
|
||||
**150,000 characters**; Claude Code at ~25k tokens. When a tool result exceeds
|
||||
the cap, the host substitutes a file-pointer string in place of your JSON. The
|
||||
widget then receives non-JSON in `ontoolresult`, `JSON.parse` throws, and the
|
||||
user sees something like *"Bad payload: SyntaxError: Unexpected token 'E'"* —
|
||||
with no hint that size was the cause.
|
||||
|
||||
## Symptom → cause
|
||||
|
||||
| Symptom | Likely cause |
|
||||
|---|---|
|
||||
| Widget shows a JSON parse error on `content[0].text` | Result over the host cap; host swapped in a file-pointer string |
|
||||
| Works for one query, breaks for "all of X" | Row count × column count crossed the cap |
|
||||
| Works in MCP Inspector, breaks in Desktop | Inspector has no cap; Desktop does |
|
||||
|
||||
## Strategy
|
||||
|
||||
Cap your own payload at ~130KB and degrade in order:
|
||||
|
||||
1. **Ship full rows** when `JSON.stringify(rows).length` is under the cap.
|
||||
2. **Prune columns** to those the rendering spec actually references. Walk the
|
||||
spec for both `field: "..."` keys *and* `datum.X` / `datum['X']` inside
|
||||
expression strings — if the spec aliases a column via a `calculate`
|
||||
transform, the alias appears as `field:` but the source column only appears
|
||||
as `datum.X`, and dropping it leaves the widget with NaN.
|
||||
3. **Truncate rows** as a last resort and include `{ truncated: N }` in the
|
||||
payload so the widget can label it.
|
||||
|
||||
```ts
|
||||
const MAX = 130_000;
|
||||
let out = rows;
|
||||
if (JSON.stringify(out).length > MAX) {
|
||||
const keep = referencedFields(spec); // field: + datum.X refs
|
||||
out = rows.map((r) => pick(r, keep));
|
||||
if (JSON.stringify(out).length > MAX) {
|
||||
const per = JSON.stringify(out[0] ?? {}).length || 1;
|
||||
out = out.slice(0, Math.floor(MAX / per));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Heavy assets go via `callServerTool`, not the result
|
||||
|
||||
Geometry, image bytes, or any blob the widget needs but Claude doesn't should
|
||||
be served by a separate tool the widget calls after mount:
|
||||
|
||||
```js
|
||||
const topo = await app.callServerTool({ name: "get-topojson", arguments: { level } });
|
||||
```
|
||||
|
||||
Mark that helper tool with `_meta.ui.visibility: ["app"]` so it doesn't appear
|
||||
in Claude's tool list.
|
||||
Reference in New Issue
Block a user