MCP Server

Expose business tools and resources to AI agents via the Model Context Protocol (MCP).

Overview

The app package lets you expose business logic to AI agents using the Model Context Protocol. MCP provides a standard way for LLMs to call your application’s functions and read its data.

Think of it as dual-protocol: your HTTP API serves humans and traditional clients, while MCP serves AI agents — same business logic, two entry points.

Developers register tools and resources using Rivaas-native types. There is no need to import mcp-go directly.

Defining a Tool

A tool is a function that an AI agent can call. Register tools with WithMCPTool.

Input is strongly typed: define a Go struct and use json + jsonschema tags.

type GetOrderInput struct {
    OrderID string `json:"order_id" jsonschema:"Order ID,minLength=1"`
}

app.WithMCP(
    app.WithMCPTool("get_order", "Get an order by ID",
        func(ctx context.Context, input GetOrderInput) (any, error) {
            return orderService.GetByID(ctx, input.OrderID)
        },
    ),
)

The handler returns any JSON-serializable value, or an error.

Defining a Resource

A resource is a read-only data source that an AI agent can fetch. Register resources with WithMCPResource:

app.WithMCP(
    app.WithMCPResource("orders://recent", "Recent Orders",
        "The 10 most recently placed orders",
        func(ctx context.Context) (any, error) {
            return orderService.ListRecent(ctx, 10)
        },
    ),
)

Input Schema via Struct Tags

Tool input schema is derived from struct tags.

Common jsonschema directives

DirectiveExample
description (first value)jsonschema:"Search query"
minLength / maxLengthjsonschema:"...,minLength=1,maxLength=100"
patternjsonschema:"...,pattern=^P[0-9]+$"
enumjsonschema:"...,enum=a,enum=b"
minimum / maximumjsonschema:"...,minimum=0,maximum=100"
defaultjsonschema:"...,default=1"
type SearchInput struct {
    Query       string  `json:"query"      jsonschema:"Search text,minLength=1"`
    Category    string  `json:"category"   jsonschema:"Category,enum=all,enum=books,enum=electronics"`
    MinPrice    float64 `json:"min_price"  jsonschema:"Minimum price,minimum=0"`
    InStockOnly bool    `json:"in_stock"   jsonschema:"Only in-stock items"`
    Page        int     `json:"page"       jsonschema:"Page number,minimum=1,default=1"`
}

Validation

Rivaas validates MCP configuration at init. These errors surface from app.New():

  • Nil handler: WithMCPTool and WithMCPResource require a non-nil handler.
  • Nil options: Nil MCPOption values produce an error (not silently skipped).
  • Duplicate tool names: Each tool name must be unique.
  • Empty names or descriptions: Tool names and descriptions must be non-empty.

Nil MCPOption and nil handlers always return config errors. Input-schema validation errors from invalid names, duplicate tools, or schema registration also surface at init.

Security Considerations

MCP is opt-in. It is only enabled when you call WithMCP().

For conditional enablement, use WithMCPIf:

app.WithMCPIf(os.Getenv("MCP_ENABLED") == "true",
    app.WithMCPTool(...),
)

In production, protect MCP endpoints with authentication middleware, just like your HTTP API.

Complete Example

package main

import (
    "context"
    "log"
    "net/http"

    "rivaas.dev/app"
)

type SearchInput struct {
    Query       string  `json:"query" jsonschema:"Search query,minLength=1"`
    MinPrice    float64 `json:"min_price" jsonschema:"Minimum price,minimum=0"`
    InStockOnly bool    `json:"in_stock_only" jsonschema:"Only in-stock items"`
    Page        int     `json:"page" jsonschema:"Page number,minimum=1,default=1"`
}

func main() {
    a, err := app.New(
        app.WithServiceName("orders-api"),
        app.WithServiceVersion("v1.0.0"),

        app.WithMCP(
            app.WithMCPTool("search_products", "Search the product catalog",
                func(ctx context.Context, input SearchInput) (any, error) {
                    return productService.Search(
                        ctx,
                        input.Query,
                        input.MinPrice,
                        input.InStockOnly,
                        input.Page,
                    )
                },
            ),

            app.WithMCPResource("orders://recent", "Recent Orders",
                "The 10 most recently placed orders",
                func(ctx context.Context) (any, error) {
                    return orderService.ListRecent(ctx, 10)
                },
            ),
        ),
    )
    if err != nil {
        log.Fatal(err)
    }

    // HTTP API for humans
    a.GET("/products", func(c *app.Context) {
        c.JSON(http.StatusOK, map[string]string{"message": "use the API"})
    })

    // MCP server for AI agents: http://localhost:8080/mcp

    if err = a.Start(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Next Steps