MCP Servers and M365 (Part 4): Connect the Message Center MCP Server to a Declarative Agent (M365 Agents Toolkit)

Connect the Microsoft 365 Message Center MCP Server to a Copilot Declarative agent built with the Microsoft 365 Agents Toolkit.

In Part 3, we built an authenticated MCP server that calls Microsoft Graph using On-Behalf-Of (OBO).

In this post, you’ll connect that Message Center MCP server to a Copilot declarative agent that you build with the Microsoft 365 Agents Toolkit.

This is the “enterprise path” integration:

  • Your declarative agent calls your MCP server.
  • The agent authenticates the signed-in user (delegated OAuth).
  • Your MCP server performs OBO token exchangeto obtain a Microsoft Graph token.
  • The MCP tool runs as the signed-in user and returns results.

Integrating the Message Center MCP server

This server implements the MCP protocol and exposes a tool that retrieves Message Center posts from Microsoft Graph. It includes OBO (on-behalf-of) authentication so that it can call Graph on behalf of the signed-in user.

Message Center MCP server repo (reference implementation used in this series):

Key details (as described in Part 3):

  • Health: GET /healthz
  • MCP endpoint: POST /mcp (JSON-RPC 2.0)
  • Discovery: GET /discover and GET /.well-known/openid-configuration
  • OAuth proxy endpoints: GET /authorize and POST /token
  • Tool: getMessages
    • Queries Microsoft Graph: GET https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages

Prerequisites

