We now transition from architectural definitions to concrete implementation. In this section, we will construct a functional MCP server capable of reading files from a local directory and serving their content to a client. This exercise consolidates the understanding of URI schemes, resource registration, and the synchronous read patterns discussed earlier.
We will build a server named filesystem-reader that exposes a specific directory of log files. This simulates a common production scenario where an LLM needs access to server logs for error analysis without requiring direct shell access to the machine.
Ensure your development environment has the MCP Python SDK installed. We will utilize the FastMCP class, which provides a streamlined interface for defining resources and tools without boilerplate event loop management.
pip install mcp
Create a directory named mcp-file-server and add a subdirectory named logs. Populate the logs directory with two simple text files, app.log and error.log, containing dummy data. This structure serves as the target for our resource exposure.
Create a file named server.py. The entry point involves importing the necessary class and instantiating the server. We define the server name as filesystem-reader, which clients will use to identify the source of the context.
from mcp.server.fastmcp import FastMCP
import os
# Initialize the MCP server
mcp = FastMCP("filesystem-reader")
# Define the base path for our resources
# In a production environment, use an absolute path or environment variable
BASE_DIR = os.path.abspath("./logs")
The first requirement for an MCP server is to inform the client about available data. Clients discover data through the resources/list capability. We implement this by scanning our target directory and converting each file into a Resource object.
The URI is the critical identifier here. We will use a custom scheme logs:// to distinguish our internal log resources from generic file paths. This abstraction allows us to decouple the internal file system structure from the external resource identifier.
@mcp.resource("logs://{filename}")
def get_log_file(filename: str) -> str:
"""
Read a log file by its filename.
"""
# Security check: Ensure the filename does not contain path traversal characters
if ".." in filename or "/" in filename or "\\" in filename:
raise ValueError("Invalid filename: Path traversal not allowed")
file_path = os.path.join(BASE_DIR, filename)
try:
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
raise ValueError(f"Log file {filename} not found")
except Exception as e:
raise RuntimeError(f"Could not read file: {str(e)}")
In the code above, the decorator @mcp.resource serves two purposes. First, it registers the function as a handler for read requests matching the pattern logs://{filename}. Second, it implicitly sets up the listing logic. When a client requests a list of resources, the SDK uses this definition to understand available endpoints.
However, for dynamic resources like a file directory where new files might appear, we often need an explicit lister to tell the client exactly what files exist right now. The pattern matching works for reading, but listing requires iterating over the directory.
from mcp.types import Resource
@mcp.resource_listing
def list_resources() -> list[Resource]:
"""
Scan the logs directory and return a list of available log resources.
"""
resources = []
if not os.path.exists(BASE_DIR):
return []
for filename in os.listdir(BASE_DIR):
if filename.endswith(".log"):
resources.append(Resource(
uri=f"logs://{filename}",
name=f"System Log: {filename}",
description=f"Server application log file: {filename}",
mimeType="text/plain"
))
return resources
This separation of concerns is significant. The listing handler acts as a catalog, returning metadata (URI, name, MIME type). The read handler executes the I/O operation when a specific URI is requested.
The following diagram illustrates the message flow when a client interacts with these handlers.
The sequence of operations separates resource discovery from content retrieval, allowing clients to index available contexts before requesting heavy data payloads.
While raw access to files is useful, prompts allow us to package this data into actionable instructions. We can define a prompt that automatically retrieves a log file and asks the LLM to analyze it. This removes the burden of prompt engineering from the end-user.
We define a prompt named analyze-log that accepts a filename argument.
@mcp.prompt("analyze-log")
def analyze_log_prompt(filename: str) -> list:
"""
Create a prompt context to analyze a specific log file.
"""
return [
{
"role": "user",
"content": {
"type": "text",
"text": f"Please analyze the following log file for errors and warning patterns: logs://{filename}"
}
}
]
When this prompt is selected in the client, it injects a user message containing the URI. The client, recognizing the URI, will fetch the content using our previously defined resource handler and embed it into the context window before sending it to the LLM.
The FastMCP class handles the underlying JSON-RPC communication over standard input/output (stdio). To run the server, you simply invoke the script.
if __name__ == "__main__":
mcp.run()
To test this integration, you do not run this Python script directly in isolation. Instead, you configure an MCP client (like the MCP Inspector or Claude Desktop) to execute this script. The client manages the process lifecycle, sending requests to stdin and reading responses from stdout.
Exposing a file system to an LLM requires strict boundary enforcement. In our get_log_file function, we implemented a basic path traversal check (..). In a production environment, you should use more rigorous path validation:
BASE_DIR path.By following these patterns, you establish a secure channel for local data to enter the model's context, transforming static files into dynamic knowledge resources.
Was this section helpful?
FastMCP and resource definition.FastMCP, demonstrating modern Python server development practices.logs:// URI design.© 2026 ApX Machine LearningEngineered with