api-workers

Cloudflare Workers deployment using `createWorkerHandler` from `@cyanheads/mcp-ts-core/worker`. Covers the full handler signature, binding types, CloudflareBindings extensibility, runtime compatibility guards, and wrangler.toml requirements.

8 stars

Best use case

api-workers is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Cloudflare Workers deployment using `createWorkerHandler` from `@cyanheads/mcp-ts-core/worker`. Covers the full handler signature, binding types, CloudflareBindings extensibility, runtime compatibility guards, and wrangler.toml requirements.

Teams using api-workers should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/api-workers/SKILL.md --create-dirs "https://raw.githubusercontent.com/cyanheads/pubchem-mcp-server/main/skills/api-workers/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/api-workers/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How api-workers Compares

Feature / Agentapi-workersStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Cloudflare Workers deployment using `createWorkerHandler` from `@cyanheads/mcp-ts-core/worker`. Covers the full handler signature, binding types, CloudflareBindings extensibility, runtime compatibility guards, and wrangler.toml requirements.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

## Overview

`@cyanheads/mcp-ts-core/worker` exports `createWorkerHandler` — the Workers entry point. It wraps tool/resource/prompt registries into a per-request `McpServer` factory that integrates with the Cloudflare Workers runtime.

---

## `createWorkerHandler(options)`

```ts
import { createWorkerHandler } from '@cyanheads/mcp-ts-core/worker';
import { echoTool } from './mcp-server/tools/definitions/echo.tool.js';
import { echoResource } from './mcp-server/resources/definitions/echo.resource.js';
import { echoPrompt } from './mcp-server/prompts/definitions/echo.prompt.js';
import { initMyService } from './services/my-domain/my-service.js';

export default createWorkerHandler({
  tools: [echoTool],
  resources: [echoResource],
  prompts: [echoPrompt],
  setup(core) {
    initMyService(core.config, core.storage);
  },
  extraEnvBindings: [['MY_API_KEY', 'MY_API_KEY']],
  extraObjectBindings: [['MY_CUSTOM_KV', 'MY_CUSTOM_KV']],
  onScheduled: async (controller, env, ctx) => {
    // Cloudflare cron trigger handler
  },
});
```

Fresh scaffolds register definitions directly in the entry point as shown above. If your project later adds barrel files for definitions, importing arrays from those barrels is also fine.

### Options

| Option | Type | Purpose |
|:-------|:-----|:--------|
| `tools` | `AnyToolDefinition[]` | Tool definitions to register |
| `resources` | `AnyResourceDefinition[]` | Resource definitions to register |
| `prompts` | `PromptDefinition[]` | Prompt definitions to register |
| `extensions` | `Record<string, object>` | SEP-2133 extensions to advertise in server capabilities |
| `instructions` | `string \| (env: CloudflareBindings) => string` | Server-level orientation forwarded to the model on every `initialize`. Resolver form runs inside `initializeApp(env)` so env-derived text is available (see Workers-specific warnings). Empty string treated as unset. |
| `setup` | `(core: CoreServices) => void \| Promise<void>` | Runs after core services are ready, during the first request (lazy init inside the fetch handler) |
| `extraEnvBindings` | `[bindingKey: string, processEnvKey: string][]` | Maps CF string bindings to `process.env` keys |
| `extraObjectBindings` | `[bindingKey: string, globalKey: string][]` | Maps CF object bindings (KV, R2, D1, AI) to `globalThis` keys |
| `onScheduled` | `(controller, env, ctx) => Promise<void>` | Cloudflare cron trigger handler |

### Key design points

- **Per-request `McpServer` factory**: a new server instance is created for each request. Required by SDK security advisory GHSA-345p-7cg4-v4c7.
- **Env bindings refreshed per-request**: Cloudflare may rotate binding object references between requests; the handler re-injects them on every call.
- **`ctx.waitUntil()` is documented but not yet called by the framework**: the `ExecutionContext` is received and passed through to `app.fetch` and `onScheduled`, but the framework does not currently call `ctx.waitUntil()` for telemetry flush. Spans complete synchronously within the request lifecycle.
- **Singleton app promise with retry-on-failure**: the framework init runs once; if it fails, the next request retries rather than leaving the Worker in a permanently broken state.

---

## Binding types

Cloudflare Workers bindings come in two kinds with different injection mechanisms:

| Type | Examples | Injection mechanism | Runtime access |
|:-----|:---------|:--------------------|:---------------|
| String values | API keys, base URLs, feature flags | `injectEnvVars()` → `process.env` | `process.env.MY_API_KEY` |
| Object bindings | KV namespace, R2 bucket, D1 database, AI | `storeBindings()` → `globalThis` | `(globalThis as any).MY_CUSTOM_KV` |

