Uniform Resource Identifiers (URIs) serve as the addressing system for the Model Context Protocol. While the Client-Host-Server topology creates the communication pipeline, URIs provide the specific coordinates required to locate and retrieve data. When an LLM or client application needs to access a specific piece of context, such as a file, a row in a database, or a system log, it relies on a structured string to uniquely identify that item.In MCP, URIs are not merely static addresses. They function alongside URI Templates, which allow servers to advertise classes of available resources rather than listing every single item individually. This distinction is critical for performance. A server wrapping a database with millions of rows cannot send a list of millions of URIs to the client during the initialization handshake. Instead, it sends a template showing the pattern for accessing those rows.The Anatomy of an MCP URIMCP URIs follow the standard syntax defined in RFC 3986. Understanding this structure is necessary for designing intuitive and collision-resistant resource names. A typical URI consists of a scheme, an optional authority, and a path.$$URI = scheme ":" ["//" authority] path ["?" query] ["#" fragment]$$In the context of MCP servers, the components function as follows:Scheme: Identifies the protocol or the specific data source type. While file, http, and https are common, MCP servers often define custom schemes like linear, postgres, or app-logs to namespace their resources.Authority: Often omitted in local MCP servers, but can be used to distinguish between different instances or hostnames if the server manages connection to multiple external systems.Path: The hierarchical identifier for the specific resource.The following diagram outlines how an MCP server parses these components to route a request to the correct internal handler.digraph G { rankdir=TB; node [fontname="Helvetica", shape=box, style=filled, color="#dee2e6", fillcolor="#f8f9fa"]; edge [color="#adb5bd"]; Request [label="Client Request\nuri='memo://notes/daily/2023-10-27'", fillcolor="#d0bfff"]; subgraph cluster_parsing { label="URI Parsing"; style=dashed; color="#868e96"; Scheme [label="Scheme: 'memo'", fillcolor="#a5d8ff"]; Path [label="Path: '/notes/daily/2023-10-27'", fillcolor="#a5d8ff"]; } Router [label="Resource Router", fillcolor="#ffc9c9"]; Handler [label="Function: read_note(date='2023-10-27')", fillcolor="#b2f2bb"]; Request -> Scheme; Request -> Path; Scheme -> Router; Path -> Router; Router -> Handler [label="Matches Pattern"]; }Flow of a URI request through the server parsing logic to a specific function handler.URI Templates and Dynamic ResourcesA static resource represents a single, unchanging URI, such as config://application/settings. This works well for singular assets. However, most applications deal with dynamic collections of data. To handle this, MCP utilizes URI Templates (RFC 6570).A template includes variables enclosed in braces, such as file:///{path}. When a server registers this resource, it tells the client: "I can handle any URI that matches this pattern."When a client requests file:///Users/jdoe/project/readme.md, the MCP SDK automatically performs pattern matching. It extracts /Users/jdoe/project/readme.md from the URI and passes it as an argument to your registered function. This separation of the interface (the template) from the implementation (the function) allows for scalable resource exposure.Defining Patterns in PythonWhen using the Python SDK, you define these patterns using decorators. The framework handles the routing logic.from mcp.server.fastmcp import FastMCP mcp = FastMCP("FileSystemServer") # Static Resource: No variables @mcp.resource("app://metadata") def get_metadata() -> str: return "Version 1.0.0 - File Reader System" # Dynamic Resource: Uses a template variable {path} @mcp.resource("file:///{path}") def read_file(path: str) -> str: """Reads a file from the local filesystem.""" try: # In a real implementation, validate 'path' to prevent directory traversal with open(path, "r") as f: return f.read() except FileNotFoundError: return "Error: File not found."In this example, the SDK registers the pattern file:///{path}. If the LLM generates a request for file:///etc/hosts, the variable path receives the value /etc/hosts.Strategy for Designing SchemesSelecting the correct scheme is an architectural decision that affects how the LLM interprets the data context. A clear, semantic scheme helps the model understand the nature of the data before it even reads the content.Use Standard Schemes for Standard Data: If you are serving actual files from a disk, use file://. This signals to the client that the path likely conforms to filesystem conventions.Namespace Custom Data: If you are building a server for a specific application, use that application's name as the scheme. For example, slack://channels/{id} is more descriptive than app://slack/channels/{id}.Avoid http/https for Proxies: Unless your resource is literally a web page being fetched, avoid http or https. If your server fetches data from a REST API and returns a JSON object, define a custom scheme like api://. Using https might confuse the model into thinking it can browse the web directly, rather than interacting with your specific API wrapper.Pattern Matching LogicThe matching process within an MCP server follows a specific precedence. Understanding this helps avoid routing conflicts, especially when you have overlapping patterns.Generally, exact matches take precedence over template matches. If you define both a generic file reader and a specific handler for a readme file, the specific handler should trigger when that exact URI is requested.Consider the routing logic for a database server exposing user records:digraph G { rankdir=LR; node [fontname="Helvetica", shape=ellipse, style=filled, color="#dee2e6", fillcolor="#e9ecef"]; edge [color="#868e96"]; Input [label="Incoming URI", shape=box, fillcolor="#d0bfff"]; CheckStatic [label="Is Exact Match?", shape=diamond, fillcolor="#ffec99"]; CheckTemplate [label="Matches Template?", shape=diamond, fillcolor="#ffec99"]; StaticEndpoint [label="Execute Static Handler", shape=box, fillcolor="#b2f2bb"]; DynamicEndpoint [label="Extract Vars & Execute", shape=box, fillcolor="#b2f2bb"]; Error [label="Return 404/Error", shape=box, fillcolor="#ffc9c9"]; Input -> CheckStatic; CheckStatic -> StaticEndpoint [label="Yes"]; CheckStatic -> CheckTemplate [label="No"]; CheckTemplate -> DynamicEndpoint [label="Yes"]; CheckTemplate -> Error [label="No"]; }Decision tree for routing incoming URIs to the appropriate handler.Handling Query ParametersMCP URIs support query parameters, which are useful for filtering or modifying the resource representation without changing the resource identity. For instance, you might want to retrieve only the last 50 lines of a log file.The URI might look like logs://system/error.log?lines=50.In your resource handler, parsing these parameters depends on the specific SDK implementation details, but generally, the full URI is available for inspection. When defining patterns, it is common practice to define the path in the template (logs://{type}/{name}) and treat the query string as optional metadata handled inside the function logic.Best Practices for URI DesignStability: URIs should remain stable. If a resource's URI changes, the LLM's context window might contain outdated references, leading to hallucinations or errors.Readability: While primarily for machine consumption, readable URIs help during debugging. users://123/profile is superior to data://obj?id=5921&type=u.Security: Never blindly trust the input derived from a URI template. Just as with web development, inputs from the "path" variable must be sanitized to prevent directory traversal attacks or SQL injection if the ID is passed directly to a database query.By rigorously defining your URI schemes and patterns, you create a predictable map of your data. This allows the LLM to act as an intelligent navigator, requesting exactly the context it needs to answer user queries.