Skip to content

Conversation

@yhsieh1
Copy link
Contributor

@yhsieh1 yhsieh1 commented Jan 7, 2026

Summary

This PR introduces a Tool Adapter layer for the B2C DX MCP server that standardizes how SDK functions are wrapped as MCP tools. The adapter provides a consistent pattern for:

  • Input validation using Zod schemas
  • Dependency injection (B2CInstance for WebDAV/OCAPI operations, AuthStrategy for MRT API operations)
  • Output formatting with helper functions (textResult, jsonResult, errorResult)
  • Centralized error handling

Additionally, MRT authentication is now resolved once at server startup via Services.create() and reused for all tool calls, improving performance and consistency.

Changes

New Files

  • packages/b2c-dx-mcp/src/tools/adapter.ts - Core tool adapter implementation with:

    • createToolAdapter<TInput, TOutput>() factory function
    • ToolExecutionContext interface providing b2cInstance, mrtConfig, and services
    • Output helpers: textResult(), jsonResult(), errorResult()
    • requiresInstance flag for B2C instance injection (WebDAV/OCAPI tools)
    • requiresMrtAuth flag for MRT API authentication injection
  • packages/b2c-dx-mcp/test/tools/adapter.test.ts - Comprehensive test suite (789 lines) covering:

    • Result helper functions
    • Input validation with Zod schemas
    • Error handling
    • B2CInstance and MRT auth injection
    • Context passing to execute functions

Modified Files

  • packages/b2c-dx-mcp/src/services.ts - Enhanced with:

    • Services.create() static factory method for MRT auth resolution at startup
    • resolveMrtAuth() method implementing priority: flag → env var → ~/.mobify
    • resolveB2CInstance() method implementing priority: flags → env vars → dw.json
    • mrtConfig property storing pre-resolved MRT auth, project, and environment
    • b2cInstance property storing pre-resolved B2CInstance (may be undefined)
  • packages/b2c-dx-mcp/src/commands/mcp.ts - Updated to use Services.create() with new auth flags

  • packages/b2c-dx-mcp/src/registry.ts - Switched from console.error to structured logging via getLogger()

  • packages/b2c-dx-mcp/src/tools/index.ts - Re-exports adapter module

  • Tool implementations migrated to adapter pattern:

    • packages/b2c-dx-mcp/src/tools/cartridges/index.ts - cartridge_deploy tool
    • packages/b2c-dx-mcp/src/tools/mrt/index.ts - mrt_bundle_push tool
    • packages/b2c-dx-mcp/src/tools/pwav3/index.ts - PWA v3 tools
    • packages/b2c-dx-mcp/src/tools/scapi/index.ts - SCAPI tools
    • packages/b2c-dx-mcp/src/tools/storefrontnext/index.ts - Storefront Next tools
  • packages/b2c-dx-mcp/README.md - Documentation for:

    • MRT flags inherited from MrtCommand.baseFlags: --api-key, --project, --environment, --cloud-origin
    • B2C instance flags inherited from InstanceCommand.baseFlags: --server, --username, --password, --client-id, --client-secret, --code-version
    • MRT configuration options (API key, project, environment)
    • B2C instance configuration options (Basic auth for WebDAV, OAuth for OCAPI)
    • Environment-specific config file support (~/.mobify--[hostname])
  • packages/b2c-tooling-sdk/src/cli/index.ts - Export loadMobifyConfig for MRT config file loading

Test Updates

  • packages/b2c-dx-mcp/test/commands/mcp.test.ts - Added test stubs for new flags

Test Plan

test-plan.md

Testing Tip:
Prompt Cursor to perform manual test in the test plan:

"Complete ALL tests in Test Plan. Create any required config files (~/.mobify, dw.json), update mcp.json for each test, wait for my restart, verify, and clean up at the end."


  • Tests pass (pnpm test)
  • Code is formatted (pnpm run format)

Copy link

@patricksullivansf patricksullivansf left a comment

Choose a reason for hiding this comment

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

love it. it's a bit obtuse to grok all at once, but it seems well reasoned.

