Difference between revisions of "Module 6"

From CSE330 Wiki
Jump to navigationJump to search
 
(143 intermediate revisions by 10 users not shown)
Line 1: Line 1:
−
In Module 4, you will learn about JavaScript, the prevailing client-side language, and AJAX, a method for performing asynchronous updates to a web page (without refreshing the page).
+
__NOTOC__
 +
In Module 6, you will learn the fundamentals of Python, a widely used scripting language, and Regular Expressions, which provide powerful tools for text processing. You will also explore the Model Context Protocol (MCP), a framework that allows software tools to communicate with large language models (LLMs).
  
−
This article contains your assignments for Module 4.
+
This article contains your assignments for Module 6.
 +
 
 +
=== Learning Goals ===
 +
* Regular Expressions
 +
* Python
 +
* MCP Fundamentals
 +
 
 +
== Using the Wiki ==
 +
{{RequiredInstructions|content=
 +
Text enclosed by <syntaxhighlight lang="bash" inline><</syntaxhighlight> and <syntaxhighlight lang="bash" inline>></syntaxhighlight> should be replaced by content unique to you.
 +
 
 +
'''Example'''
 +
 
 +
<source lang="bash">
 +
$ sudo useradd -r -m -c "<My Full Name>" <usernameHere>
 +
</source>
 +
becomes
 +
<source lang="bash">
 +
$ sudo useradd -r -m -c "Zach Cohn" zcohn
 +
</source>
 +
}}
 +
 
 +
 
 +
== Reading ==
 +
 
 +
The following articles on the online class wiki textbook contain information that will help you complete the assignments.
 +
 
 +
* [[Python]]
 +
* [[Regular Expressions]]
 +
* [[MCP Fundamentals]]
 +
* [[FAQ - Mod 6]]
  
 
== Individual Assignments ==
 
== Individual Assignments ==
  
−
=== JavaScript Calculator ===
+
=== Learn Python and Regular Expressions ===
 +
 
 +
In Module 6, you will learn a new language: Python.  Before starting this assignment, you should look over the [[Python|online textbook article about Python]].  You should be using version 3 (or greater) of Python. Additionally, you may find it helpful to look over the [[Regular Expressions|online textbook article about Regular Expressions]].
 +
 
 +
=== Write some Regular Expressions ===
 +
 
 +
Please write regular expressions that do each of the following:
 +
 
 +
# Match the substring "hello world" in a string.
 +
# Match all character strings in an input string that contain three or more consecutive vowels. The regex should match strings containing three or more consecutive vowels, regardless of whether the letters are uppercase, lowercase, or a mixture of both.
 +
# Match an input string that is '''entirely''' a flight code, of the format AA####, where '''AA''' is a two-letter uppercase airline code, and '''####''' is a three- or four-digit flight number.
 +
Examples:
 +
  Note: text in ''[[red]]'' is matched by the regular expression. Please note that each line is a '''separate''' test input string. For Regex 3, '''six''' separate test cases are given.
 +
  Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.
 +
  Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.
 +
  Regex 3:  ''[[AA312]]''
 +
            ''[[AA1298]]''
 +
            ''[[NW1234]]''
 +
            ''[[US443]]''
 +
            US31344
 +
            AA123 extratext
 +
 
 +
           
 +
 
 +
Save your regexes to text files – one each.
 +
 
 +
=== Baseball Stats Counter ===
 +
 
 +
The St. Louis Cardinals are the most legendary baseball team in the national league.  In this exercise, you will be creating a Python script that reads box scores from a file and computes the Cardinals' players' batting averages in a particular season.
 +
 
 +
==== Tips and Instructions ====
 +
 
 +
* You should write a Python script file to solve this problem.
 +
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.
 +
* You may want to create a class to hold and compute information about each player.
 +
* Your file should take one command-line argument: the path to an input file.  If no path is given, your program should print a usage message.
 +
 
 +
==== Input ====
 +
 
 +
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/
 +
 
 +
The top of the file contains a citation, and what follows is an entire season of Cardinals Baseball box scores.  Each game starts with a title; for example:
 +
 
 +
=== St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===
 +
 
 +
What follows are the performance of each player in that game.  The format of each line is as follows:
 +
 
 +
XXX batted # times with # hits and # runs
 +
 
 +
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of "at bats", the number of "hits", and the number of "runs", respectively.
 +
 
 +
==== Output ====
 +
 
 +
Your script needs to output all players' batting average across the season.  A player's batting average is that player's total hits divided by that player's total at-bats throughout the entire season.  For example, Johnny Hopp was at bat 149 times in 1940, and he had 41 hits, so his batting average was 0.275.
  
−
In Module 2, you made a calculator using PHP.  Now you will be making one using JavaScript.
+
Each line in the output should have the format:
  
−
Read the JavaScript guide first: [[Javascript and AJAX]]
+
XXX: #.###
  
−
* The web page should have two input fields and a radio button group for the operation, with the 4 basic math operations represented (add,subtract,multiply,divide).
+
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.
−
* The javascript should monitor all three fields and display the current result whenever the user changes any value in any field, without refreshing the page.
 
−
* The calculator should be completely self-contained; i.e., you should not be making any requests to any other server-side or client-side scripts or web pages after the initial page load.
 
  
−
'''Tip:''' You can embed JavaScript code into an HTML document like this:
+
The players should be sorted by batting average, with the highest batting average on top.  If two players have the exact same batting average (before rounding occurs), the order between those two players is unimportant.
  
−
<source lang="html4strict">
+
For example, the correct output for the 1940 season is:
−
<script type="text/javascript">
+
<!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 -->
−
// your code here
+
<source lang="text">
−
</script>
+
Pepper Martin: 0.316
 +
Walker Cooper: 0.316
 +
Johnny Mize: 0.314
 +
Ernie Koy: 0.310
 +
Enos Slaughter: 0.306
 +
Joe Medwick: 0.304
 +
Terry Moore: 0.304
 +
Joe Orengo: 0.286
 +
Jimmy Brown: 0.280
 +
Marty Marion: 0.279
 +
Don Gutteridge: 0.276
 +
Johnny Hopp: 0.275
 +
Creepy Crespi: 0.273
 +
Mickey Owen: 0.265
 +
Bill DeLancey: 0.250
 +
Don Padgett: 0.242
 +