**`extraEnvBindings`**: array of `[bindingKey, processEnvKey]` tuples. The value of `env[bindingKey]` is assigned to `process.env[processEnvKey]` at request time.

**`extraObjectBindings`**: array of `[bindingKey, globalKey]` tuples. The object at `env[bindingKey]` is stored on `globalThis[globalKey]` at request time.

Both are refreshed on every request. Never cache binding references between requests.

---

## `CloudflareBindings` extensibility

Core defines `CloudflareBindings` without an index signature, so servers extend it via intersection rather than module augmentation:

```ts
import type { CloudflareBindings as CoreBindings } from '@cyanheads/mcp-ts-core/worker';

interface MyBindings extends CoreBindings {
  MY_CUSTOM_KV: KVNamespace;
  MY_R2_BUCKET: R2Bucket;
}
```

Pass `MyBindings` as a type parameter where the framework accepts a generic env type (e.g., Hono route handlers, `onScheduled`).

---

## Runtime compatibility

### `runtimeCaps` feature detection

```ts
import { runtimeCaps } from '@cyanheads/mcp-ts-core/utils';

if (runtimeCaps.isWorkerLike) {
  // Workers-specific path
}

if (runtimeCaps.isNode) {
  // Node.js-specific path (e.g., filesystem access)
}
```

`runtimeCaps` is a snapshot taken at import time. Fields: `isNode`, `isBun`, `isWorkerLike`, `isBrowserLike`, `hasProcess`, `hasBuffer`, `hasTextEncoder`, `hasPerformanceNow`. All booleans, never throw.

### Serverless storage whitelist

In Workers, only these storage providers are allowed:

| Provider | Notes |
|:---------|:------|
| `in-memory` | Default — data lost on cold start, no persistence |
| `cloudflare-kv` | KV namespace binding — eventually consistent |
| `cloudflare-r2` | R2 bucket binding — object storage |
| `cloudflare-d1` | D1 database binding — SQLite-compatible |

`filesystem`, `supabase`, and unknown provider types are not on the whitelist:

- **`filesystem`** and unknown types throw `ConfigurationError` in serverless environments.
- **`supabase`** does **not** silently fall back. The framework may validate Supabase credentials first, but Worker startup still fails with `ConfigurationError` because Supabase storage is not a supported serverless provider. Do not set `STORAGE_PROVIDER_TYPE=supabase` in a Worker.

Set `STORAGE_PROVIDER_TYPE` to one of the four whitelisted values to avoid unexpected behavior.

---

## `wrangler.toml` requirements

```toml
compatibility_flags = ["nodejs_compat"]
compatibility_date = "2025-09-01"  # must be >= 2025-09-01

[[kv_namespaces]]
binding = "MY_CUSTOM_KV"
id = "..."

[[r2_buckets]]
binding = "MY_R2_BUCKET"
bucket_name = "..."
```

`nodejs_compat` is required for Node.js API shims (e.g., `process.env`, `Buffer`, `crypto`). The minimum `compatibility_date` activates the required shim set.

---

## Workers-specific warnings

**`instructions` resolver runs after env injection.** When `instructions` is a function, it runs inside `initializeApp(env)` — after `injectEnvVars()` — so env-derived text reaches the model without fighting the Workers module-load lifecycle:

```ts
export default createWorkerHandler({
  tools: [echoTool],
  instructions: (env) =>
    `Region: ${env.ENVIRONMENT ?? 'production'}.` +
    (env.MAINTENANCE_MODE ? ' Read-only mode — writes disabled.' : ''),
});
```

Plain strings work the same as on `createApp`. Type extends `Omit<CreateAppOptions, 'instructions'>`, so this is the only option whose shape differs between Node and Worker entry points.

**Lazy env parsing is mandatory.** Cloudflare injects env bindings at request time via `injectEnvVars()`, after all static module imports complete. Never parse `process.env` at module top-level in Workers:

```ts
// WRONG — parsed before env is injected
const apiKey = process.env.MY_API_KEY;  // undefined in Workers

// CORRECT — lazy parse inside a function or getter
export function getServerConfig() {
  return ServerConfigSchema.parse({ apiKey: process.env.MY_API_KEY });
}
```

**`in-memory` storage is volatile.** Data stored with the `in-memory` provider is lost between cold starts and is not shared across Worker instances. Use `cloudflare-kv`, `cloudflare-r2`, or `cloudflare-d1` for any state that must persist or be shared.

**Node-only utilities throw in Workers.** `scheduler` (`node-cron`), `sanitizePath` (fs-based), and `filesystem` storage provider all throw `ConfigurationError` when called from a Worker. Guard with `runtimeCaps.isNode` or avoid entirely.

