Build Microsoft 365 Copilot Agents That Connect to SharePoint with TypeSpec and OAuth
Table of Contents
Introduction
Following on my blog post Declarative Agents: Azure OpenAI API Key Auth with TypeSpec, I decided to try out how to authenticate to Ms Graph using sample Tasks Agent using TypeSpec for Microsoft 365 Copilot that connects to the Microsoft Graph APIs created by Sébatien Levert.
Why This Matters: Copilot out of the box can not retrieve data from SharePoint List. Instead of manually going to SharePoint Lists to check your tasks, you can simply ask Copilot: “Show me my assigned tasks” or “Create a new volunteering task for next week.”
Prerequisites: What You Need Before Starting
Step 1: Set Up App Registration (Authentication)
What this means: Before your agent can access SharePoint, you need to create an Entra ID and register it within Teams Developer portal to grant it permission to read and write data.
How to do it: Follow one of these detailed guides:

Step 2: Find Your Tenant ID
What this is: Your Tenant ID is like your organization’s unique address in Microsoft’s system.
How to find it:
- Go to the Azure Portal (portal.azure.com)
- Search for “Azure Active Directory” or “Entra ID”
- Look for “Tenant ID” on the overview page
- Copy this GUID (it looks like: 12345678-1234-1234-1234-123456789012)
Step 3: Create a SharePoint List
What you need: A SharePoint list to store your tasks.
Easy way: Use the built-in “Issue Tracker” template:
- Go to your SharePoint site
- Click “New” → “List”
- Choose “Issue tracker” template
- Give it a name like “Volunteering Tasks”
Step 4: Get Your Site ID
What this is: Every SharePoint site has a unique ID that Microsoft Graph uses to find it.
Two ways to find it:
- Easy way: Use Graph Explorer - Sign in and try this query: https://graph.microsoft.com/v1.0/sites/yourdomain.sharepoint.com:/sites/yoursite
 
- Sign in and try this query: 
- PowerShell way: Use the PnP PowerShell script 
What it looks like: contoso.sharepoint.com,c07cb069-eed7-4ccc-9a81-bf3856c75450,8d9eceb9-4576-4cd2-8d74-30ff3c87ec6d
Step 5: Get Your List ID
What this is: Each SharePoint list has its own unique ID.
How to find it:
- Use PnP PowerShell: Get-PnPList -Identity "Your List Name"
- Look for the ID field (it’s a GUID like: 265c5ba9-3569-48c4-bc83-4ab76b286e97)
Important: Once you have these IDs, you can build the Microsoft Graph URL:
https://graph.microsoft.com/v1.0/sites/{siteID}/lists/{listID}/items
Use Cases: What You Can Build
Once your agent is set up, you can:
- View Tasks: Ask Copilot “Show me my assigned volunteering tasks”
- Create Tasks: Say “Create a new volunteering task for helping at the food bank”
This blog post will show you how to create an agent that can both read existing SharePoint list items and create new ones.
Implementation Guide
Step 1: Create Your Agent with M365 Agents Toolkit
What this does: The toolkit creates a starter template with all the basic files you need.
- Start with M365 Agents Toolkit- Pick the option Create Agent with TypeSpec.
 
 
- Pick the option 
The scaffolded solution includes these important files:

