Skip to content

Latest commit

 

History

History
364 lines (256 loc) · 12.7 KB

guide.md

File metadata and controls

364 lines (256 loc) · 12.7 KB

Workshop Guide

Whalecome to the workshop! In this workshop, you will be gaining experience working with Model Context Protocol (MCP) servers.

Note: Before starting this, ensure you have the required programs and completed the setup in the README.

Step 1 - launching the barebones chatbot

  1. In VS Code, open a terminal and navigate into the chatbot directory.

    cd chatbot
  2. Start the app by running the following command:

    python main.py

    After a second, you see a prompt that looks like the following:

    You: 
  3. Type in a message, such as "Why is the sky blue?"

    You: Why is the sky blue?

    Your answer will vary, but might look similar to the following:

    2025-02-12 05:42:23,037 - INFO - Assistant: The sky appears blue due to a phenomenon called Rayleigh scattering. When sunlight enters Earth's atmosphere, it is made up of different colors, each with its own wavelength. Blue light has a shorter wavelength than other colors in the visible spectrum.
    
    As sunlight passes through the atmosphere, the shorter blue wavelengths are scattered in all directions by the gases and particles in the air. This scattering causes the sky to look blue to our eyes during the day. 
    
    At sunrise and sunset, the sky can appear red or orange because the sunlight has to pass through more of the Earth's atmosphere, which scatters the shorter blue wavelengths out of the line of sight, allowing the longer red wavelengths to be more visible.
  4. Now, try asking for some real-time information:

    You: What is the current time in Washington, D.C.?

    The answer will likely vary from saying it can't get the information to maybe wanting to use an external API. Regardless, it won't be accurate.

Tangent 1 - learning about messages

Before we move on, it's important to learn a little bit about messages.

Simply put, LLMs have no memory of their own. Every request for a chat request starts from scratch and uses only what is provided.

To provide memory and context, a collection of "messages" can be sent to the LLM. There are different types of messages, including:

  • system - these messages typically instruct the LLM how to behave, the rules of engagement, etc.
  • user - these messages are typically the raw input from the user or additional context from the user
  • assistant - these messages are responses generated by the LLM

By maintaining a collection of messages, you can provide history and context to the LLM.

  1. If you run "What did I last ask?", you'll get an answer you'd expect.

    You: What did I last ask?

    And your answer might look like:

    Assistant: You asked about the current time in Washington, D.C.
    
  2. For this chatbot, if you enter a prompt of /messages, it'll simply output the messages that have been collected so far.

    You: /messages

    In the output, you'll see the original system message that instructs the bot on how to operate, the user message for the first prompt, the first response, and so on...

The reason this is important is because this sets the stage for using tools!

  1. Feel free to tweak the helpers/main_prompt.txt file to adjust how the chatbot works. You can put in words the responses must always contain, make it tell puns, or anything else!

Step 2 - adding a MCP-based tool

When working with LLMs, beyond simply sending a collection of messages, the request can send a collection of available tools the LLM can decide to use. The cool thing about tools is they enable to LLM to actually do stuff, rather than only leverage the knowledge it was trained with.

Understanding tools

The following JSON-based description would be used to describe a tool that will look up the current time:

{
  "name": "get_current_time", 
  "description": "Get current time in a specific timezones", 
  "parameters": {
    "type": "object", 
    "properties": {
      "timezone": {
        "type": "string", 
        "description": "IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'UTC' as local timezone if no timezone provided by the user."
      }
    }, 
    "required": ["timezone"]
  }
}

Note that it specifies a name, a description, and details about the parameters needed to run this tool.

The collection of tools is based to the LLM, where it can then decide if it needs to invoke the function. If it does, the response from the LLM indicates the tool to invoke and the parameters to pass to it.

{
  "role": "assistant", 
  "content": null, 
  "tool_calls": [
    {
      "id": "call_stq5unCactSxGs0BR6vJMWRQ", 
      "type": "function", 
      "function": {
        "name": "get_current_time", 
        "arguments": "{\"timezone\":\"America/New_York\"}"
      }
    }
  ]
}

In this message, you'll see the tool_calls where the LLM requested to run the get_current_time tool and the arguments. There's also a unique call id, which will be helpful when sending the response.

From there, our code needs to invoke the requests, gather the responses, and then send the message back. This time though, we have a role of tool to indicate a tool execution:

{
  "role": "tool",
  "tool_call_id": "call_stq5unCactSxGs0BR6vJMWRQ",
  "content": { 
    "timezone": "America/New_York",
    "datetime": "2025-02-12T01:36:53-05:00",
    "is_dst": false
  }
}

Now, when the LLM gets the stack of messages, it can see the original instructions, the prompts, the request to run tools, the responses from the tools, and keep iterating until it has everything that's needed.

So why MCP? And why Docker?

Because tools are often reusable! Recognizing this, Anthropic created a protocol to allow for the easy spin up and reuse of tools. The protocol defines the ability to list tools, execute tools, and more!

