MCP Hub
Back to servers

ads-roi

A template-based MCP server skeleton for building interactive widgets with SEP-1865 support, featuring dual OAuth and API key authentication for deployment on Cloudflare Workers.

Updated
Jan 4, 2026
Validated
Jan 9, 2026

{{SERVER_NAME}} MCP Server

MCP Apps server with SEP-1865 interactive widget support, dual authentication (OAuth + API Key), and Cloudflare Workers deployment.

Quick Start

1. Replace Placeholders

Search and replace these placeholders in all files:

PlaceholderDescriptionExample
{{SERVER_NAME}}Human-readable name"Currency Converter"
{{SERVER_ID}}kebab-case identifier"currency-converter"
{{McpAgentClassName}}PascalCase class name"CurrencyConverterMcp"
{{SERVER_DESCRIPTION}}Brief description"Convert currencies using real-time exchange rates"
{{WIDGET_TITLE}}Widget HTML title"Currency Converter Widget"
{{GITHUB_ORG}}GitHub organization"your-org"

2. Update wrangler.jsonc

  1. Replace {{SERVER_ID}} with your server ID
  2. Replace {{McpAgentClassName}} with your class name
  3. Update the custom domain pattern

3. Install and Build

npm install
npm run build:widgets

4. Set Secrets

wrangler secret put WORKOS_CLIENT_ID
wrangler secret put WORKOS_API_KEY

5. Deploy

Deployment is automatic via Cloudflare Workers Builds when you push to GitHub.

For manual deployment (not recommended):

npm run deploy

Project Structure

src/
  index.ts              # Entry point with dual auth routing
  server.ts             # McpAgent class (OAuth path)
  api-key-handler.ts    # API key authentication with LRU cache
  types.ts              # Environment bindings
  server-instructions.ts # LLM system prompt instructions

  auth/
    authkit-handler.ts  # WorkOS OAuth with PKCE
    apiKeys.ts          # API key validation
    auth-utils.ts       # User lookup, HTML pages
    props.ts            # Auth context type
    session-types.ts    # Session interfaces

  helpers/
    assets.ts           # loadHtml() for widget loading

  resources/
    ui-resources.ts     # SEP-1865 UI resource definitions

  tools/
    descriptions.ts     # Tool metadata (4-part pattern)

  shared/
    logger.ts           # Structured logging

  optional/             # Advanced features (delete if not needed)

web/
  widgets/
    widget.html         # Widget entry point
    widget.tsx          # React widget component
  components/           # shadcn/ui components
  lib/                  # Utilities (cn, types)
  styles/               # Tailwind CSS
  dist/widgets/         # Built output (gitignored)

Adding New Tools

When adding a tool, update these locations:

1. Tool Metadata (src/tools/descriptions.ts)

"your-tool": {
  title: "Your Tool",
  description: {
    part1_purpose: "What it does...",
    part2_returns: "Returns X, Y, Z...",
    part3_useCase: "Use when...",
    part4_constraints: "Note: limitations..."
  },
  examples: [...]
}

2. Server Registration (src/server.ts)

registerAppTool(
  this.server,
  "your-tool",
  {
    title: TOOL_METADATA["your-tool"].title,
    description: getToolDescription("your-tool"),
    inputSchema: { /* Zod schema */ },
    _meta: { [RESOURCE_URI_META_KEY]: widgetResource.uri }
  },
  async (args) => { /* implementation */ }
);

3. API Key Handler (src/api-key-handler.ts)

Duplicate the tool registration for API key authentication path.

SEP-1865 MCP Apps Pattern

This skeleton uses the Two-Part Registration pattern:

  1. PART 1: Register Resource - UI HTML template from Assets
  2. PART 2: Register Tool - Links to resource via _meta[RESOURCE_URI_META_KEY]

Data flows:

Tool Result -> structuredContent -> postMessage -> Widget State

Authentication

OAuth 2.1 (OAuth-capable clients)

  • Flow: /authorize -> WorkOS AuthKit -> /callback -> Tools
  • PKCE support (RFC 7636)
  • Centralized login at panel.wtyczki.ai
  • Example clients: Claude Desktop

API Key (Non-OAuth clients)

  • Header: Authorization: Bearer wtyk_xxxxx
  • Keys generated via panel.wtyczki.ai
  • LRU cache prevents memory leaks
  • Example clients: AnythingLLM, Cursor

Widget Development

Key Concepts

  • React 18 with useApp() hook from @modelcontextprotocol/ext-apps/react
  • Tailwind CSS with automatic dark mode (via host context)
  • Fixed 600px height container (mandatory for MCP Apps)
  • viteSingleFile inlines all JS/CSS into single HTML

Development

# Build widget
npm run build:widgets

# Watch mode
npm run dev:widget

# Full dev (server + widget watch)
npm run dev:full

Widget Lifecycle

const { app } = useApp({
  onAppCreated: (app) => {
    app.ontoolinput = (params) => { /* tool called */ };
    app.ontoolresult = (result) => { /* display result */ };
    app.onhostcontextchanged = (ctx) => { /* theme change */ };
    app.onteardown = async () => { /* cleanup */ };
  }
});

Configuration Files

FilePurpose
wrangler.jsoncCloudflare Workers config (bindings, routes)
vite.config.tsWidget build config
package.jsonDependencies and scripts
tsconfig.jsonTypeScript config (server)
web/tsconfig.jsonTypeScript config (widget)

Environment Variables

Set via wrangler secret put:

VariableRequiredDescription
WORKOS_CLIENT_IDYesWorkOS client ID
WORKOS_API_KEYYesWorkOS API key (starts with sk_)
AI_GATEWAY_TOKENNoAI Gateway token (if using Workers AI)

Common Issues

Widget not loading

  1. Check npm run build:widgets completed successfully
  2. Verify web/dist/widgets/widget.html exists
  3. Check ASSETS binding in wrangler.jsonc

Authentication failures

  1. Verify WORKOS_CLIENT_ID and WORKOS_API_KEY secrets are set
  2. Check USER_SESSIONS KV namespace is configured
  3. Ensure custom domain is set up in Cloudflare

Tool not appearing

  1. Check tool is registered in BOTH server.ts AND api-key-handler.ts
  2. Verify tool name matches exactly in all locations
  3. Check handleToolsList() includes the tool schema

Production Checklist

  • All {{PLACEHOLDER}} values replaced
  • wrangler.jsonc configured with correct IDs
  • Secrets set via wrangler secret put
  • Custom domain configured in Cloudflare
  • GitHub repository connected to Cloudflare Workers Builds
  • Widget builds successfully (npm run build:widgets)
  • Type checking passes (npm run type-check)

Reviews

No reviews yet

Sign in to write a review