Skip to content

Conversation

@ericglau
Copy link
Member

@ericglau ericglau commented Jun 13, 2025

Overview

Adds an MCP server containing tools to generate smart contract source code for for each language, contract, and option supported by Wizard.

Packages

This will be published to NPM as @openzeppelin/wizard-mcp, with a dependency on a new package @openzeppelin/wizard-common as described below.

Changes

This PR does the following:

  • Adds packages/common to be published as @openzeppelin/wizard-common - Contains the prompt strings and options descriptions for each tool, to be used by both the MCP server (in a Node environment) and UI's AI assistant (in a Deno environment).
  • Adds packages/mcp to be published as @openzeppelin/wizard-mcp - Contains the MCP server.
  • Refactors the AI assistant's function definitions in packages/ui/api/ai-assistant/function-definitions to use common description strings from packages/common

Type Safety

Schemas for the MCP tools are intended to be type safe according to the Wizard APIs. Required fields for the Wizard API options (e.g. name and sometimes symbol) are also required in the MCP schemas, while other fields are optional.

Type checks in tests ensure that ALL supported Wizard API options are included in the schemas, except specifically omitted fields (example here where the access option is exposed in the API but not yet supported by the core code generation logic for Stylus).

This ensures that whenever new options are added in Wizard, a TypeScript compile error would be given until it is added to the MCP schema or intentionally omitted.

Error Handling

Any errors thrown by the Wizard API (for example, when incompatible options are enabled together) are wrapped as strings before returning the result to the MCP server. This allows AI agents to read the errors and self-correct by retrying with different input options.

Tests

In addition to the type safety tests mentioned above, this also adds unit tests for each MCP tool to verify that they return the exact results as the corresponding Wizard API.

For cases where an error is expected, the error messages are compared with the Wizard API, and snapshots are recorded for viewability.

Previews

PR dependencies

@ericglau ericglau marked this pull request as ready for review June 18, 2025 19:46
@ericglau ericglau requested a review from CoveMB June 18, 2025 20:15
name: z.string().describe(commonDescriptions.name),
pausable: z.boolean().optional().describe(commonDescriptions.pausable),
...commonSchema,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use types for these? or maybe some strategy to maintain them as we add new features

Copy link
Member Author

@ericglau ericglau Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are schema objects rather than primitive types. Type checking is performed in two different places instead:

  1. When the schemas are consumed to pass input to the Wizard API (for example here), if any types are mismatched, there will be a compile error.
  2. The tests have strict type checking (for example here) to ensure that all fields from the Wizard API are also in the schema, otherwise there will be a compile error.

It could be possible to move the type checks into the same file as the schema, but that would result in extra runtime code that is not actually used at runtime.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it could be possible (and beneficial) to have a light typescript type check of the schemas to provide early type feedback during development (before running tests) for team members adding new contracts kind/fields?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type checks in tests are at compile type so don't need to be run. But agree it's clearer to have types in the schemas for earlier feedback.

Added some light static type assertions for schemas in 8ac24af

Copy link
Member

@MCarlomagno MCarlomagno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just a few comments

@ericglau
Copy link
Member Author

Socket security issues are unrelated, see #540 (comment)

Copy link
Contributor

@CoveMB CoveMB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work 🔥

feeDenominator: "The denominator used to interpret a token's fee and to calculate the result fee fraction.",
};

export const cairoERC20Descriptions = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if it would be a good idea to have those description inside a root object, and then be able to type validate that each Contract kind defined for each languages has it's corresponding definition

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are just string constants and need to be importable by both Node and Deno, so there isn't much need to add type checks here. Added type checks in the schemas which consume these, in the linked comment above.

name: z.string().describe(commonDescriptions.name),
pausable: z.boolean().optional().describe(commonDescriptions.pausable),
...commonSchema,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it could be possible (and beneficial) to have a light typescript type check of the schemas to provide early type feedback during development (before running tests) for team members adding new contracts kind/fields?

import { registerCairoMultisig } from './tools/multisig.js';
import { registerCairoVesting } from './tools/vesting.js';

export function registerCairoTools(server: McpServer) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this function could be typed to return a register for each contract kind defined in the packages

@ericglau ericglau enabled auto-merge (squash) June 20, 2025 19:05
@ericglau ericglau merged commit 00589bb into OpenZeppelin:master Jun 20, 2025
16 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Jun 20, 2025
@ericglau ericglau deleted the mcp branch June 20, 2025 19:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants