Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Connecting MCP to Local Data Sources

Welcome back! In our previous lesson, you successfully built and tested your first MCP server. By serving a static, hard-coded "Hello, World!" resource, you gained hands-on experience with the MCP Node.js SDK and the fundamental mechanics of the protocol.

Today, we will take the crucial next step: making your server dynamic. The true power of MCP lies in its ability to provide models with access to live, changing data. We will modify the server you built to retrieve its context not from a hard-coded string, but from a local data source. We'll start with a simple text file and then explore how the same principles apply to more complex sources like a database. This lesson directly moves us towards building practical AI systems that can reason over proprietary and real-time information.

By the end of this session, your MCP server will be able to serve dynamic content from your local machine, a foundational capability for the advanced AI agent and RAG systems you aim to build.

From Static to Dynamic: The Core Idea

In the last lesson, our resource handler was simple: it returned a fixed string.

// Previous implementation
server.resource("hello-world", "hello://world", async (uri) => {
  return {
    contents: [{ text: "Hello, World! This is my first MCP resource." }],
  };
});

To make this dynamic, we only need to change the body of this handler function. Instead of returning a static value, it will now perform an I/O operation—reading from a file—and return the result of that operation. This small change has massive implications, as it unlocks the ability for an LLM to access any data your server can reach.

The architectural diagrams we've seen illustrate this perfectly. The MCP Server acts as a secure and standardized bridge between the AI Host (like an IDE or an LLM) and various data sources.

As this diagram shows, the MCP Server is the intermediary connecting the Host application to local data sources like files and databases, or even remote services.

Part 1: Connecting to a Local File

Let's modify the Node.js project from our last lesson. We'll use Node.js's built-in fs/promises module to read from a local file. This should feel familiar given your JavaScript background.

First, create a new file named context.txt in the root of your project directory and add some text to it. For example:

This is dynamic content being served from a local file.
The current time is: [You can manually update this to see changes]
This file could contain project notes, logs, or any other relevant information.

Next, let's update your src/index.ts to read from this file.

  1. Import the fs/promises module at the top of your file:
    import { promises as fs } from 'fs';
    import * as path from 'path';
    
  2. Modify the resource handler to read context.txt:
    // In src/index.ts
    
    // ... imports
    
    // McpServer initialization ...
    
    server.resource(
      "local-file-context",
      "file:///context.txt", // A more descriptive URI
      async (uri) => {
        try {
          // Construct the full path to the file
          const filePath = path.join(process.cwd(), 'context.txt');
          const fileContent = await fs.readFile(filePath, 'utf-8');
          
          return {
            contents: [{ text: fileContent }],
          };
        } catch (error) {
          console.error("Failed to read context file:", error);
          // In a real application, you'd return a proper MCP error response
          throw new Error("Could not read the resource.");
        }
      }
    );
    
    // ... transport and server.run() logic
    

Now, run your server and test it with the MCP Inspector (npm run inspect) just as you did in the previous lesson. When you list and then read the local-file-context resource, you should see the content of your context.txt file. If you change the file's content and re-read the resource (you may need to restart the server depending on your setup), the new content will be served. You have now created a dynamic context provider!

This pattern of reading from a local data source inside a handler is universal across all MCP server implementations. To see this in action in a different language, consider this short clip showing a Python implementation.

Model Context Protocol (MCP) Explained in 20 Minutes

This video demonstrates the same concept using the Python MCP SDK. Notice how the resource handler function is decorated and simply contains logic to open and read a local file.

Watch the section from this segment. The speaker defines three different resources, each reading from a local file (two .md templates and one .csv file). The core logic is the same: the handler function executes file I/O to provide the context.

Part 2: A Look at a Go Implementation

Given your expertise in Go, it's valuable to see how this pattern translates to a Go environment. The principles are identical: define a handler that interacts with a data source.

The following video from a GopherCon talk demonstrates building an MCP server in Go to provide context about conference sessions. The speaker has a pre-scraped, offline copy of the agenda, which serves as the local data source.

GopherCon 2025: Next-Gen AI Tooling with MCP Servers in Go - Katie Hockman

This talk by Katie Hockman is an excellent practical demonstration of building an MCP server in Go. We'll look at how she structures the data access.

First, watch this clip where the speaker explains her local data source: an offline copy of the GopherCon agenda scraped into a Go struct. This is analogous to our context.txt or a local database. Next, see how she implements a list sessions tool from this segment. The tool's handler function is remarkably simple; it just returns the entire slice of session data. This is a dynamic "list all" operation. Finally, watch how a more targeted tool, get session detail, is implemented from this part. This tool accepts a sessionID as a parameter, looks up the specific session in the local data, and returns only that single result. This is a perfect example of parameterized dynamic retrieval.

This Go example powerfully illustrates how the same fundamental MCP pattern you just implemented in Node.js is applied in a different, statically-typed language to build tools that provide targeted, dynamic access to a local dataset.

Part 3: Scaling Up to a Database

Serving from a text file is a great start, but most enterprise applications source their data from databases. Connecting an MCP server to a database follows the same pattern, but the implementation inside the handler becomes more complex, involving database connections, query execution, and object-relational mapping (ORM).

We won't build a full database-connected server in this lesson, but it's crucial to understand what one looks like. Microsoft has provided an excellent, production-grade example of an MCP server that connects to a PostgreSQL database for a retail analytics use case.

GitHub - microsoft/MCP-Server-and-PostgreSQL-Sample-Retail: A Model Context Protocol (MCP) server that provides comprehensive customer sales database access for Zava Retail DIY Business. This server enables AI assistants to query and analyze retail sales data through a secure, schema-aware interface. · GitHub

This GitHub repository is a complete, well-documented project that serves as a fantastic reference for a real-world, database-backed MCP server. Skim the README to understand its architecture and capabilities.

Focus on these key parts of the README.md file: Introduction: Read the introduction to understand the project's goal: providing AI assistants with secure access to a PostgreSQL database. Tools: Look at the execute_sales_query tool. This is the heart of the dynamic retrieval. The MCP tool's handler takes a SQL query as a parameter, executes it against the database, and returns the result. This is the database equivalent of our file-reading handler. Architecture: Review the Database Integration section and the Project Structure. Notice the sales_analysis_postgres.py file—this is the dedicated integration layer that manages the database connection and logic, cleanly separating it from the main server implementation. This is a key design pattern for complex servers.

This example from Microsoft demonstrates the endgame. While our fs.readFile is simple, this project's execute_sales_query tool is its direct, enterprise-grade equivalent. The principle is the same: the handler function is the gateway to the data source.

Conclusion

In this lesson, you transformed your static MCP server into a dynamic one. This is a fundamental leap in capability. You now have a server that can provide an LLM with information from your local filesystem, and you understand how this concept scales to more powerful data backends like databases.

Key Takeaways:

  • Dynamic Handlers: The core of a dynamic MCP server is the resource or tool handler, which performs I/O to fetch data from a source (file, database, API) at request time.
  • Language-Agnostic Pattern: You've seen that whether you're using Node.js, Python, or Go, the pattern remains the same: define a handler function that reads from a data source and returns the content in the format MCP expects.
  • Scaling Complexity: The complexity of the handler scales with the data source. A simple file read is trivial, while a database connection requires managing connections, executing queries, and handling security considerations like in the PostgreSQL example.

You now have a working, dynamic MCP server. But how does an AI application actually use it? In our next lesson, we will answer that by configuring a host application—like VS Code—to connect to your server, completing the loop from data source to LLM.

Can't find a good explanation? Sign up and we'll make it for you

Sign up