Dibbla Docs Get started Guides Workflows Changelog
Open Console
Guides

Publish an MCP

Turn the tool server your app runs into an MCP of its own with one manifest line, connect Claude Code or Cursor to it, and decide who in your organization may use it.

A tool server is a Go worker that registers functions with the platform. Run one as a service in an app, add one line to the app’s dibbla.yaml, and it becomes an MCP with an address of its own:

https://mcp.<domain>/platform/servers/<name>

Each function of the server is one tool there. Using it takes an ordinary Dibbla sign-in, and who gets the tools follows the app’s access list — the same list that decides who may open the app. You need to be able to deploy the app; no organization admin has to expose anything.

If you already have a tool server — your own, or one made from go-worker-starter-template — skip to the manifest line.

Per-server addresses are switched on per installation

An operator turns these addresses on for the whole installation. Where they are off, the deploy tells you so and the address answers 404; the app itself deploys as usual. Self-hosting? See the Helm value.

A minimal tool server

Three files. main.go connects to the platform under the name in SERVER_NAME, registers one function, and keeps a small HTTP listener for the app’s own URL and health:

package main

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

	sdk "github.com/dibbla-agents/sdk-go"
)

type In struct {
	Name string `json:"name"`
}

type Out struct {
	Message  string `json:"message"`
	CalledBy string `json:"called_by,omitempty"`
}

func main() {
	// No API token: a deployed app connects with its workload identity.
	server, err := sdk.New(sdk.WithServerName(os.Getenv("SERVER_NAME")))
	if err != nil {
		log.Fatal(err)
	}

	server.RegisterFunction(sdk.NewSimpleFunction[In, Out]("greeting", "1.0.0",
		"Greet a person by name.").WithContextHandler(func(ctx context.Context, in In) (Out, error) {
		out := Out{Message: "Hello, " + in.Name + "!"}
		// Who is calling comes from their Dibbla sign-in, not from the arguments.
		if c, ok := sdk.CallerFromContext(ctx); ok && c.IsUser() {
			out.CalledBy = c.Email
		}
		return out, nil
	}))

	// The app's own URL and health check.
	go http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		fmt.Fprintln(w, "ok")
	}))

	log.Fatal(server.Start())
}

The function’s description and its input struct are what an MCP client shows as the tool’s description and arguments, so write them for the person — or the agent — who will call it. CallerFromContext needs sdk-go v0.0.24 or later.

A Dockerfile that builds it:

FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /mcp .

FROM alpine:3.20
RUN apk add --no-cache ca-certificates && adduser -D -u 10001 app
USER app
COPY --from=build /mcp /mcp
CMD ["/mcp"]

Create go.mod and go.sum once with go mod init <module> and go mod tidy. Like every app, this one also needs its end-user handbook at docs/index.md before it deploys — see Your first deploy. The Go SDK reference covers the worker side in more depth.

Add the mcp: line

dibbla.yaml describes the service. The one line that publishes it is mcp::

version: 1

services:
  mcp:
    build: .
    port: 8080
    public: true
    mcp: my-tools                    # https://mcp.<domain>/platform/servers/my-tools
    auth:
      require_login: true
      access_policy: all_members     # or invite_only
    environment:
      SERVER_NAME: my-tools          # the same name as mcp:
      GRPC_SERVER_ADDRESS: grpc.dibbla.com:443
      GRPC_USE_TLS: "true"
  • The value is the tool server’s name — what the worker registers under — and the last part of the address. It must match SERVER_NAME exactly. 1–64 characters of letters, digits, ., _ and -.
  • Names are unique. A name that another app already owns is not published; the deploy says so, and you pick another.
  • GRPC_SERVER_ADDRESS is the workflow engine of the installation you deploy to. The value above is Dibbla’s hosted service; on a self-hosted installation use its own address.
  • Every function of the server that can be a tool becomes one. There is no per-function switch in this version.

The manifest reference has the field’s exact rules.

Deploy and read the answer

$dibbla deploy . --alias my-tools -m "feat: publish my-tools as an MCP"
 
published as MCP: my-tools  ·  https://mcp.<domain>/platform/servers/my-tools  ·  connect: dibbla mcp server my-tools

A deploy with an mcp: line always says what happened to it, and never fails because of it — the app is live either way. You get exactly one of these:

The deploy saysWhat it meansWhat to do
published as MCP: my-tools with the addressThe address is live.Connect a client.
The name was not published: another app already owns that tool server nameTool server names are unique.Rename the server — mcp: and SERVER_NAME both — and deploy again.
The name is not a valid tool server nameIt breaks the naming rule above.Fix the mcp: line and deploy again.
Per-server MCP addresses are switched off on this installationThe publication is stored, but the address answers 404 until the operator turns the addresses on.Ask whoever operates the installation. Nothing needs redeploying afterwards.
This installation has no workflow engine configured for itNothing was published.Ask whoever operates the installation.
Publishing failedThe app is deployed; the publication is not.Deploy again.
withdrawn as MCP: my-toolsThe mcp: line left the manifest, so the address stopped answering.Nothing — this is unpublishing.

With --json the same answer arrives as fields on the result: mcp_published (a list of names), mcp_notice (the reason, as a sentence) and mcp_withdrawn (a list of names).

Connect Claude Code or Cursor

dibbla mcp server my-tools prints ready-to-paste configuration for Claude Code, Cursor, Codex CLI, Gemini CLI and opencode; add --client cursor for just one. There is no token to copy in any of them: the client opens the browser and runs the sign-in itself.

Add the server, then start a session:

claude mcp add --transport http dibbla-my-tools https://mcp.<domain>/platform/servers/my-tools

If Claude Code shows the server as needing authentication, run /mcp in the session, pick dibbla-my-tools and choose Authenticate. Add the server and connect once before you authenticate, in that order.

Put the server in ~/.cursor/mcp.json (or .cursor/mcp.json in a project):

{
  "mcpServers": {
    "dibbla-my-tools": {
      "url": "https://mcp.<domain>/platform/servers/my-tools"
    }
  }
}

Cursor opens the browser the first time the server is used. Settings → MCP shows the connection, and each tool can be switched on and off there.

The sign-in is for this one server. The consent page names the server — and the app that publishes it — and asks for a single permission: to use that server’s tools. The token that comes back works on this address and nowhere else, so connecting to my-tools gives the client no way to deploy apps, read logs or reach another server. The other direction holds too: being connected to the platform connector does not sign you in here.

Then ask the assistant to greet someone. greeting appears as a tool, and the answer carries your own e-mail address in called_by.

Connected, but no tools?

The address answers the same way — connected, with an empty tool list — for a name that does not exist, a server whose worker is not running, and a server whose app does not admit you. That is deliberate: nobody can probe which servers exist. dibbla mcp server my-tools --check walks the chain and ends with the number of tools you get.

Decide who gets in

The app’s access policy decides, and you set it in the manifest (auth.access_policy) or on the app’s Access tab in the console.

PolicyWho gets the tools
all_members (default)Every member of the app’s organization.
invite_onlyThe people and teams on the app’s list, plus the organization’s owners and admins.

To restrict the MCP to named people, set access_policy: invite_only and deploy. Then open the app in the console, choose the Access tab, and add them with Add member or Add team — as in Decide who can open it.

The same tab shows a card for the tool server. By default it is Same as the app. Choose Own access when the MCP should have a list of its own — say, everyone may open the web app but only one team may use its tools, or everyone at or above a role. See the app’s tool server.

What to expect:

  • Owners and admins of the organization always get in. That is not something the list can take away.
  • The app’s author gets in through their own row. Whoever first deployed the app is on its list with a row of their own; being the author is not a separate way in.
  • Adding or removing a person applies to their next call. The tool list a connected client already holds can lag by a few seconds.
  • Switching the policy between all_members and invite_only takes effect within 30 seconds.
  • A removed person sees an empty server. The tool list is empty and a call to a tool name they remember is answered as not found — the same as for a server that does not exist.
  • A sign-in is always required. An app that is open without login does not make its MCP open.
Only members of the app's organization

An MCP can be used only by people who are members of the organization the app belongs to. Inviting someone from outside the organization by e-mail lets them open the app, but does not give them its MCP in this version.

Known limitation: invite-only governs the MCP address, nothing more

invite_only decides who may use the MCP. It does not hide the tool server’s functions from the rest of the organization: they are still in the organization’s shared function list in the workflow engine, where any member can see them and call one from a workflow of their own. Databases the organization shares are likewise not restricted by the app’s access list. Do not rely on invite_only to keep a function or its data from colleagues.

What publishing does not change

A published server and the platform connector are separate surfaces with separate rules. Publishing never adds a function to platform_tools on /platform, and an admin exposing one of the server’s functions there never lets anyone onto the server’s address. The MCP reference has the full account, including tool servers that are not published by an app.

Every call is logged, and the same per-user rate limit and per-organization quota apply as on /platform.

Unpublish

Remove the mcp: line and deploy. The deploy answers withdrawn as MCP: my-tools, the address stops serving the tools at once, and connected clients see an empty server. Deleting the app withdraws the MCP too, and frees the name for another app.