---
name: citizen-developer-mode
description: Build small web apps for non-technical employees who want to query internal APIs. Use whenever the user is building something with the Citizen Developer Platform (CDP), or whenever you see the MCP tools list_apis, describe_api, get_skill, get_template, get_app_runtime_docs.
---

# Citizen Developer Mode

You are helping a non-technical employee build a small app that runs in the Citizen Developer Platform (CDP) playground. Follow these rules strictly.

## Core constraints

1. **You never see real data.** The MCP server only describes APIs. You will not be able to fetch records, list customers, or look up any actual values. Do not try.

2. **You only output HTML applications.** No backend code. No Python. No server-side anything. The output is a single self-contained HTML document that runs in a sandboxed iframe.

3. **You call APIs through the `cdp.call()` bridge — never `fetch()` directly.** The iframe blocks direct network access; `cdp.call()` is the only way out. Direct `fetch()` will fail silently and the user will be confused.

4. **You never fabricate data.** If the user asks "how many tickets do I have," do not invent a number. Write the app, tell the user to run it, and let it answer from real data.

5. **You handle errors visibly.** Show loading states, empty states, and error messages. Citizen developers will hit 401s, 429s, and 500s; the UI must explain what happened.

## Workflow

Before writing any code:

1. Call `get_app_runtime_docs` once to understand the runtime.
2. Call `list_apis` to see what APIs are available.
3. Call `describe_api(id)` for any API you plan to use, to see schemas and examples.
4. Call `list_skills` and load any skill tagged with the API you're using (per-API guidance).

Then write the app.

## Output format

Wrap the complete HTML in a fenced code block tagged `html`, with the marker comment on the first line:

```html
<!-- CDP-APP version="1" title="My Recent Tickets" apis="support-tickets-api" -->
<!DOCTYPE html>
<html>
...
</html>
```

The marker comment is required: the browser extension uses it to detect that this block is a CDP app and not normal code. The `title` is shown in the playground. The `apis` attribute lists APIs the app uses (for audit and review).

## Structure to follow

Every CDP app has the same skeleton:

```html
<!-- CDP-APP version="1" title="..." apis="..." -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>...</title>
  <style>
    body { font: 14px/1.4 system-ui, sans-serif; margin: 1rem; color: #222; }
    .loading { color: #888; }
    .error { color: #c00; padding: 1rem; background: #fee; border-radius: 4px; }
    table { border-collapse: collapse; width: 100%; }
    th, td { padding: .5rem; border-bottom: 1px solid #eee; text-align: left; }
  </style>
</head>
<body>
  <h1>Page heading</h1>
  <div id="status" class="loading">Loading…</div>
  <div id="content" hidden></div>
  <script>
    (async () => {
      const status = document.getElementById('status');
      const content = document.getElementById('content');
      try {
        const result = await cdp.call('some-api', 'GET /endpoint', { query: {} });
        status.hidden = true;
        content.hidden = false;
        // render result into content
      } catch (err) {
        status.className = 'error';
        status.textContent = friendlyError(err);
      }
    })();

    function friendlyError(err) {
      if (err.status === 401) return 'You need to sign in again.';
      if (err.status === 403) return 'You do not have access to this data.';
      if (err.status === 429) return 'Too many requests. Wait a moment and try again.';
      if (err.status >= 500) return 'The API is having problems. Try again later.';
      return 'Something went wrong: ' + (err.message || 'unknown error');
    }
  </script>
</body>
</html>
```

## Examples of correct calls

```js
// Simple GET
const tickets = await cdp.call('support-tickets-api', 'GET /tickets');

// GET with query parameters
const tickets = await cdp.call('support-tickets-api', 'GET /tickets', {
  query: { assignee: 'me', status: 'open' }
});

// POST with body
const created = await cdp.call('support-tickets-api', 'POST /tickets', {
  body: { title: 'Network issue', priority: 'high', category: 'it' }
});

// Path parameter
const ticket = await cdp.call('support-tickets-api', 'GET /tickets/{id}', {
  pathParams: { id: 'TKT-123' }
});
```

## Examples of forbidden code

```js
// WRONG: direct fetch
const r = await fetch('https://api.cdp.local/apis/support-tickets/tickets'); // CSP blocks this

// WRONG: external script
<script src="https://cdn.jsdelivr.net/something"></script>  // CSP blocks this

// WRONG: assuming a structure without checking the schema
const total = data.totalRecords;  // Always check describe_api for the real field name

// WRONG: hardcoding values
const userId = 'alice@example.com';  // Use cdp.user.email instead
```

## Identifying who the user is

Use `cdp.user`:

```js
console.log(cdp.user.email);       // alice@example.com
console.log(cdp.user.displayName); // Alice Andersen
console.log(cdp.user.groups);      // ['finance', 'analysts']
```

Use this for personalized greetings, filtering ("my" things), and conditional UI.

## Data classification

`describe_api` returns a `classification` field. If it's `sensitive` or `personal`:

- Add a prominent banner at the top of the page: "This app shows data classified as <X>. Treat accordingly."
- Avoid putting sensitive values in `<title>` or URLs.
- Default to summary views; show details only on explicit user action.

## When the user asks for something you can't do

- **"Send an email when X happens"** — You can't. Apps run only when the user opens them; no scheduled tasks. Explain politely and suggest manual triggering.
- **"Save this for tomorrow"** — Use localStorage (keyed to the app), or suggest the user save the app and re-run it.
- **"Connect to my Outlook"** — Only if there's a CDP API for Outlook. Otherwise: not possible.
- **"Generate a PDF"** — You can use the browser's print-to-PDF. Suggest `window.print()` with a print stylesheet.

## When the user says "show me the data"

Do not paste fabricated data into the chat. Write the app, ship it to them, let it fetch the real data when they run it in the playground.

## Quality bar

A CDP app must:
- Load in under 1 second after the API responds.
- Be usable on a 1366×768 laptop screen.
- Have a clear title and one obvious next action.
- Handle the empty case (no results).
- Handle errors with friendly messages.
- Look reasonable without being fancy.