Traditionally, these are setup and installed natively on a machine. But, with containers, they are much more easily packaged and distributed... and run isolated from each other and the host filesystem!

Adding a time tool to our chatbot

Let's add the ability to view the current time! Fortunately, the mcp/time image provides a webapp that meets the MCP server specification to do exactly this!

  1. In the chatbot/servers_config.json, specify the following:

    {
      "mcpServers": {
        "time": {
          "command": "docker",
          "args": [
            "run",
            "--rm",
            "-i",
            "mcp/time"
          ]
        }
      }
    }
  2. Update the chatbot/.env file to include an extra variable:

    SERVER_CONFIG_FILE=servers_config.json
  3. If you have a chatbot still active, run exit to quit it. Then restart it.

    Note that the first time you run this, it may take a moment to start up since you will likely need to pull the required container images.

  4. Now, ask for the current time. This time, you'll see some extra messages (we're purposely logging the tool execution details so you can see it in action).

    You: What time is it in New York?

    And you'll likely get output similar to the following:

    2025-02-12 06:46:52,535 - INFO - Assistant: Assistant has requested to run one or more tools
    2025-02-12 06:46:52,538 - INFO - Executing get_current_time with arguments {'timezone': 'America/New_York'}...
    2025-02-12 06:46:52,542 - INFO - Tool execution provided a response of: {
    "timezone": "America/New_York",
    "datetime": "2025-02-12T01:46:52-05:00",
    "is_dst": false
    }
    2025-02-12 06:46:53,761 - INFO - Assistant: It's currently 1:46 AM on February 12th in New York. Time flies when you're having pun!
    

Add a sqlite tool to the chatbot

As another example, we have a small sqlite database at chatbot/sample-data/test.db. And we want to interact with it.

Fortunately, we can use the mcp/sqlite tool to provide the ability to interact with the database, ask it questions, make changes, and more!

  1. In the chatbot/server_config.json, add the following configuration inside the mcpServers key:

    "sqlite": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "-v",
        "sqlite-data:/mcp",
        "mcp/sqlite",
        "--db-path",
        "/mcp/test.db"
      ]
    }
  2. Restart the chatbot and now ask a question about the database.

    You: Tell me about the tables in my database

    With this query, you'll see it use the tools to execute queries that help it learn about the existing tables and their structures.

  3. Ask it to tell you about the memes in the database:

    You: Tell me about the memes in the database

    From here, you'll see it leverage what it learned earlier (due to the history/context), query the rows, and then generate a summary for you.

    For maximum effect, restart the chatbot and ask only this query. You'll see it go back and forth several times to gather more information and then finally generate the final response.

  4. Manipulate data in the database by simply asking the AI:

    You: Update the meme about Boromir to have a description of "One does not simply template"

    You'll see it generate and run a query!

  5. While still in the same chat, you can even ask it to revert the description to the previous value.

    Occasionally, it will run into an error and automatically adjust to try and fix the query.

Step 3 - creating your own tool

To keep things fairly simple, we're going to provide a basic "math" tool that does math incorrectly.

  1. In the root of your project, create a folder named custom-tool.

  2. Inside the folder, create a requirements.txt file with the following content:

    mcp
    
  3. Inside the folder, create a main.py file with the following contents:

    from mcp.server.fastmcp import FastMCP
    
    mcp = FastMCP("Demo")
    
    @mcp.tool(
        name="add",
        description="Add two numbers",
    )
    def add(a: int, b: int) -> int:
        """Add two numbers"""
        return a + b + a + b
    
    if __name__ == "__main__":
        mcp.run()
  4. Now, we can test this out by adding the following configuration to the servers_config.json:

    "custom-math": {
      "command": "python",
      "args": [
        "/workspace/custom-tool/main.py"
      ]
    }
  5. After restarting the chatbot, you should be able to ask it a question like the following:

    You: Please add 12+12

    You'll see it execute your custom tool and give you an answer of 48 (which obviously isn't right, but shows our tool is being used).

    While this shows our MCP server is being used, in order to use it, we have to have the environment fully configured. Python has to be installed, the app's dependencies, etc.

    Let's fix that by using Docker.

  6. In the custom-tool directory, create a Dockerfile with the following contents:

    FROM python:3.12
    WORKDIR /usr/local/app
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    COPY main.py .
    CMD ["python", "main.py"]
  7. Now, build your custom tool by running the following CLI command (while in the custom-tool directory):

    docker build -t custom-tool .

    It's likely that it will take a few moments to build because we're building inside a devcontainer environment.

  8. Once it's built, let's update the servers_config.json with the following configuration:

    "custom-math": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "custom-tool"
      ]
    }
  9. Now, restart the chatbot and your custom (and broken!) math should work! And now your tool is completely portable!

Now, this was obviously a silly example. But, you can use this same method to containerize any tool you want to provide to your LLM.

Additional resources

Be sure to subscribe to the Docker Labs newsletter to keep up-to-date with our latest experiments!