You’ll need:

  • Microsoft 365 Copilot available in your tenant (and access to test in https://m365.cloud.microsoft/chat)
  • Visual Studio Code
  • Microsoft 365 Agents Toolkit extension installed in VS Code

ℹ️ Important

The Message Center MCP server must be running locally or hosted (Part 3 walks through local setup). This post assumes it is already running and reachable.

  • A public HTTPS URL for the MCP server
    • If running locally, use a tunneling service (Dev Tunnel, Cloudflare Tunnel, etc.) - more on that below
  • A Microsoft Entra app registration configured for the Message Center MCP server (Part 3)
  • A test user with the right Microsoft 365 role to read Message Center (for example, Message Center Reader)

Create and configure an Entra app registration for the MCP server

For the purposes of this blog post we only need the server Application registration. In the next part of the series we will integrate this server with a client and at that time we will register the client application registration.

Run the client app registration script:

ℹ️ Note

Before running this script, ensure you have created the server app registration as described in Part 3 and have the Server App ClientId available.

1
pwsh -NoProfile -File .\scripts\CreateClientAppRegMCP.ps1 -AppName "MCP Message Center Client" -ServerAppId <Server-App-ClientId> -CreateSecret

Note the output values: ClientId, TenantId, ClientSecret. You’ll need them when configuring the agent in the Agents Toolkit.

Minimum configuration recap (as completed in CreateClientAppRegMCP.ps1):

  1. Authentication redirect URIs
    • Add the redirect URI your Copilot Studio connection uses :
  • Copilot gets an access token from this client app registration.
  • The token audience is your MCP server API (example: api://<server app reg clientId>).
  • The MCP server uses OBO to exchange this token for a Graph token.

Step 2 — Decide where the MCP server will run

Microsoft 365 must be able to reach:

  • https://<your-host>/mcp
  • https://<your-host>/authorize
  • https://<your-host>/token
  • https://<your-host>/.well-known/openid-configuration (or /discover)

Two common options:

Option A: Local server + tunnel (quick dev)

  1. Start the server locally (default port in Part 3 examples is 8080).
  2. Expose it with a tunnel.

Example with Dev Tunnel:

1
2
3
4
5
winget install Microsoft.devtunnel
devtunnel user login

# assuming your server is listening on 8080
devtunnel host -p 8080 --allow-anonymous --protocol https

Also see, scripts\dev-tunnel-persist.ps1 in the server repo to create a persistent Dev Tunnel.

Note the public HTTPS URL (example: https://<something>.devtunnels.ms).

âť— The steps for hosting the server are outside the scope of this post. However they are documented in the server repo README.

Step 3 — Validate the server with curl

Set a base URL:

1
2
# Tunnel/hosted example
$BaseUrl = 'https://<your-local-tunnel-host>'

3a) Health check

1
curl.exe -sS -i "$BaseUrl/healthz"

You want 200 OK and {“ok”:true} responses.

If that succeeds, your server is reachable via a public URL.

Step 4 — Create the declarative agent project (Microsoft 365 Agents Toolkit)

This post assumes you’re using Microsoft 365 Agents Toolkit to build and provision a declarative agent.

  1. In Visual Studio Code, open the Microsoft 365 Agents Toolkit extension.
  2. Select Create a New Agent/App.
  3. Choose Declarative Agent.
  4. Choose the following starting point:
    • Add an action
  5. Choose ‘Start with an MCP Server Preview’.
  6. Enter the your local-tunnel/hosted MCP server URL with ‘/mcp’. (for example: https://<your-host>/mcp).
  7. Select a destination folder for your project.
  8. Enter a project name (for example: Message Center Agent - MCP).

A new instance of VS Code will open with your new project.

  1. Name the app (for example: Message Center Assistant).
  2. When the new project opens, locate your app manifest file (commonly appPackage/manifest.json in Agents Toolkit projects).
    You see the following prompts from the toolkit regarding the MCP server:

Visual Studio Prompts for MCP

  1. Click ‘Confirm’ to add the Copilot instructions for the Agents Toolkit MCP server.

  2. Click ‘Fetch Action’ to fetch the tools from your MCP server.

  3. Select the first option, showing the URL to the MCP server.

  4. If you get an error, in mcp.json, click ‘Start’ the connect with the MCP server.
    Start MCP Server Connection

  5. Then click “ATK: Fetch Actions from MCP Server”.

  6. At the prompt ‘Select the action manifest you want to update’, Click on the ‘ai-plugin.json’.

  7. Click on ‘getMessages’ to add the Message Center tool to your agent. getMessages Tool

  8. Click ‘OK’ to confirm adding the tool.

  9. Do not click on ‘Provision’ yet.

At this point, you have a declarative agent project with the Message Center MCP server tool added. However, the agent can’t connect to the MCP server yet because authentication isn’t configured. This requires registering the client app registration with the Teams Developer portal. This provides a opaque ID that will be used to connect the agent to the MCP server using OAuth. This step stores the client app ID and client secret In the Teams developer portal. This provides a secure a way to secure the client app ID and secret.

Step 5 — Register the agent in the Teams Developer Portal

For this step you’ll the client ID and client secret created above. You’ll also need the server app registration ID created in Part 3.

  1. Open the Teams Developer Portal. Roles required: Teams app develop or Teams Administrator or Global Administrator.

  2. Click Tools

  3. Click ‘OAuth client Registration’. You may be prompted to select the previous version of the portal.

  4. click ‘New OAuth client registration’.

  5. Fill in the following fields:
    Registration name Any descriptive name (internal only).
    Example: OAuth-MessageCenterMCP

Base URL The MCP Server Endpoint URL:
https://«your-host»/mcp/

Restrict usage by org

  • My organization only → internal use
  • Any Microsoft 365 organization → multi-tenant (this setting for testing)

Restrict usage by app

  • Any Teams app (testing/validation) for development
  • Existing Teams app if binding to a specific App ID

OAuth Settings

Client ID Client ID for agent obtained above

Client secret Client secret for agent obtained above

Authorization endpoint https://login.microsoftonline.com/{tenantID}/oauth2/v2.0/authorize

Token endpoint https://login.microsoftonline.com/{tenantID}/oauth2/v2.0/token

Scope Use the exact scope your API exposes: api://«server app reg clientId»/access_as_user

PKCE

  • Keep ON for Authorization Code flow (recommended).

Review all settings and Click Save to create the OAuth client registration.

Copy the generated OAuth Client registration ID (found at the top of the form). You’ll need it in the next step.

Step 6 — Update the plugin manifest to connect to the MCP server

  1. Return the to Visual Studio Code.
  2. Open ai-plugin.json in the appPackage folder of your project.

Change the follow block from this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
"runtimes": [
        {
            "type": "RemoteMCPServer",
            "spec": {
                "url": "https://<<your-host>>/mcp",
                "mcp_tool_description": {
                    "file": "mcp-tools.json"
                }
            },
            "run_for_functions": [
                "getMessages"
            ]
        }
    ]

To this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
"runtimes": [
	{
		"type": "RemoteMCPServer",
		"auth": {
			"type": "OAuthPluginVault",
			"reference_id": "${{OAUTH2_REGISTRATION_ID}}"
		},
		"spec": {
			"url": "https://<<your-host>>/mcp"
		},
		"run_for_functions": [
			"getMessages"
		]
	}
]

Note the addition of the auth block with the reference_id property. Also, the “mcp_tool_description” has been removed. Make sure you update the ‘spec’, ‘url’ value to your MCP server endpoint. Update .env.dev or .env.production with this variable OAUTH2_REGISTRATION_ID with the actual OAuth Client registration ID obtained from the Teams Developer Portal in the previous step. OAuth Reference ID

Step 5 — Add the function definition to ai-plugin.json

Currently the function definition for getMessages is missing from ai-plugin.json. You need to add it so the agent knows how to call the tool. It is expect this will be added automatically in a future version of the Agents Toolkit.

  1. Open ai-plugin.json in the appPackage folder of your project.
  2. Locate the functions array.
  3. Add the getMessages function definition as follows:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
"functions": [
	{
		"name": "getMessages",
		"description": "Retrieve Microsoft 365 Message Center messages from Microsoft Graph (admin/serviceAnnouncement/messages).",
		"parameters": {
			"type": "object",
			"properties": {
				"orderby": {
					"type": "string",
					"default": "lastModifiedDateTime desc",
					"description": "Order by clause for sorting results. Defaults to newest messages first (lastModifiedDateTime desc)"
				},
				"count": {
					"type": "boolean",
					"default": "true",
					"description": "Total number of records returned"
				},
				"prefer": {
					"type": "string",
					"default": "odata.maxpagesize=5",
					"description": "Specifies the maximum number of items to return in a result set"
				},
				"top": {
					"type": "integer",
					"description": "Number of records to return. Set to 0 to retrieve only the count (@odata.count) without any message data. Useful for count-only queries like 'How many messages are there?'"
				},
				"skip": {
					"type": "integer",
					"description": "Number of records to skip"
				},
				"filter": {
					"type": "string",
					"description": "Filter the results based on specific conditions. For count-only queries, combine with $top=0 to get just the total number of matching records. Use `contains(tolower(title),tolower('search term'))` to search within the title property for a specific term or phrase. For multi-word searches, include the entire phrase in quotes. For create time, use the 'startDateTime' property with a date format. For example, `startDateTime ge 2025-03-01T00:00:00Z` to filter messages created after March 1, 2025. For major change messages, use `isMajorChange eq true`. To combine filters, use `and` operator. For example, `contains(tolower(title),tolower('Copilot agent')) and startDateTime ge 2025-03-01T00:00:00Z`. For the 'Tags' field, here is an example of the a query segment for the tags field: tags/any(t: t eq 'New feature'). For the 'severity' field, you can filter by severity using `severity eq 'normal'` or `severity eq 'high'` or `severity eq 'critical'`. For the 'category' field, you can filter by category using `category eq 'stayInformed'` or `category eq 'planForChange'` or `category eq 'preventOrFixIssue'`. For the 'Services' field, here is an example of a query segment for the services field: services/any(s: contains(s, 'Copilot')). When searching an message center update for roadmap ids, these are found in the 'details' field. Here is an example of a query segment for the details field, when searching for RoadmapIds: details/any(d: d/name eq 'RoadmapIds' and contains(d/value, '501107')). Note the 'name' property is used to specify the type of detail being searched, in this case 'RoadmapIds'. The 'value' property is then used to search for the specific roadmap id. RoadmapIds is case sensitive and must be used exactly as shown. To filter by message body, use `contains(tolower(body/content),tolower('search term'))` to search within the body content."
				},
				"accessToken": {
					"type": "string",
					"description": "Optional: Graph access token for proxying requests. Use only for local testing; OAuth flow will replace this after browser sign-in."
				}
			},
			"required": []
		}
	}
]

Step 6 — Provision and verify tool discovery

  1. Save your manifest changes.
  2. In Agents Toolkit, select Provision again.
  3. Open https://m365.cloud.microsoft/chat, select your agent, and start a new chat.

What you’re looking for:

  • During the first run, you should see a consent/connection experience if OAuth is configured.
  • Your MCP server should receive MCP handshake traffic (for example initialize, then tools/list).
  • When you ask a Message Center question, your server should receive a tools/call for getMessages.

Step 7 — Test the agent end-to-end

In Copilot chat (with your declarative agent selected), try:

  1. “What changed for Copilot since yesterday?”
  2. “Find messages related to Microsoft 365 Copilot published in the last 30 days.”
  3. “Show message regarding updates from the last month that are related to Copilot and marked as major changes.”

Expected behavior:

  • The agent prompts the user to login.
  • The agent calls getMessages.
  • The MCP server performs OBO and calls Graph and validates the user has Message Center access.
  • The agent summarizes results and includes IDs/links when available.

Provide instructions to the agent

To improve the agent’s behavior, you can provide additional instructions the instruction.txt file in your project. For example, you might add:

1
2
3
4
5
When answering questions about Microsoft 365 Message Center messages, always use the 'getMessages' tool to retrieve the latest messages. Use the following guidelines when calling the tool:
- Always set 'top' to 5 to limit the number of messages returned.
- Use 'orderby' set to 'lastModifiedDateTime desc' to get the most recent messages first.
- If the user asks for a count of messages, set 'top' to 0 and use the '@odata.count' property from the response.
- When filtering messages, use the 'filter' parameter to narrow down results based on user criteria.

See the project Message Center Agent - MCP for an example of a completed project, including updated instructions, ai-plugin.json, and starter prompts.

Troubleshooting

Developer Mode

  • In Copilot chat, enable Developer Mode with the following prompt:
    -developer on
    Develop remote is useful in that it gives you a preview of the composed Odata query that will be called against the Microsoft Graph. For example:
    Developer Mode Odata Query

Clicking on ‘Agent debug info’ shows the full request/response payloads exchanged between Copilot Studio and your MCP server. This is useful for debugging issues.

Summary

You now have a declarative agent (built with Microsoft 365 Agents Toolkit) calling an authenticated MCP server end-to-end:

  • The agent obtains an MCP API user token
  • Calls POST /mcp with Authorization: Bearer <token>
  • MCP server exchanges via OBO and calls Microsoft Graph
  • Returns Message Center results as tool output

In Part 5 of this series, we’ll create a custom agent in Copilot Studio that uses this MCP server to provide Message Center insights.

Built with Hugo
Theme Stack designed by Jimmy