Leni developers
Leni MCP runtime reference
Connect an MCP-compatible AI host to the Leni intelligent runtime with delegated end-user OAuth, tenant-safe data access, memory context, and bounded tools.
| Setting | Current contract |
|---|---|
| Server name | leni-runtime |
| Remote endpoint | https://mcp.leni.co/mcp |
| OAuth issuer | https://auth.leni.co |
| Transport | Stateless Streamable HTTP over HTTPS; POST requests with JSON responses |
| Authentication | OAuth 2.0 authorization code with PKCE; delegated Leni user consent is always required |
| Client registration | Approved predefined clients or Dynamic Client Registration when advertised |
OAuth discovery and lifecycle
| Resource | URL or configured default |
|---|---|
| Authorization-server metadata | https://auth.leni.co/.well-known/oauth-authorization-server |
| Protected-resource metadata | https://mcp.leni.co/.well-known/oauth-protected-resource |
| OAuth resource / token audience | https://mcp.leni.co/mcp |
| Access token | Up to 10 minutes; use expires_in from the token response |
| Refresh-token idle lifetime | 30 days for trusted clients; 7 days for restricted registration clients |
| Refresh-family absolute lifetime | 365 days by default; rotation cannot extend this deadline |
| Authorization code | 2 minutes |
| Interactive authorization transaction | 10 minutes |
Discover endpoints from the live metadata. Standard authorization, code exchange, and refresh requests require the exact resource=https://mcp.leni.co/mcp. Use S256 PKCE and the exact registered redirect URI. Public clients use token_endpoint_auth_method: none; they do not send a client secret.
Request the permissions the integration needs. The authorization server also advertises offline_access for refresh access; this is not a tool scope. Refresh tokens are issued for clients registered for the refresh_token grant. Store each replacement refresh token atomically and serialize refreshes for a grant. Reusing an old token revokes its refresh family.
Rotation renews the idle expiry up to the family's absolute deadline. Lifetimes depend on client policy; the table gives service defaults, not a promise of a fixed connection lifetime. Reconnect after expiry, revocation, or a permission change. Revocation stops refresh access immediately; an issued access token can remain valid until its short expiry. Leni rechecks active organization membership on tool calls.
Registration must match discovery
registration_endpoint for Dynamic Client Registration with an approved callback host. Wildcard redirects are not supported. Published Client ID Metadata Documents are available only when discovery includes client_id_metadata_document_supported: true. Leni's public metadata did not advertise that option when checked on October 26, 2026. In Claude, choose Register automatically.Backend and autonomous agents
OAuth scopes
| Scope | Allows |
|---|---|
| leni:account:read | Read account, plan eligibility, and percentage usage remaining. |
| leni:analysis:run | Start a Leni analysis that can consume account usage. |
| leni:analysis:read | Read status and completed analysis sessions. |
| leni:data:read | Discover and query authorized Leni semantic-layer data. |
| leni:memory:read | List authorized memory and folders and consult relevant context. |
| leni:memory:files:read | Create short-lived links for files attached to authorized memory notes. |
| leni:memory:write | Create and update memory after explicit user intent. |
| leni:memory:delete | Delete an authorized memory note after explicit user intent. |
Protocol requests and results
Use an MCP client to initialize the connection, negotiate a protocol version, send the initialized notification, and discover tools/list. Send the negotiated MCP-Protocol-Version on subsequent requests, with Authorization: Bearer <OAuth-access-token>, Content-Type: application/json, and Accept: application/json, text/event-stream. Leni does not issue an MCP session ID. Authenticated GET and DELETE requests to /mcp return 405.
A tool call after initialization has this JSON-RPC body:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "leni_get_account", "arguments": {} }
}Successful calls return result.structuredContent plus human-readable result.content. Use structured values for programmatic handling. The response key depends on the tool, as shown below. Check HTTP status, a JSON-RPC error, and result.isError. HTTP 200 alone does not establish tool success. Missing scopes can produce a JSON-RPC error; backend failures and tool-level limits can produce isError: true with text instead of structured output.
Tool inputs and structured results
Treat the schemas returned by tools/list as authoritative. The shared catalog contains the tools below; discovery does not mean every tool is permitted by the current grant. Each call checks its scope.
| Tool | Inputs | structuredContent key |
|---|---|---|
| leni_get_account | No arguments. | account |
| leni_get_portfolio_occupancy | Optional includeUnitCounts and includePropertyBreakdown, both default false. | occupancy |
| leni_list_data_models | No arguments. | dataCatalog |
| leni_describe_data_model | Required modelName from model discovery. | dataModel |
| leni_query_portfolio_data | Required measures; optional dimensions, filters, timeDimensions, order, and limit. | data |
| leni_consult_memory_context | Required question; optional scope, modelType, and modelId. scope defaults to combined. | context |
| leni_analyze_real_estate_finance | Required question; optional depth, buildingIds, memoryIds, and workbookContext. | analysis |
| leni_get_analysis_status | Required analysisId from a previous analysis. | analysis |
| leni_get_analysis_session | Required sessionId; optional limit, default 20 and maximum 50, and cursor from nextCursor. | session |
| leni_list_memory | Required scope; optional modelType, modelId, search, page, and pageSize. Defaults are page 1 and pageSize 20, maximum 100. | memory |
| leni_list_memory_folders | Required scope; optional modelType and modelId. | memoryFolders |
| leni_get_memory_files | Required scope and memoryId; optional modelType, modelId, page, and pageSize. Defaults are page 1 and pageSize 20, maximum 25. | memoryFiles |
| leni_create_memory | Required scope, title, and content; optional modelType, modelId, folderId, and folderName. | memory |
| leni_update_memory | Required scope, memoryId, title, and content; optional modelType and modelId. | memory |
| leni_delete_memory | Required scope and memoryId; optional modelType and modelId. | memory |
Context tools accept scope: user or scope: organization. Only consultation accepts combined. The default model partition is leniq-pro. Preserve a returned custom analyst's modelType and modelId. Note titles accept 1–500 characters and note content accepts 1–50,000 characters. Create and update write text notes; they do not upload files.
Analysis lifecycle and saved-file analysis
Check leni_get_account before starting analysis. The analysis result contains an opaque analysisId, an optional sessionId, and a status of queued, running, completed, or failed. Wait at least pollAfterSeconds when supplied, then call leni_get_analysis_status with the same analysis ID until terminal. Completed output can contain summary or result. Never start another analysis just because the first call is still running.
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"structuredContent": {
"analysis": {
"analysisId": "<analysis-id>",
"sessionId": "<session-id>",
"status": "running",
"pollAfterSeconds": 2
}
},
"content": [{ "type": "text", "text": "Analysis is running. Poll the same analysis ID." }]
}
}Use leni_get_analysis_session for transcript pages, following session.nextCursor. Pass the returned session ID, not the analysis ID. The analysis-start tool does not accept a session ID to append a turn. Provide relevant prior context explicitly for a new analysis. Generated attachment metadata contains id, filename, and optional contentType; it does not expose download URLs. Context-file links are a separate operation.
To analyze a saved file, discover its note with leni_list_memory or leni_list_memory_folders, then pass the note reference to analysis. memoryIds is an array of objects, not strings. All selected notes must use the same model partition:
{
"question": "Summarize the maturity terms in the selected loan document.",
"depth": "quick",
"memoryIds": [{
"scope": "organization",
"memoryId": "<note-id-from-discovery>",
"modelType": "leniq-pro"
}]
}Include modelId when the note belongs to a custom analyst. A standard partition must use leniq-pro. Use leni_get_memory_files only when the user asks to retrieve the file or its link. Follow memoryFiles.hasMore and retry the same page with bounded backoff when partial is true. Search Personal and Organization Context separately when both are needed. A consultation result of empty means no relevant notes were found. degraded means retrieval was incomplete; explain that limitation when using the returned context.
Microsoft Copilot and Excel
Microsoft 365 Copilot uses the shared tools through an approved Leni agent package. Each user signs in with an eligible Leni subscription; active paid plans, trials, Student accounts, and eligible Enterprise seats are supported. Microsoft tenant installation policies still apply. Completing OAuth does not establish subscription eligibility. The agent does not read or edit the active Office document through the shared catalog.
Registered Copilot clients use the dedicated /oauth/microsoft-copilot/authorize and /oauth/microsoft-copilot/token endpoints on auth.leni.co. These aliases supply the MCP resource only when it is omitted and accept only the registered Copilot clients. Other integrations use the standard endpoints from discovery with an explicit resource.
The Leni Excel add-in additionally discovers leni_excel_chat, restricted to the leni-excel client with leni:analysis:run. It sends bounded workbook observations and returns proposed reads, edits, or an answer. The Office host performs edits only after the user selects Apply changes. Other MCP clients cannot request this host-specific tool. Installation and subscription eligibility remain separate from the generic MCP connection.
Optional workbookContext on the shared analysis tool is a user-selected snapshot with worksheet, address, values, and displayedValues. Both matrices must describe the same rectangular selection, at most 500 cells, 50 columns, and 6,000 serialized characters. The combined question and annotated selection must fit 8,000 characters. This snapshot grants no workbook editing capability.
Structured-data boundaries
- Call model discovery before constructing a query; use only the returned member names.
- Queries require 1–12 measures and allow up to 12 dimensions, 20 filters, 2 time dimensions, and 5 order fields.
- Row limit defaults to 50 and is capped at 100.
- A rowCount equal to limit may be incomplete. Narrow the query or query aggregate totals directly; never sum a limited breakdown into a portfolio total. Missing rows and nulls do not mean zero.
- Clients cannot submit SQL or select a warehouse, schema, organization, or unauthorized property.
- Server-owned organization and property filters are applied to every direct query.
| MCP input | Bounds |
|---|---|
| Structured-query filters | equals, notEquals, contains, notContains, startsWith, endsWith, gt, gte, lt, lte, inDateRange, notInDateRange, beforeDate, afterDate, set, and notSet |
| Analysis question | 3–8,000 characters |
| Analysis property selection | Up to 100 authorized building IDs |
| Analysis memory selection | Up to 20 scoped note references in the same model partition |
| Analysis depth | quick or deep |
| Memory consultation scope | combined, user, or organization |
| Memory-file page size | Defaults to 20 and is capped at 25 |
| Shared tool data text | Truncated around 100,000 characters; prefer structuredContent |
MCP rate limits
| Limit | Identity | Current allowance |
|---|---|---|
| Runtime requests | Signed-in OAuth subject + client | 120 requests per 60 seconds |
| Structured semantic queries | Signed-in principal + client | 20 requests per 60 seconds |
| Memory context consultation | Leni account across connected clients | 12 requests per 60 seconds |
Limits overlap. Runtime HTTP 429 responses include Retry-After. Semantic-query and Context consultation failures can instead return a tool result with isError: true inside HTTP 200; do not treat that as empty data or a successful analysis. Honor a supplied retry delay and use bounded backoff for safe reads. Occupancy can issue separate aggregate and breakdown queries, each consuming query allowance. Invalid or unauthenticated traffic is separately throttled.
Read and write behavior
MCP troubleshooting
| Symptom | Action |
|---|---|
| Connector will not register | Read live OAuth metadata. Use automatic registration for an approved callback host; use published identity only when advertised. Preserve the exact redirect URI. |
| OAuth expired | Let the host refresh the access token. Reconnect only when refresh fails, the grant was revoked, or scopes changed. |
| invalid_target | Send resource=https://mcp.leni.co/mcp on authorization, code exchange, and refresh. |
| invalid_grant during refresh | An expired, revoked, or replayed refresh token requires reconnecting. Serialize refreshes and save each replacement token. |
| HTTP 200 but no result | Check JSON-RPC error and result.isError before reading structuredContent. |
| Tool is not selected | Enable Leni in the conversation and ask explicitly to use Leni for the task. |
| Data member is rejected | List and describe models again, then use exact current member names. |
| Analysis is processing | Keep analysisId for status checks and sessionId for completed-session retrieval. Do not start a duplicate. |
| Memory file is unavailable | Recheck the note scope, request a fresh link, and retry a page when partial is true. |