Skip to main content
Glama

Sisense Meta-Management MCP Server

⚠️ Experimental Project Notice

Community-Contributed Tool from Sisense Field Engineering (FES)

This project is an experimental tool developed by Sisense Field Engineering to facilitate customer learning and exploration of Sisense capabilities. It is not part of the core Sisense product release lifecycle and does not undergo the same validation, support, or certification processes as generally available (GA) Sisense features. It is provided "as-is" — see Support and contributing.

A standards-conformant MCP server that exposes Sisense environment operations as AI-ready tools, backed by the PySisense SDK: governance, asset and user/group management, lifecycle tasks, and well-checks — not chart-building or analytics Q&A.

It works for any Sisense user: every tool call runs with the calling user's own Sisense credential, so results and permissions are exactly what that user can see and do in Sisense itself, enforced natively by Sisense's APIs.

Tools only, no agent. Claude Desktop, Claude Code, claude.ai, Cursor — any MCP client brings its own agent; this project advertises and executes a curated subset of the ~170 SDK methods in the registry (dashboards, data models, users/groups, folders, plugins, queries, …) — one tool per capability, with near-duplicates excluded so an agent never has to choose between near-identical methods.

Architecture

The MCP spec's modern shape, as two cooperating services shipped as two Docker images (sisense-fes-auth, sisense-fes-mcp — built from one multi-stage Dockerfile with shared layers):

  • fes-auth — the authorization server (AS). Owns everything about who is calling: OAuth 2.1 for MCP clients (PKCE, dynamic client registration, discovery), the browser sign-in page, and the credential vault mapping each issued MCP token to the user's Sisense token. It proxies every tool call to the resource server with the Sisense credential injected.

  • fes-mcp — the resource server (RS). Stateless, OAuth-unaware. Reads the injected credential from each request, verifies it against Sisense (cached), and runs the tool as that user against that Sisense instance.

flowchart LR
    subgraph clients [MCP clients]
        C1[Claude Desktop]
        C2[Claude Code]
        C3[claude.ai / Cursor]
    end

    subgraph box [one host - docker compose]
        subgraph AS [fes-auth : authorization server]
            O[OAuth 2.1\nPKCE + DCR + discovery]
            L[/login page/]
            V[(vault\nMCP token → Sisense credential)]
            P[/mcp proxy\ninjects credential headers/]
        end
        subgraph RS [fes-mcp : resource server]
            T[Tool layer\nregistry-driven, curated]
            D[Dispatcher\nper-credential PySisense client]
        end
    end

    R[(tool registry JSON\nauto-generated from SDK)] -.defines.-> T

    C1 & C2 & C3 -- "MCP over HTTPS\nBearer <MCP token>" --> P
    C1 & C2 & C3 -. "browser: sign in once" .-> L
    P -- "Authorization: Bearer <Sisense token>\nX-Sisense-Url: <instance>\n(internal network only)" --> T
    T --> D
    D -- "REST, as the signed-in user" --> F[(Sisense Fusion Deployment)]

The seam between the two services is just those two headers plus the 401 contract, so each half can evolve — or be replaced — without the other noticing. There is no shared secret between the two; trust is the internal network (the RS's port is never published).

Sign-in flow (what a user experiences)

Each user adds the connector once in their MCP client, naming their own Sisense instance in the URL:

https://your-host/mcp?target=https://acme.sisense.com
sequenceDiagram
    participant U as User (browser)
    participant C as MCP client
    participant A as fes-auth
    participant S as Sisense

    C->>A: POST /mcp?target=<sisense url>  (no token)
    A-->>C: 401 + resource metadata URL (carries target)
    C->>A: discovery + client registration (RFC 7591)
    C->>U: open browser at A's /login
    Note over U,A: target present → instance fixed,<br/>only username/password asked<br/>(no target → domain field shown)
    U->>A: username/password (or API token for SSO)
    A->>S: POST /api/v1/authentication/login
    S-->>A: user's Sisense token (kept server-side, in the vault)
    A-->>C: authorization code → MCP access token (PKCE)
    Note over C,A: from here, silent — token refresh is automatic

The client never sees Sisense credentials; the server never stores passwords (used once to mint the user's token, then discarded). Users on SSO/MFA instances sign in by pasting their personal Sisense API token instead.

Tool call (steady state)

sequenceDiagram
    participant C as MCP client
    participant A as fes-auth (proxy)
    participant R as fes-mcp (tools)
    participant S as Sisense (target)

    C->>A: POST /mcp  (Bearer <MCP token>)
    A->>A: validate token → vault → Sisense credential
    A->>R: same request + Authorization: Bearer <Sisense token><br/>+ X-Sisense-Url: <instance>
    R->>S: verify credential (TTL-cached) · SDK call as that user
    S-->>R: result (user's permissions, user in audit log)
    R-->>A: MCP response (streamed)
    A-->>C: MCP response (streamed)

Credential lifecycle and self-healing

  • The resource server re-verifies each (instance, token) pair against Sisense after FES_MCP_VERIFY_TTL seconds (default 300). A token revoked in Sisense turns into an HTTP 401 within at most that window.

  • fes-auth treats an RS 401 as credential dead: it deletes the vault entry and re-challenges the MCP client, whose next move is to re-run the sign-in flow. Server-side revocation therefore propagates with no manual steps.

  • Sessions are in-memory (no database): restarting fes-auth signs everyone out — each user's next call pops the browser login again (with ?target= set, that's just username/password). Dynamically registered OAuth clients are also in-memory, so a client that registered via DCR may need to reconnect (or re-add the connector) after a restart; CIMD clients are unaffected, since their client ID is a URL the server resolves fresh. Restarting fes-mcp is invisible: it holds no state.

Related MCP server: Dynamics 365 Business Central Admin MCP Server

Deployment (docker compose)

Tagged releases publish ready-to-run images to GitHub Container Registry: ghcr.io/hnegi01/sisense-fes-auth and ghcr.io/hnegi01/sisense-fes-mcp (no registry login needed). Or build from source:

docker compose up --build

This builds the two images (docker build --target fes-auth|fes-mcp) and publishes only fes-auth on :8200; fes-mcp stays internal. Terminate TLS in front (ALB / nginx / Caddy) — MCP clients require HTTPS for OAuth — and set FES_MCP_PUBLIC_URL to that public URL:

FES_MCP_PUBLIC_URL=https://your-host.example.com docker compose up -d --build

Users then add https://your-host.example.com/mcp?target=https://their-instance.sisense.com as a custom connector. The ?target= part is optional — without it the login page asks for the Sisense URL as a third field.

Endpoints on fes-auth: /mcp (proxied MCP), /login, /.well-known/* + /authorize + /token + /register (OAuth 2.1), / (status), /healthz. Hardening included: per-IP login rate limiting, CSRF-protected login form, access logs with request ids.

OAuth discovery requires the server's paths at the origin root, so give it its own hostname — or, on a shared hostname, route exactly these paths to fes-auth at the proxy. A path prefix (https://host/some-prefix/mcp) will not work.

Quick start (local dev)

Requires Python 3.11+ and uv. Local dev skips the AS entirely: stdio transport defaults to env auth — one credential from .env, everything runs as you.

uv sync
cp .env.example .env   # set SISENSE_DOMAIN / SISENSE_TOKEN
uv run fes-mcp         # stdio transport

MCP client config (e.g. claude_desktop_config.json):

{
  "mcpServers": {
    "sisense-fes": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/fes_mcp", "fes-mcp"]
    }
  }
}

To run the full split locally without Docker:

FES_MCP_TRANSPORT=http uv run fes-mcp &                 # RS on :8200 (upstream auth)
FES_MCP_PORT=8300 FES_MCP_RS_URL=http://127.0.0.1:8200 uv run fes-auth
# connector: http://127.0.0.1:8300/mcp?target=https://your.sisense.com

Layout

  • src/fes_mcp/ — settings (env config) · registry (load/filter) · schema_patches (field-description overlay) · dispatcher (per-credential SDK dispatch) · upstream (RS credential verification) · auth (OAuth provider + login page) · authserver (fes-auth service + proxy) · middleware (access logs) · server (FastMCP assembly)

  • config/tools.registry.with_examples.json — auto-generated tool registry. Never handwritten; regenerate with ./refresh_registry.sh when PySisense updates.

  • config/allowlist.txt — the curated tool surface, one tool per line. Delete/comment a line to remove a tool. Tools not listed are never exposed, so registry refreshes can't silently widen the surface. (Migration tools are not listed — they need a dual-instance connection this server doesn't model.)

  • Mutating tools are gated behind FES_MCP_ALLOW_MUTATIONS=true — see Security for the full mutation safeguards.

  • Payload parameters carry full nested schemas straight from the SDK's TypedDict contracts (pysisense ≥ 1.1.0) — e.g. create_user's user_data declares email and role as required, so an agent gathers them before calling instead of failing inside the SDK. schema_patches.py overlays only human-written per-field descriptions (the contracts carry structure, not prose); free-form payloads like JAQL stay unconstrained.

Technical and security considerations

Credential handling

The MCP client never sees Sisense credentials, and the server never stores passwords — a password is used once against Sisense's login API to mint the user's own token, then discarded. Sisense tokens live in fes-auth's in-memory vault, keyed to the MCP access token, and survive refresh rotation. In dev mode the single env credential (SISENSE_DOMAIN/SISENSE_TOKEN) stays on your machine. Nothing is persisted to disk — there is no database and no encryption-at-rest surface; a fes-auth restart clears the vault and users sign in again.

Hardening on the hosted surface: per-IP login rate limiting, CSRF-protected login form, access logs with request ids, and per-call tool logs (tool / user domain / outcome / duration).

Authorization

Nothing custom: authorization is Sisense's job. Every tool call runs with the calling user's own Sisense token, so Sisense enforces their real permissions on every API call and permission errors surface to the client verbatim. This is also why the server is not admin-only — any Sisense user gets exactly their own scope.

Trust between the two services

fes-auth ↔ fes-mcp trust is network-level: no shared secret. The RS's port must never be reachable from outside the internal network (compose publishes only fes-auth). Defense in depth: FES_MCP_ALLOWED_SISENSE_ORIGINS pins which Sisense origins the RS will accept in X-Sisense-Url.

Mutations

Mutating tools are exposed only when FES_MCP_ALLOW_MUTATIONS=true, always carry destructiveHint, are blocked server-side as a second layer when disabled, and are written to a mutation audit log.

On top of that, mutating tools ask the human for approval before executing. The confirmation shows the exact arguments about to run (secrets masked), the approval is bound to those arguments, and abort or decline changes nothing. It works on both protocol generations: current (stateless) connections use an MCP input_required round trip, older connections use MCP elicitation.

A client that cannot render the confirmation proceeds under its own tool-approval flow plus the destructiveHint annotation, like any standard MCP server — the authorization boundary is always the user's own Sisense permissions.

Data flow to the LLM provider

This server has no summarization or data-redaction layer: every tool result — full rows, not {ok, count} metadata — is returned to the MCP client and lands in the model's context. This is what makes multi-step tool chaining work: the model can only reason over, filter, and feed one tool's output into the next call if it actually sees the data.

The consequence: whoever connects this server to an MCP client is accepting that Sisense data (dashboard contents, query results, user lists, …) flows to that client's LLM provider — e.g. Anthropic, for Claude — under their own terms with that provider. The server cannot enforce or scope this; it is a per-deployment acceptance to make consciously.

  • Start read-only: keep FES_MCP_ALLOW_MUTATIONS=false (the default) until you've built confidence in a non-production environment.

  • Curate config/allowlist.txt down to the tools your deployment actually needs — fewer tools means less data exposure and a clearer approval story.

  • Prefer non-production Sisense instances while exploring; the tools are only as safe as the signed-in user's permissions.

  • Test destructive operations in a non-production environment first; the server asks for confirmation with the exact arguments before any write.

Configuration

Variable

Default

Used by

Purpose

SISENSE_DOMAIN / SISENSE_TOKEN

—

fes-mcp

dev-mode (env) credential

SISENSE_SSL_VERIFY

true

both

verify TLS when calling Sisense

FES_MCP_AUTH

by transport: http ⇒ upstream, stdio ⇒ env

fes-mcp

credential source

FES_MCP_TRANSPORT

stdio

fes-mcp

stdio or http

FES_MCP_HOST / FES_MCP_PORT

127.0.0.1 / 8200

both

HTTP bind

FES_MCP_PUBLIC_URL

—

fes-auth

public base URL (OAuth discovery/redirects)

FES_MCP_RS_URL

—

fes-auth

the resource server to proxy tool calls to

FES_MCP_VERIFY_TTL

300

fes-mcp

seconds a verified (instance, token) pair is trusted

FES_MCP_ALLOWED_SISENSE_ORIGINS

— (accept any)

both

allowed Sisense instances (exact origins and *.suffix wildcards): login page and X-Sisense-Url checks

FES_MCP_TOOLS

config/allowlist.txt

fes-mcp

comma-separated tool_ids / modules override

FES_MCP_ALLOW_MUTATIONS

false

fes-mcp

expose mutating tools

FES_MCP_REGISTRY_PATH

bundled registry

fes-mcp

alternate registry JSON

FES_MCP_LOG_LEVEL

INFO

both

log verbosity

FES_MCP_LOG_DIR

logs/

both

directory for fes-mcp.log / fes-auth.log (also logged to stderr)

Tests

uv run python -m pytest              # unit tests: mocked, no credentials

Integration tests run against a real Sisense instance and are read-only; see tests/integration/README.md for setup:

uv run python -m pytest tests/integration -m integration

Registry regeneration

./refresh_registry.sh   # rebuild config/ from the installed PySisense SDK

New SDK methods land in the registry but stay hidden until explicitly added to config/allowlist.txt.

Support and contributing

This is an experimental, community-contributed project maintained by Sisense Field Engineering and provided "as-is."

  • Do not open a GSS ticket — this is not a GA Sisense feature.

  • For usage questions or help getting started, contact your Customer Success Manager (CSM), who will route feedback to the Field Engineering team.

  • Issues and contributions are welcome through the repository.

License

MIT

Available Tools

36 tools
access_management_get_all_dashboard_sharesaccess_management.get_all_dashboard_sharesA
Read-only

Retrieve all dashboard shares, including user and group details for each shared dashboard. Uses pagination to retrieve all dashboards and their share information, and collects the corresponding user and group details for each share. Returns: list[dict[str, Any]] A list of dictionaries containing the dashboard title, share type (user or group), and share name (email or group name) — one row per share, so the row count equals the number of shares. …

Inputs: no named parameters.

access_management_get_datamodel_columnsaccess_management.get_datamodel_columnsA
Read-only

Retrieve columns from a DataModel by collecting them from its datasets and tables. Resolves the DataModel ID by title, then walks its datasets and tables to gather every column. Returns: list[dict[str, Any]] A list of dictionaries, each containing datamodel_id, datamodel_name, table, and column. An empty list is returned if the DataModel cannot be found or has no columns.

Inputs: datamodel_name (required).

access_management_get_groupsaccess_management.get_groupsA
Read-only

Retrieve groups — one named group, or all of them. Fetches the groups defined on the Sisense server. With name the API filters server-side to that group; without it, every group is returned. One row per group. Returns: list[dict[str, Any]] | dict[str, Any] A list of raw group objects as returned by the API (each with _id, name, defaultRole, and related fields). Without name, an empty list means the server genuinely has …

Inputs: name.

access_management_get_my_useraccess_management.get_my_userA
Read-only

Retrieve the currently logged-in user for the API token. Sends GET /api/users/loggedin. Use this to resolve migration user identity (email, username, internal ID) for the authenticated admin token. Returns: dict[str, Any] The logged-in user object from the API, or {"error": "..."} on failure.

Inputs: no named parameters.

access_management_get_rolesaccess_management.get_rolesA
Read-only

Retrieve all Sisense roles. Sends GET /api/roles. Returns the raw role list used to build role name-to-ID maps (for example in multi-tenant migration workflows). Returns: list[dict[str, Any]] | dict[str, Any] A list of role objects on success, or {"error": "..."} on failure.

Inputs: no named parameters.

access_management_get_unused_columns_bulkaccess_management.get_unused_columns_bulkA
Read-only

Run unused-column analysis for one or more data models and return a combined per-model outcome. Returns: dict[str, Any] Always a dict with "results" and "errors": - "results": flat list of column rows across all processed data models, each row shaped as get_datamodel_columns rows plus a "used" boolean. …

Inputs: datamodels (required).

access_management_get_useraccess_management.get_userA
Read-only

Retrieve one named user by email address, in the canonical user row shape. Fetches users with expanded groups and role data and returns the record matching the provided email address. Changed in 2.0: ROLE_NAME held the display name in 1.x (that value is now ROLE_DISPLAY_NAME); GROUPS still holds the group names and is joined by the new GROUP_IDS. See docs/upgrading.md. Returns: dict[str, Any] The canonical user row: USER_ID, USER_NAME, EMAIL, FIRST_NAME, LAST_NAME, IS_ACTIVE, ROLE_ID, ROLE_NAME (the raw Sisense value, e.g. "consumer"), ROLE_DISPLAY_NAME …

Inputs: user_email (required).

access_management_get_users_allaccess_management.get_users_allA
Read-only

Retrieve every user, one canonical user row each. Reports exactly what Sisense stores: group memberships are unfiltered (Everyone is included — consumers that want to hide a universal group can drop it; a consumer that never received it cannot put it back), and ROLE_NAME carries the raw Sisense value with the UI-facing name in ROLE_DISPLAY_NAME. Changed in 2.0: ROLE_NAME held the display name in 1.x (that value is now ROLE_DISPLAY_NAME), GROUPS is joined by the new GROUP_IDS, and Everyone is no longer filtered out of GROUPS. See docs/upgrading.md. Returns: list[dict[str, Any]] | dict[str, Any] One row per user, each with USER_ID, USER_NAME, EMAIL, FIRST_NAME, LAST_NAME, IS_ACTIVE, ROLE_ID, ROLE_NAME (raw, e.g. "consumer"), …

Inputs: no named parameters.

access_management_users_per_groupaccess_management.users_per_groupA
Read-only

Retrieve group memberships — one group's members, or every membership. Returns one flat row per (group, user) membership. With group_name the rows are that group's members; without it, every membership on the instance is returned (ask get_groups for the per-group view). Everyone and All users in system are omitted from the all-groups view: Sisense fills both with every user, so they restate get_users_all rather than describing group structure. Naming either one still returns its members. The rule across the SDK: **targeted questions give complete answers; only the … Returns: list[dict[str, Any]] | dict[str, Any] One row per (group, user) membership, each with GROUP_ID, GROUP_NAME, USER_ID, USER_NAME, EMAIL, FIRST_NAME, LAST_NAME, IS_ACTIVE, ROLE_ID, …

Inputs: group_name.

dashboard_can_be_owneddashboard.can_be_ownedA
Read-only

Check whether a dashboard can be owned by the current user. Sends GET /api/v1/dashboards/{dashboard_id}/can_be_owned. Returns: dict[str, Any] The API response on success, or {"error": "..."} on failure.

Inputs: dashboard_id (required).

dashboard_find_widgets_by_typedashboard.find_widgets_by_typeA
Read-only

Find all widgets matching a given type across one or more dashboards. Iterates over the specified dashboards (or all dashboards when dashboards is None) and returns every widget whose type field matches widget_type. Returns: list[dict[str, Any]] A list of match records. Each record contains: - dashboard_id (str): The oid of the containing dashboard. - dashboard_title (str): The title of the containing dashboard. - widget_id …

Inputs: widget_type (required), dashboards, max_results, admin_access.

dashboard_get_all_dashboardsdashboard.get_all_dashboardsA
Read-only

Retrieve all dashboards from the Sisense server. Sends GET /api/v1/dashboards/admin?dashboardType=owner using the admin endpoint, which requires elevated access. Returns: list[dict[str, Any]] | dict[str, Any] A list of dashboard objects on success, or {"error": "..."} on failure.

Inputs: no named parameters.

dashboard_get_dashboard_by_iddashboard.get_dashboard_by_idA
Read-only

Retrieve a specific dashboard by its ID. Sends GET /api/v1/dashboards/admin?dashboardType=owner&id={dashboard_id} against the admin endpoint. Returns: list[dict[str, Any]] | dict[str, Any] The matching dashboard objects on success, or {"error": "..."} when the request fails or no dashboard is found.

Inputs: dashboard_id (required).

dashboard_get_dashboard_columnsdashboard.get_dashboard_columnsA
Read-only

Retrieve columns referenced by a dashboard, including widget and filter columns. Resolves the dashboard by title with get_dashboard_by_name, exports its full metadata, then extracts column references from both filters and widgets. The final list is deduplicated by table and column. Returns: list[dict[str, Any]] | dict[str, Any] A list of distinct column entries. Each entry contains dashboard_name, source ("filter" or "widget"), widget_id, table, and column — an empty list means …

Inputs: dashboard_name (required).

dashboard_get_dashboardsdashboard.get_dashboardsA
Read-only

Retrieve dashboards visible to the authenticated user. Sends GET /api/v1/dashboards, which returns dashboards the current user owns or has been shared to — as opposed to get_all_dashboards, which uses the admin endpoint and requires elevated access. Returns: list[dict[str, Any]] | dict[str, Any] List of dashboard objects on success, or {"error": "..."} on failure.

Inputs: fields.

dashboard_get_dashboard_scriptdashboard.get_dashboard_scriptA
Read-only

Build a formatted dashboard script helper object. Retrieves a dashboard export payload and wraps its script content in a :class:SisenseScript helper that can render plain text, markdown, or file output. Returns: SisenseScript | dict[str, str] A :class:SisenseScript instance when the dashboard is retrieved and has a script. {"error": "..."} when the export fails (including status_code for HTTP failures such as missing …

Inputs: dashboard_id (required).

dashboard_get_dashboard_sharedashboard.get_dashboard_shareA
Read-only

Retrieve share details (users and groups) for a dashboard by title. Resolves the dashboard by title, then maps each share's shareId to a readable name using the users and groups lists: user shares resolve to the user's email and group shares to the group name. Returns: list[dict[str, Any]] A list of share entries, each containing type ("user" or "group") and name (email or group name). An empty list means the dashboard genuinely has no shares. On failure (dashboard not …

Inputs: dashboard_name (required).

dashboard_get_widget_by_iddashboard.get_widget_by_idA
Read-only

Retrieve a single widget by its dashboard and widget IDs. Sends GET /api/v1/dashboards/{dashboard_id}/widgets/{widget_id}. Returns the full widget object as returned by the Sisense API. Returns: dict[str, Any] The widget object returned by the API, or {"error": "..."} on failure.

Inputs: widget_id (required), dashboard_id (required), admin_access.

dashboard_get_widget_scriptdashboard.get_widget_scriptA
Read-only

Build a formatted widget script helper object. Retrieves a dashboard export payload, selects a widget by index/key, and wraps its script content in a :class:SisenseScript helper for downstream rendering. Returns: SisenseScript | dict[str, str] A :class:SisenseScript instance when the widget is found and has a script. {"error": "..."} when the export fails (including status_code for HTTP failures such as missing …

Inputs: widget_id (required), dashboard_id (required).

datamodel_describe_datamodeldatamodel.describe_datamodelA
Read-only

Retrieve data model structure in a flat, row-based format. Resolves the data model by name and flattens its datasets and tables into one row per table, suitable for DataFrame or CSV export. Returns: list[dict[str, Any]] List of row dicts, each representing a single table with model, dataset, connection, and table context. Returns an empty list if the data model is not found.

Inputs: datamodel_name (required).

datamodel_generate_connections_payloaddatamodel.generate_connections_payloadA
Read-only

Generate a connection payload for a given data source type. Builds the provider-specific request body consumed by create_connections. The datasource_type is matched case-insensitively. Supported types are "Athena", "RedShift", "BigQuery", and "DataBricks". Returns: dict[str, Any] The provider-specific connection payload.

Inputs: datasource_type (required), connection_params (required).

datamodel_get_all_datamodeldatamodel.get_all_datamodelA
Read-only

Retrieve metadata for all data models using an internal API. Sends a POST /api/v2/ecm/ GraphQL query (elasticubesMetadata). This includes additional fields such as build status, size, and timestamps that may not be available through the standard public endpoints. Returns: list[dict[str, Any]] | dict[str, Any] List of data model metadata objects (each with oid, title, type, status, sizeInMb) on success, or {"error": "..."} on failure. Uses an internal Linux-only route; on Windows-based Sisense use get_elasticubes instead.

Inputs: no named parameters.

datamodel_get_connectiondatamodel.get_connectionA
Read-only

Retrieve connections matching a name. Sends GET /api/v2/connections?name=<connection_name> and returns the matching connection list. Returns: list[dict[str, Any]] | dict[str, Any] List of matching connection objects if found, or {"error": "..."} on failure or when no match is found.

Inputs: connection_name (required).

datamodel_get_connections_alldatamodel.get_connections_allA
Read-only

Retrieve all connections. Sends GET /api/v2/connections and returns the full connection list. Returns: list[dict[str, Any]] | dict[str, Any] List of connection objects on success, or {"error": "..."} on failure.

Inputs: no named parameters.

datamodel_get_datadatamodel.get_dataA
Read-only

Retrieve data from a specific table in a data model. Runs a SQL query against the data model and returns the rows in a row-based format (a list of dicts) compatible with to_dataframe. Returns: list[dict[str, Any]] | dict[str, Any] List of dictionaries where each dict represents a row — an empty list means the query genuinely returned no data. On failure, returns the standard ``{"ok": False, "error": "...", …

Inputs: table_name (required), datamodel_name (required), query.

datamodel_get_datamodel_sharesdatamodel.get_datamodel_sharesA
Read-only

Retrieve all share entries (users and groups) for a given data model. Resolves user and group identifiers to names/emails and returns the shares in a flat row format. Permission codes are mapped to "EDIT", "READ", or "USE". Returns: list[dict[str, Any]] List of dicts, each with "datamodel_name", "datamodel_id", "party_name", "party_type", and "permission" — an empty list means the model genuinely has no shares. On failure, …

Inputs: datamodel_name (required).

datamodel_get_datasecurity_detaildatamodel.get_datasecurity_detailA
Read-only

Retrieve detailed datasecurity rules for a data model, including share-level visibility. Each row represents a unique column-level rule and is repeated per share for clarity. Special handling is applied to interpret member values: - If members is an empty list and exclusionary is missing/null, it is interpreted as "Nothing". - If members is empty and exclusionary is False, it is interpreted as "Everything". - If values exist and exclusionary is True, it is treated as a restricted subset. Returns: list[dict[str, Any]] | dict[str, Any] List of dicts representing datasecurity rules in flat, share-resolved format, each with "datamodel_name", "table_name", "column_name", "data_type", "value", …

Inputs: datamodel_name (required).

datamodel_get_elasticubesdatamodel.get_elasticubesA
Read-only

List all ElastiCubes using the legacy v1 endpoint. Sends GET /api/v1/elasticubes/getElasticubes. This endpoint is supported on both Linux and Windows Sisense deployments and returns basic ElastiCube metadata including title, address, and fullname. Prefer get_all_datamodel for Linux deployments when richer metadata (build status, size, timestamps) is needed. Use get_elasticubes when targeting Windows environments or when a lightweight list suffices. Returns: list[dict[str, Any]] | dict[str, Any] List of ElastiCube objects on success, or {"error": "..."} on failure.

Inputs: no named parameters.

datamodel_get_model_schemadatamodel.get_model_schemaA
Read-only

Retrieve the schema of a data model, including tables and columns. Resolves the data model by name and emits one row per column, mapping Sisense numeric column type codes to readable type names. Returns: list[dict[str, Any]] | dict[str, Any] A list of dicts (one per column) with datamodel_name, datamodel_type, dataset_name, table_name, column_name, and column_type on success, or ``{"error": …

Inputs: datamodel_name (required).

datamodel_get_row_countdatamodel.get_row_countA
Read-only

Retrieve the row count for each table in a specific data model. Resolves the data model's tables, counts rows per table, and returns the results in a flat row-based structure suitable for tabular representation. Returns: list[dict[str, Any]] | dict[str, Any] List of dictionaries, each with "table_name" and "row_count", plus a final entry with the total row count. On failure, returns the standard ``{"ok": False, "error": "...", …

Inputs: datamodel_name (required).

datamodel_get_table_schemadatamodel.get_table_schemaA
Read-only

Retrieve the schema of a table within a connection's data source. Resolves the connection by name to obtain its oid and provider, then sends POST /api/v1/connection/{id}/table_schema_details. Returns: dict[str, Any] Table schema details if found, or {"error": "..."} on failure or when no schema is found. Note: This endpoint is undocumented and may change in future Sisense versions. Use with caution.

Inputs: table_name (required), schema_name (required), database_name (required), connection_name (required).

folder_get_all_foldersfolder.get_all_foldersA
Read-only

Retrieve the full folder tree. Convenience wrapper for get_folders("tree"). Returns the nested folder hierarchy used by Sisense for organizing dashboards. Returns: list[dict[str, Any]] | dict[str, Any] A list of root-level folder nodes (each may contain nested folders and dashboards keys), or {"error": "..."} on failure.

Inputs: no named parameters.

folder_get_folder_idfolder.get_folder_idA
Read-only

Retrieve a single folder by OID. Sends GET /api/v1/folders/{folder_id} and returns the folder metadata object. Returns: dict[str, Any] Folder metadata from the API, or {"error": "..."} if the request fails or no folder is found.

Inputs: folder_id (required).

plugins_get_all_pluginsplugins.get_all_pluginsA
Read-only

Retrieve all plugins installed on the Sisense instance. Fetches the complete plugin list using paginated requests to GET /api/v1/plugins. All pages are collected and returned as a single flat list. Returns: list[dict[str, Any]] A list of plugin objects, each containing at minimum: - name (str): API identifier for the plugin. - folderName (str): Filesystem folder name (e.g. "plugin-MyPlugin"). - isEnabled …

Inputs: no named parameters.

report_manager_get_reportsreport_manager.get_reportsA
Read-only

Retrieve all reports configured in Report Manager. Sends paginated requests to GET /api/v1/report_manager/reports and collects every page into a single flat list. Filters are applied server-side. Returns: list[dict[str, Any]] | dict[str, Any] A flat list of report objects, or {"error": "..."} on failure.

Inputs: ids, name, sort, limit, +5 more.

wellcheck_run_full_wellcheckwellcheck.run_full_wellcheckA
Read-only

Run a composite "full" wellcheck across dashboards and data models. This method is a convenience wrapper that orchestrates multiple dashboard-level and data-model-level checks and returns a structured report that groups their results. It additionally delegates unused-column analysis to AccessManagement.get_unused_columns_bulk when an AccessManagement instance is configured on this WellCheck (the default constructor configures one); otherwise the unused_columns section is an empty list and a warning is logged. Returns: dict A dictionary with two top-level sections: - "dashboards": { "structure": [...], "widget_counts": [...], "pivot_widget_fields": [...], } - "datamodels": { "custom_tables": [...], "island_tables": [...], … Can be slow: the m2m check runs real aggregate SQL. Scope it with the dashboards/datamodels parameters whenever the user named specific assets.

Inputs: dashboards, datamodels, max_pivot_fields.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.6/5.0

    Scored across 36 tools

    Disambiguation3/5

    The domain prefixes and resource nouns separate most tools, but several pairs overlap in obvious ways: get_all_dashboards vs get_dashboards, get_all_datamodel vs get_elasticubes, and get_connection vs get_connections_all require close reading of descriptions to disambiguate. The shared read-only 'get everything' pattern across access, dashboards, and datamodels adds selection risk, so the boundaries are not crisp enough for a higher score.

    Naming Consistency4/5

    The dominant pattern is domain_prefix_get_resource, and snake_case is used throughout, which makes the set feel predictable. Deviations like access_management_users_per_group, dashboard_can_be_owned, datamodel_describe_datamodel, datamodel_generate_connections_payload, and wellcheck_run_full_wellcheck break the pattern, and singular/plural forms like get_connection vs get_connections_all are inconsistent.

    Tool Count2/5

    With 36 tools, this server is well above the 25-tool threshold for a heavy surface. The broad read-only scope would feel more manageable if consolidated into fewer aggregate tools per domain, but as-is the agent will carry a large tool list for what is largely a retrieval/wellcheck server.

    Completeness2/5

    The tool surface is almost entirely read-only: nearly every tool is a getter plus a single wellcheck runner. There are no create, update, delete, or ownership-changing operations, and datamodel_generate_connections_payload mentions create_connections without actually exposing that tool, creating a clear dead end. Agents can inspect the Sisense instance but cannot act on it.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers