Difference between revisions of "Module 6"

From CSE330 Wiki
Jump to navigationJump to search
 
(80 intermediate revisions by 9 users not shown)
Line 1: Line 1:
 
__NOTOC__
 
__NOTOC__
−
In Module 6, you will learn JavaScript, the dominant client-side web language. JavaScript is the third and final programming language you will learn in CSE 330.
+
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.
 
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 ==
 
== Reading ==
Line 8: Line 29:
 
The following articles on the online class wiki textbook contain information that will help you complete the assignments.
 
The following articles on the online class wiki textbook contain information that will help you complete the assignments.
  
−
* [[JavaScript]]
+
* [[Python]]
−
* [[AJAX and JSON]]
+
* [[Regular Expressions]]
−
* [[Ext JS]]
+
* [[MCP Fundamentals]]
−
* [[Extensible Calendar]]
+
* [[FAQ - Mod 6]]
−
* [[Web Application Security, Part 3]]
 
  
 
== Individual Assignments ==
 
== Individual Assignments ==
  
−
'''''IMPORTANT:'' Both of the individual assignments must be completed ''without'' using a JavaScript library like Ext JS.'''  However, you may (and should) use Ext JS for the group portion.
+
=== 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.
  
−
=== JavaScript Calculator ===
+
Each line in the output should have the format:
  
−
In Module 2, you made a calculator using PHP. Now you will be making one using JavaScript.
+
  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.
−
* JavaScript should monitor all three fields and display the current result whenever the user changes any value in any field, without refreshing the page.
 
−
*: '''Tip 1:''' Consider using either the ''change'' or the ''keyup'' event on the text fields, and either the ''change'' or the ''click'' event on the radio buttons.
 
−
*: '''Tip 2:''' Re-read [[JavaScript|the JavaScript guide]] if you need help figuring our how to bind callback functions to events, or how to determine which radio button is currently selected.  (There are examples for both of these!)
 
−
* 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-Shift-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.
  
−
=== Weather Widget ===
+
==== What Is Docker? ====
  
−
In this section, you will make a web page that displays the weather forecast using AJAX requests to a weather server.
+
'''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.
  
−
# Make an empty HTML document; name it '''weather.html'''
+
Claude uses Docker to safely run MCP tools '''without touching your main system'''.   
−
#: Refer to the [[HTML and CSS]] guide for the skeleton of an HTML document
+
When you say:
−
# 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://research.engineering.wustl.edu/~todd/cse330/module6/weather_json.php
 
−
#: We kindly thank Yahoo Weather for providing us with up-to-date weather information.
 
−
#* '''IMPORTANT:''' Under normal circumstances, AJAX requests ''cannot'' be performed cross-domain for security reasons.  We have set the <code>Access-Control-Allow-Origin</code> header on our server to allow requests from your EC2 instances, or from localhost.  '''You will therefore need to upload your code to your EC2, or access it via localhost, in order for the AJAX requests to work.'''  This means that you cannot, for example, complete this part of the individual portion in JSFiddle.
 
−
# 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 DOMContentLoaded event so that your weather widget is automatically initialized when the page is loaded:
 
−
#: <source lang="javascript">document.addEventListener("DOMContentLoaded", fetchWeather, false);</source>
 
−
# In addition, add a button that runs your ''fetchWeather'' function when clicked.  This should update your widget with the current weather conditions.
 
  
−
Use the following HTML:
+
<pre>“Use CardinalsMCP to calculate batting averages.”</pre>
  
−
<source lang="html4strict">
+
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.
−
<div class="weather" id="weatherWidget">
+
 
−
<div class="weather-loc"></div>
+
==== Goal ====
−
<div class="weather-humidity"></div>
+
 
−
<div class="weather-temp"></div>
+
You will build a '''Python-based MCP server''' that:
−
<img class="weather-tomorrow" />
+
 
−
<img class="weather-dayaftertomorrow" />
+
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files
−
</div>
+
* 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:
 +
 
 +
<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>
 +
 
 +
Claude will automatically run your tool and show the results.
 +
 
 +
==== Project Structure ====
 +
 
 +
Your final folder will look like this:
 +
 
 +
<source lang="text">
 +
cardinals-mcp/
 +
├── Dockerfile
 +
├── cardinals_server.py
 +
├── requirements.txt
 +
└── data/
 +
    ├── cardinals-1930.txt
 +
    └── cardinals-1940.txt
 
</source>
 
</source>
  
−
Include the CSS file from here: http://classes.engineering.wustl.edu/cse330/content/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.
  
−
When everything is working, the weather widget should look something like this:
+
==== Step 1: Prerequisites ====
  
−
[[File:WeatherWidget.png]]
+
You will need:
  
−
'''Important:''' The widget in this section needs to work in only Firefox and Chrome. It does '''''not''''' need to work Internet Explorer.
+
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]
 +
* [https://claude.ai/download Claude Desktop] (''not the browser version'')
  
−
==== Tips ====
+
==== Enable MCP in Docker ====
  
−
===== JSON Structure =====
+
# Open '''Docker Desktop'''.
 +
# Go to '''Settings → Beta Features'''.
 +
# Enable '''“Docker MCP Toolkit”'''.
 +
# Click '''Apply & Restart'''.
  
−
The JSON from our server looks like this:
+
===== Connect Claude to Docker =====
  
−
<source lang="javascript">
+
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''. 
−
{
+
If it says '''“Disconnect”''', you are good to go.
−
   "updated": "Thu, 11 Oct 2012 5:54 pm CDT",
+
 
−
   "location": {
+
==== Step 2: Create the Files ====
−
      "city": "St. Louis",
+
 
−
      "state": "MO"
+
===== <code>cardinals_server.py</code> =====
−
   },
+
 
−
   "wind": {
+
This is your main MCP script. It should:
−
      "chill": "62",
+
 
−
      "direction": "150",
+
* Load player data from <code>/data/cardinals-1930.txt</code> and <code>/data/cardinals-1940.txt</code>
−
      "speed": "3 mph"
+
* Implement at least '''two tools''', such as:
−
   },
+
** <code>get_player_stats</code>: Show batting averages for one player
−
   "atmosphere": {
+
** <code>get_team_roster</code>: List all players for a given season (1930 or 1940)
−
      "humidity": "50",
+
** <code>get_top_players</code>: Show the top hitters for a given season (by batting average)
−
      "visibility": "10",
+
** <code>search_players</code>: Find players by partial name and show their batting averages
−
      "pressure": "30.12 in"
+
 
−
   },
+
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.
−
   "current": {
+
 
−
      "code": "28",
+
In this script, you need to:
−
      "text": "Mostly Cloudy",
+
 
−
      "temp": "62°F",
+
'''Create MCP Tools:'''
−
      "date": "Thu, 11 Oct 2012 5:54 pm CDT"
+
* 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. 
−
   "tomorrow": {
+
* Your functions should:
−
      "code": "29",
+
** Retrieve data from the loaded player stats.
−
      "text": "Clouds Early/Clearing Late",
+
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).
−
      "low": "45°F",
+
** Return well-formatted strings with the results.
−
      "high": "61°F"
+
 
−
   },
+
===== Starter Code =====
−
   "dayafter": {
+
 
−
      "code": "30",
+
<source lang="python">
−
      "text": "Partly Cloudy",
+
import os
−
      "low": "53°F",
+
import sys
−
      "high": "65°F"
+
import logging
−
   },
+
import re
−
   "credit": "http://us.rd.yahoo.com/dailynews/rss/weather/St._Louis__MO/*http://weather.yahoo.com/forecast/USMO0170_f.html"
+
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)
 +
</source>
 +
 
 +
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]
 +
 
 +
===== <code>Dockerfile</code> =====
 +
 
 +
<source lang="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"]
 +
</source>
 +
 
 +
===== <code>requirements.txt</code> =====
 +
 
 +
<source lang="text">
 +
mcp
 
</source>
 
</source>
  
−
===== Weather Condition Images =====
+
That is all you need unless you use extra libraries.
  
−
Each day's forecast has a code.  There are images associated with these codes.
+
==== Step 3: Build and Test the Container ====
  
−
One place to get the images is from here: <nowiki>http://us.yimg.com/i/us/nws/weather/gr/</nowiki>'''##'''<nowiki>ds.png</nowiki>
+
Build your Docker image:
  
−
Replace the '''##''' with the forecast code.  For example, for code 32, the URL would be: http://us.yimg.com/i/us/nws/weather/gr/32ds.png
+
<source lang="bash">
 +
$ docker build -t cardinals-mcp-server:latest .
 +
</source>
  
−
== Group Project ==
+
Run it:
  
−
I forgot if I already mentioned this, but '''start early on this project!''' It will take longer than you think, I promise!
+
<source lang="bash">
 +
$ docker run --rm -it cardinals-mcp-server
 +
</source>
  
−
You are encouraged to use the advanced version control practices you learned in Module 5 when completing this assignment.
+
If it launches correctly, you will see something like:
  
−
=== Calendar ===
+
<source lang="text">
 +
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...
 +
</source>
  
−
Build a simple calendar that allows users to add and remove events dynamically.
+
==== Step 4: Connect in Claude Desktop ====
  
−
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 Ext JS.
+
===== Open Claude Desktop Settings =====
  
−
Your application should utilize AJAX to run server-side scripts that query your database to save and retrieve information, including user accounts and events.
+
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.'')
  
−
==== Examples ====
+
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.
  
−
* https://calendar.google.com/
+
===== Go to Developer Settings =====
−
* http://www.zoho.com/calendar/
 
  
−
==== JavaScript Calendar APIs ====
+
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.
  
−
One nice thing about web development is that if there is something you want to write, the chances are that someone else thought of it first and released an API.  As it turns out, there are a dozen or so APIs for calendars, two of which run on top of Ext JS.
+
Click '''Edit Config''' to open the configuration file.   
 +
If you do not have one yet, Claude will create it automatically.
  
−
'''In this group project, you may use the [[Extensible Calendar]].''' There are examples and documentation on their web site.  You may not use any other calendar API for Module 6; doing so will result in a zero for the client-side portion of the group project.
+
'''Configuration file locations:'''
  
−
Of course, you may choose to build your own calendar from scratch. If you do, you may earn at most 5 points of extra credit. We have also written some functions to help you get started: [[JavaScript Calendar Library]]
+
* macOS: <code>~/Library/Application Support/Claude/claude_desktop_config.json</code>
 +
* Windows: <code>%APPDATA%\Claude\claude_desktop_config.json</code>
  
−
==== Server Side Language ====
+
===== Add the MCP Gateway Configuration =====
  
−
You now know two different server side languages/frameworks: [[PHP]] and [[Web Frameworks|Django]].  You and your partner may choose to use either one for this assignment.
+
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.
  
−
Here are some things to keep in mind:
+
<source lang="json">
 +
{
 +
  "mcpServers": {
 +
    "cardinals-mcp-server": {
 +
      "command": "docker",
 +
      "args": [
 +
        "run",
 +
        "--rm",
 +
        "-i",
 +
        "cardinals-mcp-server:latest"
 +
      ]
 +
    }
 +
  }
 +
}
 +
</source>
  
−
* With Django, user management is almost as easy as a "pip install"
+
===== Restart Claude Desktop =====
−
* With Django, you can set up your server to serve JSON data from the database automatically; see the [[Extensible Calendar]] guide for an example
 
−
* You have spent more time with PHP than with Django, so PHP might be more familiar as you write it
 
−
* There is an example in the Extensible Calendar download that uses PHP as a back end*
 
  
−
Additionally, you may use a database of your choice. MySQL is probably the logical choice since you have been using it since Module 2, but if you're up for a challenge we encourage you to look at other database options like [[wikipedia:MongoDB|MongoDB]].
+
After saving the file:
 +
* Completely quit Claude Desktop (not just close the window).
 +
* Reopen it.
  
−
''* While it is fine to use the provided example for reference, copying it in its entirety is a violation of Academic Integrity and will result in a 0 for the group portion of this module.  Don't plagiarize.''
+
Claude will now start the MCP gateway automatically.
  
−
==== Requirements ====
+
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'''.
  
−
* Support a month-by-month view of the calendar.
+
Once connected, you can run the hello tool in the chat of Claude Desktop:
−
*: 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 module 3 to get started.
 
−
*: You may alternatively use [[Web Application Security, Part 2#OpenID|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.
 
−
*: Your AJAX should not ask the server for events from a certain username.  Instead, your AJAX should ask the server for events, and the server should respond with the events for only the currently-logged-in user (from the session).  Can you think of why?
 
−
* Registered users can delete their events, but not the events of others.
 
−
*: Again, the server should delete events based on the username present in the session.  (If it deletes only based on an Event ID, an attacker could feed any arbitrary Event ID to your server, even if he/she didn't own that event.)
 
−
* 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.
+
<pre>"Use CardinalsMCP to say hello"</pre>
  
−
=== Web Security and Validation ===
+
If everything is working correctly, you should see a "Hello CSE 3300 Student" response.
  
−
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]]
+
After you have implemented the MCP tools, you can run commands like this:
 +
<pre>“Use CardinalsMCP to list the top 10 hitters from 1940.”</pre>
  
−
In particular:
+
==== Step 5: Test It Out ====
  
−
* '''Your application needs to prevent XSS attacks.'''  Be careful when transmitting data over JSON that will be reflected in an event title!  (Note: JSON data should be sanitized on the client side, not the server side.)
+
Open a new chat in Claude and try:
−
* '''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.
 
−
* '''Optional:''' Validate your JavaScript code using [http://www.jshint.com/ JSHint] '''''or''''' [http://jslint.com/ JSLint] for up to 3 points of extra credit.
 
  
−
You should continue the practices that you have learned in past weeks:
+
<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>
  
−
* Pass tokens in forms to prevent CSRF attacks.
+
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.
−
*: '''Hint:''' You will need to send your CSRF tokens in your AJAX requests.  Remember that AJAX still submits forms and runs server-side scripts, just like the vanilla forms you've been using in Modules 2 and 3.
 
−
* Use prepared queries (or object-relational mapping in Django) to prevent SQL Injection attacks.
 
−
* If storing passwords in a database, always store them salted and encrypted.
 
−
* Your page should validate with no errors through the W3C validator.
 
  
 
== Grading ==
 
== Grading ==
  
−
We will be grading the following aspects of your work.  There are 100 points total.
 
  
−
# '''JavaScript Calculator (10 Points):'''
+
We will be grading the following aspects of your work.  There are 70 points total.
−
#* Calculator successfully adds, subtracts, multiplies, and divides numbers (3 points)
+
 
−
#* The result is automatically computed when a value in either field is changed (3 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 is written entirely in pure JavaScript with no external libraries (4 points)*
+
 
−
# '''Weather Widget (15 Points):'''
+
Upload your baseball code and a file with your Regular expressions.
−
#* Widget performs an AJAX request to fetch the current weather (5 points)
+
 
−
#* The HTML template is filled with the correct information, including an image (5 points)
+
# '''Regular Expressions (15 Points):'''
−
#* Widget is written entirely in pure JavaScript with no external libraries (5 points)*
+
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''
−
# '''AJAX Calendar (60 Points):'''
+
#* Regular Expression 1 Correct and saved in a file named <code>regex1.txt</code> (5 points)
−
#* '''''Calendar View (10 Points):'''''
+
#* Regular Expression 2 Correct and saved in a file named <code>regex2.txt</code> (5 points)
−
#** The calendar is displayed as a table grid with days as the columns and weeks as the rows, one month at a time (5 points)
+
#* Regular Expression 3 Correct and saved in a file named <code>regex3.txt</code> (5 points)
−
#** The user can view different months as far in the past or future as desired (5 points)
 
−
#* '''''User and Event Management (25 Points):'''''
 
−
#** Events can be added, modified, and deleted (5 points)
 
−
#** Events have a title, date, and time (2 points)
 
−
#** Users can log into the site, and they cannot view or manipulate events associated with other users (8 points)
 
−
#**: ''Don't fall into the Abuse of Functionality trap!  Check user credentials on the server side as well as on the client side.''
 
−
#** All actions are performed over AJAX, without ever needing to reload the page (10 points)
 
−
#* '''''Best Practices (20 Points):'''''
 
−
#** Code is well formatted and easy to read (2 points)
 
−
#** If storing passwords, they are stored salted and encrypted; if using OpenID, you are storing the user's OpenID identifier in the database (2 points)
 
−
#** All AJAX requests that either contain sensitive information or modify something on the server are performed via POST, not GET (3 points)
 
−
#** Safe from XSS attacks; that is, all content is escaped on output (3 points)
 
−
#** Safe from SQL Injection attacks (2 points)
 
−
#** CSRF tokens are passed when editing or removing events (3 points)
 
−
#** Session cookie is HTTP-Only (3 points)
 
−
#** Page passes the W3C validator (2 points)
 
−
#** ''Extra Credit:'' Your code passes ''JSHint'' or ''JSLint'' (+3 bonus points)
 
−
#* '''''Usability (5 Points):'''''
 
−
#** Site is intuitive to use and navigate (4 points)
 
−
#** Site is visually appealing (1 point)
 
−
# '''Creative Portion (15 Points)'''
 
  
 +
# '''Baseball Stats Counter (25 Points):'''
 +
#* 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.''
 +
#* 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 <code>str.split</code> 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 [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''
 +
#* Output is correct for all test cases (12 points)
 +
#*: ''This includes sorting and rounding.''
  
−
''* These points can be earned only if at least 3 other points have been earned in that category.  This is to prevent these points from being "free."''
+
# '''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)
  
 
[[Category:Module 6]]
 
[[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)