Using MCP Resources in Copilot Studio
Resources in MCP: How Copilot Studio Uses Them
If you’re building MCP (Model Context Protocol) servers for Copilot Studio, here’s something important to understand: Copilot Studio’s orchestrator always lists MCP resources through tools, never directly.
This is an architectural choice in Copilot Studio’s implementation that makes sense for enterprise environments with large-scale resource catalogs.
How Copilot Studio Lists Resources
Here’s the flow in Copilot Studio:
- Discovery through tools: The agent calls 
tools/callto invoke a tool that returns resources - Tool returns resources: The tool can return either:
- Resource links (
resource_link): References that the agent can read viaresources/readrequests - Embedded resources (
resource): Full content delivered immediately in the tool response 
 - Resource links (
 - Reading resources (for resource links): The agent can send 
resources/readrequests to fetch resource content - Response generation: The agent uses the retrieved information to generate its response
 
This design enables scalability—imagine a documentation system with 10,000 articles. The agent doesn’t enumerate all 10,000; it searches for why do penguins waddle and gets back 5 relevant references.
Note: The MCP protocol itself supports direct resource enumeration through
resources/listand clients can call this directly. However, Copilot Studio’s orchestrator architecture is designed to use tool-based discovery for resource access. Other MCP clients may implement resource access differently.
The Biological Species Sample
The new search-species-resources-typescript sample demonstrates this pattern with a practical example. It provides information about animal species through search-based discovery.
What’s in the sample:
- 5 species (African Elephant, Monarch Butterfly, Great White Shark, Red Panda, Blue Whale)
 - 8 total resources (5 text overviews + 3 images)
 - Two tools: 
searchSpeciesData(fuzzy search) andlistSpecies(simple listing) 
How It Works
1. Define Your Resources
Resources are dynamically generated from a species database:
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
// Species data with details
export const SPECIES_DATA: Species[] = [
  {
    id: "monarch-butterfly",
    commonName: "Monarch Butterfly",
    scientificName: "Danaus plexippus",
    description: "Famous for its distinctive orange and black wing pattern...",
    habitat: "North America, with migration routes...",
    diet: "Larvae feed on milkweed; adults feed on nectar",
    conservationStatus: "Vulnerable",
    interestingFacts: [...],
    tags: ["insect", "butterfly", "migration"],
    image: encodeImage("butterfly.png")
  },
  // ... more species
];
// Resources generated from species data
export const RESOURCES: SpeciesResource[] = SPECIES_DATA.flatMap(species => {
  const resources = [
    {
      uri: `species:///${species.id}/info`,
      name: `${species.commonName} - Species Overview`,
      description: `Detailed information about the ${species.commonName}`,
      mimeType: "text/plain",
      speciesId: species.id,
      resourceType: "text"
    }
  ];
  // Add image resource if available
  if (species.image) {
    resources.push({
      uri: `species:///${species.id}/image`,
      name: `${species.commonName} - Photo`,
      mimeType: "image/png",
      speciesId: species.id,
      resourceType: "image"
    });
  }
  return resources;
});
This generates 8 resources from 5 species (5 text overviews + 3 images).
2. Implement Search Tool
The searchSpeciesData tool uses Fuse.js for fuzzy matching and returns resource_link references (rather than embedded resources).
When Copilot Studio calls your tool (via tools/call request), your handler responds with resource links:
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
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "searchSpeciesData") {
    const searchResults = fuse.search(searchTerms);
    const topResults = searchResults.slice(0, 5);
    // Return resource references, not full content
    const content = [
      {
        type: "text",
        text: `Found ${topResults.length} resources matching "${searchTerms}"`
      }
    ];
    topResults.forEach(result => {
      content.push({
        type: "resource_link",
        uri: result.item.uri,
        name: result.item.name,
        description: result.item.description,
        mimeType: result.item.mimeType,
        annotations: {
          audience: ["assistant"],
          priority: 0.8
        }
      });
    });
    return { content };
  }
});
3. Handle Resource Reads
When the agent sends resources/read requests, your server provides the full content:
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
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const uri = request.params.uri;
  const resource = RESOURCES.find(r => r.uri === uri);
  const species = SPECIES_DATA.find(s => s.id === resource.speciesId);
  if (resource.resourceType === 'text') {
    return {
      contents: [{
        uri,
        mimeType: "text/plain",
        text: formatSpeciesText(species)
      }]
    };
  }
  if (resource.resourceType === 'image') {
    return {
      contents: [{
        uri,
        mimeType: "image/png",
        blob: species.image  // Base64-encoded PNG
      }]
    };
  }
});
Quickstart with Copilot Studio
To get started quickly:
- Clone the repository
1 2
git clone https://github.com/microsoft/CopilotStudioSamples.git cd CopilotStudioSamples/MCPSamples/search-species-resources-typescript - Install dependencies
1
npm install - Start the server
1
npm run dev
 Create a public dev tunnel for port 3000
- Configure in Copilot Studio under Tools → + Add a tool → + New Tool → Model Context Protocol
 
Important URL Format: Use
https://abc123-3000.devtunnels.ms/mcp(port in hostname with hyphen), nothttps://abc123.devtunnels.ms:3000/mcp. The wrong format will cause connection failures.
The sample includes detailed instructions for dev tunnel setup and deployment options in the README.
How It Works in Copilot Studio
Let’s walk through a real example of how the agent uses resources to answer questions.
User asks: “Tell me about the Blue Whale”
The agent:
- Calls 
searchSpeciesData("blue whale")tool - Receives resource links for two resources: Blue Whale text overview and photo
 - Reads both resources via 
resources/readrequests - Generates a response combining information from the text resource and displays the image
 
The response includes details about habitat, diet, conservation status, and interesting facts, along with a photo of the blue whale.
 Agent provides comprehensive information from text resource with embedded image from image resource
User asks: “What does the Red Panda look like?”
Following the same pattern:
- Agent calls 
searchSpeciesData("red panda") - Receives resource links for both text and image resources
 - Selects and reads resources via 
resources/read - Provides a detailed visual description based on the image that was returned from the server
 
 Agent selects the appropriate resource (image) from the links and provides a detailed description
Key Takeaways
- Resources need tool-based discovery: Copilot Studio won’t enumerate resources directly. Tools provide filtered references or content
 - Tools can return references or content: Use 
resource_linkfor lazy loading or embeddedresourcefor immediate delivery - The agent uses resources to inform responses: It determines which resource content is relevant to answering the user’s question
 - The pattern scales: Works for 5 resources or 50,000
 
Important Note: Resource Pre-Registration Not Required
If you check Copilot Studio’s UI under Tools → Resources, you might see only 5 resources listed, or even an error message about limiting resources. Don’t worry—this isn’t a problem when using resource_link in your tools.
 Copilot Studio UI shows a limitation, but this doesn’t affect resource_link functionality
Here’s why: when your tool returns a resource_link, the agent can request that resource via resources/read even if it was never listed in a resources/list response. According to the MCP specification:
Resource links returned by tools are not guaranteed to appear in the results of a resources/list request.
This means you can:
- Generate resource URIs dynamically based on search results
 - Return links to resources that don’t exist until they’re requested
 - Scale to large numbers of potential resources without listing them all
 
Try It Yourself
Clone the search-species-resources-typescript sample and experiment with your Copilot Studio agent. Try:
Have you used MCP resources for Copilot Studio? What discovery patterns are you using? Share your experiences in the comments!