Stu Martin: 0.238
 +
Eddie Lake: 0.222
 +
Hal Epps: 0.214
 +
Lon Warneke: 0.209
 +
Harry Walker: 0.185
 +
Max Lanier: 0.179
 +
Bill McGee: 0.178
 +
Carl Doyle: 0.174
 +
Mort Cooper: 0.157
 +
Clyde Shoun: 0.145
 +
Carden Gillenwater: 0.130
 +
Bob Bowman: 0.067
 
</source>
 
</source>
  
−
'''Tip:''' If your code isn't working the way you expect, use a JavaScript error console.  In Chrome, for example, press Ctrl-Alt-I (or Cmd-Option-I) to open the WebKit inspector.
+
=== Baseball Stats MCP Extension ===
 +
 
 +
====Cardinals MCP Server ====
 +
 
 +
This project builds a '''custom MCP server''' that lets you explore ''St. Louis Cardinals'' batting data from the '''1930''' and '''1940''' seasons using the text files you made in the Baseball Stats Counter section.
 +
 
 +
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.
 +
 
 +
For all of the assignment, feel free to use your favorite AI tool to help.
 +
 
 +
==== What Is an MCP Server? ====
 +
 
 +
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.   
 +
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.
 +
 
 +
Learn more: 
 +
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]
 +
 
 +
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:
 +
 
 +
* '''Prompts''': Predefined templates or instructions that guide language model interactions
 +
* '''Resources''': Structured data or content that provides additional context to the model
 +
* '''Tools''': Executable functions that allow models to perform actions or retrieve information
 +
 
 +
Each primitive can be summarized in the following control hierarchy:
 +
 
 +
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======
 +
 
 +
* '''Prompts''': In the context of this project, prompts are what the user will provide to interact with the MCP server. For example, if you want to fetch the top hitters from a particular season, you would issue a prompt like: 
 +
  <pre>"Show Johnny Mize’s batting averages for 1930 and 1940."</pre> 
 +
 
 +
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.
 +
 
 +
* '''Resources''': These are the data files, such as <code>cardinals-1930.txt</code> and <code>cardinals-1940.txt</code>, that your MCP server uses to provide additional context. These text files are '''resources''' that store structured data (player stats like batting averages) which the server can access to provide meaningful results when the user queries it.
 +
 
 +
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:
 +
** <code>get_player_stats</code>: Retrieve and show batting averages for a player.
 +
** <code>get_team_roster</code>: List all players from a given season.
 +
** <code>get_top_players</code>: Sort and show the top hitters.
 +
** <code>search_players</code>: Search for players by name.
 +
 
 +
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.
 +
 
 +
==== What Is Docker? ====
 +
 
 +
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''. 
 +
A container is like a tiny computer that holds everything your app needs (code, libraries, and dependencies) so it runs the same way on every machine.
 +
 
 +
Claude uses Docker to safely run MCP tools '''without touching your main system'''. 
 +
When you say:
 +
 
 +
<pre>“Use CardinalsMCP to calculate batting averages.”</pre>
 +
 
 +
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.
 +
 
 +
==== Goal ====
 +
 
 +
You will build a '''Python-based MCP server''' that:
 +
 
 +
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files
 +
* Lists team rosters and top hitters
 +
* Lets you search players or compare across seasons
 +
* Runs inside Docker and connects to Claude Desktop
 +
 
 +
After setup, you will be able to say things like:
  
−
=== Weather Widget ===
+
<pre>“Use CardinalsMCP to show the top 10 hitters from 1940.”</pre> 
 +
<pre>“Get batting averages for Johnny Mize in both 1930 and 1940.”</pre>
  
−
In this section, you will make a web page that displays the weather forecast using AJAX requests to a weather server.  You should complete this section ''without'' using JavaScript libraries like jQuery.
+
Claude will automatically run your tool and show the results.
  
−
# Make an empty HTML document; name it '''weather.html'''
+
==== Project Structure ====
−
#: Refer to the [[HTML and CSS]] guide for the skeleton of an HTML document
 
−
# Define a function in JavaScript; call it '''fetchWeather()'''.  You may write your JavaScript in an embedded script in your head tag.
 
−
# Inside your '''fetchWeather()''' function, make an AJAX request to the weather server.
 
−
#: We have a server that outputs the current weather in JSON format.  The format is documented in the [[#JSON Structure]] section below.
 
−
#: '''URL:''' http://cloud.sffc1.com/wustl/weather_json.php
 
−
#: We kindly thank Yahoo Weather for providing us with up-to-date weather information.
 
−
#* '''Note:''' You normally cannot perform AJAX requests cross-domain.  We have set the <code>Access-Control-Allow-Origin</code> header on our server to allow requests from your EC2 instances.
 
−
# In your callback, process the JSON and use JavaScript to manipulate the HTML DOM to display the following information on your page:
 
−
#* Location
 
−
#** City, in a <nowiki><strong></nowiki> tag
 
−
#** State, not in any tag
 
−
#* Humidity
 
−
#* Current Temperature
 
−
#* Image for Tomorrow's Forecast (see [[#Weather Condition Images]] below for more information)
 
−
#* Image for the Day After Tomorrow's Forecast
 
−
# Finally, bind '''fetchWeather()''' to the DOMContentReady event so that your weather widget is automatically initialized when the page is loaded:
 
−
#: <source lang="javascript">document.addEventListener("DOMContentReady", fetchWeather, false);</source>
 
  
−
Use the following HTML:
+
Your final folder will look like this:
  
−
<source lang="html4strict">
+
<source lang="text">
−
<div class="weather" id="weatherWidget">
+
cardinals-mcp/
−
<div class="weather-loc"></div>
+
├── Dockerfile
−
<div class="weather-humidity"></div>
+
├── cardinals_server.py
−
<div class="weather-temp"></div>
+
├── requirements.txt
−
<img class="weather-tomorrow" />
+
└── data/
−
<img class="weather-dayaftertomorrow" />
+
    ├── cardinals-1930.txt
−
</div>
+
    └── cardinals-1940.txt
 
</source>
 
</source>
  
−
Include the CSS file from here: http://cloud.sffc1.com/wustl/weather.css
+
The <code>/data</code> directory contains the <code>.txt</code> files you created from the Baseball Stats Counter section, listing player names and batting averages. 
 +
If you only printed the results, copy and paste them into <code>.txt</code> files.
 +
 
 +
==== Step 1: Prerequisites ====
 +
 
 +
You will need:
 +
 
 +
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]
 +
* [https://claude.ai/download Claude Desktop] (''not the browser version'')
 +
 
 +
==== Enable MCP in Docker ====
 +
 
 +
# Open '''Docker Desktop'''.
 +
# Go to '''Settings → Beta Features'''.
 +
# Enable '''“Docker MCP Toolkit”'''.
 +
# Click '''Apply & Restart'''.
 +
 
 +
===== Connect Claude to Docker =====
 +
 
 +
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''. 
 +
If it says '''“Disconnect”''', you are good to go.
 +
 
 +
==== Step 2: Create the Files ====
 +
 
 +
===== <code>cardinals_server.py</code> =====
 +
 
 +
This is your main MCP script. It should:
 +
 
 +
* Load player data from <code>/data/cardinals-1930.txt</code> and <code>/data/cardinals-1940.txt</code>
 +
* Implement at least '''two tools''', such as:
 +
** <code>get_player_stats</code>: Show batting averages for one player
 +
** <code>get_team_roster</code>: List all players for a given season (1930 or 1940)
 +
** <code>get_top_players</code>: Show the top hitters for a given season (by batting average)
 +
** <code>search_players</code>: Find players by partial name and show their batting averages
 +
 
 +
Each tool should be implemented as an '''asynchronous function''' decorated with <code>@mcp.tool()</code>. For example, the provided <code>get_player_stats</code> function uses the <code>@mcp.tool()</code> decorator to indicate that it is a tool that can be invoked by Claude.
 +
 
 +
In this script, you need to:
 +
 
 +
'''Create MCP Tools:'''
 +
* Implement at least two MCP tools (<code>get_player_stats</code>, <code>get_team_roster</code>, <code>get_top_players</code>, <code>search_players</code>). 
 +
* Use the <code>@mcp.tool()</code> decorator to register each function as a tool that Claude can use. 
 +
* Your functions should:
 +
** Retrieve data from the loaded player stats.
 +
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).
 +
** Return well-formatted strings with the results.
 +
 
 +
===== Starter Code =====
 +
 
 +
<source lang="python">
 +
import os
 +
import sys
 +
import logging
 +
import re
 +
from datetime import datetime
 +
from mcp.server.fastmcp import FastMCP
 +
 
 +
# Configure logging to stderr
 +
logging.basicConfig(
 +
    level=logging.INFO,
 +
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 +
    stream=sys.stderr,
 +
)
 +
logger = logging.getLogger("CardinalsMCP-server")
 +
 
 +
 
 +
# Initialize MCP server
 +
mcp = FastMCP("CardinalsMCP")
 +
 
 +
# Data file paths
 +
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
 +
CARDINALS_1930_FILE = os.path.join(DATA_DIR, "cardinals-1930.txt")
 +
CARDINALS_1940_FILE = os.path.join(DATA_DIR, "cardinals-1940.txt")
 +
 
 +
# ==== Helper Functions (You DO NOT need to use these) ====
 +
 
 +
def load_cardinals_data():
 +
    """Load Cardinals player data from both 1930 and 1940 files."""
 +
    data = {"1930": [], "1940": []}
 +
   
 +
    for year, file_path in [("1930", CARDINALS_1930_FILE), ("1940", CARDINALS_1940_FILE)]:
 +
        try:
 +
            with open(file_path, 'r', encoding='utf-8') as f:
 +
                content = f.read()
 +
                data[year] = parse_player_data(content)
 +
        except FileNotFoundError:
 +
            logger.warning(f"Data file not found: {file_path}")
 +
        except Exception as e:
 +
            logger.error(f"Error loading {year} data: {e}")
 +
   
 +
    return data
 +
 
 +
def parse_player_data(content):
 +
    """Parse player data from the simplified Cardinals text files."""
 +
    players = []
 +
   
 +
    for line in content.split('\n'):
 +
        line = line.strip()
 +
        if not line or line.startswith('#'):
 +
            continue
 +
           
 +
        # Parse format: "Player Name: 0.XXX"
 +
        if ':' in line:
 +
            parts = line.split(':')
 +
            if len(parts) == 2:
 +
                name = parts[0].strip()
 +
                try:
 +
                    batting_avg = float(parts[1].strip())
 +
                    players.append({
 +
                        'name': name,
 +
                        'batting_average': batting_avg
 +
                    })
 +
                except ValueError:
 +
                    continue
 +
   
 +
    return players
 +
 
 +
def get_all_players(data):
 +
    """Get all unique players from the data."""
 +
    players = set()
 +
    for year_data in data.values():
 +
        for player in year_data:
 +
            players.add(player['name'])
 +
    return sorted(list(players))
 +
 
 +
def get_player_by_name(data, player_name):
 +
    """Get player data for a specific player."""
 +
    player_data = []
 +
    for year, year_data in data.items():
 +
        for player in year_data:
 +
            if player['name'].lower() == player_name.lower():
 +
                player_data.append({
 +
                    'year': year,
 +
                    'name': player['name'],
 +
                    'batting_average': player['batting_average']
 +
                })
 +
    return player_data
 +
 
 +
# ==== MCP Tools ====
 +
 
 +
# This tool works - use to test your MCP connection.
 +
@mcp.tool()
 +
async def hello(name: str = "world") -> str:
 +
    """Return a simple greeting to verify the server is working."""
 +
    return f"Hello CSE 3300 Student, {name}!"
  
−
When everything is working, the weather widget should look something like this:
+
# Only two tools are required here
  
−
[[File:WeatherWidget.png]]
+
@mcp.tool()
 +
async def get_player_stats(player: str = "") -> str:
 +
    """Get a player's batting averages for both 1930 and 1940 seasons."""
 +
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'
 +
    # and 'get_player_by_name' functions
 +
 
  
−
'''Important:''' The widget in this section needs to work in only Firefox and Chrome. It does '''''not''''' need to work Internet Explorer.
+
@mcp.tool()
 +
async def get_team_roster(year: str = "1930") -> str:
 +
    """Get the roster of the Cardinals for the given year, listing player names and batting averages."""
 +
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)
 +
 
  
−
==== Tips ====
+
@mcp.tool()
 +
async def get_top_players(year: str = "1930", limit: int = 10) -> str:
 +
    """Get the top Cardinals players for a specific year, sorted by batting average."""
 +
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages
  
−
===== JSON Structure =====
 
  
−
The JSON from our server looks like this:
+
@mcp.tool()
 +
async def search_players(search_term: str = "") -> str:
 +
    """Search for Cardinals players by name and show their batting averages."""
 +
    # TODO: Implement this function to search for players by name and return their batting averages
  
−
<source lang="javascript">
+
# ==== Server Startup ====
−
{
+
 
−
   "updated": "Thu, 11 Oct 2012 5:54 pm CDT",
+
if __name__ == "__main__":
−
   "location": {
+
    logger.info("Starting Cardinals Historical Data MCP server...")
−
      "city": "St. Louis",
+
    try:
−
      "state": "MO"
+
        mcp.run(transport="stdio")
−
   },
+
    except Exception as e:
−
   "wind": {
+
        logger.error(f"Server error: {e}", exc_info=True)
−
      "chill": "62",
+
        sys.exit(1)
−
      "direction": "150",
 
−
      "speed": "3 mph"
 
−
   },
 
−
   "atmosphere": {
 
−
      "humidity": "50",
 
−
      "visibility": "10",
 
−
      "pressure": "30.12 in"
 
−
   },
 
−
   "current": {
 
−
      "code": "28",
 
−
      "text": "Mostly Cloudy",
 
−
      "temp": "62°F",
 
−
      "date": "Thu, 11 Oct 2012 5:54 pm CDT"
 
−
   },
 
−
   "tomorrow": {
 
−
      "code": "29",
 
−
      "text": "Clouds Early/Clearing Late",
 
−
      "low": "45°F",
 
−
      "high": "61°F"
 
−
   },
 
−
   "dayafter": {
 
−
      "code": "30",
 
−
      "text": "Partly Cloudy",
 
−
      "low": "53°F",
 
−
      "high": "65°F"
 
−
   },
 
−
   "credit": "http://us.rd.yahoo.com/dailynews/rss/weather/St._Louis__MO/*http://weather.yahoo.com/forecast/USMO0170_f.html"
 
−
}
 
 
</source>
 
</source>
  
−
===== Weather Condition Images =====
+
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]
  
−
Each day's forecast has a code.  There are images associated with these codes.
+
===== <code>Dockerfile</code> =====
  
−
You can get images from here: <nowiki>http://us.i1.yimg.com/us.yimg.com/i/us/nws/weather/gr/</nowiki>'''##'''<nowiki>ds.png</nowiki>
+
<source lang="dockerfile">
 +
FROM python:3.11-slim
  
−
Replace the '''##''' with the forecast code.  For example, for code 32, the URL would be: http://us.i1.yimg.com/us.yimg.com/i/us/nws/weather/gr/32ds.png
+
LABEL maintainer="cardinals_server"
 +
ENV PYTHONUNBUFFERED=1
  
−
=== jQuery Dialogs ===
+
WORKDIR /app
  
