wormclaude 1.0.119 → 1.0.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/theme.js +1 -1
- package/dist/tui.js +6 -1
- package/package.json +1 -1
- package/skills/build-mcp-app/SKILL.md +0 -393
- package/skills/build-mcp-app/references/abuse-protection.md +0 -60
- package/skills/build-mcp-app/references/apps-sdk-messages.md +0 -227
- package/skills/build-mcp-app/references/directory-checklist.md +0 -18
- package/skills/build-mcp-app/references/iframe-sandbox.md +0 -164
- package/skills/build-mcp-app/references/payload-budgeting.md +0 -54
- package/skills/build-mcp-app/references/widget-templates.md +0 -249
- package/skills/build-mcp-server/SKILL.md +0 -222
- package/skills/build-mcp-server/references/auth.md +0 -108
- package/skills/build-mcp-server/references/deploy-cloudflare-workers.md +0 -106
- package/skills/build-mcp-server/references/elicitation.md +0 -129
- package/skills/build-mcp-server/references/remote-http-scaffold.md +0 -211
- package/skills/build-mcp-server/references/resources-and-prompts.md +0 -122
- package/skills/build-mcp-server/references/server-capabilities.md +0 -164
- package/skills/build-mcp-server/references/tool-design.md +0 -189
- package/skills/build-mcp-server/references/versions.md +0 -25
- package/skills/build-mcpb/SKILL.md +0 -200
- package/skills/build-mcpb/references/local-security.md +0 -149
- package/skills/build-mcpb/references/manifest-schema.md +0 -156
- package/skills/docx/script/__init__.py +0 -1
- package/skills/docx/script/accept_chages.py +0 -135
- package/skills/docx/script/comment.py +0 -318
- package/skills/docx/script/office/helpers/__init__.py +0 -0
- package/skills/docx/script/office/helpers/merge_runs.py +0 -199
- package/skills/docx/script/office/helpers/simplify_redlines.py +0 -197
- package/skills/docx/script/office/pack.py +0 -159
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/skills/docx/script/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/skills/docx/script/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/skills/docx/script/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/skills/docx/script/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/skills/docx/script/office/schemas/mce/mc.xsd +0 -75
- package/skills/docx/script/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/skills/docx/script/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/skills/docx/script/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/skills/docx/script/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/skills/docx/script/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/skills/docx/script/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/skills/docx/script/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/skills/docx/script/office/soffice.py +0 -183
- package/skills/docx/script/office/unpack.py +0 -132
- package/skills/docx/script/office/validate.py +0 -117
- package/skills/docx/script/office/validators/__init__.py +0 -15
- package/skills/docx/script/office/validators/base.py +0 -851
- package/skills/docx/script/office/validators/docx.py +0 -446
- package/skills/docx/script/office/validators/pptx.py +0 -275
- package/skills/docx/script/office/validators/redlining.py +0 -247
- package/skills/docx/script/templates/comments.xml +0 -3
- package/skills/docx/script/templates/commentsExtended.xml +0 -3
- package/skills/docx/script/templates/commentsExtensible.xml +0 -3
- package/skills/docx/script/templates/commentsIds.xml +0 -3
- package/skills/docx/script/templates/people.xml +0 -3
- package/skills/docx/skill.md +0 -593
- package/skills/explain.md +0 -14
- package/skills/frontend-design/SKILL.md +0 -42
- package/skills/pdf/FORMS.md +0 -294
- package/skills/pdf/REFERENCE.md +0 -612
- package/skills/pdf/SKILL.md +0 -314
- package/skills/pdf/scripts/check_bounding_boxes.py +0 -65
- package/skills/pdf/scripts/check_fillable_fields.py +0 -11
- package/skills/pdf/scripts/convert_pdf_to_images.py +0 -33
- package/skills/pdf/scripts/create_validation_image.py +0 -37
- package/skills/pdf/scripts/extract_form_field_info.py +0 -122
- package/skills/pdf/scripts/extract_form_structure.py +0 -115
- package/skills/pdf/scripts/fill_fillable_fields.py +0 -98
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -107
- package/skills/playground/SKILL.md +0 -77
- package/skills/playground/templates/code-map.md +0 -158
- package/skills/playground/templates/concept-map.md +0 -73
- package/skills/playground/templates/data-explorer.md +0 -67
- package/skills/playground/templates/design-playground.md +0 -67
- package/skills/playground/templates/diff-review.md +0 -179
- package/skills/playground/templates/document-critique.md +0 -171
- package/skills/pptx/SKILL.md +0 -230
- package/skills/pptx/editing.md +0 -205
- package/skills/pptx/pptxgenjs.md +0 -437
- package/skills/pptx/scripts/__init__.py +0 -0
- package/skills/pptx/scripts/add_slide.py +0 -195
- package/skills/pptx/scripts/clean.py +0 -286
- package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- package/skills/pptx/scripts/office/helpers/merge_runs.py +0 -199
- package/skills/pptx/scripts/office/helpers/simplify_redlines.py +0 -197
- package/skills/pptx/scripts/office/pack.py +0 -159
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/skills/pptx/scripts/office/schemas/mce/mc.xsd +0 -75
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/skills/pptx/scripts/office/soffice.py +0 -183
- package/skills/pptx/scripts/office/unpack.py +0 -132
- package/skills/pptx/scripts/office/validate.py +0 -117
- package/skills/pptx/scripts/office/validators/__init__.py +0 -15
- package/skills/pptx/scripts/office/validators/base.py +0 -851
- package/skills/pptx/scripts/office/validators/docx.py +0 -446
- package/skills/pptx/scripts/office/validators/pptx.py +0 -275
- package/skills/pptx/scripts/office/validators/redlining.py +0 -247
- package/skills/pptx/scripts/thumbnail.py +0 -289
- package/skills/recon.md +0 -16
- package/skills/security-audit/SKILL.md +0 -26
- package/skills/talent-creator/SKILL.md +0 -486
- package/skills/talent-creator/agents/analyzer.md +0 -274
- package/skills/talent-creator/agents/comparator.md +0 -202
- package/skills/talent-creator/agents/grader.md +0 -223
- package/skills/talent-creator/assets/eval_review.html +0 -146
- package/skills/talent-creator/eval-viewer/generate_review.py +0 -471
- package/skills/talent-creator/eval-viewer/viewer.html +0 -1325
- package/skills/talent-creator/references/schemas.md +0 -430
- package/skills/talent-creator/scripts/__init__.py +0 -0
- package/skills/talent-creator/scripts/aggregate_benchmark.py +0 -401
- package/skills/talent-creator/scripts/generate_report.py +0 -326
- package/skills/talent-creator/scripts/improve_description.py +0 -247
- package/skills/talent-creator/scripts/package_skill.py +0 -136
- package/skills/talent-creator/scripts/quick_validate.py +0 -146
- package/skills/talent-creator/scripts/run_eval.py +0 -310
- package/skills/talent-creator/scripts/run_loop.py +0 -328
- package/skills/talent-creator/scripts/utils.py +0 -47
- package/skills/xlsx/SKILL.md +0 -300
- package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- package/skills/xlsx/scripts/office/helpers/merge_runs.py +0 -199
- package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +0 -197
- package/skills/xlsx/scripts/office/pack.py +0 -159
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +0 -75
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/skills/xlsx/scripts/office/soffice.py +0 -183
- package/skills/xlsx/scripts/office/unpack.py +0 -132
- package/skills/xlsx/scripts/office/validate.py +0 -117
- package/skills/xlsx/scripts/office/validators/__init__.py +0 -15
- package/skills/xlsx/scripts/office/validators/base.py +0 -851
- package/skills/xlsx/scripts/office/validators/docx.py +0 -446
- package/skills/xlsx/scripts/office/validators/pptx.py +0 -275
- package/skills/xlsx/scripts/office/validators/redlining.py +0 -247
- package/skills/xlsx/scripts/recalc.py +0 -184
package/dist/theme.js
CHANGED
package/dist/tui.js
CHANGED
|
@@ -12,7 +12,7 @@ import { itemAnsi, markdownAnsi } from './ansi.js';
|
|
|
12
12
|
import { theme, VERSION } from './theme.js';
|
|
13
13
|
import { cleanModelText } from './textclean.js';
|
|
14
14
|
import { stripInlineToolCalls, stripEchoBlocks } from './inlinetools.js';
|
|
15
|
-
import { COMMANDS, runSlashCommand } from './commands.js';
|
|
15
|
+
import { COMMANDS, runSlashCommand, getPendingPentestCommand } from './commands.js';
|
|
16
16
|
import { cmdDesc, t, setLang, loadLang, getLang } from './i18n.js';
|
|
17
17
|
import { getSkill, getSkills, buildSkillPrompt } from './skills.js';
|
|
18
18
|
import { getExtCommand, getExtCommands, buildExtCommandPrompt } from './extensions.js';
|
|
@@ -621,6 +621,11 @@ export async function runTui() {
|
|
|
621
621
|
return;
|
|
622
622
|
}
|
|
623
623
|
inputHistory.push(v);
|
|
624
|
+
// Bekleyen güvenlik taraması onayı: "run"/"onayla" yazıldıysa tam pentest komutuna çevir
|
|
625
|
+
// (yoksa "run" modele chat olarak gidip "I'm not sure I follow" diyordu — çift işlem).
|
|
626
|
+
const _pending = getPendingPentestCommand(v);
|
|
627
|
+
if (_pending)
|
|
628
|
+
v = _pending;
|
|
624
629
|
// ! shell modu — LLM'siz doğrudan shell komutu; çıktıyı modele bağlam olarak ekle
|
|
625
630
|
if (v.startsWith('!') && v.length > 1) {
|
|
626
631
|
const cmd = v.slice(1).trim();
|
package/package.json
CHANGED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: build-mcp-app
|
|
3
|
-
description: Reach for this skill when someone wants to build an "MCP app", bolt "interactive UI" or "widgets" onto an MCP server, "render components in chat", create "MCP UI resources", make a tool that pops a "form", "picker", "dashboard", or "confirmation dialog" inline in the conversation, or brings up the "apps SDK" in an MCP context. Use it AFTER the build-mcp-server skill has nailed down the deployment model, or when the user already knows they want UI widgets.
|
|
4
|
-
license: WormClaude
|
|
5
|
-
version: 0.1.0
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Build an MCP App (Interactive UI Widgets)
|
|
9
|
-
|
|
10
|
-
An MCP app is an ordinary MCP server that **additionally serves UI resources** — interactive components that render inline in the chat surface. Write it once and it runs in WormClaude *and* ChatGPT, plus any other host that implements the apps surface.
|
|
11
|
-
|
|
12
|
-
The UI layer is **purely additive**. Underneath, it's still tools, resources, and the same wire protocol. If you've never built a plain MCP server, the `build-mcp-server` skill covers that base layer; this skill stacks widgets on top of it.
|
|
13
|
-
|
|
14
|
-
> **Testing in WormClaude:** Add the server as a custom connector in claude.ai (through a Cloudflare tunnel for local dev) — this puts the real iframe sandbox and `hostContext` through their paces. See https://claude.com/docs/connectors/building/testing.
|
|
15
|
-
|
|
16
|
-
## WormClaude host specifics
|
|
17
|
-
|
|
18
|
-
| `_meta.ui.*` key | Where | Effect |
|
|
19
|
-
|---|---|---|
|
|
20
|
-
| `resourceUri` | tool | Which `ui://` resource the host renders for this tool's results. |
|
|
21
|
-
| `visibility: ["app"]` | tool | Keep a widget-only helper tool (e.g. a geometry/image fetcher invoked through `callServerTool`) out of WormClaude's tool list. |
|
|
22
|
-
| `prefersBorder: false` | resource | Remove the host's outer card border (mobile). |
|
|
23
|
-
| `csp.{connectDomains, resourceDomains, baseUriDomains}` | resource | Declare external origins; the default blocks everything. `frameDomains` is currently restricted in WormClaude. |
|
|
24
|
-
|
|
25
|
-
- `hostContext.safeAreaInsets: {top, right, bottom, left}` (px) — respect these for notches and the composer overlay.
|
|
26
|
-
- Directory submission needs OAuth or **authless** (`none`) — static bearer is private-deploy only and blocks listing — along with tool `annotations` and 3–5 PNG screenshots; see `references/directory-checklist.md`.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## When a widget beats plain text
|
|
31
|
-
|
|
32
|
-
Don't bolt on UI just because you can — most tools are perfectly fine returning text or JSON. Add a widget only when one of these holds:
|
|
33
|
-
|
|
34
|
-
| Signal | Widget type |
|
|
35
|
-
|---|---|
|
|
36
|
-
| Tool needs structured input WormClaude can't reliably infer | Form |
|
|
37
|
-
| User has to pick from a list WormClaude can't rank (files, contacts, records) | Picker / table |
|
|
38
|
-
| A destructive or billable action needs explicit confirmation | Confirm dialog |
|
|
39
|
-
| Output is spatial or visual (charts, maps, diffs, previews) | Display widget |
|
|
40
|
-
| A long-running job the user wants to watch | Progress / live status |
|
|
41
|
-
|
|
42
|
-
If none of these apply, drop the widget. Text is quicker to build and quicker for the user.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Widgets vs Elicitation — route correctly
|
|
47
|
-
|
|
48
|
-
Before you build a widget, check whether **elicitation** already covers the case. Elicitation is spec-native, needs no UI code, and runs in any compliant host.
|
|
49
|
-
|
|
50
|
-
| Need | Elicitation | Widget |
|
|
51
|
-
|---|---|---|
|
|
52
|
-
| Confirm yes/no | ✅ | overkill |
|
|
53
|
-
| Pick from short enum | ✅ | overkill |
|
|
54
|
-
| Fill a flat form (name, email, date) | ✅ | overkill |
|
|
55
|
-
| Pick from a large/searchable list | ❌ (no scroll/search) | ✅ |
|
|
56
|
-
| Visual preview before choosing | ❌ | ✅ |
|
|
57
|
-
| Chart / map / diff view | ❌ | ✅ |
|
|
58
|
-
| Live-updating progress | ❌ | ✅ |
|
|
59
|
-
|
|
60
|
-
If elicitation handles it, go with elicitation. See `../build-mcp-server/references/elicitation.md`.
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Architecture: two deployment shapes
|
|
65
|
-
|
|
66
|
-
### Remote MCP app (most common)
|
|
67
|
-
|
|
68
|
-
A hosted streamable-HTTP server. Widget templates are served as **resources**, and tool results point at them. The host fetches the resource, renders it inside an iframe sandbox, and relays messages between the widget and WormClaude.
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
┌──────────┐ tools/call ┌────────────┐
|
|
72
|
-
│ Claude │─────────────> │ MCP server │
|
|
73
|
-
│ host │<── result ────│ (remote) │
|
|
74
|
-
│ │ + widget ref │ │
|
|
75
|
-
│ │ │ │
|
|
76
|
-
│ │ resources/read│ │
|
|
77
|
-
│ │─────────────> │ widget │
|
|
78
|
-
│ ┌──────┐ │<── template ──│ HTML/JS │
|
|
79
|
-
│ │iframe│ │ └────────────┘
|
|
80
|
-
│ │widget│ │
|
|
81
|
-
│ └──────┘ │
|
|
82
|
-
└──────────┘
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### MCPB-packaged MCP app (local + UI)
|
|
86
|
-
|
|
87
|
-
The same widget mechanism, except the server runs locally inside an MCPB bundle. Use this when the widget has to drive a **local** application — say, a file picker that browses the real local disk, or a dialog that controls a desktop app.
|
|
88
|
-
|
|
89
|
-
For the MCPB packaging mechanics, hand off to the **`build-mcpb`** skill. Everything below holds for both shapes.
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## How widgets attach to tools
|
|
94
|
-
|
|
95
|
-
A widget-enabled tool comes with **two separate registrations**:
|
|
96
|
-
|
|
97
|
-
1. **The tool** points to a UI resource via `_meta.ui.resourceUri`. Its handler returns plain text/JSON — NOT the HTML.
|
|
98
|
-
2. **The resource** is registered on its own and serves the HTML.
|
|
99
|
-
|
|
100
|
-
When WormClaude calls the tool, the host notices `_meta.ui.resourceUri`, fetches that resource, renders it in an iframe, and feeds the tool's return value into the iframe through the `ontoolresult` event.
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
104
|
-
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE }
|
|
105
|
-
from "@modelcontextprotocol/ext-apps/server";
|
|
106
|
-
import { z } from "zod";
|
|
107
|
-
|
|
108
|
-
const server = new McpServer({ name: "contacts", version: "1.0.0" });
|
|
109
|
-
|
|
110
|
-
// 1. The tool — returns DATA, declares which UI to show
|
|
111
|
-
registerAppTool(server, "pick_contact", {
|
|
112
|
-
description: "Open an interactive contact picker",
|
|
113
|
-
annotations: { title: "Pick Contact", readOnlyHint: true },
|
|
114
|
-
inputSchema: { filter: z.string().optional() },
|
|
115
|
-
_meta: { ui: { resourceUri: "ui://widgets/contact-picker.html" } },
|
|
116
|
-
}, async ({ filter }) => {
|
|
117
|
-
const contacts = await db.contacts.search(filter);
|
|
118
|
-
// Plain JSON — the widget receives this via ontoolresult
|
|
119
|
-
return { content: [{ type: "text", text: JSON.stringify(contacts) }] };
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// 2. The resource — serves the HTML
|
|
123
|
-
registerAppResource(
|
|
124
|
-
server,
|
|
125
|
-
"Contact Picker",
|
|
126
|
-
"ui://widgets/contact-picker.html",
|
|
127
|
-
{},
|
|
128
|
-
async () => ({
|
|
129
|
-
contents: [{
|
|
130
|
-
uri: "ui://widgets/contact-picker.html",
|
|
131
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
132
|
-
text: pickerHtml, // your HTML string
|
|
133
|
-
}],
|
|
134
|
-
}),
|
|
135
|
-
);
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
The `ui://` URI scheme is just a convention. The mime type MUST be `RESOURCE_MIME_TYPE` (`"text/html;profile=mcp-app"`) — that's how the host knows to render it as an interactive iframe instead of merely showing the source.
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## Widget runtime — the `App` class
|
|
143
|
-
|
|
144
|
-
Inside the iframe, your script communicates with the host through the `App` class from `@modelcontextprotocol/ext-apps`. It's a **persistent, two-way connection** — the widget stays alive for as long as the conversation is active, taking in new tool results and pushing out user actions.
|
|
145
|
-
|
|
146
|
-
```html
|
|
147
|
-
<script type="module">
|
|
148
|
-
/* ext-apps bundle inlined at build time → globalThis.ExtApps */
|
|
149
|
-
/*__EXT_APPS_BUNDLE__*/
|
|
150
|
-
const { App } = globalThis.ExtApps;
|
|
151
|
-
|
|
152
|
-
const app = new App({ name: "ContactPicker", version: "1.0.0" }, {});
|
|
153
|
-
|
|
154
|
-
// Set handlers BEFORE connecting
|
|
155
|
-
app.ontoolresult = ({ content }) => {
|
|
156
|
-
const contacts = JSON.parse(content[0].text);
|
|
157
|
-
render(contacts);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
await app.connect();
|
|
161
|
-
|
|
162
|
-
// Later, when the user clicks something:
|
|
163
|
-
function onPick(contact) {
|
|
164
|
-
app.sendMessage({
|
|
165
|
-
role: "user",
|
|
166
|
-
content: [{ type: "text", text: `Selected contact: ${contact.id}` }],
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
</script>
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
At startup the server swaps the `/*__EXT_APPS_BUNDLE__*/` placeholder for the contents of `@modelcontextprotocol/ext-apps/app-with-deps` — `references/iframe-sandbox.md` explains why this is required and gives the rewrite snippet. **Don't** `import { App } from "https://esm.sh/..."`; the iframe's CSP blocks the transitive dependency fetches and the widget comes up blank.
|
|
173
|
-
|
|
174
|
-
| Method | Direction | Use for |
|
|
175
|
-
|---|---|---|
|
|
176
|
-
| `app.ontoolresult = fn` | Host → widget | Receive the tool's return value |
|
|
177
|
-
| `app.ontoolinput = fn` | Host → widget | Receive the tool's input args (what Claude passed) |
|
|
178
|
-
| `app.sendMessage({...})` | Widget → host | Inject a message into the conversation |
|
|
179
|
-
| `app.updateModelContext({...})` | Widget → host | Update context silently (no visible message) |
|
|
180
|
-
| `app.callServerTool({name, arguments})` | Widget → server | Call another tool on your server |
|
|
181
|
-
| `app.openLink({url})` | Widget → host | Open a URL in a new tab (sandbox blocks `window.open`) |
|
|
182
|
-
| `app.getHostContext()` / `app.onhostcontextchanged` | Host → widget | Theme, host CSS vars, `containerDimensions`, `displayMode`, `deviceCapabilities` |
|
|
183
|
-
| `app.requestDisplayMode({mode})` | Widget → host | Ask for `inline` / `pip` / `fullscreen` |
|
|
184
|
-
| `app.downloadFile({name, mimeType, content})` | Widget → host | Host-mediated download (base64 content) |
|
|
185
|
-
| `new App(info, caps, {autoResize: true})` | — | Iframe height tracks rendered content |
|
|
186
|
-
|
|
187
|
-
`sendMessage` is the usual "user picked something, let WormClaude know" path. `updateModelContext` is for state WormClaude should be aware of but that shouldn't clutter the chat. `openLink` is **required** for any outbound navigation — the sandbox attribute blocks `window.open` and `<a target="_blank">`.
|
|
188
|
-
|
|
189
|
-
**What widgets cannot do:**
|
|
190
|
-
- Reach the host page's DOM, cookies, or storage
|
|
191
|
-
- Make network calls to arbitrary origins (CSP-restricted — go through `callServerTool`)
|
|
192
|
-
- Open popups or navigate on their own — use `app.openLink({url})`
|
|
193
|
-
- Load remote images reliably — inline them as `data:` URLs server-side
|
|
194
|
-
|
|
195
|
-
Keep widgets **small and single-purpose**. A picker picks. A chart displays. Don't cram a whole sub-app into the iframe — break it into several tools, each with a focused widget.
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## Scaffold: minimal picker widget
|
|
200
|
-
|
|
201
|
-
**Install:**
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps zod express
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
**Server (`src/server.ts`):**
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
211
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
212
|
-
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE }
|
|
213
|
-
from "@modelcontextprotocol/ext-apps/server";
|
|
214
|
-
import express from "express";
|
|
215
|
-
import { readFileSync } from "node:fs";
|
|
216
|
-
import { createRequire } from "node:module";
|
|
217
|
-
import { z } from "zod";
|
|
218
|
-
|
|
219
|
-
const require = createRequire(import.meta.url);
|
|
220
|
-
const server = new McpServer({ name: "contact-picker", version: "1.0.0" });
|
|
221
|
-
|
|
222
|
-
// Inline the ext-apps browser bundle into the widget HTML.
|
|
223
|
-
// The iframe CSP blocks CDN script fetches — bundling is mandatory.
|
|
224
|
-
const bundle = readFileSync(
|
|
225
|
-
require.resolve("@modelcontextprotocol/ext-apps/app-with-deps"), "utf8",
|
|
226
|
-
).replace(/export\{([^}]+)\};?\s*$/, (_, body) =>
|
|
227
|
-
"globalThis.ExtApps={" +
|
|
228
|
-
body.split(",").map((p) => {
|
|
229
|
-
const [local, exported] = p.split(" as ").map((s) => s.trim());
|
|
230
|
-
return `${exported ?? local}:${local}`;
|
|
231
|
-
}).join(",") + "};",
|
|
232
|
-
);
|
|
233
|
-
const pickerHtml = readFileSync("./widgets/picker.html", "utf8")
|
|
234
|
-
.replace("/*__EXT_APPS_BUNDLE__*/", () => bundle);
|
|
235
|
-
|
|
236
|
-
registerAppTool(server, "pick_contact", {
|
|
237
|
-
description: "Open an interactive contact picker. User selects one contact.",
|
|
238
|
-
annotations: { title: "Pick Contact", readOnlyHint: true },
|
|
239
|
-
inputSchema: { filter: z.string().optional().describe("Name/email prefix filter") },
|
|
240
|
-
_meta: { ui: { resourceUri: "ui://widgets/picker.html" } },
|
|
241
|
-
}, async ({ filter }) => {
|
|
242
|
-
const contacts = await db.contacts.search(filter ?? "");
|
|
243
|
-
return { content: [{ type: "text", text: JSON.stringify(contacts) }] };
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
registerAppResource(server, "Contact Picker", "ui://widgets/picker.html", {},
|
|
247
|
-
async () => ({
|
|
248
|
-
contents: [{ uri: "ui://widgets/picker.html", mimeType: RESOURCE_MIME_TYPE, text: pickerHtml }],
|
|
249
|
-
}),
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
const app = express();
|
|
253
|
-
app.use(express.json());
|
|
254
|
-
app.post("/mcp", async (req, res) => {
|
|
255
|
-
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
256
|
-
res.on("close", () => transport.close());
|
|
257
|
-
await server.connect(transport);
|
|
258
|
-
await transport.handleRequest(req, res, req.body);
|
|
259
|
-
});
|
|
260
|
-
app.listen(process.env.PORT ?? 3000);
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
For local-only widget apps (driving a desktop app, reading local files), switch the transport to `StdioServerTransport` and package it with the `build-mcpb` skill.
|
|
264
|
-
|
|
265
|
-
**Widget (`widgets/picker.html`):**
|
|
266
|
-
|
|
267
|
-
```html
|
|
268
|
-
<!doctype html>
|
|
269
|
-
<meta charset="utf-8" />
|
|
270
|
-
<style>
|
|
271
|
-
body { font: 14px system-ui; margin: 0; }
|
|
272
|
-
ul { list-style: none; padding: 0; margin: 0; max-height: 300px; overflow-y: auto; }
|
|
273
|
-
li { padding: 10px 14px; cursor: pointer; border-bottom: 1px solid #eee; }
|
|
274
|
-
li:hover { background: #f5f5f5; }
|
|
275
|
-
.sub { color: #666; font-size: 12px; }
|
|
276
|
-
</style>
|
|
277
|
-
<ul id="list"></ul>
|
|
278
|
-
<script type="module">
|
|
279
|
-
/*__EXT_APPS_BUNDLE__*/
|
|
280
|
-
const { App } = globalThis.ExtApps;
|
|
281
|
-
(async () => {
|
|
282
|
-
const app = new App({ name: "ContactPicker", version: "1.0.0" }, {});
|
|
283
|
-
const ul = document.getElementById("list");
|
|
284
|
-
|
|
285
|
-
app.ontoolresult = ({ content }) => {
|
|
286
|
-
const contacts = JSON.parse(content[0].text);
|
|
287
|
-
ul.innerHTML = "";
|
|
288
|
-
for (const c of contacts) {
|
|
289
|
-
const li = document.createElement("li");
|
|
290
|
-
li.innerHTML = `<div>${c.name}</div><div class="sub">${c.email}</div>`;
|
|
291
|
-
li.addEventListener("click", () => {
|
|
292
|
-
app.sendMessage({
|
|
293
|
-
role: "user",
|
|
294
|
-
content: [{ type: "text", text: `Selected contact: ${c.id} (${c.name})` }],
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
ul.append(li);
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
await app.connect();
|
|
302
|
-
})();
|
|
303
|
-
</script>
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
See `references/widget-templates.md` for additional widget shapes.
|
|
307
|
-
|
|
308
|
-
---
|
|
309
|
-
|
|
310
|
-
## Design notes that save you a rewrite
|
|
311
|
-
|
|
312
|
-
**One widget per tool.** Don't give in to the temptation to build a single mega-widget that does it all. One tool → one focused widget → one clear result shape. WormClaude reasons about these much more reliably.
|
|
313
|
-
|
|
314
|
-
**The tool description has to mention the widget.** WormClaude only sees the tool description when it decides what to call. Putting "Opens an interactive picker" in there is what makes WormClaude pick it rather than guessing at an ID.
|
|
315
|
-
|
|
316
|
-
**Widgets are optional at runtime.** Hosts that don't implement the apps surface just ignore `_meta.ui` and render the tool's text content as usual. Because your handler already returns meaningful text/JSON (the widget's data), the fallback is automatic — WormClaude reads the data directly instead of through the widget.
|
|
317
|
-
|
|
318
|
-
**Don't block on widget results for read-only tools.** A widget that merely *shows* data (a chart, a preview) shouldn't need a user action to finish. Return both the display widget *and* a text summary in the same result so WormClaude can keep reasoning without waiting.
|
|
319
|
-
|
|
320
|
-
**Fork the layout by item count, not by tool count.** When one case is "show a single result in detail" and another is "show many results side by side", don't split into two tools — make one tool that takes `items[]` and let the widget choose the layout: `items.length === 1` → detail view, `> 1` → carousel. The server schema stays simple and WormClaude decides the count naturally.
|
|
321
|
-
|
|
322
|
-
**Put WormClaude's reasoning into the payload.** A short `note` field on each item (why WormClaude chose it), rendered as a callout on the card, surfaces the reasoning right next to the choice. Mention the field in the tool description so WormClaude fills it in.
|
|
323
|
-
|
|
324
|
-
**Normalize image shapes server-side.** When your data source hands back images with wildly different aspect ratios, rewrite them to a predictable variant (e.g. square-bounded) *before* fetching them for the inline data URL. Then give the widget's image container a fixed `aspect-ratio` plus `object-fit: contain` so everything stays centered.
|
|
325
|
-
|
|
326
|
-
**Match the host theme.** Use `app.getHostContext()?.theme` (after `connect()`) together with `app.onhostcontextchanged` for live updates. Toggle a `.dark` class on `<html>`, keep colors in CSS custom properties with a `:root.dark {}` override block, and set `color-scheme`. Turn off `mix-blend-mode: multiply` in dark mode — it makes images disappear.
|
|
327
|
-
|
|
328
|
-
---
|
|
329
|
-
|
|
330
|
-
## Testing
|
|
331
|
-
|
|
332
|
-
**WormClaude Desktop** — current builds still expect the `command`/`args` config shape (there's no native `"type": "http"`). Wrap it with `mcp-remote` and force the `http-only` transport so the SSE probe doesn't eat the widget-capability negotiation:
|
|
333
|
-
|
|
334
|
-
```json
|
|
335
|
-
{
|
|
336
|
-
"mcpServers": {
|
|
337
|
-
"my-server": {
|
|
338
|
-
"command": "npx",
|
|
339
|
-
"args": ["-y", "mcp-remote", "http://localhost:3000/mcp",
|
|
340
|
-
"--allow-http", "--transport", "http-only"]
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
Desktop caches UI resources aggressively. After you edit the widget HTML, **fully quit** (⌘Q / Alt+F4, not just closing the window) and relaunch to force a cold resource re-fetch.
|
|
347
|
-
|
|
348
|
-
**Headless JSON-RPC loop** — quick iteration without clicking through Desktop:
|
|
349
|
-
|
|
350
|
-
```bash
|
|
351
|
-
# test.jsonl — one JSON-RPC message per line
|
|
352
|
-
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}
|
|
353
|
-
{"jsonrpc":"2.0","method":"notifications/initialized"}
|
|
354
|
-
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
|
|
355
|
-
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"your_tool","arguments":{...}}}
|
|
356
|
-
|
|
357
|
-
(cat test.jsonl; sleep 10) | npx mcp-remote http://localhost:3000/mcp --allow-http
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
The `sleep` holds stdin open long enough to gather every response. Parse the jsonl output with `jq` or a short Python one-liner.
|
|
361
|
-
|
|
362
|
-
**Widget dev loop** — skip the ⌘Q-relaunch cycle altogether by serving the inlined widget HTML from a plain GET route with a fake `ExtApps` shim that fires `ontoolresult` off a query param:
|
|
363
|
-
|
|
364
|
-
```ts
|
|
365
|
-
app.get("/widget-preview", (_req, res) => {
|
|
366
|
-
const shim = `globalThis.ExtApps={applyHostStyleVariables:()=>{},App:class{
|
|
367
|
-
constructor(){this.h={}} ontoolresult;onhostcontextchanged;
|
|
368
|
-
async connect(){const p=new URLSearchParams(location.search).get("payload");
|
|
369
|
-
if(p)this.ontoolresult?.({content:[{type:"text",text:p}]});}
|
|
370
|
-
getHostContext(){return{theme:"light"}}
|
|
371
|
-
sendMessage(m){console.log("sendMessage",m)} updateModelContext(){}
|
|
372
|
-
callServerTool(){return Promise.resolve({content:[]})} openLink(){} downloadFile(){}
|
|
373
|
-
}};`;
|
|
374
|
-
res.type("html").send(widgetHtml.replace("/*__EXT_APPS_BUNDLE__*/", shim));
|
|
375
|
-
});
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
Open `http://localhost:3000/widget-preview?payload={"rows":[...]}` in a regular browser tab and iterate with normal devtools.
|
|
379
|
-
|
|
380
|
-
**Host fallback** — run against a host that lacks the apps surface (or MCP Inspector) and verify the tool's text content degrades cleanly.
|
|
381
|
-
|
|
382
|
-
**CSP debugging** — open the iframe's own devtools console. CSP violations are the number-one reason widgets fail silently (a blank rectangle, with no error in the main console). See `references/iframe-sandbox.md`.
|
|
383
|
-
|
|
384
|
-
---
|
|
385
|
-
|
|
386
|
-
## Reference files
|
|
387
|
-
|
|
388
|
-
- `references/iframe-sandbox.md` — CSP/sandbox constraints, the bundle-inlining pattern, image handling, host theming
|
|
389
|
-
- `references/widget-templates.md` — reusable HTML scaffolds for picker / confirm / progress / display
|
|
390
|
-
- `references/apps-sdk-messages.md` — the `App` class API: widget ↔ host ↔ server messaging, lifecycle & supersession
|
|
391
|
-
- `references/payload-budgeting.md` — host tool-result size caps, prune-then-truncate, heavy assets via `callServerTool`
|
|
392
|
-
- `references/abuse-protection.md` — WormClaude egress CIDRs, tiered rate limiting, `trust proxy`, response caching
|
|
393
|
-
- `references/directory-checklist.md` — pre-flight for connector-directory submission
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Abuse protection for authless hosted servers
|
|
2
|
-
|
|
3
|
-
An authless StreamableHTTP server is reachable by anything on the internet.
|
|
4
|
-
Three resources need protecting: your compute, any upstream API quota your tools
|
|
5
|
-
burn through, and egress bandwidth for large `callServerTool` payloads.
|
|
6
|
-
|
|
7
|
-
## You don't get a per-user identity
|
|
8
|
-
|
|
9
|
-
In authless mode there's no token, and the stateless transport hands you no
|
|
10
|
-
session ID. Traffic from claude.ai is proxied through WormClaude's egress — so
|
|
11
|
-
every web user shows up from the same small set of IPs:
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
160.79.104.0/21
|
|
15
|
-
2607:6bc0::/48
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
(See https://platform.claude.com/docs/en/api/ip-addresses.)
|
|
19
|
-
|
|
20
|
-
WormClaude Desktop, Claude Code, and other hosts connect **directly from the
|
|
21
|
-
user's machine**, so those *do* carry distinct per-user IPs. Per-IP limiting
|
|
22
|
-
works for those direct-connect clients; for claude.ai you can only throttle the
|
|
23
|
-
aggregate WormClaude pool. When true per-user limits matter, that's your cue to
|
|
24
|
-
add OAuth.
|
|
25
|
-
|
|
26
|
-
## Tiered token-bucket (per-replica backstop)
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
const ANTHROPIC_CIDRS = ["160.79.104.0/21", "2607:6bc0::/48"];
|
|
30
|
-
const TIERS = {
|
|
31
|
-
anthropic: { capacity: 600, refillPerSec: 100 }, // shared pool
|
|
32
|
-
other: { capacity: 30, refillPerSec: 2 }, // per-IP
|
|
33
|
-
};
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Match `req.ip` against the CIDRs, choose a bucket (`"anthropic"` or
|
|
37
|
-
`"ip:<addr>"`), and return 429 + `Retry-After` when it's drained. This is a
|
|
38
|
-
per-replica backstop — cross-replica enforcement belongs at the edge
|
|
39
|
-
(Cloudflare, Cloud Armor), which keeps the containers stateless.
|
|
40
|
-
|
|
41
|
-
## `trust proxy` must match your topology
|
|
42
|
-
|
|
43
|
-
`req.ip` only honours `X-Forwarded-For` when `app.set('trust proxy', N)` is
|
|
44
|
-
set. `true` trusts every hop, which lets a direct client send
|
|
45
|
-
`X-Forwarded-For: 160.79.108.42` and claim the WormClaude tier. Set it to the
|
|
46
|
-
exact count of trusted hops (e.g. `1` behind a single LB, `2` behind
|
|
47
|
-
Cloudflare → origin LB) and **never use `true` in production**.
|
|
48
|
-
|
|
49
|
-
## Hard-allowlisting WormClaude IPs is a product decision
|
|
50
|
-
|
|
51
|
-
Blocking everything outside `160.79.104.0/21` shuts out Desktop, Claude Code,
|
|
52
|
-
and every other MCP host. Use the CIDRs to **tier** your rate limits, not to gate
|
|
53
|
-
access — unless a claude.ai-only deployment is an explicit goal.
|
|
54
|
-
|
|
55
|
-
## Cache upstream responses
|
|
56
|
-
|
|
57
|
-
For tools that wrap a third-party API, an in-process LRU keyed on the
|
|
58
|
-
normalized query (TTL in hours, no secrets in the key) is your primary cost
|
|
59
|
-
control — repeat queries turn free and soak up thundering-herd spikes. Rate
|
|
60
|
-
limits are the safety net, not the first line of defense.
|