Custom Binary Embedding (pkg/app)
Embed the complete Moul engine in your Go binary with embedded Web Admin Console, custom HTTP routes, and background worker handlers.
pkg/app allows embedding the complete moul server into custom Go binaries with tailored HTTP endpoints, background workers, and the pre-built Web Admin Console out-of-the-box.
Overview
When embedding Moul as a Go library:
- Built-in Web Admin Console: The production Web Admin Console (TanStack Router, Meta StyleX, React Aria) is embedded directly into your binary via
pkg/ui.DistFS()and served at/_moul_/. - Zero Node.js / Bun Requirement: Compiled static assets are bundled inside
github.com/moul-dev/moul-dev/pkg/ui/dist, allowing custom binary compilation using standardgo build. - Safe Route Prefixing: By default, the admin console mounts strictly at
AdminUIPrefix(/_moul_/) without intercepting/admin, preventing route collision with your host application. - Full Customizability: Override the console with your own frontend SPA, mount on a custom URL prefix, or disable the console entirely for headless API microservices.
Quickstart Example
Here is a complete, runnable custom binary example embedding Moul with custom routes, a background worker, and the embedded Web Admin Console:
package main
import (
"context"
"fmt"
"log/slog"
"net/http"
"os"
"time"
"github.com/labstack/echo/v5"
"github.com/moul-dev/moul-dev/pkg/app"
"github.com/moul-dev/moul-dev/pkg/worker"
)
func main() {
// 1. Initialize Moul application instance
// The Web Admin Console is automatically bundled and served at http://localhost:8090/_moul_/
moulApp := app.New(app.Config{
Version: "1.0.0-custom",
})
// 2. Register custom HTTP route
moulApp.RegisterRoute("GET", "/api/custom/ping", func(c *echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"status": "pong",
"server": "custom-moul",
})
})
// 3. Attach directly to raw Echo router
moulApp.OnRouterInit(func(router *echo.Echo) error {
v1 := router.Group("/api/v1")
v1.GET("/health", func(c *echo.Context) error {
return c.String(http.StatusOK, "Engine healthy")
})
return nil
})
// 4. Register custom background job worker
moulApp.RegisterWorker("ProcessThumbnail", func(ctx context.Context, job *worker.Job) error {
imageURL, ok := job.Args["image_url"].(string)
if !ok {
return fmt.Errorf("missing image_url")
}
slog.Info("Processing thumbnail", "url", imageURL)
return nil
})
// 5. Register periodic cron worker
moulApp.RegisterPeriodicWorker(24*time.Hour, "DailyReport", func(ctx context.Context, job *worker.Job) error {
slog.Info("Running daily report cleanup...")
return nil
})
// 6. Start the server (supports CLI subcommands such as "mcp", "worker", "seed", "start")
if !app.IsMCP() {
fmt.Println("🚀 Custom Moul server running at http://localhost:8090")
fmt.Println("🛠️ Web Admin Console at http://localhost:8090/_moul_/")
}
if err := moulApp.Start(context.Background()); err != nil {
slog.Error("Server failed", "err", err)
os.Exit(1)
}
}Configuring the Web Admin Console
Mount the Web Admin Console on a different path, such as /dashboard:
// Via Config:
moulApp := app.New(app.Config{
AdminUIPrefix: "/dashboard",
})
// Or via fluent builder:
moulApp.WithAdminPrefix("/dashboard")Supply your own embedded SPA (fs.FS) instead of the default Moul admin console:
//go:embed all:frontend/dist
var myCustomFrontend embed.FS
// Strip dist prefix to root filesystem
subFS, _ := fs.Sub(myCustomFrontend, "frontend/dist")
// Attach custom SPA filesystem:
moulApp.WithAdminUI(subFS).WithAdminPrefix("/app")All static assets and client-side HTML5 SPA sub-routes will automatically be resolved and served with caching and SPA index fallback.
Disable mounting the Web Admin Console completely for lightweight or API-only microservices:
// Via Config:
moulApp := app.New(app.Config{
DisableAdminUI: true,
})
// Or via fluent builder:
moulApp.DisableAdminUI()Customize the URL path prefix for all API routes (or mount at root using ""):
// Mount at /v1 instead of default /api
moulApp.WithAPIPrefix("/v1")
// Or mount directly at root level
moulApp.WithAPIPrefix("")Configuration Reference (app.Config)
| Field | Type | Default | Description |
|---|---|---|---|
Version | string | "dev" | Application release version string. |
Env | string | "development" | Runtime environment ("development" or "production"). |
DBPath | string | "moul-local.db" | SQLite database file path or ":memory:". |
Port | string | "8090" | HTTP listener port. |
APIPrefix | *string | nil ("/api") | URL path prefix for API endpoints. Set to pointer to "" or use WithAPIPrefix("") to mount at root. |
JWTSecret | string | MOUL_JWT_SECRET | Secret key for signing and verifying JWT tokens. |
AdminKey | string | MOUL_ADMIN_KEY | Master administrative API key. |
AdminUIFS | fs.FS | pkg/ui.DistFS() | Filesystem containing web console assets. Defaults to embedded Moul UI bundle. |
AdminUIPrefix | string | "/_moul_" | URL path prefix where the Web Admin Console is mounted. |
DisableAdminUI | bool | false | When true, completely disables registering the Web Admin Console routes. |
DisableCLIParsing | bool | false | When true, disables automatic CLI argument dispatching so Start() always boots the HTTP server. |
Model Context Protocol (MCP) in Custom Binaries
Custom binaries built with pkg/app natively support the built-in MCP server over stdio (my-binary mcp) and Streamable HTTP / SSE (/api/mcp).
Stdio Transport Mode (my-binary mcp)
Configure your custom binary directly in your AI assistant's MCP configuration (Claude Desktop, Cursor, Gemini, Antigravity):
{
"mcpServers": {
"my-app": {
"command": "/path/to/my-binary",
"args": ["mcp"],
"env": {
"MOUL_DB_PATH": "./moul-local.db"
}
}
}
}Key features of stdio mode in embedded custom binaries:
- Clean Stdio Channel: Stdio mode reserves
stdoutexclusively for JSON-RPC messages. Always guard terminal banners or rawfmt.Printlnoutput withif !app.IsMCP() { ... }or write diagnostic logs tostderrusinglogger.Info/slog.Info. - Integrated Worker Queues: Custom background workers registered with
moulApp.RegisterWorkerare immediately accessible to MCP tools (such asmoul_list_worker_jobsandmoul_enqueue_job). - No Secrets Required: Stdio mode runs locally with the user's execution privileges and operates directly on SQLite without requiring
MOUL_JWT_SECRETorMOUL_ADMIN_KEY. - Programmatic Control: Use
moulApp.RegisterMCPTool(tool, handler)to add custom domain tools to the AI assistant, or hook intomoulApp.OnMCPInit(...). - Dual HTTP Authentication: In HTTP mode (
my-binary start), remote MCP clients can authenticate using eitherAuthorization: Bearer <ADMIN_KEY>orX-Admin-Key: <ADMIN_KEY>.
Lifecycle Hooks Reference
RegisterRoute(method, path, handler, middleware...)
Registers a single HTTP handler directly on the embedded router before server start.
RegisterMCPTool(tool, handler)
Registers a custom Model Context Protocol (MCP) tool that is automatically exposed across both Stdio and Streamable HTTP / SSE transports.
OnMCPInit(callback)
Hook that executes when the built-in MCP server is initialized, allowing low-level access to the underlying MCP server.
OnRouterInit(callback)
Provides access to the underlying *echo.Echo instance immediately after core Moul routes are configured. Enables custom middleware registration, route grouping (router.Group(...)), and specialized handler mounting.
OnBeforeStart(callback)
Fires after SQLite database connections and dynamic collections are initialized, but before HTTP listening begins. Ideal for running custom migrations or seeding initial collection fixtures via app.DB().
OnWorkerInit(callback)
Executes when the worker engine initializes, providing access to *worker.Engine for advanced queue registration.
Standalone Examples
Complete, standalone runnable examples are available in the repository:
examples/custom-binary-with-mcp/: Demonstrates custom binary embedding with full MCP support, custom MCP tool registration, dual HTTP auth (BearerandX-Admin-Key), and stdio execution.examples/custom-binary/: Demonstrates custom binary embedding with the Web Admin Console, custom API endpoints, and worker tasks.examples/custom-routes/: Custom route groupings and Echo middlewares.examples/custom-worker/: Background job dispatching and retries.