# TUUI's MCP Elicitation Protocol: How User Consent Flows from Server to UI

> Explore TUUI's MCP elicitation protocol. Understand the 9-step server to UI consent flow for secure user approval before MCP servers execute privileged operations. Learn how user consent is managed.

- Repository: [AIQL/tuui](https://github.com/ai-ql/tuui)
- Tags: how-to-guide
- Published: 2026-02-23

---

**TUUI's MCP elicitation protocol implements a secure 9-step round-trip between the Electron main process and renderer UI to capture explicit user consent before MCP servers execute privileged operations.**

The Model-Context Protocol (MCP) requires explicit user authorization when servers request sensitive data or permissions. In the `ai-ql/tuui` repository, TUUI's MCP elicitation protocol manages this consent flow through a carefully orchestrated IPC bridge between the main process and the Vue-based renderer interface.

## The 9-Step MCP Elicitation Flow

### Step 1: Server Initiates ElicitRequest

The flow begins when an MCP server sends an `ElicitRequest` to request user permission or data. This request follows the `ElicitRequestSchema` defined in the protocol specification, signaling that the server requires explicit consent before proceeding.

### Step 2: Main Process Handler Registration

In [`src/main/mcp/client.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/mcp/client.ts), the MCP client registers a request handler using `setRequestHandler` with the `ElicitRequestSchema`. When the server request arrives, this handler invokes `elicitationTransferInvoke` to delegate the decision to the UI layer.

```ts
// src/main/mcp/client.ts
client.setRequestHandler(ElicitRequestSchema, async (request) => {
  const response = await elicitationTransferInvoke(request)
  return response
})

```

### Step 3: Creating the Response Channel

The `elicitationTransferInvoke` function in [`src/main/index.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/index.ts) generates a unique response channel using `uuidv4()` to ensure one-time use. It registers a listener via `listenOnceForRendererResponse` and forwards the request to the renderer through IPC using `mainWindow.webContents.send`.

```ts
// src/main/index.ts
const responseChannel = `${msgElicitationTransferResultChannel}-${uuidv4()}`
listenOnceForRendererResponse(responseChannel, resolve)
mainWindow.webContents.send('msgElicitationTransferInvoke', {
  request,
  responseChannel,
})

```

### Step 4: Renderer IPC Bridge Setup

On the renderer side, [`src/renderer/utils/index.ts`](https://github.com/ai-ql/tuui/blob/main/src/renderer/utils/index.ts) exports the `ElicitationTransfer` object that maps the IPC channels. This bridge exposes `msgElicitationTransferInvoke` for receiving requests and `msgElicitationTransferResult` for sending responses back to the main process.

```ts
// src/renderer/utils/index.ts
export const ElicitationTransfer = {
  request: Elicitation.msgElicitationTransferInvoke,
  response: Elicitation.msgElicitationTransferResult,
}

```

### Step 5: UI Modal Presentation

When the renderer receives the request, [`src/renderer/components/common/ElicitationCard.vue`](https://github.com/ai-ql/tuui/blob/main/src/renderer/components/common/ElicitationCard.vue) opens a persistent modal dialog. The component populates form fields based on `request.params.requestedSchema`, presenting the user with the specific consent request details.

```vue
<!-- src/renderer/components/common/ElicitationCard.vue -->
<script setup>
const elicitationDialog = ref(false)
const elicitationParams = ref<ElicitRequestParams | {}>({})
const elicitationChannel = ref('')
// ...dialog UI rendered in template based on requestedSchema
</script>

```

### Step 6: User Consent Decision

The user interacts with the modal to either **accept** or **decline** the request. The `acceptElicitation` function constructs an `ElicitResponse` with `action: 'accept'` and the user-provided values, while `declineElicitation` creates a response with `action: 'decline'`.

```ts
// src/renderer/components/common/ElicitationCard.vue
const acceptElicitation = () => {
  const response: ElicitResponse = {
    action: 'accept',
    content: toRaw(elicitationResults.value)
  }
  ElicitationTransfer.response(elicitationChannel.value, response)
}

const declineElicitation = () => {
  const response: ElicitResponse = { action: 'decline' }
  ElicitationTransfer.response(elicitationChannel.value, response)
}

```

### Step 7: Returning the Response

The renderer sends the response back through the unique channel established in Step 3. The `msgElicitationTransferResult` method in [`src/renderer/utils/index.ts`](https://github.com/ai-ql/tuui/blob/main/src/renderer/utils/index.ts) invokes `window.mainApi.send` to transmit the `ElicitResponse` to the main process.

```ts
// src/renderer/utils/index.ts
static async msgElicitationTransferResult(channel: string, response: ElicitResponse) {
  await window.mainApi.send(channel, response)
}

```

### Step 8: Main Process Promise Resolution

In [`src/main/IPCs.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/IPCs.ts), the `listenOnceForRendererResponse` function receives the response via `ipcMain.once` on the unique channel. It resolves the promise created in Step 3, returning the user's decision to the MCP client handler.

```ts
// src/main/IPCs.ts
ipcMain.once(responseChannel, (_event, response) => {
  resolve(response)
})

```

### Step 9: Server Completion

The MCP client in [`src/main/mcp/client.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/mcp/client.ts) returns the `ElicitResponse` to the requesting server. The server can now proceed with the privileged operation if the user accepted, or handle the denial appropriately if the user declined, completing the consent round-trip.

## Key Implementation Files

The MCP elicitation protocol spans five critical files across the Electron main and renderer processes:

- **[`src/main/mcp/client.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/mcp/client.ts)** – Registers the MCP request handler with `setRequestHandler` and initiates the elicitation transfer to the UI layer
- **[`src/main/index.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/index.ts)** – Implements `elicitationTransferInvoke`, generates UUID-based response channels, and manages the IPC forwarding to the renderer
- **[`src/main/IPCs.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/IPCs.ts)** – Registers one-time listeners via `listenOnceForRendererResponse` to capture the renderer's response without persistent handlers
- **[`src/renderer/utils/index.ts`](https://github.com/ai-ql/tuui/blob/main/src/renderer/utils/index.ts)** – Provides the `ElicitationTransfer` IPC bridge that maps `msgElicitationTransferInvoke` and `msgElicitationTransferResult` between processes
- **[`src/renderer/components/common/ElicitationCard.vue`](https://github.com/ai-ql/tuui/blob/main/src/renderer/components/common/ElicitationCard.vue)** – Renders the consent modal dialog, populates fields from `requestedSchema`, and emits **accept** or **decline** actions

## Summary

- TUUI's MCP elicitation protocol creates a **secure 9-step round-trip** between the MCP server and the user interface to obtain explicit consent before privileged operations execute.
- The flow uses **unique one-time channels** (UUID-based) to ensure responses are securely matched to their pending requests without risk of cross-talk or replay attacks.
- **Five core files** coordinate across the Electron main process ([`client.ts`](https://github.com/ai-ql/tuui/blob/main/client.ts), [`index.ts`](https://github.com/ai-ql/tuui/blob/main/index.ts), [`IPCs.ts`](https://github.com/ai-ql/tuui/blob/main/IPCs.ts)) and renderer process ([`utils/index.ts`](https://github.com/ai-ql/tuui/blob/main/utils/index.ts), [`ElicitationCard.vue`](https://github.com/ai-ql/tuui/blob/main/ElicitationCard.vue)) to bridge the Model-Context Protocol with the Vue UI.
- Users interact with a **modal dialog** ([`ElicitationCard.vue`](https://github.com/ai-ql/tuui/blob/main/ElicitationCard.vue)) that presents the specific permission request and captures either **accept** or **decline** actions, ensuring informed consent.
- The protocol guarantees that **privileged operations cannot proceed** without explicit user authorization, satisfying the Model-Context Protocol's security and consent requirements.

## Frequently Asked Questions

### What triggers the MCP elicitation protocol in TUUI?

The protocol triggers when an MCP server sends an `ElicitRequest` to the client, typically when the server needs permission to access sensitive data or execute privileged operations. The request is received by the handler registered in [`src/main/mcp/client.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/mcp/client.ts), which immediately initiates the UI consent flow by calling `elicitationTransferInvoke`.

### How does TUUI ensure elicitation responses match their original requests?

TUUI generates a **unique UUID** for each elicitation request in [`src/main/index.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/index.ts) using `uuidv4()`. This UUID creates a one-time response channel (formatted as `msgElicitationTransferResultChannel-${uuid}`) that is registered with `ipcMain.once` in [`src/main/IPCs.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/IPCs.ts). This ensures the response is securely matched to its pending promise without risk of cross-talk.

### Can users modify the requested data before accepting an elicitation?

Yes. When the [`ElicitationCard.vue`](https://github.com/ai-ql/tuui/blob/main/ElicitationCard.vue) component renders the modal, it populates form fields based on `request.params.requestedSchema`. Users can review and modify these values before clicking accept. The `acceptElicitation` function captures these modified values using `toRaw(elicitationResults.value)` and includes them in the `ElicitResponse` content field sent back to the server.

### What happens if a user declines an elicitation request?

If the user clicks decline, the `declineElicitation` function in [`ElicitationCard.vue`](https://github.com/ai-ql/tuui/blob/main/ElicitationCard.vue) creates an `ElicitResponse` with `action: 'decline'` and no content payload. This response travels back through the same IPC channels to the main process, where it resolves the pending promise in [`src/main/mcp/client.ts`](https://github.com/ai-ql/tuui/blob/main/src/main/mcp/client.ts). The MCP client then returns this decline response to the server, which must abort the privileged operation or handle the denial appropriately.