−
Helpful Resource: [http://docs.jquery.com/Main_Page jQuery Documentation]
+
# Copy dependencies
 +
COPY requirements.txt /app/requirements.txt
 +
RUN pip install --no-cache-dir -r requirements.txt
  
−
Create a simple page using jQuery dialogs.
+
# Copy application files
 +
COPY cardinals_server.py /app/cardinals_server.py
 +
COPY data/ /app/data/
  
−
* Create a page that has the words "Google" and "Yahoo".
+
# Create non-root user
−
* When the user clicks the "Google" text, open a jQuery dialog window with one jQuery dialog button.  If the button is pressed, display the Google logo image (the image can be stored on your web server) on the web page, and if the dialog is closed any other way, do nothing.
+
RUN useradd -m -u 1000 mcpuser && chown -R mcpuser:mcpuser /app
−
* When the user clicks the "Yahoo" text, open a jQuery dialog window with one jQuery dialog button.  If the button is pressed, display the Yahoo logo image (the image can be stored on your web server) on the web page, and if the dialog is closed any other way, do nothing.
+
USER mcpuser
−
* Initially there should be no image on the page (i.e., the img should be hidden with the CSS display attribute).  After one of the two images is displayed, if the user clicks on the image, a jQuery dialog box should be opened with one dialog button, and if the button is pressed then the image should be hidden again.  Otherwise, do nothing.
 
  
−
== Group Project ==
+
CMD ["python", "/app/cardinals_server.py"]
 +
</source>
  
−
You will work in pairs on this project.
+
===== <code>requirements.txt</code> =====
  
−
'''Important Reminder:''' frequently commit your work to your subversion repository as a backup!
+
<source lang="text">
 +
mcp
 +
</source>
 +
 
 +
That is all you need unless you use extra libraries.
 +
 
 +
==== Step 3: Build and Test the Container ====
 +
 
 +
Build your Docker image:
 +
 
 +
<source lang="bash">
 +
$ docker build -t cardinals-mcp-server:latest .
 +
</source>
  
−
I forgot if I already mentioned this, but '''start early on this project!''' It will take longer than you think, I promise!
+
Run it:
  
−
=== Calendar ===
+
<source lang="bash">
 +
$ docker run --rm -it cardinals-mcp-server
 +
</source>
  
−
Build a simple calendar that allows users to add and remove events dynamically.
+
If it launches correctly, you will see something like:
  
−
You will use JavaScript to process user interactions at the web browser, without ever refreshing the browser after the initial web page load. You may use a JavaScript library of your choice, including jQuery. (No extra credit will be given for ''not'' using a JavaScript library.)
+
<source lang="text">
 +
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...
 +
</source>
  
−
Your application should utilize AJAX to run server-side PHP scripts that query your database to save and retrieve information, including user accounts and events.
+
==== Step 4: Connect in Claude Desktop ====
  
−
==== Examples ====
+
===== Open Claude Desktop Settings =====
  
−
* https://calendar.google.com/
+
On macOS, click '''Claude → Settings…''' from the top menu bar.
−
* http://www.zoho.com/calendar/
+
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')
  
−
==== Requirements ====
+
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.
  
−
* Support a month-by-month view of the calendar.
+
===== Go to Developer Settings =====
−
*: Show one month at a time, with buttons to move forward or backward.
 
−
*: There should be no limit to how far forward or backward the user can go.
 
−
* Users can register and log in to the website.
 
−
*: You may leverage your MySQL project code and database from last week to get started.
 
−
*: You may alternatively use OpenID for user authentication.  Note that you will still need a Users table in order associate calendar events with a certain OpenID.
 
−
* Unregistered users should see no events on the calendar.
 
−
* Registered users can add events.
 
−
*: All events should have a date and time, but do not need to have a duration.
 
−
*: You do ''not'' need to support recurring events (where you add an event that repeats, for example, every monday).
 
−
* Registered users see only events that they have added.
 
−
* Registered users can delete their events, but not the events of others.
 
−
* All user and event data should be kept in a database.
 
−
* At no time should the main page need to be reloaded.
 
−
*: User registration, user authentication, event addition, and event deletion should all be handled by JavaScript and AJAX requests to your server.
 
−
* Your page needs to work in the versions of Firefox and Chrome installed on the lab computers.
 
  
−
'''Tip:''' Run your database schema by a TA before implementing it.
+
In the Settings window, select the '''Desktop app''' tab in the left sidebar.
 +
Then scroll all the way down to Extensions and select '''Manage'''.
 +
This section manages MCP servers and other developer tools.
  
−
==== Calendar Helper Library ====
+
Click '''Edit Config''' to open the configuration file. 
 +
If you do not have one yet, Claude will create it automatically.
  
−
To help you get started with your calendar, we have written some JavaScript helper functions.  The code is available at:
+
'''Configuration file locations:'''
−
* Full Version (for you to refer): http://cloud.sffc1.com/wustl/calendar.js
 
−
* Minified Version (to include in your pages): http://cloud.sffc1.com/wustl/calendar.min.js
 
  
−
For example, this is how you would make a button to load the next month and show alerts containing all the days in the weeks contained by that month.
+
* macOS: <code>~/Library/Application Support/Claude/claude_desktop_config.json</code>
−
<source lang="javascript">
+
* Windows: <code>%APPDATA%\Claude\claude_desktop_config.json</code>
−
// For our purposes, we can keep the current month in a variable in the global scope
 
−
var currentMonth = new Month(2012, 9); // October 2012
 
  
−
// Change the month when the "next" button is pressed
+
===== Add the MCP Gateway Configuration =====
−
document.getElementById("next_month_btn").addEventListener("click", function(event){
 
−
currentMonth = currentMonth.nextMonth(); // Previous month would be currentMonth.prevMonth()
 
−
updateCalendar(); // Whenever the month is updated, we'll need to re-render the calendar in HTML
 
−
alert("The new month is "+currentMonth.month+" "+currentMonth.year);
 
−
}, false);
 
  
 +
Replace (or merge) the contents of the file with the JSON below. 
 +
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.
  
−
// This updateCalendar() function only alerts the dates in the currently specified month.  You need to write
+
<source lang="json">
−
// it to modify the DOM (optionally using jQuery) to display the days and weeks in the current month.
+
{
−
function updateCalendar(){
+
  "mcpServers": {
−
var weeks = currentMonth.getWeeks();
+
    "cardinals-mcp-server": {
−
+
      "command": "docker",
−
for(var w in weeks){
+
      "args": [
−
var days = weeks[w].getDates();
+
        "run",
−
// days contains normal JavaScript Date objects.
+
        "--rm",
−
+
        "-i",
−
alert("Week starting on "+days[0]);
+
        "cardinals-mcp-server:latest"
−
+
      ]
−
for(var d in days){
+
    }
−
// You can see console.log() output in your JavaScript debugging tool, like Firebug,
+
  }
−
// WebWit Inspector, or Dragonfly.
 
−
console.log(days[d].toISOString());
 
−
}
 
−
}
 
 
}
 
}
 
</source>
 
</source>
  
−
'''Important:''' JavaScript starts the months of a year, and the days of a week, at index 0.  This means that '''''in JavaScript, January is month 0, and December is month 11.'''''  Our calendar helper functions maintain this JavaScript convention.
+
===== Restart Claude Desktop =====
  
−
=== Web Security and Validation ===
+
After saving the file:
 +
* Completely quit Claude Desktop (not just close the window).
 +
* Reopen it.
  
−
Your project needs to demonstrate that thought was put into web security and best practice. For more information, see this week's Web Application Security guide: [[Web Application Security, Part 3]]
+
Claude will now start the MCP gateway automatically.
  
−
In particular:
+
If everything is configured correctly, you will see a small MCP indicator in the lower-right corner of the chat input box. Click it to view all available tools, including your '''Cardinals MCP Server'''.
  
−
* '''Your application needs to prevent XSS attacks.'''  The easiest way to prevent this is to continue sanitizing ''all'' of your output using '''htmlentities()'''.
+
Once connected, you can run the hello tool in the chat of Claude Desktop:
−
* '''Perform precautionary measures to prevent session hijacking attacks.'''
 
−
*: You should specify your session cookie to be HTTP-Only.  However, for the means of this module, you need not test for user agent consistency.
 
−
* '''Extra Credit:''' Validate your JavaScript code using [http://www.jshint.com/ JSHint]
 
  
−
You should continue the practices that you have learned in past weeks:
+
<pre>"Use CardinalsMCP to say hello"</pre>
  
−
* Pass tokens in forms to prevent CSRF attacks
+
If everything is working correctly, you should see a "Hello CSE 3300 Student" response.
−
* Use prepared queries to prevent SQL Injection attacks
+
 
−
* If storing passwords in a database, always store them encrypted
+
After you have implemented the MCP tools, you can run commands like this:
−
* Your page should validate with no errors through the W3C validator.
+
<pre>“Use CardinalsMCP to list the top 10 hitters from 1940.”</pre>
 +
 
 +
==== Step 5: Test It Out ====
 +
 
 +
Open a new chat in Claude and try:
 +
 
 +
<pre>“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”</pre> 
 +
<pre>“Show Johnny Mize’s batting averages for 1930 and 1940.”</pre> 
 +
<pre>“Who were the top 5 hitters on the 1930 Cardinals?”</pre> 
 +
<pre>“Search for players with ‘Martin’ in their name.”</pre>
 +
 
 +
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.
  
 
== Grading ==
 
== Grading ==
  
−
<span style="font-size:2em; line-height:2em;">'''Due Date: _____ (both individual and group)'''</span>
 
  
−
{|
+
We will be grading the following aspects of your work.  There are 70 points total.
−
!Assignment
+
 
−
!Points
+
'''Assignments (including code) must be committed to Github by the end of class on the due date (commit early and often). Failing to commit by the end of class on the due date will result in a 0. '''
−
|-
+
 
−
|Calculator
+
Upload your baseball code and a file with your Regular expressions.
−
|2
+
 
−
|-
+
# '''Regular Expressions (15 Points):'''
−
|Weather Widget
+
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''
−
|3
+
#* Regular Expression 1 Correct and saved in a file named <code>regex1.txt</code> (5 points)
−
|-
+
#* Regular Expression 2 Correct and saved in a file named <code>regex2.txt</code> (5 points)
−
|jQuery Dialogs
+
#* Regular Expression 3 Correct and saved in a file named <code>regex3.txt</code> (5 points)
−
|1
+
 
−
|-
+
# '''Baseball Stats Counter (25 Points):'''
−
!colspan="2"|Group Portion:
+
#* Solution is written entirely in Python and saved in a file named <code>baseball.py</code> (5 points)
−
|-
+
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''
−
|Calendar month view correct
+
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)
−
|1
+
#*: ''Note: You should not be using <code>str.split</code> to extract data.''
−
|-
+
#* Script prints a usage message if a command line argument is not present (3 points)
−
|Move between months (without page reload)
+
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''
−
|1
+
#* Output is correct for all test cases (12 points)
−
|-
+
#*: ''This includes sorting and rounding.''
−
|User authentication and registration (without page reload)
+
 
−
|1
+
# '''Baseball Stats MCP Extension (30 Points):'''
−
|-
+
#* Docker image builds successfully without errors (5 points)
−
|Add events (new event shown in calendar without page reload)
+
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''
−
|1
+
#* Includes at least 2 MCP tools (14 points)
−
|-
+
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''
−
|Delete events (event removed from calendar without page reload)
+
#* README includes 3 different example prompts used in Claude (6 points)
−
|1
+
#*: ''Each example should show the prompt you asked and the MCP’s response.''
−
|-
+
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)
−
|User events are private
 
−
|1
 
−
|-
 
−
|Safe from XSS
 
−
|1
 
−
|-
 
−
|Safe from Session Hijacking
 
−
|1
 
−
|-
 
−
|Validation, CSRF, Salted Passwords, Safe SQL Queries
 
−
|1
 
−
|-
 
−
|Creative Portion
 
−
|2
 
−
|-
 
−
|'''Extra Credit:''' JavaScript passes JSHint test
 
−
|1
 
−
|-
 
−
|}
 
  
−
[[Category:Module 4]]
+
[[Category:Module 6]]
 
