AI Agent Manager: using a MCP server to generate widgets

The AI Agent Manager allows you to seamlessly integrate agentic workflows into your Cumulocity Platform. In this blog post, I will describe how you can easily add tools to an agent that lets you execute actions on your Cumulocity platform. In this case, we will enable our agents to generate widgets based on user prompts.

The Basics

To provide tools to the AI Agent Manager, you need to build an MCP server that exposes the tools for an agent to consume. MCP (Model Context Protocol) is a standard that can be thought of as the “USB-C standard” for agentic tool calling. We have a more detailed article about MCP here.

When connected to the Agent Manager, these tools receive the authentication credentials of the current Cumulocity user, allowing them to access the Cumulocity tenant data on behalf of the user.

In this tutorial, we will:

  • Build an MCP server in NestJS and deploy it as a microservice to the platform
  • Provide two widget generator tools, so users only need to pass the dashboard ID and a use case—the tools will then generate the widgets automatically
  • Connect the Agent Manager to the newly deployed MCP server and create an agent that uses it

Let’s Get Started: Creating the MCP Server

We will use NestJS for the server, which can be deployed to Cumulocity as a microservice. NestJS is a backend framework that aligns with Angular design principles, making it easy for Angular developers to learn. You can find a basic MCP example on our GitHub.

There are two important parts in this microservice:

1. Authentication and Subscription Layer

This layer ensures that every user is authenticated and that the microservice is accessible from multiple tenants while maintaining tenant isolation.

Check out the auth folder for details. You will find two middlewares (functionality that executes for every incoming request):

Since authentication implementation is not the main focus of this blog post, we won’t go into full details. The key point is that the middleware authenticates two @c8y/client instances (an npm library for Cumulocity API access):

  • userClient: Reflects the authorization level of the user
  • microserviceClient: Reflects the authentication level of the microservice (always limited to the current tenant scope)

These clients can be accessed in any controller via the request injector:

import { Controller, Get, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';

@Controller('/test')
export class TestController {
  constructor(
    @Inject(REQUEST) protected readonly request: Request,
  ) {}

  @Get()
  async getUsers(): Promise<any> {
    // User client
    const { data: user } = await this.request.userClient.user.current();

    // Microservice client
    const { data: microservice } = await this.request.microserviceClient.user.current();
    return { user, microservice };
  }
}

Important: The microserviceClient receives the access rights defined in the microservice manifest. This means you can decide in the controller which access level to use. For an MCP server, you’ll most likely want to execute requests on behalf of the user, since you only want to return data the user has access to.

2. MCP Tool Server

Fortunately, there’s a library that handles the Model Context Protocol details: MCP NestJS provides everything you need so you can focus on implementation.

With this library, you can create an MCP tool using the @Tool decorator. It accepts:

Zod is a library that allows you to define and describe type-safe properties in TypeScript using an easy chained function syntax. For example, a Cumulocity dashboard widget Zod schema looks like this:

const WidgetToolParams = z.object({
  dashboardId: z.string().describe('ID of the dashboard'),
  title: z.string().describe('Title of the widget'),
  x: z.number().describe('X position of the widget on the dashboard'),
  y: z.number().describe('Y position of the widget on the dashboard'),
  width: z.number().describe('Width of the widget'),
  height: z.number().describe('Height of the widget'),
});

type WidgetToolParamsType = z.infer<typeof WidgetToolParams>;

The descriptions make each property’s usage clear to both developers and the LLM. With this foundation, we can write our first tool to generate an empty widget:

@Tool({
    name: 'c8y-create-empty-widget',
    description:
      'Creates an empty widget on the specified dashboard with given configuration.',
    parameters: WidgetToolParams,
  })
  async createEmptyWidget(
    params: WidgetToolParamsType,
    _context: Context,
    request: Request,
  ): Promise<any> {
    this.logger.log(
      `createEmptyWidget called with params: ${JSON.stringify(params)}`,
    );

    // 1. Get the user API client (to create the widget on behalf of the user)
    const client = request.userClient;

    // 2. Get the dashboard JSON where the widget will be created
    const dashboard = await client.inventory.detail(params.dashboardId);

    // 3. Generate a new widget ID
    const widgetId = randomUUID();

    // 4. Add the new widget to the dashboard JSON
    dashboard.data.c8y_Dashboard.children = {
      ...dashboard.data.c8y_Dashboard.children,
      [widgetId]: {
        _x: params.x,
        _y: params.y,
        _width: params.width,
        _height: params.height,
        config: {}, // Empty for now
        title: params.title,
        componentId: 'Alarm list',
        id: widgetId,
        classes: {
          card: true,
          'card-dashboard': true,
          'alarm-list': true,
          'panel-title-regular': true,
        },
      },
    };
   
    // 5. Save the updated dashboard back to the API
    await client.inventory.update(dashboard.data);

    // 6. Return success message to the LLM
    return {
      content: [
        {
          type: 'text',
          text: 'Widget successfully added to dashboard.',
        },
      ],
    };
  }

Code Breakdown:

  1. Get the user API client (to create the widget on behalf of the user)
  2. Retrieve the dashboard JSON where the widget will be created
  3. Generate a unique widget ID
  4. Add the widget configuration to the dashboard’s children object
  5. Save the updated dashboard via the API
  6. Return a success message to the LLM

After adding this tool to the mcp.module providers, it becomes available in the AI Agent Manager. By defining additional widget configuration parameters, you can create more tools for various use cases. Find the empty widget service and more examples (like the alarms-widget generator) in the example repository branch.

Running the Full Example

Prerequisites

The following must be installed and available on your computer:

  • Node.js 20+
  • Git
  • Docker

Step 1: Clone the Repository

git clone git@github.com:Cumulocity-IoT/c8y-mcp-example.git
cd c8y-mcp-example
git checkout widget-tool-example 

Step 2: Install Dependencies

npm install

Step 3: Configure and Start the Server

Create a .development.env file with your Cumulocity credentials. A template is available in the repository.

Note: You can request the C8Y_BOOTSTRAP_USER and password via the Cumulocity API, but you need to deploy the microservice once before doing so.

Start the server:

npm run start:dev

You can inspect the microservice using tools like the MCP Inspector.

Step 4: Build the Docker Container

On Linux systems, run:

npm run docker:build

This generates a Docker image based on the provided Dockerfile and creates a ZIP file containing the manifest and binaries in the /dist folder.

Deploying the Microservice and Configuring the AI Agent Manager

Prerequisites

  • A configured and working AI Agent Manager
  • Cumulocity Microservice hosting enabled

Step 1: Deploy the Microservice

  1. Log in to your Cumulocity Administration Application
  2. Navigate to Ecosystem → Microservices
  3. Click “Add microservice” in the toolbar
  4. Select the created ZIP file and wait for the upload to complete

Step 2: Configure the MCP Server

  1. Navigate to the AI Agent Manager in the Administration application
  2. Open the Tools section
  3. Click “Configure MCP Server”
  4. At the bottom, add a new MCP Server with the context-path to your microservice (default: <<your-base-url>>/service/mcp-example/sse)
  5. If configured correctly, all tools will be listed
  6. Provide a name for the server
  7. Keep “Send user authentication token to MCP Server” enabled
  8. Click Save

Step 3: Create an Agent

  1. Go to Agents in the AI Agent Manager UI
  2. Click “Add agent”
  3. Enter the following system prompt:
You are a Cumulocity IoT Data insights agent. You can query the API and use the given dashboard to place widgets on it to display IoT data.

- Interpret the user input to understand what they want to see. Query the dashboard using the dashboard id "{/{dashboardId}}" and the `cumulocity-api-request` to check existing widgets. Widgets are stored in the children object with _x, _y, _width, _height properties that define their positions. Always request the dashboard on each user message to avoid overlapping widgets. The maximum width is 24.
- If needed, use additional API calls with `cumulocity-api-request` to find the right device and data the user wants to display.
- Use the widget tools to create the necessary widgets. For dates, always use UTC ISO strings.
- After creating widgets, explain the data and what the user sees on the dashboard.
  1. Go to the Tools section
  2. Add the cumulocity-api-request tool and all custom tools
  3. Click Save

Testing the Agent

We’re ready to test!

  1. Go to your Cockpit application and open any device

  2. Create a new empty dashboard by clicking “Add dashboard” and save

  3. Check the URL—it should look like:

    /apps/cockpit/index.html#/device/2698590822/dashboard/22136874210
    
    • First ID: Device ID (2698590822)
    • Second ID: Dashboard ID (22136874210)
  4. Return to the Agent Manager and open your agent’s test tab

  5. Enter the following prompt:

My Dashboard ID is 22136874210 and my device id is 2698590822. Please create a dashboard with two sections:
1. An overview of my measurements
2. A list of all CRITICAL alarms

After sending this prompt, the agent analyzes the request and creates the necessary widgets. You can observe the tool calls in the chat output:

The resulting dashboard matches the request. The Agent Manager correctly resolved my device data, and it appears there are no critical alarms on this device:

Conclusion

This tutorial demonstrated how easy it is to extend the Agent Manager with tailored tools for IoT use cases. The MCP tools are versatile—they can be used not only by the Agent Manager but also by tools like Claude Desktop, VS Code, or any application that supports MCP Servers. Additionally, any external system can potentially be integrated via MCP tooling. The AI Agent Manager provides an agentic workflow by orchestrating multiple tools logically and autonomously.

Possible Next Steps

  • Contact your Customer Success Manager (CSM) to request early access, share your use cases, and participate in validation activities to ensure AI capabilities align with your operational goals
  • Explore the example MCP server
6 Likes