# How MCP connects

MCP is a way for an AI application to discover and call named tools. **Connecting an MCP server does not give Iris unrestricted access to a business system.** There are two different connections:

| Connection | Who uses it? | Authentication | Purpose |
| --- | --- | --- | --- |
| Your AI app → Flowlith | You, the business owner | Flowlith browser OAuth | Set up and manage Iris, teaching and approved Actions |
| Iris → your external MCP server | Iris during a customer conversation | A privately saved token/header, or no auth for deliberately public tools | Execute only the customer Actions you explicitly selected, tested and enabled |

The first connection does not automatically create the second. Your customer's WhatsApp account does not sign them into your CRM, ERP or HIS either. Your backend still owns record authorization. See the [backend readiness guide](https://docs.flowlith.com/backend-integration).

## 1. Connect your AI app to Flowlith

Use a client that supports remote MCP and browser OAuth. Add a connection named **Flowlith** with this public endpoint:

```text
https://mcp.flowlith.com/mcp
```

For clients with this JSON configuration format, the configuration contains no credentials:

```json
{
  "mcpServers": {
    "iris": { "url": "https://mcp.flowlith.com/mcp" }
  }
}
```

1. Add the remote connection or install the [public client package](https://github.com/jubsnfzm/iris-plugin#connect), using the instructions for your app. App plans and workspace policies may restrict custom connections.
2. Complete Flowlith sign-in in your browser: email, email code, then **Allow**. Never paste the code, a token or an Authorization header into chat or configuration.
3. Enable Flowlith in the conversation if your app requires it. Ask for this read-only check:

```text
Call list_businesses using Flowlith. Do not start onboarding or change anything.
If more than one business is returned, ask which one I want to manage.
If sign-in or tool access is missing, explain that instead of guessing.
```

An empty business list can be a valid signed-in result. A tool list or an installed skill is not proof of access: Flowlith allows discovery before sign-in, but business tools require authorization. An unsigned tool call asks the client to complete sign-in; it does not read your business.

| What you see | What it proves |
| --- | --- |
| Package installed | The client has the package, not necessarily a working connection. |
| Tools discovered | The server can describe its tools, not that you are authorized. |
| Browser sign-in completed | An account authorized the app; verify business access next. |
| Successful `list_businesses` | The app can access your authorized business list. |
| A successful customer preview | That preview worked; it does not prove external-system or channel delivery. |

The owner connection can help with onboarding, business information, teaching drafts/Apply, business status, approved launch, customer previews and external Action setup. It is not a customer inbox, transcript export or contact-list browser. Website API keys, Meta sign-in and account settings remain secure browser steps. Read [Iris capabilities](https://docs.flowlith.com/capabilities) for the full boundary.

## 2. Decide whether your backend needs MCP

**MCP is optional.** If the system already provides a suitable HTTPS API or automation webhook, use a direct API Action instead. A CRM/ERP/HIS does not need to install the Flowlith owner plugin.

Use external MCP when your team already runs a compatible tool server or wants to expose narrowly defined business operations. A database address, admin website, local command or vendor name is not an MCP endpoint. If your system only offers an interactive OAuth login, this external connection cannot use it yet; provide a supported token API, token MCP server or secure integration adapter. Do not reuse a personal session cookie.

## 3. Register your external MCP system

In your connected AI app, first call **list_businesses**, select the business, then **list_systems** to avoid duplicate setup. For multiple businesses, include the returned `business` identifier in setup calls.

The following are non-secret arguments for **add_system**. This is a fictional inventory server, not a hosted Flowlith integration:

```json
{
  "name": "Inventory tools",
  "kind": "mcp",
  "base_url": "https://inventory.example.com/mcp",
  "auth": { "kind": "bearer" },
  "headers": []
}
```

1. Save the system, then call **get_key_link** with the returned `connection_id` and required `slot` (normally `auth`). Enter the token only on that secure page. The single-save link expires after 15 minutes; do not share it.
2. Call **list_systems** again after saving a key. Use the current connection version, not the version from before the key save.
3. Call **list_system_tools** with that `connection_id` and `version`. This refreshes the tool menu and can mark changed Actions for review; it is not a customer tool execution.
4. Choose one task-specific tool with no refusal reason. Do not choose discovery, installation or tool-management tools. Review its meaning, inputs and actual effects; a `readOnlyHint` is not proof that it cannot write.
5. Save the Action draft, Preview, Test with permission, then separately approve activation. Detailed steps follow below.

External MCP supports **Bearer**, a named credential **header**, or **None** for an intentionally public server. Basic/query-key authentication and interactive OAuth are not supported for external MCP. Additional fixed headers are allowed; secret values still go through the private key-entry flow, never JSON examples or tool arguments.

## 4. What your MCP server must implement

Use one final, publicly reachable **HTTPS endpoint on port 443**, with valid TLS and no redirects. Iris sends JSON-RPC POST requests. It accepts `application/json` responses or JSON-RPC messages in `text/event-stream` response frames.

```text
Iris → initialize
Server → protocolVersion + capabilities.tools
Iris → notifications/initialized
Server → HTTP 202 or 204
Iris → tools/list (and any cursor pages)
Server → named tools and supported input schemas
Iris → tools/call for the selected, approved tool
Server → matching JSON-RPC result or error
```

Iris currently negotiates protocol versions **2025-11-25**, **2025-06-18** or **2025-03-26**. It proposes 2025-11-25. If initialization returns an `Mcp-Session-Id`, subsequent requests carry it along with `MCP-Protocol-Version`. Match each request's JSON-RPC ID in the response. Treat an initialized notification as a notification, not a request requiring a JSON-RPC result.

Do not require a local stdio process, a separate legacy GET/SSE endpoint, sampling, elicitation, or another server-initiated request. This connection calls tools; it does not automatically import your prompts, download resources, browse files, or install extra tools. Keep each response at most **64 KiB** and operations short enough for the configured Action timeout, at most **10 seconds**. That time budget includes initialization and tool discovery, not just your tool handler. A large reporting job should return a bounded status/result through a purpose-built endpoint, not block the chat.

### A compatible tool definition

Return this tool inside the `tools` array of your `tools/list` result. It checks public product availability, not a private customer record:

```json
{
  "name": "get_availability",
  "description": "Read current availability for a public product SKU.",
  "inputSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "sku": { "type": "string", "maxLength": 40 }
    },
    "required": ["sku"]
  },
  "annotations": { "readOnlyHint": true }
}
```

Use a flat object with at most 12 input fields: bounded strings, booleans, bounded numbers/integers, or arrays of up to 20 strings. Strings are limited to 500 characters. Nested input objects, unions, nullable types, references such as `$ref`, and arbitrary JSON Schema features are not supported. Expose a simpler task-specific tool if your backend has a more complex native schema. Do not make Iris supply credentials as tool arguments.

### The call and response

After setup and activation, the call for this example looks like this. The ID is illustrative; echo the actual request ID:

```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "get_availability",
    "arguments": { "sku": "MUG-001" }
  }
}
```

A successful JSON response can contain both a short text summary and structured fields:

```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "isError": false,
    "content": [{ "type": "text", "text": "MUG-001 is available." }],
    "structuredContent": { "sku": "MUG-001", "available": true, "quantity": 12 }
  }
}
```

The Action can select `text`, `structured.sku`, `structured.available` and `structured.quantity` as returned fields. Image/binary content is not forwarded as customer knowledge. Linked resources are not fetched automatically. Return only the fields needed for the task, not the whole account or record. `isError: true` is a tool failure, not a successful business operation. An HTTP 200 alone is not proof of success.

## 5. Turn one selected tool into an Action

Use **save_action_draft** with the exact tool name/hash returned by **list_system_tools**. The sample below uses a placeholder connection ID, version and hash; replace them with current returned values. It is read-only only because the example endpoint truly does not change data:

```json
{
  "name": "product_availability",
  "display_label": "Check product availability",
  "hard": {
    "mode": "mcp",
    "connection_id": "11111111-1111-4111-8111-111111111111",
    "connection_version": 2,
    "input_schema": {
      "type": "object", "additionalProperties": false,
      "properties": { "sku": { "type": "string", "maxLength": 40 } },
      "required": ["sku"]
    },
    "fixed_fields": {}, "system_fields": {},
    "requires_confirmation": false,
    "bind_customer_path": "", "link_hosts": [],
    "timeout_ms": 10000, "idempotent_retry": false,
    "mcp": {
      "tool_name": "get_availability",
      "tool_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    }
  },
  "soft": {
    "when_to_use": "When a customer asks whether a product is available.",
    "synonyms": ["stock", "availability"],
    "field_hints": { "sku": { "hint": "Ask which product to check.", "examples": ["MUG-001"] } },
    "response_paths": ["text", "structured.sku", "structured.available", "structured.quantity"]
  }
}
```

This example intentionally has no private-record customer binding. **Do not copy that choice for private CRM, ERP or patient records.** Your backend must authorize the specific customer and the Action must apply the appropriate customer check. A customer-typed record number or phone number is not proof of identity. See [backend requirements](https://docs.flowlith.com/backend-integration).

1. **preview_action**: use the returned `action_id`. Read the destination, inputs, customer check, selected output, warnings and exact `revision` to the owner.
2. **test_action**: use that `action_id`, `revision` and fictional `input`. Test reaches the real system. Explain effects and obtain permission first; provide `real_write_ok: true` only after the requested permission. An MCP Test can require this flag because its transport uses POST even when the business operation is read-only.
3. Inspect the actual result. For writes, also verify the external record; never automatically repeat a sent write after a failed or uncertain response.
4. Ask whether Iris should use this exact version with customers. Only after a clear yes, call **turn_on_action** with the exact `action_id`, `revision` and `owner_said_yes: true`.

Activation requires a successful, recent Test of that exact version. Customer writes also require confirmation in a later customer turn. Owner Test permission and customer write confirmation are separate. A draft, discovered tool or saved key is never activation.

## 6. Keep the connection working

Changed or removed tool definitions require fresh review, Test and approval; even a changed tool description can invalidate the saved selection. Do not silently substitute a similarly named tool. After key rotation or system edits, reload the current version and review affected Actions. Use **turn_off_action** to stop new calls; it cannot undo work already sent to your backend.

Before customer use, test a normal request, unavailable record, invalid input, expired key, lost response and customer-authorization rejection. Check the Action from each intended customer channel. Keep read and write credentials narrowly scoped, with a test environment where possible.

[Backend readiness](https://docs.flowlith.com/backend-integration) · [What Iris can do](https://docs.flowlith.com/capabilities) · [Direct APIs and Actions](https://docs.flowlith.com/actions) · [AI workspace](https://docs.flowlith.com/workspace)
