Skip to content
published Visibility internal Owner erik@uvilo.com Approver _ Created _ Updated _

Write Typescript

When to Use

Creating or modifying TypeScript MCP server projects under Forge/Configs/MCP_Servers/ or Forge/Configs/Agents/.

Two Patterns

PatternUse forRun withOutputCommitted?
Bundled projectMCP servers, long-running servicesnode dist/index.jsdist/ (gitignored)No — rebuilt at checkout
Standalone scriptSkill utilities, one-shot tasksnpx tsx script.tsNoneSource only

Conventions

  1. Bundled projects: esbuild bundles src/index.ts to dist/index.js — a single-file ESM bundle executed with node dist/index.js. No node_modules at runtime. dist/ is gitignored; rebuild with the Build Typescript skill.
  2. Standalone scripts: Executed directly via npx tsx script.ts. No build step, no dist/.
  3. Module system: ESM only ("type": "module" in package.json).
  4. MCP SDK: McpServer from @modelcontextprotocol/sdk/server/mcp.js, StdioServerTransport from @modelcontextprotocol/sdk/server/stdio.js.
  5. Schema validation: Zod via zod/v4 import.
  6. Project structure (bundled): src/index.ts entry, dist/ output (gitignored), package.json, tsconfig.json.
  7. Build command (bundled): esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js
  8. Type checking: tsc --noEmit (separate from build; esbuild doesn’t type-check).
  9. Error handling: Tools return { content: [{ type: 'text', text: error }], isError: true }.
  10. Logging: console.error() for debug/diagnostic output (stdout is reserved for MCP protocol).
  11. Naming: kebab-case for directories and file names; PascalCase for TypeScript types/interfaces.
  12. Railway env: Shared utility for reading /proc/1/environ to inject Railway environment variables.
  13. No hard-coded configs: Config values like secrets, urls, etc. must never be hard-coded, but read from the environment
  14. No env var fallback: When using an environment variable, don’t supply a default value in case it is undefined - fail loudly instead.
  15. Exact versions: All dependencies and devDependencies must use exact version strings (e.g., "1.8.2", not "^1.8.2"). No ranges or prefixes.

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

package.json scripts

  • "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js"
  • "typecheck": "tsc --noEmit"

Dependencies

  • Runtime (bundled): @modelcontextprotocol/sdk, zod, @cfworker/json-schema (peer dep of SDK)
  • Dev (bundled): esbuild, typescript, @types/node
  • Standalone scripts: Only Node.js built-ins; no external deps. If deps are needed, it should be a bundled project instead.
  • All versions must be exact (no ^ or ~ prefixes)