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

# Ask questions about your security data with Oplane MCP

> Use Oplane MCP to ask your AI assistant about your organisation's threat models and security requirements, from open findings by severity to trends and time to resolve.

export const WalkthroughVideo = ({children, src, hdSrc, poster, label, descriptionId, dark = false}) => {
  const found = [];
  const collect = node => {
    if (!node) return;
    if (Array.isArray(node)) return node.forEach(collect);
    if (node.props) {
      if (node.props.src) found.push(node.props.src); else collect(node.props.children);
    }
  };
  collect(children);
  const posterUrl = found[0] || poster;
  const srcUrl = found[1] || src;
  const hdUrl = found[2] || hdSrc;
  const PLAY = '<path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.6-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5Z" />';
  const PAUSE = '<rect x="6" y="4.5" width="4" height="15" rx="1.2" /><rect x="14" y="4.5" width="4" height="15" rx="1.2" />';
  const filled = (d, cls) => `<svg class="${cls}" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor">${d}</svg>`;
  const REPLAY = '<path d="M3 12a9 9 0 1 0 3-6.7" /><path d="M3 4v5h5" />';
  const CLOSE = '<path d="M18 6 6 18" /><path d="m6 6 12 12" />';
  const icon = (d, size = 14, cls = "") => `<svg${cls ? ` class="${cls}"` : ""} viewBox="0 0 24 24" width="${size}" height="${size}" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${d}</svg>`;
  const REPLAY_FROM = 0.9;
  const REST_BEFORE_END = 0.7;
  const MAX_PLAYS = 3;
  const reducedMotion = () => typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const showState = (container, playing) => {
    if (!container) return;
    container.dataset.state = playing ? "playing" : "paused";
    const toggle = container.querySelector("[data-act='toggle']");
    if (toggle) {
      const name = playing ? "Pause the video" : "Play the video";
      toggle.setAttribute("aria-label", name);
      toggle.setAttribute("title", playing ? "Pause" : "Play");
    }
  };
  const restart = v => {
    v.dataset.plays = "0";
    const go = () => {
      v.currentTime = REPLAY_FROM;
      v.play().catch(() => {});
    };
    if (v.error || v.readyState === 0) {
      v.addEventListener("loadedmetadata", go, {
        once: true
      });
      v.load();
    } else {
      go();
    }
  };
  const toggle = v => {
    if (!v.paused) return v.pause();
    if ((Number(v.dataset.plays) || 0) >= MAX_PLAYS) return restart(v);
    v.play().catch(() => {});
  };
  const expand = inline => {
    document.querySelectorAll(".walkthrough-overlay").forEach(n => n.remove());
    const opener = document.activeElement;
    const wasPlaying = !inline.paused;
    const o = document.createElement("div");
    o.className = "walkthrough-overlay";
    o.setAttribute("role", "dialog");
    o.setAttribute("aria-modal", "true");
    o.setAttribute("aria-label", `${inline.getAttribute("aria-label") || "Walkthrough video"}, larger`);
    o.innerHTML = `<div class="walkthrough-overlay-frame" data-state="loading">` + `<video class="walkthrough-overlay-video" playsinline loop disablepictureinpicture></video>` + `<button type="button" class="walkthrough-play" data-act="toggle" aria-label="Play the video" title="Play">` + `${filled(PLAY, "when-paused")}${filled(PAUSE, "when-playing")}</button>` + `<button type="button" class="walkthrough-icon walkthrough-icon--replay" data-act="replay" aria-label="Replay the video" title="Replay">${icon(REPLAY, 16)}</button>` + `</div>` + `<button type="button" class="walkthrough-close" data-act="close" aria-label="Close the larger video" title="Close">${icon(CLOSE)}</button>`;
    const frame = o.querySelector(".walkthrough-overlay-frame");
    const v = o.querySelector("video");
    const describedBy = inline.getAttribute("aria-describedby");
    if (describedBy) v.setAttribute("aria-describedby", describedBy);
    v.muted = true;
    v.addEventListener("play", () => showState(frame, true));
    v.addEventListener("pause", () => showState(frame, false));
    const from = inline.currentTime;
    v.addEventListener("loadedmetadata", () => {
      v.currentTime = from;
    }, {
      once: true
    });
    v.src = inline.dataset.hdSrc || inline.currentSrc || inline.src;
    inline.pause();
    const buttons = () => [...o.querySelectorAll("button")];
    const onKey = ev => {
      if (ev.key === "Escape") return close();
      if (ev.key !== "Tab") return;
      const list = buttons();
      const i = list.indexOf(document.activeElement);
      if (ev.shiftKey && i <= 0) {
        ev.preventDefault();
        list[list.length - 1].focus();
      } else if (!ev.shiftKey && i === list.length - 1) {
        ev.preventDefault();
        list[0].focus();
      }
    };
    const close = () => {
      document.removeEventListener("keydown", onKey);
      document.documentElement.classList.remove("walkthrough-open");
      o.remove();
      if (wasPlaying) inline.play().catch(() => {});
      if (opener && opener.focus) opener.focus();
    };
    o.addEventListener("click", ev => {
      const btn = ev.target.closest("[data-act]");
      const act = btn && btn.dataset.act;
      if (act === "toggle") v.paused ? v.play().catch(() => {}) : v.pause(); else if (act === "replay") {
        v.currentTime = 0;
        v.play().catch(() => {});
      } else if (act === "close" || ev.target === o) close(); else if (ev.target === v) v.paused ? v.play().catch(() => {}) : v.pause();
    });
    document.addEventListener("keydown", onKey);
    document.body.appendChild(o);
    document.documentElement.classList.add("walkthrough-open");
    o.querySelector(".walkthrough-close").focus();
    v.play().catch(() => showState(frame, false));
  };
  const videoOf = el => el.closest(".walkthrough").querySelector("video");
  return <div className="walkthrough" data-state="loading">
      <video ref={v => {
    if (!v) return;
    v.muted = true;
    v.defaultMuted = true;
    v.setAttribute("muted", "");
    if (!v.paused || v.dataset.plays) return;
    if (reducedMotion()) return showState(v.parentElement, false);
    v.play().catch(() => showState(v.parentElement, false));
  }} muted playsInline controls={false} disablePictureInPicture disableRemotePlayback controlsList="nodownload noplaybackrate nofullscreen noremoteplayback" preload="auto" aria-label={label} aria-describedby={descriptionId} className={`walkthrough-video${dark ? " walkthrough-dark" : ""} w-full aspect-video`} style={{
    cursor: "pointer",
    pointerEvents: "auto",
    backgroundColor: dark ? "#151517" : "#f1f7fa"
  }} src={srcUrl} poster={posterUrl} data-hd-src={hdUrl} onPlay={e => showState(e.currentTarget.parentElement, true)} onPause={e => {
    const v = e.currentTarget;
    if (v.ended && (Number(v.dataset.plays) || 0) + 1 < MAX_PLAYS) return;
    showState(v.parentElement, false);
  }} onEnded={e => {
    const v = e.currentTarget;
    const plays = (Number(v.dataset.plays) || 0) + 1;
    v.dataset.plays = String(plays);
    if (plays < MAX_PLAYS) {
      v.currentTime = 0;
      v.play().catch(() => {});
    } else {
      v.currentTime = Math.max(0, v.duration - REST_BEFORE_END);
    }
  }} onClick={e => toggle(e.currentTarget)} />
      <button type="button" className="walkthrough-icon walkthrough-icon--expand" aria-label="Show the video larger" title="Expand" onClick={e => expand(videoOf(e.currentTarget))}>
        <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6" /><path d="M9 21H3v-6" /><path d="M21 3l-7 7" /><path d="M3 21l7-7" /></svg>
      </button>
      <button type="button" className="walkthrough-play" data-act="toggle" aria-label="Play the video" title="Play" onClick={e => toggle(videoOf(e.currentTarget))}>
        <svg className="when-paused" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.6-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5Z" /></svg>
        <svg className="when-playing" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6" y="4.5" width="4" height="15" rx="1.2" /><rect x="14" y="4.5" width="4" height="15" rx="1.2" /></svg>
      </button>
      <button type="button" className="walkthrough-icon walkthrough-icon--replay" aria-label="Replay the video" title="Replay" onClick={e => restart(videoOf(e.currentTarget))}>
        <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7" /><path d="M3 4v5h5" /></svg>
      </button>
    </div>;
};

When Oplane is connected to your AI assistant over MCP, you can ask it questions about your organisation's threat models, security requirements, and workspaces in plain language. It can count and break down findings, show how they change week by week, work out how long fixes take, and pull up the exact requirements behind a number with links into Oplane. Use it when the [Analytics dashboard](/docs/prove/analytics) doesn't show the view you need, or when you want the answer without leaving the tool you're already working in.

<Frame>
  <WalkthroughVideo dark label="Walkthrough: asking an AI assistant about Acme's security data through Oplane MCP" descriptionId="insights-video-description">
    <img src="https://mintcdn.com/oplane-6a173d70/e-cGBFncDiI85364/images/insights/insights-flow-poster.webp?fit=max&auto=format&n=e-cGBFncDiI85364&q=85&s=3cea293958b5a4e3cb9f9e08a4f97b49" alt="" width="1280" height="720" data-path="images/insights/insights-flow-poster.webp" />

    <video src="https://mintcdn.com/oplane-6a173d70/qWX552J763USuwk5/images/insights/insights-flow.mp4?fit=max&auto=format&n=qWX552J763USuwk5&q=85&s=4f50a72d4651215166fca0a8c76215f0" data-path="images/insights/insights-flow.mp4" />

    <video src="https://mintcdn.com/oplane-6a173d70/qWX552J763USuwk5/images/insights/insights-flow-hd.mp4?fit=max&auto=format&n=qWX552J763USuwk5&q=85&s=9319d2a470e2744d395fe827f7281e62" data-path="images/insights/insights-flow-hd.mp4" />
  </WalkthroughVideo>
</Frame>

<Accordion title="What the video shows">
  <div id="insights-video-description">
    The video uses made-up data for an example organisation called Acme. It shows an AI assistant connected to Oplane MCP, answering three questions.

    1. You ask "Break down our open requirements by severity." The assistant calls the `analyze` tool and answers that Acme has 214 open requirements, 79 of them critical or high. A table lists 18 critical, 61 high, 84 medium, 40 low, and 11 info.
    2. You ask "How many new requirements did we get each week since August?" The assistant calls `analyze` and answers that Acme got 395 new requirements since 3 August. A weekly table shows between 19 and 28 new requirements a week until the week of 14 September, then between 58 and 71 a week. Most of them are already resolved.
    3. You ask "For each workspace, how many requirements from the last 3 months are open and how many are resolved?" The assistant calls `analyze` and `get_workspaces` and answers that 455 requirements came in and 85% of them are resolved. A table lists each workspace, and acme/checkout-web has the most still open, 29 out of 150.

    The video ends on an empty message box that asks "What's your question?"
  </div>
</Accordion>

## Before you start

This works in any AI assistant that supports MCP, whether that's an IDE like Cursor, a terminal tool like Claude Code or Codex, or a chat app like Claude. If you already use Oplane MCP for threat modeling, you're set, because the same server answers these questions. If not, [add the Oplane MCP server](/docs/find/mcp) to your assistant first.

Answers only cover workspaces you have access to, so two people asking the same question can get different numbers. An org admin sees every shared and repository workspace in the organisation, while an org member sees the workspaces they have access to. See [Roles and permissions](/docs/administration/roles) for how access works.

Each question covers one organisation. If you belong to more than one, name the organisation in your prompt. Otherwise Oplane returns a list of your organisations, so your assistant can pick one or ask you.

To ask from a CI pipeline or a script, use an [application](/docs/administration/applications) with **Read** on **Contents**.

## What you can ask

| You want to know | Example prompt |
| - | - |
| How many requirements are open | `How many open critical requirements do we have?` |
| Where open findings sit by severity | `Break down our open requirements by severity.` |
| How the number of new findings changes | `How many new requirements did we get each week since July?` |
| How long fixes take | `What's the median time to resolve a critical requirement?` |
| Which workspaces have the most new findings | `Which 10 workspaces got the most new requirements in the last 30 days?` |
| How each workspace keeps up | `For each workspace, how many requirements from the last 3 months are open and how many are resolved?` |
| Which requirements got fixed after Oplane flagged them | `Which requirements were not implemented when first assessed and are implemented now?` |
| Which threat models have gone stale | `Which threat models haven't been updated in 6 months?` |
| The requirements behind a number | `List the open critical requirements in the payments workspace, with links.` |

## How your assistant gets the answer

For counts, breakdowns, trends, and durations, your assistant calls the `analyze` tool. Oplane computes the result on its side and returns just the numbers, so one call covers thousands of requirements without your assistant reading through them. A breakdown can split the results by up to two things at once, such as workspace and open or resolved, and group dates by day, week, or month. A breakdown returns the top 20 groups by default and up to 100 if you ask for more.

To show the records themselves, your assistant first finds the matching threat models, requirements, or workspaces, and then fetches their details. Each record includes a link that opens it in the Oplane app. See [How read tools work](/docs/reference/mcp-tools#how-read-tools-work) for the details.

## How Oplane counts

A requirement counts as **open** while it's not assessed, not implemented, or partially implemented. It counts as **resolved** once it's implemented, accepted as a risk, out of scope, or not applicable. See [Statuses and severity](/docs/reference/statuses-and-severity#requirement-status) for what each status means.

Time to resolve runs from when Oplane created the requirement to the first time it reached a resolved status. If it later goes back to open, the first resolution still counts.

When you ask about threat models from pull requests, Oplane only counts pull requests that merged into the repository's production branch, which is the repository's default branch unless your team set another one. That way the numbers reflect code that shipped. To include pull requests that are still open or never merged, say so in your prompt, for example `Include all pull requests, merged or not.`

## Read next

* [Analytics](/docs/prove/analytics), for the organisation and workspace dashboards in the Oplane app.
* [MCP tools](/docs/reference/mcp-tools), for every tool your assistant can call.
* [Statuses and severity](/docs/reference/statuses-and-severity), for what each status and severity counts as.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.