[[Category:Modules]]
 
[[Category:Modules]]

Latest revision as of 14:59, 3 November 2026

In Module 6, you will learn the fundamentals of Python, a widely used scripting language, and Regular Expressions, which provide powerful tools for text processing. You will also explore the Model Context Protocol (MCP), a framework that allows software tools to communicate with large language models (LLMs).

This article contains your assignments for Module 6.

Learning Goals

  • Regular Expressions
  • Python
  • MCP Fundamentals

Using the Wiki

Text enclosed by < and > should be replaced by content unique to you.

Example

$ sudo useradd -r -m -c "<My Full Name>" <usernameHere>

becomes

$ sudo useradd -r -m -c "Zach Cohn" zcohn


Reading

The following articles on the online class wiki textbook contain information that will help you complete the assignments.

Individual Assignments

Learn Python and Regular Expressions

In Module 6, you will learn a new language: Python. Before starting this assignment, you should look over the online textbook article about Python. You should be using version 3 (or greater) of Python. Additionally, you may find it helpful to look over the online textbook article about Regular Expressions.

Write some Regular Expressions

Please write regular expressions that do each of the following:

  1. Match the substring "hello world" in a string.
  2. Match all character strings in an input string that contain three or more consecutive vowels. The regex should match strings containing three or more consecutive vowels, regardless of whether the letters are uppercase, lowercase, or a mixture of both.
  3. Match an input string that is entirely a flight code, of the format AA####, where AA is a two-letter uppercase airline code, and #### is a three- or four-digit flight number.

Examples:

  Note: text in red is matched by the regular expression. Please note that each line is a separate test input string. For Regex 3, six separate test cases are given.
  Regex 1:  Programmers will often write hello world as their first project with a programming language.
  Regex 2:  The gooey peanut butter and jelly sandwich was a beauty.
  Regex 3:  AA312
            AA1298
            NW1234
            US443
            US31344
            AA123 extratext


Save your regexes to text files – one each.

Baseball Stats Counter

The St. Louis Cardinals are the most legendary baseball team in the national league. In this exercise, you will be creating a Python script that reads box scores from a file and computes the Cardinals' players' batting averages in a particular season.

Tips and Instructions

  • You should write a Python script file to solve this problem.
  • You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.
  • You may want to create a class to hold and compute information about each player.
  • Your file should take one command-line argument: the path to an input file. If no path is given, your program should print a usage message.

Input

Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/

The top of the file contains a citation, and what follows is an entire season of Cardinals Baseball box scores. Each game starts with a title; for example:

=== St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===

What follows are the performance of each player in that game. The format of each line is as follows:

XXX batted # times with # hits and # runs

where XXX is the player's name (you may assume that the name is unique), and each # are integers representing the number of "at bats", the number of "hits", and the number of "runs", respectively.

Output

Your script needs to output all players' batting average across the season. A player's batting average is that player's total hits divided by that player's total at-bats throughout the entire season. For example, Johnny Hopp was at bat 149 times in 1940, and he had 41 hits, so his batting average was 0.275.

Each line in the output should have the format:

XXX: #.###

where XXX is the player's name, and #.### is the player's batting average, rounded (not truncated) to three decimal places.

The players should be sorted by batting average, with the highest batting average on top. If two players have the exact same batting average (before rounding occurs), the order between those two players is unimportant.

For example, the correct output for the 1940 season is:

Pepper Martin: 0.316
Walker Cooper: 0.316
Johnny Mize: 0.314
Ernie Koy: 0.310
Enos Slaughter: 0.306
Joe Medwick: 0.304
Terry Moore: 0.304
Joe Orengo: 0.286
Jimmy Brown: 0.280
Marty Marion: 0.279
Don Gutteridge: 0.276
Johnny Hopp: 0.275
Creepy Crespi: 0.273
Mickey Owen: 0.265
Bill DeLancey: 0.250
Don Padgett: 0.242
Stu Martin: 0.238
Eddie Lake: 0.222
Hal Epps: 0.214
Lon Warneke: 0.209
Harry Walker: 0.185
Max Lanier: 0.179
Bill McGee: 0.178
Carl Doyle: 0.174
Mort Cooper: 0.157
Clyde Shoun: 0.145
Carden Gillenwater: 0.130
Bob Bowman: 0.067

Baseball Stats MCP Extension

Cardinals MCP Server

This project builds a custom MCP server that lets you explore St. Louis Cardinals batting data from the 1930 and 1940 seasons using the text files you made in the Baseball Stats Counter section.

The MCP server reads player batting averages from text files, sorts them, and returns the results through Claude Desktop.

For all of the assignment, feel free to use your favorite AI tool to help.

What Is an MCP Server?

An MCP (Model Context Protocol) server is a local tool that Claude Desktop can talk to directly. It runs safely inside Docker, allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.

Learn more: Model Context Protocol: Getting Started

Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:

  • Prompts: Predefined templates or instructions that guide language model interactions
  • Resources: Structured data or content that provides additional context to the model
  • Tools: Executable functions that allow models to perform actions or retrieve information

Each primitive can be summarized in the following control hierarchy:

Where Prompts, Resources, and Tools Fit in the MCP Server
  • Prompts: In the context of this project, prompts are what the user will provide to interact with the MCP server. For example, if you want to fetch the top hitters from a particular season, you would issue a prompt like:
"Show Johnny Mize’s batting averages for 1930 and 1940."

The prompt is user-controlled, and the system will interpret it to execute the appropriate action.

  • Resources: These are the data files, such as cardinals-1930.txt and cardinals-1940.txt, that your MCP server uses to provide additional context. These text files are resources that store structured data (player stats like batting averages) which the server can access to provide meaningful results when the user queries it.
  • Tools: The server exposes various tools that perform specific actions, like:
    • get_player_stats: Retrieve and show batting averages for a player.
    • get_team_roster: List all players from a given season.
    • get_top_players: Sort and show the top hitters.
    • search_players: Search for players by name.

Each of these functions is a tool in the MCP system that the language model can call when responding to prompts from the user.

What Is Docker?

Docker lets you run software in isolated, self-contained environments called containers. A container is like a tiny computer that holds everything your app needs (code, libraries, and dependencies) so it runs the same way on every machine.

Claude uses Docker to safely run MCP tools without touching your main system. When you say:

“Use CardinalsMCP to calculate batting averages.”

Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.

Goal

You will build a Python-based MCP server that:

  • Reads Cardinals player batting averages from 1930 and 1940 text files
  • Lists team rosters and top hitters
  • Lets you search players or compare across seasons
  • Runs inside Docker and connects to Claude Desktop

After setup, you will be able to say things like:

“Use CardinalsMCP to show the top 10 hitters from 1940.”
“Get batting averages for Johnny Mize in both 1930 and 1940.”

Claude will automatically run your tool and show the results.

Project Structure

Your final folder will look like this:

cardinals-mcp/
├── Dockerfile
├── cardinals_server.py
├── requirements.txt
└── data/
    ├── cardinals-1930.txt
    └── cardinals-1940.txt

The /data directory contains the .txt files you created from the Baseball Stats Counter section, listing player names and batting averages. If you only printed the results, copy and paste them into .txt files.

Step 1: Prerequisites

You will need:

Enable MCP in Docker

  1. Open Docker Desktop.
  2. Go to Settings → Beta Features.
  3. Enable “Docker MCP Toolkit”.
  4. Click Apply & Restart.
Connect Claude to Docker

In Docker Desktop, go to MCP Toolkit → Clients → Claude Desktop → Connect. If it says “Disconnect”, you are good to go.

Step 2: Create the Files

cardinals_server.py

This is your main MCP script. It should:

  • Load player data from /data/cardinals-1930.txt and /data/cardinals-1940.txt
  • Implement at least two tools, such as:
    • get_player_stats: Show batting averages for one player
    • get_team_roster: List all players for a given season (1930 or 1940)
    • get_top_players: Show the top hitters for a given season (by batting average)
    • search_players: Find players by partial name and show their batting averages

Each tool should be implemented as an asynchronous function decorated with @mcp.tool(). For example, the provided get_player_stats function uses the @mcp.tool() decorator to indicate that it is a tool that can be invoked by Claude.

In this script, you need to:

Create MCP Tools:

  • Implement at least two MCP tools (get_player_stats, get_team_roster, get_top_players, search_players).
  • Use the @mcp.tool() decorator to register each function as a tool that Claude can use.
  • Your functions should:
    • Retrieve data from the loaded player stats.
    • Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).
    • Return well-formatted strings with the results.
Starter Code
import os
import sys
import logging
import re
from datetime import datetime
from mcp.server.fastmcp import FastMCP

# Configure logging to stderr
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    stream=sys.stderr,
)
logger = logging.getLogger("CardinalsMCP-server")


# Initialize MCP server
mcp = FastMCP("CardinalsMCP")

# Data file paths
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
CARDINALS_1930_FILE = os.path.join(DATA_DIR, "cardinals-1930.txt")
CARDINALS_1940_FILE = os.path.join(DATA_DIR, "cardinals-1940.txt")

# ==== Helper Functions (You DO NOT need to use these) ====

def load_cardinals_data():
    """Load Cardinals player data from both 1930 and 1940 files."""
    data = {"1930": [], "1940": []}
    
    for year, file_path in [("1930", CARDINALS_1930_FILE), ("1940", CARDINALS_1940_FILE)]:
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
                data[year] = parse_player_data(content)
        except FileNotFoundError:
            logger.warning(f"Data file not found: {file_path}")
        except Exception as e:
            logger.error(f"Error loading {year} data: {e}")
    
    return data

