Edit

Show citations with response semantics

Citations build trust that a Microsoft 365 Copilot response is accurate and grounded. The response body automatically includes citations for Copilot synthesized responses. However, the end user might or might not be able to open the source of information. When Copilot grounds a response on public web content, the URL is cited automatically.

For content coming from a Model Context Protocol (MCP) server or an API, that content must return a URL that an end user can open and review. You define response_semantics in your plugin definition so that Copilot knows where that URL is located in the plugin response, and can make the citation clickable with the right link.

If you skip this step, the response still includes a citation, but only with a representative pill or icon. The end user can't click through and confirm the data from your site. That's why clickable citations are also a Microsoft 365 Copilot Agents store policy requirement for apps published to the store.

On hover experience for clickable citations in a Copilot response.

Important

You don't need an Adaptive Card to get citations. Response semantics alone - a data_path plus a few properties mappings - is enough for Copilot to render a clickable citation pointing back to your source.

Consider using interactive MCP apps for rich UX beyond citations.

Using response semantics

Response semantics defined in your plugin manifest act as a contract between your MCP server or API and Copilot.

  1. Your tool returns JSON.

  2. In the plugin manifest:

    • You tell Copilot where in that JSON the citable items are located (data_path).
    • You tell Copilot which fields on each item map to the citation's title, subtitle, and URL (properties).
  3. Copilot renders a citation for each item. Users click through to your source.

Minimum configuration

Your response_semantics configuration is driven by the shape of your tool's response, not by the protocol of your tool response. Copilot agents with all tool protocols (MCP, OpenAPI, message extensions) use the same manifest schema.

Most tool responses fall into one of two shapes:

  • An array of results (like a search tool): the tool returns multiple items, each of which should become its own citation.
  • A single object (like a fetch tool): the tool returns exactly one document or record, which becomes a single citation.

Array of results (search-style)

Tools that return multiple items typically return an array under a results (or equivalent) key, as shown in the following example.

{
  "results": [
    {
      "id": "tr-001",
      "title": "Forecasting AI adoption in the enterprise (2026)",
      "url": "https://www.treyresearch.net/notes/ai-adoption-2026",
      "publishedDate": "2026-03-12",
      "thumbnailUrl": "https://www.treyresearch.net/assets/trey-research-logo.png"
    },
    {
      "id": "tr-005",
      "title": "Enterprise AI spend, deep dive",
      "url": "https://www.treyresearch.net/notes/ai-spend",
      "publishedDate": "2026-03-28",
      "thumbnailUrl": "https://www.treyresearch.net/assets/trey-research-logo.png"
    }
  ]
}

The following example shows a minimum response semantics configuration in the plugin manifest.

"capabilities": {
  "response_semantics": {
    "data_path": "$.results",
    "properties": {
      "title": "$.title",
      "subtitle": "$.publishedDate",
      "url": "$.url"
    }
  }
}

The data_path property points to the array. Each element produces its own clickable citation. The properties JSONPaths are resolved relative to each array element, not the root.

Single object (fetch-style)

The following example shows a response with exactly one record - a document, an entity, a file - to be cited as one source.

{
  "id": "tr-001",
  "title": "Forecasting AI adoption in the enterprise (2026)",
  "text": "Trey Research surveyed 412 enterprise CIOs across North America and EMEA between January and February 2026. We forecast that 64% of Fortune 500 firms will be running at least one production generative AI workload by end of 2026, up from 38% at the close of 2025...",
  "url": "https://www.treyresearch.net/notes/ai-adoption-2026",
  "publishedDate": "2026-03-12",
  "thumbnailUrl": "https://www.treyresearch.net/assets/trey-research-logo.png",
  "metadata": { "source": "trey-research", "category": "AI" }
}

The following example shows a minimum response semantics configuration in the plugin manifest.

"capabilities": {
  "response_semantics": {
    "data_path": "$",
    "properties": {
      "title": "$.title",
      "subtitle": "$.publishedDate",
      "url": "$.url"
    }
  }
}

The data_path property set to $ selects the root object as a single citation item. This is the right choice whenever the tool returns one record - even if the record includes nested fields like metadata.

MCP content wrapper

MCP tools wrap their response in a content array of TextContentBlock items. Copilot parses the text field of each block as JSON and then applies your data_path against the parsed value. The shape inside the text string is what drives your configuration - not the outer content wrapper.

Example MCP response

{
  "content": [
    {
      "type": "text",
      "text": "{\"id\":\"tr-001\",\"title\":\"Forecasting AI adoption in the enterprise (2026)\",\"url\":\"https://www.treyresearch.net/notes/ai-adoption-2026\"}"
    }
  ]
}

The parser unwraps the text payload first, leaving a single object. You use the single object configuration ("data_path": "$"). An MCP search tool that returns an array inside the text field uses the array of results configuration (data_path: "$.results").

Citation properties

The following properties are available on citations. All values are relative JSONPath expressions against one item selected by data_path.

Property Required What it does
title Yes (practically) The clickable heading of the citation.
subtitle No Second line - dates, authors, categories.
url Yes (practically) Where the citation navigates on click. Must be a canonical link back to your source.
thumbnail_url No Small image shown alongside the citation.

Note

If url is missing, the citation isn't clickable. This missing property is a very common reason developers see non-functional citations.

Setting data_path

The data_path property is a JSONPath (RFC 9535) expression. Using an incorrect JSONPath expression is one of the most common reasons citations don't show up.

If your response looks like... Use this data_path
{ "results": [ ... ] } $.results
{ "content": [ { "results": [ ... ] } ] } (MCP-style nested) $.content[0].results
A single object at the root (no array wrapper) $
{ "content": [ { "type": "text", "text": "<stringified JSON>" } ] } (raw MCP) $ for root, or $.results if the inner JSON has an array

Tip

Flatten your arrays. Multi-level nested arrays (for example, $.content[0].results[0].items) are the schema pattern most likely to silently fail. If you own the tool response shape, return a flat results: [...] array.

Going beyond response semantics

As a first preference, consider adding rich UI widgets to your agent. This approach is more future-ready and AI-native, allowing more intelligent, adaptive, and seamless interactions.

Add an Adaptive Card as a last resort if - and only if - you need one of the following conditions:

  • A custom visual layout for the citation alone (multicolumn, image banners, formatted text blocks), or multiple fields rendered in the citation card body (beyond title, subtitle, and URL).
  • Action buttons beyond the default "click the citation" behavior (for example, Action.Execute, multibutton toolbars).

For the vast majority of citation scenarios - "show me the source and let me click through" - skip Adaptive Cards entirely. They add complexity, are harder to debug, and the default citation UI is clean and consistent with the rest of Copilot.

Example with Adaptive Card

"response_semantics": {
  "data_path": "$.content[1].results",
  "properties": {
    "title": "$.title",
    "subtitle": "$.publishedDate",
    "url": "$.url"
  },
  "staticTemplate": {
    "type": "AdaptiveCard",
    "version": "1.4",
    "body": [
      {
        "type": "TextBlock",
        "text": "${title}",
        "weight": "bolder",
        "size": "medium"
      },
      {
        "type": "TextBlock",
        "text": "${subtitle}",
        "isSubtle": true
      },
      {
        "type": "TextBlock",
        "text": "${text}",
        "wrap": true
      }
    ],
    "selectAction": {
      "type": "OpenUrl",
      "url": "${url}"
    }
  }
}

Notice the ${title}, ${subtitle}, and ${url} tokens - these tokens are populated by the same properties map shown earlier. The Adaptive Card is a presentation layer on top of response semantics; it doesn't replace it.

Troubleshooting checklist

If citations don't show up, use the following checklist.

  • Is data_path pointing to the right node? Paste your tool's raw JSON response into a JSONPath tester and confirm your expression returns the array or object you expect.
  • Does each item have a non-empty url? Missing URLs result in non-clickable citations.
  • For MCP tools, is the text field inside TextContentBlock valid JSON? Parse it manually to confirm.
  • Is the schema flat? If you have deeply nested arrays, try returning a single flat array.
  • Did you declare response_semantics per function (inside that function's capabilities in the plugin manifest) and not at the plugin root? You must scope it to the function.