**DataCanvas is unavailable in Workers.** DuckDB has no V8-isolate build, so `core.canvas` is always `undefined` on Workers. Setting `CANVAS_PROVIDER_TYPE=duckdb` (the only non-default value) in `wrangler.toml` triggers a fail-closed `ConfigurationError` at init time:

> `DuckDB canvas requires Node.js or Bun. Set CANVAS_PROVIDER_TYPE=none or omit it for Cloudflare Workers deployment.`

Leave the env unset (or set to `none`) for Worker deployments. Tools that conditionally use canvas should check `if (!ctx.core.canvas) { ... }` and surface a clear "feature unavailable on this deployment" message. See `api-canvas` for the full DataCanvas reference.

Related Skills

tool-defs-analysis

8
from cyanheads/pubchem-mcp-server

Read-only audit of MCP definition language across an existing surface — tools, resources, prompts. Walks every definition file and checks 12 categories the LLM reads to decide whether and how to call: voice & tense, internal leaks, audience leaks, defaults, recovery hints, output descriptions, cross-references, sparsity, examples, structure, mutator observability, unit-bearing numeric names. Produces grouped findings with file:line citations and a numbered options list. Use during polish, after a refactor, or before a release. Complements `field-test` (behavior testing) and `security-pass` (security audit).

setup

8
from cyanheads/pubchem-mcp-server

Post-init orientation for an MCP server built on @cyanheads/mcp-ts-core. Use after running `@cyanheads/mcp-ts-core init` to understand the project structure, conventions, and skill sync model. Also use when onboarding to an existing project for the first time.

security-pass

8
from cyanheads/pubchem-mcp-server

Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / sampling / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass. Produces grouped findings and a numbered options list.

report-issue-local

8
from cyanheads/pubchem-mcp-server

File a bug or feature request against this MCP server's own repo. Use for server-specific issues — tool logic, service integrations, config problems, or domain bugs that aren't caused by the framework.

report-issue-framework

8
from cyanheads/pubchem-mcp-server

File a bug or feature request against @cyanheads/mcp-ts-core when you hit a framework issue. Use when a builder, utility, context method, or config behaves contrary to the documented API — not for server-specific application bugs.

release-and-publish

8
from cyanheads/pubchem-mcp-server

Ship a release end-to-end across every registry the project targets (npm, MCP Registry, GitHub Releases for `.mcpb` bundles, GHCR). Runs the final verification gate, pushes commits and tags, then publishes to each applicable destination. Assumes git wrapup (version bumps, changelog, commit, annotated tag) is already complete — this skill is the post-wrapup publish workflow. Retries transient network failures on publish steps; halts with a partial-state report when retries are exhausted or the failure is terminal.

polish-docs-meta

8
from cyanheads/pubchem-mcp-server

Finalize documentation and project metadata for a ship-ready MCP server. Use after implementation is complete, tests pass, and devcheck is clean. Safe to run at any stage — each step checks current state and only acts on what still needs work.

orchestrations

8
from cyanheads/pubchem-mcp-server

Pick and run a multi-phase workflow that chains foundational task skills (`git-wrapup`, `release-and-publish`, `maintenance`, `field-test`, `setup`, etc.) end-to-end. Routes user intent to a workflow file under `workflows/` — greenfield builds, maintenance + release, field-test + fix, or known-work + release. Single source for the universal rules (no commits without authorization, no destructive git, no marketing language), the orchestrator posture (own the goal, ground sub-agents in primary sources, verify against the goal), and the sub-agent strategy (orient block, parallel fanout, isolation, normalization) that apply across every workflow. Sub-agents are an optional capability — workflows run linearly when fanout isn't available.

maintenance

8
from cyanheads/pubchem-mcp-server

Investigate, adopt, and verify dependency updates — with special handling for `@cyanheads/mcp-ts-core`. Captures what changed, understands why, cross-references against the codebase, adopts framework improvements, syncs project skills, and runs final checks. Supports two entry modes: run the full flow end-to-end, or review updates you already applied.

git-wrapup

8
from cyanheads/pubchem-mcp-server

Land working-tree changes as logical commits — the work grouped by concern, topped by a release commit (version bump, changelog, regenerated artifacts) and an annotated tag. Verify, commit, tag. Stops at "committed and tagged locally" — no push, no publish. The release-and-publish skill picks up from here. Distilled from the git_wrapup_instructions protocol.

field-test

8
from cyanheads/pubchem-mcp-server

Exercise tools, resources, and prompts against a live HTTP server via MCP JSON-RPC over curl. Starts the server, surfaces the catalog, runs real and adversarial inputs, and produces a tight report with concrete findings and numbered follow-up options. Use after adding or modifying definitions, or when the user asks to test, try out, or verify their MCP surface.

devcheck

8
from cyanheads/pubchem-mcp-server

Lint, format, typecheck, and verify the project is clean. Use after making changes, before committing, or when the user asks to verify quality.