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 |
- Your backend passes
env and secrets when exchanging credentials for a widget JWT
- 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.
- 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. 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.
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).
Using Variables in MCP Server Config
MCP servers are configured in your project settings (via the 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.
Step 1 — Configure the MCP server in your project:
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
}'
Step 2 — Pass the values when issuing a widget token:
// 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:
{
"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:
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
{
"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:
// 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:
{
"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, 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.
// 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
This is the same mechanism used by the Task API for passing environment variables. If you are already using env/secrets with the Task API, they work identically in the widget.