diff --git a/.changeset/config.json b/.changeset/config.json index a1e6e616e..4c1c9d360 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -12,8 +12,18 @@ "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", - "ignore": [ - "@openzeppelin/wizard-cairo-alpha", - "ui" - ] + "privatePackages": { + "version": true, + "tag": false + }, + "changedFilePatterns": [ + "**", + "!src/cairo_alpha/**", + "!src/polkadot/**", + "!api/**", + "!public/**", + "!scripts/deno/**", + "!src/**/App.svelte" + ], + "ignore": ["@openzeppelin/wizard-cairo-alpha"] } diff --git a/.claude/skills/changeset/SKILL.md b/.claude/skills/changeset/SKILL.md index 8180602a1..46ee76dfa 100644 --- a/.claude/skills/changeset/SKILL.md +++ b/.claude/skills/changeset/SKILL.md @@ -30,6 +30,7 @@ First line is a high-level summary without leading dash (PR number gets appended 5. **Multiple packages**: Multiple packages can share a changeset file with different bump levels in the frontmatter. Use separate files only when packages need unrelated descriptions. 6. **Bump levels**: Follow semver based on current package version. `x.y.z` (>=1.0.0): major for breaking, minor for features, patch for fixes. `0.x.y`: minor for breaking, patch for features/fixes. `0.0.x`: patch for everything. 7. **New unpublished packages**: Still need a changeset to bump the initial version in package.json and for the changes to appear in the resulting changelog. +8. **UI that ships in MCP Apps**: `ui` is private (versioned in-repo, not published to npm). MCP App HTML is built from `ui` when `@openzeppelin/contracts-mcp` is published. Changes to Wizard controls or other UI that ships in MCP Apps need **one changeset listing both** `ui` and `@openzeppelin/contracts-mcp`. Web-only UI (Cairo Alpha, Polkadot, `App.svelte`) does not need a changeset. Never version `ui` in a changeset without also listing `@openzeppelin/contracts-mcp`. ## Steps diff --git a/.github/workflows/changeset.yml b/.github/workflows/changeset.yml index 4a4ecac92..ea900ab32 100644 --- a/.github/workflows/changeset.yml +++ b/.github/workflows/changeset.yml @@ -26,3 +26,5 @@ jobs: uses: ./.github/actions/setup - name: Check changeset run: npx changeset status --since=origin/${{ github.base_ref }} + - name: Check UI changesets include contracts-mcp + run: node scripts/release/check-mcp-ui-changeset.mjs origin/${{ github.base_ref }} diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index bad20d1a0..ae826ecef 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -4,6 +4,9 @@ on: push: branches: - master + # Skip pushes that only affect the Netlify UI deployment. If changes affect + # contract-specific controls which are part of the packaged HTML files for MCP Apps, + # those PRs also add a .changeset file, so this workflow still runs. paths-ignore: - 'packages/ui/**' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c9829099..a07a31754 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,5 +96,8 @@ From the `packages/mcp` directory: As a contributor, we ask that you fork this repository, work on your own fork and then submit pull requests. The pull requests will be reviewed and eventually merged into the main repo. See ["Fork-a-Repo"](https://help.github.com/articles/fork-a-repo/) for how this works. ### Adding Changesets -If your PR modifies code generation logic under `packages/core`, you will need to add changesets for the relevant packages to summarize the changes. The PR's `Changeset` GitHub check will give an error if this condition is not satisfied. +Published packages (`packages/core/*`, `packages/common`, `packages/cli`, `packages/mcp`) use [Changesets](https://github.com/changesets/changesets) for versioning. The PR's `Changeset` GitHub check fails when a changed publishable package has no changeset. Use the `ignore-changeset` label only when a bump is genuinely not needed. + +The `ui` package is private (Netlify + MCP App source) and is versioned in-repo but not published. MCP App HTML is built from `ui` when `@openzeppelin/contracts-mcp` is published. If your PR changes Wizard controls or other UI that ships in MCP Apps, add **one changeset that lists both** `ui` and `@openzeppelin/contracts-mcp`. Web-only UI (for example Cairo Alpha, Polkadot, or the web Wizard `App.svelte` shells) does not require a changeset. + - To add a changeset: from the root directory, run `yarn changeset` diff --git a/package.json b/package.json index 7ce24e546..6ad086e74 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "devDependencies": { "@changesets/changelog-github": "^0.5.1", "@changesets/cli": "^2.29.2", + "@changesets/read": "^0.6.5", "@eslint/js": "^9.21.0", "concurrently": "^9.1.2", "eslint": "^9.33.0", @@ -49,4 +50,3 @@ "typescript-eslint": "^8.29.0" } } - diff --git a/packages/cli/src/cli.test.ts.md b/packages/cli/src/cli.test.ts.md index 0b67d12f7..0563ba4fd 100644 --- a/packages/cli/src/cli.test.ts.md +++ b/packages/cli/src/cli.test.ts.md @@ -53,7 +53,7 @@ Generated by [AVA](https://avajs.dev). --permit Whether without paying gas, token holders will be able to allow third parties to transfer from their account.␊ --votes Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.␊ --flashmint Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.␊ - --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ + --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for crosschain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ --crossChainLinkAllowOverride Whether to allow replacing a crosschain link that has already been registered. Only used if crossChainBridging is set to "erc7786native".␊ --namespacePrefix The prefix for ERC-7201 namespace identifiers. It should be derived from the project name or a unique naming convention specific to the project. Used only if the contract includes storage variables and upgradeability is enabled. Default is "myProject".␊ --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ @@ -81,7 +81,7 @@ Generated by [AVA](https://avajs.dev). --mintable Whether privileged accounts will be able to create more supply or emit more tokens␊ --incremental Whether new tokens will be automatically assigned an incremental id␊ --votes Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps (defaulting to block number if not specified).␊ - --crossChainBridging Whether to embed an ERC-7786 based bridge directly in the token contract, making it natively crosschain. Cross-chain transfers with registered counterparts burn the token on the source chain and mint it on the destination chain. If also using incremental token ids, mint only on a single chain and link counterparts without minting, otherwise colliding ids can strand bridged tokens.␊ + --crossChainBridging Whether to embed an ERC-7786 based bridge directly in the token contract, making it natively crosschain. Crosschain transfers with registered counterparts burn the token on the source chain and mint it on the destination chain. If also using incremental token ids, mint only on a single chain and link counterparts without minting, otherwise colliding ids can strand bridged tokens.␊ --crossChainLinkAllowOverride Whether to allow replacing a crosschain link that has already been registered. Only used if crossChainBridging is set to "erc7786native".␊ --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ --upgradeable Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract. Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.␊ @@ -106,7 +106,7 @@ Generated by [AVA](https://avajs.dev). --mintable Whether privileged accounts will be able to create more supply or emit more tokens␊ --supply Whether to keep track of total supply of tokens␊ --updatableUri Whether privileged accounts will be able to set a new URI for all token types␊ - --crossChainBridging Whether to embed an ERC-7786 based bridge directly in the token contract, making it natively crosschain. Cross-chain transfers with registered counterparts burn the tokens on the source chain and mint them on the destination chain.␊ + --crossChainBridging Whether to embed an ERC-7786 based bridge directly in the token contract, making it natively crosschain. Crosschain transfers with registered counterparts burn the tokens on the source chain and mint them on the destination chain.␊ --crossChainLinkAllowOverride Whether to allow replacing a crosschain link that has already been registered. Only used if crossChainBridging is set to "erc7786native".␊ --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ --upgradeable Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract. Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.␊ @@ -135,7 +135,7 @@ Generated by [AVA](https://avajs.dev). --permit Whether without paying gas, token holders will be able to allow third parties to transfer from their account.␊ --votes Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.␊ --flashmint Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.␊ - --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ + --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for crosschain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ --crossChainLinkAllowOverride Whether to allow replacing a crosschain link that has already been registered. Only used if crossChainBridging is set to "erc7786native".␊ --namespacePrefix The prefix for ERC-7201 namespace identifiers. It should be derived from the project name or a unique naming convention specific to the project. Used only if the contract includes storage variables and upgradeability is enabled. Default is "myProject".␊ --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ @@ -166,7 +166,7 @@ Generated by [AVA](https://avajs.dev). --permit Whether without paying gas, token holders will be able to allow third parties to transfer from their account.␊ --votes Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.␊ --flashmint Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.␊ - --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ + --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for crosschain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ --crossChainLinkAllowOverride Whether to allow replacing a crosschain link that has already been registered. Only used if crossChainBridging is set to "erc7786native".␊ --namespacePrefix The prefix for ERC-7201 namespace identifiers. It should be derived from the project name or a unique naming convention specific to the project. Used only if the contract includes storage variables and upgradeability is enabled. Default is "myProject".␊ --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ diff --git a/packages/cli/src/cli.test.ts.snap b/packages/cli/src/cli.test.ts.snap index d947d2712..3f2666151 100644 Binary files a/packages/cli/src/cli.test.ts.snap and b/packages/cli/src/cli.test.ts.snap differ diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index 73d3c1d11..796ea5554 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog +## 0.5.5 (2026-08-07) + +- Standardize crosschain terminology in user-facing text. ([#840](https://github.com/OpenZeppelin/contracts-wizard/pull/840)) + ## 0.5.4 (2026-07-31) - Add Solidity cross-chain options for ERC721, ERC1155, and Governor using OpenZeppelin Contracts 5.7. ([#825](https://github.com/OpenZeppelin/contracts-wizard/pull/825)) diff --git a/packages/common/package.json b/packages/common/package.json index 5734fb178..913821c05 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/wizard-common", - "version": "0.5.4", + "version": "0.5.5", "description": "Common library for OpenZeppelin Contracts Wizard components. Used internally.", "license": "AGPL-3.0-only", "repository": "https://github.com/OpenZeppelin/contracts-wizard", @@ -34,7 +34,7 @@ "zod": "^4.0" }, "devDependencies": { - "@openzeppelin/wizard": "^0.10.12", + "@openzeppelin/wizard": "^0.10.13", "@openzeppelin/wizard-cairo": "^3.0.0", "@openzeppelin/wizard-stellar": "^0.6.3", "@openzeppelin/wizard-stylus": "^0.3.0", diff --git a/packages/common/src/ai/descriptions/solidity.ts b/packages/common/src/ai/descriptions/solidity.ts index 19504ad32..d176019bc 100644 --- a/packages/common/src/ai/descriptions/solidity.ts +++ b/packages/common/src/ai/descriptions/solidity.ts @@ -36,7 +36,7 @@ export const solidityERC20Descriptions = { flashmint: "Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.", crossChainBridging: - 'Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.', + 'Whether to allow authorized bridge contracts to mint and burn tokens for crosschain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.', crossChainLinkAllowOverride: crossChainLinkAllowOverrideDescription, premintChainId: 'The chain ID of the network on which to premint tokens.', callback: @@ -52,7 +52,7 @@ export const solidityERC721Descriptions = { votes: 'Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps (defaulting to block number if not specified).', crossChainBridging: - 'Whether to embed an ERC-7786 based bridge directly in the token contract, making it natively crosschain. Cross-chain transfers with registered counterparts burn the token on the source chain and mint it on the destination chain. If also using incremental token ids, mint only on a single chain and link counterparts without minting, otherwise colliding ids can strand bridged tokens.', + 'Whether to embed an ERC-7786 based bridge directly in the token contract, making it natively crosschain. Crosschain transfers with registered counterparts burn the token on the source chain and mint it on the destination chain. If also using incremental token ids, mint only on a single chain and link counterparts without minting, otherwise colliding ids can strand bridged tokens.', crossChainLinkAllowOverride: crossChainLinkAllowOverrideDescription, }; @@ -61,7 +61,7 @@ export const solidityERC1155Descriptions = { supply: 'Whether to keep track of total supply of tokens', updatableUri: 'Whether privileged accounts will be able to set a new URI for all token types', crossChainBridging: - 'Whether to embed an ERC-7786 based bridge directly in the token contract, making it natively crosschain. Cross-chain transfers with registered counterparts burn the tokens on the source chain and mint them on the destination chain.', + 'Whether to embed an ERC-7786 based bridge directly in the token contract, making it natively crosschain. Crosschain transfers with registered counterparts burn the tokens on the source chain and mint them on the destination chain.', crossChainLinkAllowOverride: crossChainLinkAllowOverrideDescription, }; diff --git a/packages/core/solidity/CHANGELOG.md b/packages/core/solidity/CHANGELOG.md index eadb312c4..175bf0b25 100644 --- a/packages/core/solidity/CHANGELOG.md +++ b/packages/core/solidity/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog +## 0.10.13 (2026-08-07) + +- Standardize crosschain terminology in user-facing text. ([#840](https://github.com/OpenZeppelin/contracts-wizard/pull/840)) + ## 0.10.12 (2026-07-31) - Add Solidity cross-chain options for ERC721, ERC1155, and Governor using OpenZeppelin Contracts 5.7. ([#825](https://github.com/OpenZeppelin/contracts-wizard/pull/825)) diff --git a/packages/core/solidity/package.json b/packages/core/solidity/package.json index 3252652fb..ddd9086e6 100644 --- a/packages/core/solidity/package.json +++ b/packages/core/solidity/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/wizard", - "version": "0.10.12", + "version": "0.10.13", "description": "A boilerplate generator to get started with OpenZeppelin Contracts", "license": "AGPL-3.0-only", "repository": "https://github.com/OpenZeppelin/contracts-wizard", diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index 51690d840..99c533077 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -300,7 +300,7 @@ test('erc20 crossChainBridging superchain, premintChainId required', async t => ); t.is( (error as OptionsError).messages.premintChainId, - 'Chain ID is required when using Premint with Cross-Chain Bridging', + 'Chain ID is required when using Premint with Crosschain Bridging', ); }); diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 0e3055dbb..b4b070826 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -273,7 +273,7 @@ function addPremint( if (crossChainBridging) { if (premintChainId === '') { throw new OptionsError({ - premintChainId: 'Chain ID is required when using Premint with Cross-Chain Bridging', + premintChainId: 'Chain ID is required when using Premint with Crosschain Bridging', }); } diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md index 8564b8a27..24548b021 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,6 +1,24 @@ # Changelog +## 0.6.3 (2026-08-07) + +- Standardize crosschain terminology in user-facing text. ([#840](https://github.com/OpenZeppelin/contracts-wizard/pull/840)) +- Updated dependencies [[`2c2d516`](https://github.com/OpenZeppelin/contracts-wizard/commit/2c2d5165d9b6fda16a1415cedc0e9ac065adcfa9)]: + - @openzeppelin/wizard-common@0.5.5 + - @openzeppelin/wizard@0.10.13 + +## 0.6.2 (2026-08-07) + +- Shrink MCP App package size by sharing one HTML template per language. ([#838](https://github.com/OpenZeppelin/contracts-wizard/pull/838)) + - Inject contract kind at serve time instead of shipping near-duplicate HTML per tool. + - Scope MCP App Tailwind content away from web-only Wizard shells. + - Bundle the Zama logo so it renders in self-contained MCP Apps. + +## 0.6.1 (2026-08-05) + +- Fix Governor Cross-Chain Execution appearing as an empty dropdown in the MCP App UI. ([#836](https://github.com/OpenZeppelin/contracts-wizard/pull/836)) + ## 0.6.0 (2026-07-31) - **Breaking changes**: Renamed the `erc7984` tool to `confidential-erc7984`. ([#828](https://github.com/OpenZeppelin/contracts-wizard/pull/828)) diff --git a/packages/mcp/package.json b/packages/mcp/package.json index a4e19de7f..676c5262a 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/contracts-mcp", - "version": "0.6.0", + "version": "0.6.3", "description": "OpenZeppelin Contracts MCP Server", "license": "AGPL-3.0-only", "repository": "https://github.com/OpenZeppelin/contracts-wizard", @@ -27,8 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", - "@openzeppelin/wizard-common": "^0.5.4", - "@openzeppelin/wizard": "^0.10.12", + "@openzeppelin/wizard-common": "^0.5.5", + "@openzeppelin/wizard": "^0.10.13", "@openzeppelin/wizard-stylus": "^0.3.0", "@openzeppelin/wizard-stellar": "^0.6.3", "@openzeppelin/wizard-cairo": "^3.0.0", diff --git a/packages/mcp/src/apps/register.test.ts b/packages/mcp/src/apps/register.test.ts index 50abbf98e..f8df848cf 100644 --- a/packages/mcp/src/apps/register.test.ts +++ b/packages/mcp/src/apps/register.test.ts @@ -1,7 +1,16 @@ import test from 'ava'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { testMcpInfo } from '../helpers.test'; -import { appResourceUri, readAppHtml, registerWizardAppTool, RESOURCE_MIME_TYPE, wizardAppResult } from './register'; +import { + appResourceUri, + getToolAppSpec, + MCP_KIND_PLACEHOLDER, + readAppHtml, + registerWizardAppTool, + RESOURCE_MIME_TYPE, + TOOL_APP_SPECS, + wizardAppResult, +} from './register'; import { registerSolidityERC20 } from '../solidity/tools/erc20'; test('solidity-erc20 registers UI metadata', t => { @@ -12,25 +21,42 @@ test('solidity-erc20 registers UI metadata', t => { t.is(tool._meta?.['ui/resourceUri'], appResourceUri('solidity-erc20')); }); -test('MCP App HTML artifacts exist for Wizard-backed tools', async t => { - // One tool per language; registerWizardAppTool fails closed for the rest at server start. - const tools = [ - 'solidity-erc20', - 'solidity-erc721', - 'cairo-erc20', - 'stellar-fungible', - 'stylus-erc20', - 'confidential-erc7984', - 'uniswap-hooks', - ]; +test('TOOL_APP_SPECS entries have distinct per-tool URIs and known overrides', t => { + const tools = Object.keys(TOOL_APP_SPECS); + t.true(tools.length >= 26, `expected at least 26 tools, got ${tools.length}`); for (const tool of tools) { + const spec = getToolAppSpec(tool); + t.truthy(spec.template, `${tool} missing template`); + t.truthy(spec.kind, `${tool} missing kind`); + t.is(appResourceUri(tool), `ui://openzeppelin/${tool}.html`); + } + t.is(TOOL_APP_SPECS['solidity-rwa']?.kind, 'RealWorldAsset'); + t.is(TOOL_APP_SPECS['uniswap-hooks']?.kind, 'Hooks'); +}); + +test('MCP App HTML artifacts exist and inject kind for Wizard-backed tools', async t => { + const tools = Object.keys(TOOL_APP_SPECS).sort(); + for (const tool of tools) { + const spec = getToolAppSpec(tool); const html = await readAppHtml(tool); t.true(html.includes(''), `${tool} missing doctype`); t.true(html.includes('