async handler(args) {
const timestamp = new Date().toISOString();
console.error(`[${timestamp}] CARTRIDGES tool '${name}' called with:`, args);
interface CartridgeDeployInput {
Copy link
Contributor Author

@yhsieh1 yhsieh1 Jan 7, 2026

Choose a reason for hiding this comment

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

These interfaces are temporary placeholders and might change once we start implement the tool.

/**
* Input type for mrt_bundle_push tool.
*/
interface MrtBundlePushInput {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These interfaces are temporary placeholders and might change once we start implement the tool.

@yhsieh1 yhsieh1 marked this pull request as ready for review January 7, 2026 16:39
@yhsieh1 yhsieh1 requested a review from clavery as a code owner January 7, 2026 16:39

node "%~dp0\run" %*


Copy link
Contributor Author

Choose a reason for hiding this comment

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

A new line is added after pnpm build

Copy link
Collaborator

@clavery clavery left a comment

Choose a reason for hiding this comment

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

Overall looks good 👍

My comments are around configuration loading. We should push as much of this as possible into the OCLIF and SDK layers so it's consistent (And not repeated) across all tooling.

We can refactor some of this in followup PRs though. I have one coming now where I've identified 2 places we're loading dw.json in the SDK (which is bad). As mentioned I'd like to add an even simpler abstraction for getting configuration for instance, MRT, etc (for instance I don't want the CLIs to have to load the mobify config files themselves). I can help with that in followup PRs. See if there's anything we can simplify now from my comments below.

parse: async (input) => input.toLowerCase(),
}),

// Auth flags
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note you can get and spread these flags from ...MrtCommand.baseFlags

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@clavery That sounds good! For MCP tools, it might need different API keys, do you consider rename api-key to mrt-api-key?

Copy link
Collaborator

Choose a reason for hiding this comment

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

perhaps. although we do want to maximum compatibility with existing conventions (like pwa-kit-dev, etc) which uses --api-key. I'll think about this. Right now we don't have any auth needs that conflict with api-key (all other auths use distinct naming).

One thing that's important is the env variables are the same (which in this case is unambiguous (SFCC_MRT_API_KEY)). So if you don't want to spread from the base command make sure we're keeping those consistent. The idea is that folks can set their configuration once (.env files, dw.json, etc) and ALL of our tools respond in the same way

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@clavery I updated to spread from the command and it works well. One thing to note, if config Cursor MCP with .env instead of "env" attribute in mcp.json, .env needs to be under home directory (~) because Cursor runs MCP servers with cwd = home directory.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Both .env and env attribute in mcp.json will be supported.

Yeah I know about cursors weird behavior there. Here's what I told Shaurye a few months ago: Once we move this MCP out of PWA kit we'll want to use a more generic env var AND we should support this as an argument as well (i.e. --project ${workspaceFolder}). Again falling back to cwd when not specified (edited)

Basically we should default everything to assuming the current working directory is our project but allow for a --project (or similar) flag to root (or change our directory) to. The ${workspaceFolder} expansion works in cursor

I think this might be a useful flag at the BaseCommand too rather than just being an MCP concern.

All that being said if they don't configure it that way then we don't know where the project is and configuration will just load what it can (And many tools may be useful but others useless)

* Environment variables take precedence over dw.json values.
* Empty strings are treated as undefined (fall back to dw.json).
*/
function getEnvironmentOverrides(): Record<string, string | undefined> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this function needed? OCLIF supports and resolves environment variables for flags automatically. We should be using the flags from InstanceCommand.baseFlags (or at least duplicating them) for compatibility with the CLI.

function createInstance(services: Services): B2CInstance {
const envOverrides = getEnvironmentOverrides();

return B2CInstance.fromEnvironment({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here. In a CLI B2CInstance.fromEnvironment should be seeded with the values obtained from the CLI entry (i.e. flags) which would automatically have the environment variables applied.

}

// 2. Check environment variable
const envApiKey = process.env.SFCC_MRT_API_KEY?.trim();
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should come from the OCLIF flags as well.

}

// 3. Check ~/.mobify config file (or ~/.mobify--[hostname] if cloud origin specified)
const mobifyConfig = loadMobifyConfig(options.cloudOrigin);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd like to improve this so you don't have to call this utility function by having a more consistent "configuration" facade. I will work on that in a separate PR though.

@yhsieh1 yhsieh1 merged commit bd85dc6 into main Jan 8, 2026
3 checks passed
@yhsieh1 yhsieh1 deleted the t/commerce/W-20591366/tool-adapter branch January 8, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants