Skip to main content

Exposing a Service as MCP

An MCP service is a WSO2 Integrator artifact that publishes a set of tools over the Model Context Protocol. Any MCP-compatible client (Claude Desktop, GitHub Copilot, or another AI agent) can connect, discover the tools, and call them. This page covers the configuration options for the listener, service, and tools, including how to read request metadata and HTTP request data, handle sessions, and define tools dynamically.

WSO2 Integrator MCP services run over the Streamable HTTP transport. The generated code uses the mcp:StreamableHttpListener listener and the mcp:StreamableHttpService service type from the ballerina/mcp module.

Creating an MCP service​

  1. In the design view, select Add Artifact manually.

  2. Under the AI Integration category, select MCP Service.

    Artifacts page showing the AI Integration category with Chat Agent Service, Durable Agentic Workflow, Voice Agent Service, and MCP Service. Above are Automation and Durable Workflow; below is Integration as API.Artifacts page showing the AI Integration category with Chat Agent Service, Durable Agentic Workflow, Voice Agent Service, and MCP Service. Above are Automation and Durable Workflow; below is Integration as API.
  3. Choose how to create the service, fill in the creation form fields, and click Create.

    Create MCP Service form with the Design From Scratch and Import From OpenAPI Specification options, and fields for Service Name, Version, Port (default 8080), Base Path (/mcp), and an expandable Advanced Configurations section.Create MCP Service form with the Design From Scratch and Import From OpenAPI Specification options, and fields for Service Name, Version, Port (default 8080), Base Path (/mcp), and an expandable Advanced Configurations section.
FieldDescription
Design From Scratch / Import From OpenAPI SpecificationDesign From Scratch (default) creates an empty MCP service that you add tools to. Import From OpenAPI Specification generates an MCP server from an existing OpenAPI specification.
Service NameDisplay name advertised to MCP clients (for example, MCP Service). Sent to clients as serverInfo.name during initialization.
VersionService version string (for example, 1.0.0). Sent to clients as serverInfo.version during initialization.
PortListening port for the MCP listener. Defaults to 8080.
Base PathURL prefix for the MCP service (for example, /mcp).
Advanced ConfigurationsExpand to set the name of the listener that is created for the service. Defaults to mcpListener.

After clicking Create, WSO2 Integrator opens the service in the MCP Service Editor. The header shows the listener and base path, a Tools section, and Configure / Try It buttons in the top-right.

The MCP Service editor showing the listener and base path chips, and the Tools section with 'No tools found. Add a new tool.' and a + Add Tool button.The MCP Service editor showing the listener and base path chips, and the Tools section with 'No tools found. Add a new tool.' and a + Add Tool button.
ElementWhat it does
ListenerThe mcp:StreamableHttpListener the service runs on.
Base PathThe URL prefix the service is mounted on.
ToolsThe tools the service exposes. Empty by default; click + Add Tool to add one.
ConfigureService-level settings: base path, server info, session mode, HTTP options, and listener settings.
Try ItSend sample MCP requests to the service from inside the editor.

Service configuration​

Service configuration controls the base path, the server identity advertised to clients, the session management mode, and HTTP-level options such as CORS and authentication.

In the MCP Service Editor, click Configure in the header to open the MCP Service Configuration page. The left-hand navigation lists MCP Service and, under Attached Listeners, each listener the service uses (for example, mcpListener). Selecting MCP Service shows the service-level fields below.

FieldDescription
Streamable HTTP ServiceThe service type. Read-only; WSO2 Integrator always creates an mcp:StreamableHttpService, which handles MCP tool calls over the Streamable HTTP transport.
Base PathURL prefix for the MCP endpoint (for example, /mcp). Required.
StreamableHttpServiceConfigThe @mcp:StreamableHttpServiceConfig annotation record. Click the field to open the Record Configuration editor.

The same form also shows the configuration of the attached listener. See Listener configuration.

Record Configuration editor for StreamableHttpServiceConfiguration showing the info field (with version and name), httpConfig, sessionMode (auto), and the optional options field.Record Configuration editor for StreamableHttpServiceConfiguration showing the info field (with version and name), httpConfig, sessionMode (auto), and the optional options field.
FieldDescription
info.nameName of your server implementation (for example, MCP Weather Server). Sent to clients as serverInfo.name during initialization. Required.
info.versionVersion of your server implementation (for example, 1.0.0). Sent as serverInfo.version. This is the version of your service, not the MCP protocol version; the protocol version is negotiated by the runtime. Required.
httpConfigHTTP service configuration (CORS, auth, compression, validation, and so on). Same fields as the HTTP service @http:ServiceConfig.
sessionModeSession management mode. Options: auto (default), stateful, stateless. See Session modes.
options.capabilitiesCapabilities the server advertises during initialization. Defaults to {tools: {}}.
options.instructionsInstructions describing how to use the server and its tools. Sent to clients during initialization.
options.enforceStrictCapabilitiesWhether to enforce strict compliance with the advertised capabilities.

Session modes​

ModeUse when…
mcp:AUTO (default)Mode is decided automatically based on how the client initializes. Recommended for most services.
mcp:STATEFULThe transport tracks session IDs and your tools need per-session state (for example, a shopping cart). Tools can declare an mcp:Session first parameter to read and write session-scoped data. See Stateful tools.
mcp:STATELESSEach request is independent. Use for pure functions and read-only lookups.

Listener configuration​

The listener binds to a port and handles incoming MCP connections over Streamable HTTP.

In the MCP Service Configuration form, select the listener under Attached Listeners to configure it.

Configuration for mcpListener showing Name, Listen To (8080), Host, HTTP1 Settings, Secure Socket, HTTP Version, and Timeout fields.Configuration for mcpListener showing Name, Listen To (8080), Host, HTTP1 Settings, Secure Socket, HTTP Version, and Timeout fields.

For standard HTTP setups, only Listen To (the port) is required. Configure Secure Socket to enable HTTPS.

FieldDescriptionDefault
NameIdentifier for the listener (for example, mcpListener).
Listen ToListening port (or an existing http:Listener reference). Required.
HostHost name or IP address the listener binds to.0.0.0.0
HTTP1 SettingsHTTP/1.x protocol settings (keep-alive, max pipelined requests).{}
Secure SocketTLS/SSL configuration. Configure this to enable HTTPS.()
HTTP VersionHighest HTTP version the endpoint supports.HTTP/2.0
TimeoutRead/write timeout in seconds. Set to 0 to disable.60
Request LimitsInbound size limits for URI, headers, and request body.{}
Graceful Stop TimeoutGrace period in seconds before the listener force-stops.0

Tools​

Tools are the operations the MCP service exposes. Each tool has a name, a description, typed parameters, and a typed return value. The framework derives the JSON Schema sent to clients automatically from the Ballerina types.

Click + Add Tool in the editor to open the New Tool Configuration panel.

The New Tool Configuration panel showing Tool Name, Tool Description, Parameters, Return Type, and a collapsed Advanced Configurations section, with the MCP Service editor visible behind it.The New Tool Configuration panel showing Tool Name, Tool Description, Parameters, Return Type, and a collapsed Advanced Configurations section, with the MCP Service editor visible behind it.
FieldRequiredDescription
Tool NameYesName MCP clients will see. Use camel-case and be descriptive: getOrderStatus, searchProducts.
Tool DescriptionNoWhat the tool does and when the assistant should use it. Although optional, this is the single most important field for tool discoverability. See Writing a good tool description.
ParametersNoClick + Add Parameter to add an argument the tool call must (or may) supply. Each parameter has a name, type, and description. Descriptions are included in the schema sent to the client.
Return TypeYesBallerina type returned by the tool.
Advanced ConfigurationsNoBind request metadata and HTTP request data to the tool. See Transport parameters and request metadata.

After clicking Save, WSO2 Integrator generates a remote function within the service and opens the tool's flow diagram, where you implement the tool logic. The tool also appears under the MCP service in the project sidebar and as a row under Tools in the MCP Service Editor.

The flow diagram of the add remote function, showing an empty flow with a Start node, with add listed under MCP Service in the project sidebar.The flow diagram of the add remote function, showing an empty flow with a Start node, with add listed under MCP Service in the project sidebar.

Stateful tools with mcp:Session​

When the service is configured with sessionMode: mcp:STATEFUL, tools can take mcp:Session as the first parameter to read and write per-client session state.

@mcp:StreamableHttpServiceConfig {
info: {name: "Shopping Cart Server", version: "1.0.0"},
sessionMode: mcp:STATEFUL
}
service mcp:StreamableHttpService /mcp on mcpListener {

@mcp:Tool {
description: "Add an item to the shopping cart"
}
remote function addToCart(mcp:Session session, string productName, decimal price)
returns string|error {
CartItem[] cart = session.hasKey("cart")
? check session.getWithType("cart")
: [];
cart.push({productName, price});
session.set("cart", cart);
return string `Added ${productName}. Total items: ${cart.length()}`;
}
}

Writing a good tool description​

The tool description is what the AI client reads to decide whether and when to call the tool. Two patterns matter most:

Be explicit about scope

"Search the product catalog by keyword. Returns up to 10 matching products with name, price, and availability. Use for general product discovery questions."

"Retrieves a single product by its exact SKU. Use only when the user provides a SKU."

These two are clearly distinct, and the AI will not confuse them.

Bake in safety rules

"Cancel a customer order. IMPORTANT: Always confirm with the customer before calling this tool. This action cannot be undone."

The AI client will follow it most of the time. For deterministic enforcement, also keep server-side checks.

Don't leave it generic

"Customer endpoint." ❌

A generic description leads to bad tool selection downstream. If you can't write a useful one-liner, the tool is probably too vague to be useful.

Transport parameters and request metadata​

Besides the arguments the client supplies, a tool can bind data that the runtime injects from the incoming request:

  • Request metadata (mcp:Meta?): the _meta object the client attached to the tool call.
  • HTTP request data: individual HTTP headers (@http:Header), all headers (http:Headers), or the full HTTP request (http:Request) that carried the tool call.

These parameters are excluded from the tool's input schema, so MCP clients never see them as tool arguments. Use them to read authentication tokens, tenant IDs, correlation IDs, or other request context without asking the AI client to pass them.

In the New Tool Configuration panel, expand Advanced Configurations.

The Advanced Configurations section of the New Tool Configuration panel showing the Meta checkbox, Transport Parameters with HTTP Headers and a + Header button, and Request Access with Request and Headers checkboxes.The Advanced Configurations section of the New Tool Configuration panel showing the Meta checkbox, Transport Parameters with HTTP Headers and a + Header button, and Request Access with Request and Headers checkboxes.
FieldGenerated parameterDescription
Metamcp:Meta? metaBinds the MCP request metadata (for example, a progress token) attached to the tool call.
HTTP Headers > + Header@http:Header <type> <name>Binds an individual HTTP header as a typed parameter. Set the parameter name, its type (string, int, boolean, decimal, float, their nilable forms, or string[]), an optional default value and description, and the Header Name if the wire header name differs from the parameter name (for example, X-Request-Id).
Requesthttp:Request requestBinds the underlying HTTP request that carried the tool call.
Headershttp:Headers headersBinds all the HTTP headers of the request that carried the tool call.

Advanced: mcp:StreamableHttpAdvancedService​

When tool definitions need to come from configuration, a database, or some runtime source (not from compile-time remote functions), use mcp:StreamableHttpAdvancedService. You implement two callbacks:

  • onListTools(): return the list of tools advertised to clients.
  • onCallTool(params, session): dispatch a tool call by name.
note

The visual designer creates only mcp:StreamableHttpService services. Write an advanced service in code.

import ballerina/mcp;

listener mcp:StreamableHttpListener mcpListener = new (8080);

@mcp:StreamableHttpServiceConfig {
info: {
name: "MCP Crypto Server",
version: "1.0.0"
},
sessionMode: mcp:STATELESS
}
service mcp:StreamableHttpAdvancedService /mcp on mcpListener {

remote isolated function onListTools()
returns mcp:ListToolsResult|mcp:ServerError {
return {
tools: [
{
name: "hashText",
description: "Generate a hash for the given text.",
inputSchema: {
"type": "object",
"properties": {
"text": {"type": "string", "description": "Text to hash"},
"algorithm": {
"type": "string",
"enum": ["md5", "sha1", "sha256"],
"default": "sha256"
}
},
"required": ["text"]
}
}
]
};
}

remote isolated function onCallTool(mcp:CallToolParams params, mcp:Session? session)
returns mcp:CallToolResult|mcp:ServerError {
match params.name {
"hashText" => {
return self.handleHashText(params.arguments ?: {});
}
_ => {
return error mcp:ServerError(string `Unknown tool: ${params.name}`);
}
}
}

private isolated function handleHashText(record {} arguments)
returns mcp:CallToolResult|mcp:ServerError {
// Implementation…
return {content: [{'type: "text", text: "..."}]};
}
}

Constraints

  • Both onListTools and onCallTool must be declared, and no other remote methods are allowed.
  • onCallTool must accept exactly one mcp:CallToolParams parameter, and may accept an mcp:Session? parameter for stateful services.
  • An mcp:Meta? parameter is not accepted. Read the request metadata from the _meta field of mcp:CallToolParams instead.
  • Both methods can bind HTTP request data with @http:Header, http:Headers, and http:Request parameters, the same way tools can.
Use mcp:StreamableHttpService when…Use mcp:StreamableHttpAdvancedService when…
Tools map cleanly to compile-time remote functions.Tools are dynamic (defined in configuration, a database, or another service).
You want WSO2 Integrator to derive schemas automatically.You want hand-crafted JSON schemas or extra validation.
You want to build the service in the visual designer.You want a dispatch-table architecture.
info

In onCallTool, content items use the field name 'type (with a leading quote) because type is a reserved keyword in Ballerina.

Error handling​

Tools should return informative errors so AI clients can recover or suggest alternatives. The framework propagates the error message back to the client as a tool-call error.

When you build a tool flow in the visual designer, errors returned or propagated with check from the flow are sent back to the client automatically. To return a custom error message, use a Return node with an error value:

return error(string `Invalid invoice ID '${invoiceId}'. Expected format INV-XXXXX.`);

The error message is what the AI client will read, so make it actionable. Avoid bare error() with no message; the assistant has nothing to reason about.

Configuring an MCP client​

Once your service is running, MCP clients connect by URL.

Claude Desktop

{
"mcpServers": {
"wso2-integrator": {
"url": "http://localhost:8080/mcp"
}
}
}

Another agent inside WSO2 Integrator: see Consuming MCP from an Agent.

Operational notes​

  • Keep the tool list small. Fewer, better-described tools beat dozens of overlapping ones.
  • Authenticate. Anything you expose over MCP is, by default, accessible to whoever connects. Configure auth on httpConfig.auth (JWT, OAuth2, basic).
  • Log tool calls. Just like any other API surface; knowing who called what is essential for debugging and audit.
  • Version explicitly. When tool shapes change, prefer adding a new tool over silently changing an existing one. AI clients depend on stable schemas.

FAQ​

My existing code uses mcp:Listener, mcp:Service, or mcp:AdvancedService. Does it still work?

Yes. These types still compile and run:

  • mcp:Listener is deprecated. Replace it with mcp:StreamableHttpListener, which takes the same arguments.
  • mcp:Service and mcp:AdvancedService are transport-agnostic service types and are not deprecated. Their tools can't bind HTTP request data (@http:Header, http:Headers, http:Request). Use mcp:StreamableHttpService and mcp:StreamableHttpAdvancedService, which WSO2 Integrator generates by default, when you need that data.
  • The httpConfig and sessionMode fields of @mcp:ServiceConfig are deprecated. Set them in @mcp:StreamableHttpServiceConfig on an mcp:StreamableHttpService instead.

What's next​