def parse_player_data(content):
    """Parse player data from the simplified Cardinals text files."""
    players = []
    
    for line in content.split('\n'):
        line = line.strip()
        if not line or line.startswith('#'):
            continue
            
        # Parse format: "Player Name: 0.XXX"
        if ':' in line:
            parts = line.split(':')
            if len(parts) == 2:
                name = parts[0].strip()
                try:
                    batting_avg = float(parts[1].strip())
                    players.append({
                        'name': name,
                        'batting_average': batting_avg
                    })
                except ValueError:
                    continue
    
    return players

def get_all_players(data):
    """Get all unique players from the data."""
    players = set()
    for year_data in data.values():
        for player in year_data:
            players.add(player['name'])
    return sorted(list(players))

def get_player_by_name(data, player_name):
    """Get player data for a specific player."""
    player_data = []
    for year, year_data in data.items():
        for player in year_data:
            if player['name'].lower() == player_name.lower():
                player_data.append({
                    'year': year,
                    'name': player['name'],
                    'batting_average': player['batting_average']
                })
    return player_data

# ==== MCP Tools ====

# This tool works - use to test your MCP connection.
@mcp.tool()
async def hello(name: str = "world") -> str:
    """Return a simple greeting to verify the server is working."""
    return f"Hello CSE 3300 Student, {name}!"

# Only two tools are required here

@mcp.tool()
async def get_player_stats(player: str = "") -> str:
    """Get a player's batting averages for both 1930 and 1940 seasons."""
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'
    # and 'get_player_by_name' functions
  

@mcp.tool()
async def get_team_roster(year: str = "1930") -> str:
    """Get the roster of the Cardinals for the given year, listing player names and batting averages."""
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)
  

@mcp.tool()
async def get_top_players(year: str = "1930", limit: int = 10) -> str:
    """Get the top Cardinals players for a specific year, sorted by batting average."""
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages


@mcp.tool()
async def search_players(search_term: str = "") -> str:
    """Search for Cardinals players by name and show their batting averages."""
    # TODO: Implement this function to search for players by name and return their batting averages

# ==== Server Startup ====

if __name__ == "__main__":
    logger.info("Starting Cardinals Historical Data MCP server...")
    try:
        mcp.run(transport="stdio")
    except Exception as e:
        logger.error(f"Server error: {e}", exc_info=True)
        sys.exit(1)

You can find an explanation of the hello function in the MCP Fundamentals

Dockerfile
FROM python:3.11-slim

LABEL maintainer="cardinals_server"
ENV PYTHONUNBUFFERED=1

WORKDIR /app

# Copy dependencies
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Copy application files
COPY cardinals_server.py /app/cardinals_server.py
COPY data/ /app/data/

# Create non-root user
RUN useradd -m -u 1000 mcpuser && chown -R mcpuser:mcpuser /app
USER mcpuser

CMD ["python", "/app/cardinals_server.py"]
requirements.txt
mcp

That is all you need unless you use extra libraries.

Step 3: Build and Test the Container

Build your Docker image:

$ docker build -t cardinals-mcp-server:latest .

Run it:

$ docker run --rm -it cardinals-mcp-server

If it launches correctly, you will see something like:

2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...

Step 4: Connect in Claude Desktop

Open Claude Desktop Settings

On macOS, click Claude → Settings… from the top menu bar. (Do not use the settings inside the Claude chat window; you need the app-level settings.)

This opens the Claude Desktop Settings panel, separate from your Claude account settings.

Go to Developer Settings

In the Settings window, select the Desktop app tab in the left sidebar. Then scroll all the way down to Extensions and select Manage. This section manages MCP servers and other developer tools.

Click Edit Config to open the configuration file. If you do not have one yet, Claude will create it automatically.

Configuration file locations:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
Add the MCP Gateway Configuration

Replace (or merge) the contents of the file with the JSON below. This tells Claude to launch the Docker MCP Gateway, which connects to your custom Cardinals MCP server.

{
  "mcpServers": {
    "cardinals-mcp-server": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "cardinals-mcp-server:latest"
      ]
    }
  }
}
Restart Claude Desktop

After saving the file:

  • Completely quit Claude Desktop (not just close the window).
  • Reopen it.

Claude will now start the MCP gateway automatically.

If everything is configured correctly, you will see a small MCP indicator in the lower-right corner of the chat input box. Click it to view all available tools, including your Cardinals MCP Server.

Once connected, you can run the hello tool in the chat of Claude Desktop:

"Use CardinalsMCP to say hello"

If everything is working correctly, you should see a "Hello CSE 3300 Student" response.

After you have implemented the MCP tools, you can run commands like this:

“Use CardinalsMCP to list the top 10 hitters from 1940.”

Step 5: Test It Out

Open a new chat in Claude and try:

“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”
“Show Johnny Mize’s batting averages for 1930 and 1940.”
“Who were the top 5 hitters on the 1930 Cardinals?”
“Search for players with ‘Martin’ in their name.”

Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.

Grading

We will be grading the following aspects of your work. There are 70 points total.

Assignments (including code) must be committed to Github by the end of class on the due date (commit early and often). Failing to commit by the end of class on the due date will result in a 0.

Upload your baseball code and a file with your Regular expressions.

  1. Regular Expressions (15 Points):
    • Your regex files should contain your regex (with no delimiters or flags) and nothing else.
    • Regular Expression 1 Correct and saved in a file named regex1.txt (5 points)
    • Regular Expression 2 Correct and saved in a file named regex2.txt (5 points)
    • Regular Expression 3 Correct and saved in a file named regex3.txt (5 points)
  1. Baseball Stats Counter (25 Points):
    • Solution is written entirely in Python and saved in a file named baseball.py (5 points)
      Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.
    • Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)
      Note: You should not be using str.split to extract data.
    • Script prints a usage message if a command line argument is not present (3 points)
      Note: For an example of a usage message, see this link.
    • Output is correct for all test cases (12 points)
      This includes sorting and rounding.
  1. Baseball Stats MCP Extension (30 Points):
    • Docker image builds successfully without errors (5 points)
      MCP server (`cardinals_server.py`) loads player data and returns correct results.
    • Includes at least 2 MCP tools (14 points)
      Each tool must function correctly and return expected output through Claude Desktop.
    • README includes 3 different example prompts used in Claude (6 points)
      Each example should show the prompt you asked and the MCP’s response.
    • Completion of the AI Reflections for the assignment following the AI Best Practices (5 points)