- main.tsp: This is like the “brain” of your agent - it defines what the agent can do
- actions.tsp: This defines how your agent talks to SharePoint/Microsoft Graph
- env file: This stores IDs and configurations safely
Step 2: Configure the Main Agent File (main.tsp)
Replace the generated main.tsp with this configuration:
Amend the instructions, agent name and other details as appropriate.
import "@typespec/http";
import "@typespec/openapi3";
import "@microsoft/typespec-m365-copilot";
import "./actions.tsp";
using TypeSpec.Http;
using TypeSpec.M365.Copilot.Agents;
using TypeSpec.M365.Copilot.Actions;
@agent(
  "TasksItem",
  "Declarative agent created with Microsoft 365 Agents Toolkit and TypeSpec for Microsoft 365 Copilot."
)
@instructions("""
## Creating a New Volunteering Task
- When a user requests to create a new volunteering task, first call the function `getUserInformation` to get the user id and assign it to `AssignedTo0LookupId` before calling the function `createListItem` to create a new task in the SharePoint list. Also provide some examples by searching for "volunteering opportunities" using the function `searchVolunteerOpportunities`.
- After has an interest in an opportunity, use the information from their response to create a title and description for the task.
- The user should then be prompted to provide the following information:
  - Start Date
- The other fields should be set to default values:
  - Progress: "In Progress"
  - Priority: "Low"
  - Due Date: Start Date plus 1 day
  - AssignedTo0LookupId: The user id retrieved from `getUserInformation` that matches the logged-in user email address or name
- Pass the details provided by the user in the following JSON format. Within the AssignedTo0 used the claims id of the logged in user in the following format:
{
  "fields": {
    "Title": "Task Title",
    "Description": "Task Description", # Determined from the task type selected by the user
    "Progress": "In Progress", # Determined from the task type selected by the user
    "Priority": "Low",
    "StartDate":"2025-04-15", # User provided date
    "DueDate":"2025-04-16", # Start Date + 1 day
    "AssignedTo0LookupId": "{userId}", # User id retrieved from getUserInformation
    "Notes": "Task Notes" # Determined from the task type selected by the user
  }
}
## Listing Assigned Volunteering Tasks
- When a user requests to see assigned volunteering tasks, first call the function `getUserInformation` with parameter `expand=fields` to retrieve the user id of the logged-in user using the user name.
- Then assign it to `userId` replacing the  filter parameter with fields/AssignedTo0LookupId eq {userId} and call the function `getListItems` with parameter `expand=fields` to get the list of tasks assigned to the user
- List the tasks in a user-friendly format showing the following details:
  - Title
  - Description
  - Progress
  - Priority
  - Start Date
  - Due Date
  - Notes
- If no tasks are found, respond with "You have no volunteering tasks assigned to you."
""")
// Uncomment this part to add a conversation starter to the agent.
// This will be shown to the user when the agent is first created.
// Uncomment this part to add a conversation starter to the agent.
// This will be shown to the user when the agent is first created.
 @conversationStarter(#{
    title: "Get Volunteering tasks assigned to me",
    text: "Get the latest volunteering tasks assigned to you"
  })
namespace TasksItem {  
  @service
  @server(global.MsGraphAPI.SERVER_URL)
  @actions(global.MsGraphAPI.ACTIONS_METADATA)
  @useAuth(global.MsGraphAPI.TaskItemsAgentAuth)
  namespace MsGraphAPIActions {
   op getListItems is global.MsGraphAPI.getListItems; 
   op getUserInformation is global.MsGraphAPI.getUserInformation;
   op createListItem is global.MsGraphAPI.createListItem;
  }
}
Step 3: Set Up the Actions File (actions.tsp)
Key Configuration: Replace these values with yours:
const SITE_ID = "your-site-id-here";
const LIST_ID = "your-list-id-here";  
Step 4: Configure Authentication (OAuth)
Add OAuth configuration to your actions.tsp file. Replace YOUR-TENANT-ID with your actual Tenant ID:
model TaskItemsAgentAuth is OAuth2Auth<[{
  type: OAuth2FlowType.authorizationCode;
  authorizationUrl: "https://login.microsoftonline.com/YOUR-TENANT-ID/oauth2/v2.0/authorize";
  tokenUrl: "https://login.microsoftonline.com/YOUR-TENANT-ID/oauth2/v2.0/token";  
  refreshUrl: "https://login.microsoftonline.com/YOUR-TENANT-ID/oauth2/v2.0/refresh";
  scopes: ["User.Read", "Directory.Read.All", "SharePoint.ReadWrite.All"];
}]> { }
Required scopes:
- User.Read: Basic user profile access
- Directory.Read.All: User lookup functionality
- SharePoint.ReadWrite.All: Read/write SharePoint lists
Environment setup: Add this to your .env file:
TASKITEMSAGENTAUTH_REGISTRATION_ID="your-registration-id-here"

Full code for actions.tsp, example
import "@typespec/http";
import "@microsoft/typespec-m365-copilot";
using TypeSpec.Http;
using TypeSpec.M365.Copilot.Actions;
@service
@server(MsGraphAPI.SERVER_URL)
@actions(MsGraphAPI.ACTIONS_METADATA)
namespace MsGraphAPI {
  const ACTIONS_METADATA = #{
    nameForHuman: "Volunteering Tracker",
    descriptionForHuman: "Helps users retrieve volunteering opportunities assigned to them.",
    descriptionForModel: "Helps users retrieve volunteering opportunities assigned to them.",
  };
  /**
   * The base URL for the Microsoft Graph API.
   */
  const SERVER_URL = "https://graph.microsoft.com/v1.0";  
  const SITE_ID = "reshmee.sharepoint.com,541d6ad1-322f-419d-8575-5309a29c3cf8,6b96c932-79a5-4f2a-91dc-41689290ebe7";
  const LIST_ID = "cb530b8f-5af3-44ad-8270-ec89d0357ae5";
  /**
   * Get all tasks in a list
   * @param filter The filter to apply to the list items.
   * @param expand The properties to expand in the response.
   */
  @route("/sites/${SITE_ID}/lists/${LIST_ID}/items")
  @get op getListItems(
    @query filter: string,
    @query expand?: string = "fields",
  ): ListItemsResponse;
  
  /**
   * Create a new item in the specified list.
   * @param input The fields for the new list item.
   */
  @route("/sites/${SITE_ID}/lists/${LIST_ID}/items")
  @post op createListItem(
    @body input: CreateListItemRequest
  ): CreateListItemResponse;
  
  /**
   * Get user information from the "User Information List"
   * @param siteId The ID of the site. If you don't know the 'id'
   */
  @route("/sites/${SITE_ID}/lists/User Information List/items")
  @get op getUserInformation(
    @query expand?: string = "fields",
    @header Prefer?: string
  ): UserInformationListResponse;
  model ListItemsResponse {
    @doc("Metadata context URL")
    "@odata.context": string;
    value: ListItem[];
  }
  model ListItem {
    id: string;
    createdDateTime: string;
    lastModifiedDateTime: string;
    webUrl: string;
    createdBy: CreatedBy;
    lastModifiedBy: CreatedBy;
    fields: ListItemFields;
  }
  model CreatedBy {
    user: UserInfo;
  }
  model UserInfo {
    email?: string;
    id?: string;
    displayName?: string;
  }
  model ListItemFields {
    Title?: string;
    Description?: string;
    Progress?: string;
    Priority?: string;
    StartDate?: string;
    DueDate?: string;
    AssignedTo0LookupId?: string;
    Created?: string;
    Modified?: string;
    AuthorLookupId?: string;
    EditorLookupId?: string;
    Attachments?: boolean;
    Notes?: string;
  }
  model CreateListItemRequest {
    fields: ListItemFields;
  }
  model CreateListItemResponse {
    id: string;
    fields: ListItemFields;
  }
  model UserInformationListResponse {
    "@odata.context": string;
    value: UserInformationListItem[];
  }
  model UserInformationListItem {
    id: string;
    createdDateTime: string;
    lastModifiedDateTime: string;
    webUrl: string;
    createdBy: SimpleUser;
    lastModifiedBy: SimpleUser;
    fields: UserInformationFields;
  }
  model SimpleUser {
    user: SimpleUserInfo;
  }
  model SimpleUserInfo {
    displayName?: string;
  }
  model UserInformationFields {
    id?: string;
    EMail?: string;
  }
  model TaskItemsAgentAuth is OAuth2Auth<[{
    type: OAuth2FlowType.authorizationCode;
    authorizationUrl: "https://login.microsoftonline.com/39f0e149-6ce0-483e-8acf-b6faa229a203/oauth2/v2.0/authorize";
    tokenUrl: "https://login.microsoftonline.com/39f0e149-6ce0-483e-8acf-b6faa229a203/oauth2/v2.0/token";
    refreshUrl: "https://login.microsoftonline.com/39f0e149-6ce0-483e-8acf-b6faa229a203/oauth2/v2.0/refresh";
    scopes: ["User.Read", "Directory.Read.All", "SharePoint.ReadWrite.All"];
  }]> { }
}
env file
Add the key TASKITEMSAGENTAUTH_REGISTRATION_ID to the env file.
TASKITEMSAGENTAUTH_REGISTRATION_ID="MzlmMGUxNDktNmNlMC00ODNlLThhY2YtYjZmYWEyMjlhMjAzIyNjNTFhMTlmZS0wNGExLTQ4MWMtYjY4NC05ZmE3ODFjZWNjOTY="
Handle Authentication
If the above key is not defined in the env file , you’ll be prompted to create a registration ID for it if not already defined in the environment as above.
Limitations & Gotchas
Summary of Key Limitations:
- Cannot reference environment variables directly in actions file
- No clear way to reference external instructions files; some characters (e.g., \) are not allowed
Details:
- Environment Variables - You can’t directly reference environment variables in the actions file similar to directly within the .yaml file for the API Specification. The generated .yaml and .json are read only and can’t be modified post generation.
 
- Instructions File - I was unable to find a way to reference an external instructions file, so I had to hard-code the instructions directly into the main.tsp file. Additionally, certain characters—such as the ‘' escape character—are not allowed, which makes it difficult to include more complex or lengthy instructions.
 
Troubleshooting: Common Issues and Solutions
Problem: Build Errors in TypeSpec
What this means: Sometimes your TypeSpec code has syntax errors (like typos or missing commas).
How to find the error: If errors aren’t clear in the terminal, run this command to see detailed error messages:
npx --package=@typespec/compiler tsp compile ./main.tsp --config ./tspconfig.yaml
Common fixes:
- Check for typos in your Tenant ID, Site ID, or List ID
- Make sure all quotes and brackets match up
- Verify your OAuth configuration has the correct URLs
Problem: Authentication Fails
What this means: Your agent can’t access SharePoint because of permission issues.
How to fix:
- Double-check your app registration has the right permissions
- Make sure you granted admin consent for the permissions
- Verify your Tenant ID is correct in the OAuth configuration
Problem: Can’t Find Site or List
What this means: Your Site ID or List ID might be wrong.
How to fix:
- Re-run the Site ID query in Graph Explorer
- Check that your list name is exactly correct (case-sensitive)
- Make sure you’re using the GUID for the list ID, not the display name
Output: Generated Files
After provisioning, you’ll get the JSON and YAML files:

Please note that the generated files are read only and can’t be modified.
Testing
Conclusion
Building a declarative agent that connects to SharePoint might seem complex at first, but TypeSpec makes it much easier than writing raw configuration files.
What you’ve learned:
- How to set up authentication for Microsoft Graph
- How to create an agent that can read and write SharePoint data
- How to use TypeSpec to define your agent’s capabilities
- Common troubleshooting steps when things go wrong
Next steps:
- Try modifying the agent instructions for your specific use case
- Explore adding more SharePoint list operations
- Consider connecting to other Microsoft Graph APIs like Teams or Outlook
Remember: Start small, test frequently, and don’t be afraid to experiment. The declarative agent ecosystem is evolving rapidly, and the community is very helpful for solving specific challenges.
References
- Microsoft 365 Copilot Pro Dev Samples
- Copilot Dev camp - Extend M365 Copilot
- TypeSpec 101: Building Microsoft 365 Declarative Agents
- Integrating Microsoft Copilot Declarative Agents with Azure AI Search - A TypeSpec Approach
- TypeSpec
- Tasks Agent using TypeSpec for Microsoft 365 Copilot that connects to the Microsoft Graph APIs
- M365 Agents Toolkit
- TypeSpec
- Ms Graph API
- Oauth
- SharePoint
- Declarative Agents
- Agents
- M365 Copilot Extensibility
