> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sigmic.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Passing Secrets

> Pass per-user credentials and configuration to MCP servers through the widget

When you embed the widget, each end user likely needs the agent to connect to **their** data — their database, their API, their tenant. Environment variables and secrets let you pass per-user credentials at token exchange time so the agent's MCP servers connect to the right services for each end user.

## How It Works

```
Your Backend                        Sigmic AI                          Agent Sandbox
     |                                   |                                |
     | 1. POST /api/v1/auth/token        |                                |
     |   { env: { TENANT_ID: "acme" },   |                                |
     |     secrets: { API_KEY: "sk-…" } } |                                |
     |---------------------------------->|                                |
     |   Stores env + secrets            |                                |
     |   server-side (encrypted)         |                                |
     |                                   |                                |
     |                                   | 2. User sends a message        |
     |                                   |   (Authorization: Bearer JWT)  |
     |                                   |                                |
     |                                   | 3. Retrieves stored env+secrets|
     |                                   |   Injects into MCP server      |
     |                                   |   configuration                |
     |                                   |------------------------------->|
     |                                   |   {{mcp.KEY}} → actual value   |
```

1. **Your backend** passes `env` and `secrets` when exchanging credentials for a widget JWT
2. **Sigmic AI stores them server-side** — secrets are encrypted at rest. Neither `env` nor `secrets` are included in the JWT itself or sent to the browser.
3. **When the user chats**, the platform retrieves the stored values and injects them into every MCP server in the project. Template placeholders like `{{mcp.API_KEY}}` in your server config are resolved to the actual values before the connection is established.

## `env` vs `secrets`

Both `env` and `secrets` are key-value string maps passed in the [token exchange request](/widget#exchange-credentials-for-a-jwt). The difference is how they are stored:

|                               | `env`                              | `secrets`                            |
| ----------------------------- | ---------------------------------- | ------------------------------------ |
| **Storage**                   | Encrypted at rest                  | Encrypted at rest                    |
| **Returned in API responses** | Never                              | Never                                |
| **Sent to the browser**       | Never                              | Never                                |
| **Use for**                   | Tenant IDs, regions, feature flags | API tokens, database URLs, passwords |
| **Available as**              | `{{mcp.KEY}}` in MCP config        | `{{mcp.KEY}}` in MCP config          |

If the same key appears in both `env` and `secrets`, the `secrets` value takes precedence.

<Note>
  The `env`/`secrets` distinction exists for forward compatibility. Today both are stored securely. Use `secrets` for anything sensitive (tokens, passwords, connection strings) and `env` for non-sensitive configuration (tenant IDs, regions, feature flags).
</Note>

## Using Variables in MCP Server Config

MCP servers are configured in your project settings (via the [Console](/console) or the project config API). Use `{{mcp.KEY}}` placeholders in the server's `headers`, `env`, or `url` fields. These placeholders are resolved at runtime using the `env`/`secrets` from the active widget token.

### Example: HTTP server with API key in headers

**Step 1 — Configure the MCP server in your project:**

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT "https://api.sigmic.ai/api/users/you/projects/my-project/config/mcp/my-api-server" \
    -H "Authorization: Bearer YOUR_JWT" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://mcp.example.com/v1",
      "type": "http",
      "headers": {
        "Authorization": "Bearer {{mcp.MY_API_TOKEN}}",
        "X-Tenant-ID": "{{mcp.TENANT_ID}}"
      },
      "enabled": true
    }'
  ```

  ```json MCP Server Config theme={null}
  {
    "url": "https://mcp.example.com/v1",
    "type": "http",
    "headers": {
      "Authorization": "Bearer {{mcp.MY_API_TOKEN}}",
      "X-Tenant-ID": "{{mcp.TENANT_ID}}"
    },
    "enabled": true
  }
  ```
</CodeGroup>

**Step 2 — Pass the values when issuing a widget token:**

```javascript theme={null}
// Your backend — token exchange
const response = await fetch('https://api.sigmic.ai/api/v1/auth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    appId: process.env.SIGMIC_APP_ID,
    appSecret: process.env.SIGMIC_APP_SECRET,
    endUserId: 'user-123',
    env: {
      TENANT_ID: currentUser.tenantId
    },
    secrets: {
      MY_API_TOKEN: currentUser.apiToken
    }
  })
});
```

**Result:** When the agent connects to `my-api-server`, the headers are resolved to:

```
Authorization: Bearer sk-actual-token-value
X-Tenant-ID: acme-corp
```

### Example: stdio server with environment variables

**MCP server config:**

```json theme={null}
{
  "command": "npx",
  "args": ["-y", "@your-org/db-mcp-server"],
  "type": "stdio",
  "env": {
    "DATABASE_URL": "{{mcp.DATABASE_URL}}",
    "SCHEMA": "{{mcp.DB_SCHEMA}}"
  },
  "enabled": true
}
```

**Widget token request:**

```javascript theme={null}
const response = await fetch('https://api.sigmic.ai/api/v1/auth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    appId: process.env.SIGMIC_APP_ID,
    appSecret: process.env.SIGMIC_APP_SECRET,
    endUserId: 'user-123',
    env: { DB_SCHEMA: 'public' },
    secrets: { DATABASE_URL: 'postgres://user:pass@host/acme_db' }
  })
});
```

**Result:** The MCP server process launches with these environment variables:

```
DATABASE_URL=postgres://user:pass@host/acme_db
SCHEMA=public
```

### Example: SSE server with dynamic URL

```json theme={null}
{
  "url": "https://{{mcp.REGION}}.analytics.example.com/mcp/sse",
  "type": "sse",
  "headers": {
    "X-API-Key": "{{mcp.ANALYTICS_KEY}}"
  },
  "enabled": true
}
```

With `env: { REGION: 'us-east-1' }` and `secrets: { ANALYTICS_KEY: 'ak-12345' }`, the agent connects to `https://us-east-1.analytics.example.com/mcp/sse` with header `X-API-Key: ak-12345`.

## Template Reference

| Aspect                | Detail                                                                                 |
| --------------------- | -------------------------------------------------------------------------------------- |
| **Syntax**            | `{{mcp.KEY_NAME}}`                                                                     |
| **Supported fields**  | `headers`, `env`, and `url` in MCP server configs                                      |
| **Resolution timing** | When the MCP server connection is established (not at token issuance)                  |
| **Missing keys**      | If a `{{mcp.KEY}}` has no matching value, it is left unchanged and a warning is logged |
| **Scope**             | All MCP servers in the project receive the same set of variables                       |

### Key naming rules

Keys must be valid environment variable names:

* Start with a letter or underscore
* Contain only letters, digits, and underscores
* All values must be strings

| Valid       | Invalid                           |
| ----------- | --------------------------------- |
| `TENANT_ID` | `123-invalid` (starts with digit) |
| `API_KEY`   | `has-dashes` (contains hyphens)   |
| `db_url`    | `has spaces` (contains spaces)    |
| `_INTERNAL` | `key.with.dots` (contains dots)   |

## Multi-Tenant Example

A common pattern is passing per-tenant credentials so each end user's widget session connects the agent to that user's data:

```javascript theme={null}
// Express backend — /api/sigmic-chat/token
app.post('/api/sigmic-chat/token', async (req, res) => {
  // 1. Authenticate your own user
  const user = await authenticateRequest(req);

  // 2. Look up tenant-specific config
  const tenant = await db.tenants.findById(user.tenantId);

  // 3. Exchange for widget JWT with tenant-scoped context
  const response = await fetch('https://api.sigmic.ai/api/v1/auth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      appId: process.env.SIGMIC_APP_ID,
      appSecret: process.env.SIGMIC_APP_SECRET,
      endUserId: user.id,
      expiresIn: '1h',
      env: {
        TENANT_ID: tenant.id,
        TENANT_NAME: tenant.name,
        REGION: tenant.region
      },
      secrets: {
        DATABASE_URL: tenant.databaseUrl,
        API_TOKEN: tenant.apiToken
      }
    })
  });

  const data = await response.json();
  res.json({ token: data.data.token });
});
```

With the corresponding project MCP server config:

```json theme={null}
{
  "tenant-api": {
    "url": "https://{{mcp.REGION}}.api.example.com/mcp",
    "type": "http",
    "headers": {
      "Authorization": "Bearer {{mcp.API_TOKEN}}",
      "X-Tenant-ID": "{{mcp.TENANT_ID}}"
    },
    "enabled": true
  },
  "tenant-db": {
    "command": "npx",
    "args": ["-y", "@your-org/postgres-mcp"],
    "type": "stdio",
    "env": {
      "DATABASE_URL": "{{mcp.DATABASE_URL}}"
    },
    "enabled": true
  }
}
```

Each end user's widget session connects the agent to that user's database and API — with credentials that never leave the server.

## Token Refresh and Variables

Each widget token has its own set of `env`/`secrets`. When you [refresh a token](/widget#token-refresh), you must pass `env` and `secrets` again — the new token does not inherit values from the previous one.

This also means you can **update credentials on refresh** without interrupting the session. For example, if an end user's API key is rotated, the next token refresh can include the new key.

```javascript theme={null}
// fetchToken callback — re-fetch credentials on every refresh
fetchToken: async function() {
  const response = await fetch('/api/sigmic-chat/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId: currentUser.id })
  });
  const data = await response.json();
  return data.token;  // Backend re-fetches tenant credentials each time
}
```

## Security

* `env` and `secrets` are **never included in the JWT** — they are stored server-side only
* They are **never sent to the browser** — the widget iframe only receives the JWT
* Values are stored encrypted and automatically cleaned up when the token expires
* Secrets are only decrypted when the agent session needs them

<Info>
  This is the same mechanism used by the [Task API](/tasks/create) for passing environment variables. If you are already using `env`/`secrets` with the Task API, they work identically in the widget.
</Info>
