<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://classes.engineering.wustl.edu/cse330/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Todd</id>
	<title>CSE330 Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="http://classes.engineering.wustl.edu/cse330/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Todd"/>
	<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Special:Contributions/Todd"/>
	<updated>2026-11-15T18:17:16Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.34.4</generator>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Web_Frameworks&amp;diff=3541</id>
		<title>Web Frameworks</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Web_Frameworks&amp;diff=3541"/>
		<updated>2026-11-11T19:27:13Z</updated>

		<summary type="html">&lt;p&gt;Todd: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''The following recommendations were last updated Fall 2026''&lt;br /&gt;
&lt;br /&gt;
So far in this class, you have been using PHP, HTML, and JS to run your website.  You may have found that PHP can become burdensome for larger applications and difficult to use in collaboration.&lt;br /&gt;
&lt;br /&gt;
Web Frameworks are an alternative method for writing web applications.  Frameworks exist to make writing code more maintainable, and scalable, as well as to introduce base functionality for the application you make.&lt;br /&gt;
&lt;br /&gt;
There are ''hundreds'' of frameworks available.  We've categorized and listed some of the more popular below to help you decide what's right for your application.&lt;br /&gt;
&lt;br /&gt;
== Common Features of Frameworks ==&lt;br /&gt;
&lt;br /&gt;
=== Routing ===&lt;br /&gt;
&lt;br /&gt;
A '''key difference''' between PHP and web frameworks is how routing occurs.&lt;br /&gt;
&lt;br /&gt;
In PHP, if you went to an address like http://www.example.com/welcome.php then Apache would run the script located at, for example, ''/var/www/html/welcome.php''.&lt;br /&gt;
&lt;br /&gt;
With routers, however, you can set arbitrary URLs to run different controller files.&lt;br /&gt;
&lt;br /&gt;
=== Less Code ===&lt;br /&gt;
&lt;br /&gt;
Frameworks are designed to separate your application logic from your HTML and CSS.  In fact, most web frameworks automatically generate all of the HTML and CSS that you need.  So, you could write a fully functional (albeit bland-looking) website in a Web Framework without writing a single line of HTML or CSS!&lt;br /&gt;
&lt;br /&gt;
=== MVC Architecture ===&lt;br /&gt;
&lt;br /&gt;
In PHP, your code probably ended up jumbled together in various disorganized places.  This is part of the reason why many Web Frameworks choose to use a Model-View-Controller architecture.&lt;br /&gt;
&lt;br /&gt;
You learned about the Model-View-Controller architecture in CSE 132.  If you need a review, [[wikipedia:Model–view–controller|read the article on Wikipedia]].&lt;br /&gt;
&lt;br /&gt;
A directory structure for an MVC framework might look like the following:&lt;br /&gt;
&lt;br /&gt;
* '''app''' (holds MVC components)&lt;br /&gt;
** '''models''' (contains models)&lt;br /&gt;
** '''controllers''' (contains controllers)&lt;br /&gt;
** '''views''' (contains views)&lt;br /&gt;
* '''config''' (holds global server configurations)&lt;br /&gt;
* '''lib''' (other assorted libraries)&lt;br /&gt;
&lt;br /&gt;
With an MVC architecture, a request to the server typically follows the following path:&lt;br /&gt;
&lt;br /&gt;
# The server routes the request to a certain controller.&lt;br /&gt;
# The controller interprets the request, loading requested information from the models.&lt;br /&gt;
# The controller passes the information from the models to a view.&lt;br /&gt;
# The final view is sent to the user.&lt;br /&gt;
&lt;br /&gt;
== So what do I do? ==&lt;br /&gt;
&lt;br /&gt;
=== Front-end Frameworks ===&lt;br /&gt;
=====React=====&lt;br /&gt;
Get started [https://react.dev/ Here]&lt;br /&gt;
&lt;br /&gt;
React is the most popular front-end framework. React uses JS to create HTML (JSX), as opposed to updating HTML with JS. This makes manipulating the DOM more efficient and smoother.&lt;br /&gt;
&lt;br /&gt;
=====Vue=====&lt;br /&gt;
Get started [https://vuejs.org/v2/guide/ Here]&lt;br /&gt;
&lt;br /&gt;
A really good happy medium between React and Angular. Not fully contained, but separates/organizes your code very nicely. Don't have to use JSX or Angular-specific methods. The code structure is better organized than React, but not as opinionated as in Angular.&lt;br /&gt;
&lt;br /&gt;
Vue is partnered with PHP Laravel so a lot of the integrations are very code-friendly.&lt;br /&gt;
&lt;br /&gt;
=====Angular=====&lt;br /&gt;
Get started [https://angular.io/guide/quickstart Here]&lt;br /&gt;
&lt;br /&gt;
Not to be confused with Angular.js (the old version), Angular is the Google-developed front-end framework that competes with React and Vue. Angular has its own more opinionated code practices, making it easy for developers to understand but with a higher initial learning curve. &lt;br /&gt;
&lt;br /&gt;
=== Back-end Frameworks ===&lt;br /&gt;
=====Express (NodeJS)=====&lt;br /&gt;
Get started [https://expressjs.com/en/starter/generator.html Here]&lt;br /&gt;
&lt;br /&gt;
If you're planning on using a NodeJS backend, Express is a great option. Other options include [https://hapijs.com/ Hapi.js] or [https://koajs.com/ KOA] if you're already familiar with Express and want to try something else.&lt;br /&gt;
&lt;br /&gt;
=====Laravel (PHP)=====&lt;br /&gt;
Get started [https://laravel.com/docs/5.8 Here]&lt;br /&gt;
&lt;br /&gt;
Laravel is certainly the most popular PHP back-end option. If you aren't sure you want to venture over to a Node, Python, or another back-end, I urge you to try Laravel instead of vanilla PHP. Super clean, super simple, and genuinely makes writing PHP enjoyable. As mentioned above, it's coupled extremely well with Vue.js, I would recommend searching for tutorials of the two together if that's the approach you'd like to take.&lt;br /&gt;
&lt;br /&gt;
=====Django (Python)=====&lt;br /&gt;
Get started [https://docs.djangoproject.com/en/2.1/ Here]&lt;br /&gt;
&lt;br /&gt;
Django's tagline is &amp;quot;The Web framework for perfectionists with deadlines&amp;quot;. Take that as you will.&lt;br /&gt;
&lt;br /&gt;
Django is a fairly opinionated full-stack framework, meaning both your front-end and back-end will be written and handled by Django (as opposed to, say Express, which can integrate with whatever sort of front-end you'd like). This doesn't mean it's more difficult to learn, but it does kind of force you to learn how Django does its front-end and back-end which is why it counts for both in terms of points. &lt;br /&gt;
&lt;br /&gt;
=====Flask (Python)=====&lt;br /&gt;
Get started [http://flask.pocoo.org/ Here]&lt;br /&gt;
&lt;br /&gt;
Flask is a micro back-end framework and is extremely easy to setup. Unlike Django, Flask won't handle your Front-end for you. This leaves you the option to use any of the recommended front-end frameworks or to just use regular HTML/JS. &lt;br /&gt;
&lt;br /&gt;
=====Bottle (Python)=====&lt;br /&gt;
Get started [https://bottlepy.org/docs/dev/ Here]&lt;br /&gt;
&lt;br /&gt;
Same gist as Flask, just an alternative.&lt;br /&gt;
&lt;br /&gt;
=====FastAPI (Python)=====&lt;br /&gt;
Get started [https://fastapi.tiangolo.com/ Here]&lt;br /&gt;
&lt;br /&gt;
FastAPI is a modern Python micro framework built for writing APIs. It uses standard Python type hints to validate requests and automatically generates interactive documentation for your endpoints at /docs. Like Flask, it won’t handle your front-end for you, so pair it with any of the recommended front-end frameworks or regular HTML/JS.&lt;br /&gt;
&lt;br /&gt;
=== Other and Database Info ===&lt;br /&gt;
=====MongoDB=====&lt;br /&gt;
Get started [https://docs.mongodb.com/manual/ Here]&lt;br /&gt;
&lt;br /&gt;
MongoDB is a very popular no-sql based database. It may seem like a hassle to learn another type of database querying, but as soon as you realize you don't have to mess with foreign keys and it's all essentially javascript objects, it becomes a pretty attractive option. They also have free cloud databases now which is awesome because you don't have to worry about hosting a database on your computer, but much of the security is handled on their end.&lt;br /&gt;
&lt;br /&gt;
=====Firebase=====&lt;br /&gt;
Get started [https://firebase.google.com/ Here]&lt;br /&gt;
&lt;br /&gt;
Firebase is another cloud-based no-sql option. Firebase provides everything from the database itself to real-time crash reporting and analytics. Very easy to integrate into your app and get started with.&lt;br /&gt;
&lt;br /&gt;
=====Other SQL=====&lt;br /&gt;
[https://www.postgresql.org/ PostgreSQL]&lt;br /&gt;
&lt;br /&gt;
[https://mariadb.org/ MariaDB]&lt;br /&gt;
&lt;br /&gt;
=== Structuring Your Project ===&lt;br /&gt;
Once you've outlined your project and have chosen what languages and frameworks you'd like to use, knowing where to start can be a bit daunting. We recommend a straightforward file structure similar to the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
client/&lt;br /&gt;
server/&lt;br /&gt;
config/&lt;br /&gt;
entrypoint&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Depending on what frameworks you're using, it's easy to just place your full front-end framework in the client folder, all of your back-end files in the server folder, and all of your database and other configurations in the config folder. Your entry point is just a file containing the command(s) to start everything. Whether this is a package.json file, or a python file that starts your Django server, it's nice to keep it in the root of your project.&lt;br /&gt;
&lt;br /&gt;
Have fun!&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Web_Frameworks&amp;diff=3540</id>
		<title>Web Frameworks</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Web_Frameworks&amp;diff=3540"/>
		<updated>2026-11-11T19:27:04Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Back-end Frameworks */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''The following recommendations were last updated Fall 2024''&lt;br /&gt;
&lt;br /&gt;
So far in this class, you have been using PHP, HTML, and JS to run your website.  You may have found that PHP can become burdensome for larger applications and difficult to use in collaboration.&lt;br /&gt;
&lt;br /&gt;
Web Frameworks are an alternative method for writing web applications.  Frameworks exist to make writing code more maintainable, and scalable, as well as to introduce base functionality for the application you make.&lt;br /&gt;
&lt;br /&gt;
There are ''hundreds'' of frameworks available.  We've categorized and listed some of the more popular below to help you decide what's right for your application.&lt;br /&gt;
&lt;br /&gt;
== Common Features of Frameworks ==&lt;br /&gt;
&lt;br /&gt;
=== Routing ===&lt;br /&gt;
&lt;br /&gt;
A '''key difference''' between PHP and web frameworks is how routing occurs.&lt;br /&gt;
&lt;br /&gt;
In PHP, if you went to an address like http://www.example.com/welcome.php then Apache would run the script located at, for example, ''/var/www/html/welcome.php''.&lt;br /&gt;
&lt;br /&gt;
With routers, however, you can set arbitrary URLs to run different controller files.&lt;br /&gt;
&lt;br /&gt;
=== Less Code ===&lt;br /&gt;
&lt;br /&gt;
Frameworks are designed to separate your application logic from your HTML and CSS.  In fact, most web frameworks automatically generate all of the HTML and CSS that you need.  So, you could write a fully functional (albeit bland-looking) website in a Web Framework without writing a single line of HTML or CSS!&lt;br /&gt;
&lt;br /&gt;
=== MVC Architecture ===&lt;br /&gt;
&lt;br /&gt;
In PHP, your code probably ended up jumbled together in various disorganized places.  This is part of the reason why many Web Frameworks choose to use a Model-View-Controller architecture.&lt;br /&gt;
&lt;br /&gt;
You learned about the Model-View-Controller architecture in CSE 132.  If you need a review, [[wikipedia:Model–view–controller|read the article on Wikipedia]].&lt;br /&gt;
&lt;br /&gt;
A directory structure for an MVC framework might look like the following:&lt;br /&gt;
&lt;br /&gt;
* '''app''' (holds MVC components)&lt;br /&gt;
** '''models''' (contains models)&lt;br /&gt;
** '''controllers''' (contains controllers)&lt;br /&gt;
** '''views''' (contains views)&lt;br /&gt;
* '''config''' (holds global server configurations)&lt;br /&gt;
* '''lib''' (other assorted libraries)&lt;br /&gt;
&lt;br /&gt;
With an MVC architecture, a request to the server typically follows the following path:&lt;br /&gt;
&lt;br /&gt;
# The server routes the request to a certain controller.&lt;br /&gt;
# The controller interprets the request, loading requested information from the models.&lt;br /&gt;
# The controller passes the information from the models to a view.&lt;br /&gt;
# The final view is sent to the user.&lt;br /&gt;
&lt;br /&gt;
== So what do I do? ==&lt;br /&gt;
&lt;br /&gt;
=== Front-end Frameworks ===&lt;br /&gt;
=====React=====&lt;br /&gt;
Get started [https://react.dev/ Here]&lt;br /&gt;
&lt;br /&gt;
React is the most popular front-end framework. React uses JS to create HTML (JSX), as opposed to updating HTML with JS. This makes manipulating the DOM more efficient and smoother.&lt;br /&gt;
&lt;br /&gt;
=====Vue=====&lt;br /&gt;
Get started [https://vuejs.org/v2/guide/ Here]&lt;br /&gt;
&lt;br /&gt;
A really good happy medium between React and Angular. Not fully contained, but separates/organizes your code very nicely. Don't have to use JSX or Angular-specific methods. The code structure is better organized than React, but not as opinionated as in Angular.&lt;br /&gt;
&lt;br /&gt;
Vue is partnered with PHP Laravel so a lot of the integrations are very code-friendly.&lt;br /&gt;
&lt;br /&gt;
=====Angular=====&lt;br /&gt;
Get started [https://angular.io/guide/quickstart Here]&lt;br /&gt;
&lt;br /&gt;
Not to be confused with Angular.js (the old version), Angular is the Google-developed front-end framework that competes with React and Vue. Angular has its own more opinionated code practices, making it easy for developers to understand but with a higher initial learning curve. &lt;br /&gt;
&lt;br /&gt;
=== Back-end Frameworks ===&lt;br /&gt;
=====Express (NodeJS)=====&lt;br /&gt;
Get started [https://expressjs.com/en/starter/generator.html Here]&lt;br /&gt;
&lt;br /&gt;
If you're planning on using a NodeJS backend, Express is a great option. Other options include [https://hapijs.com/ Hapi.js] or [https://koajs.com/ KOA] if you're already familiar with Express and want to try something else.&lt;br /&gt;
&lt;br /&gt;
=====Laravel (PHP)=====&lt;br /&gt;
Get started [https://laravel.com/docs/5.8 Here]&lt;br /&gt;
&lt;br /&gt;
Laravel is certainly the most popular PHP back-end option. If you aren't sure you want to venture over to a Node, Python, or another back-end, I urge you to try Laravel instead of vanilla PHP. Super clean, super simple, and genuinely makes writing PHP enjoyable. As mentioned above, it's coupled extremely well with Vue.js, I would recommend searching for tutorials of the two together if that's the approach you'd like to take.&lt;br /&gt;
&lt;br /&gt;
=====Django (Python)=====&lt;br /&gt;
Get started [https://docs.djangoproject.com/en/2.1/ Here]&lt;br /&gt;
&lt;br /&gt;
Django's tagline is &amp;quot;The Web framework for perfectionists with deadlines&amp;quot;. Take that as you will.&lt;br /&gt;
&lt;br /&gt;
Django is a fairly opinionated full-stack framework, meaning both your front-end and back-end will be written and handled by Django (as opposed to, say Express, which can integrate with whatever sort of front-end you'd like). This doesn't mean it's more difficult to learn, but it does kind of force you to learn how Django does its front-end and back-end which is why it counts for both in terms of points. &lt;br /&gt;
&lt;br /&gt;
=====Flask (Python)=====&lt;br /&gt;
Get started [http://flask.pocoo.org/ Here]&lt;br /&gt;
&lt;br /&gt;
Flask is a micro back-end framework and is extremely easy to setup. Unlike Django, Flask won't handle your Front-end for you. This leaves you the option to use any of the recommended front-end frameworks or to just use regular HTML/JS. &lt;br /&gt;
&lt;br /&gt;
=====Bottle (Python)=====&lt;br /&gt;
Get started [https://bottlepy.org/docs/dev/ Here]&lt;br /&gt;
&lt;br /&gt;
Same gist as Flask, just an alternative.&lt;br /&gt;
&lt;br /&gt;
=====FastAPI (Python)=====&lt;br /&gt;
Get started [https://fastapi.tiangolo.com/ Here]&lt;br /&gt;
&lt;br /&gt;
FastAPI is a modern Python micro framework built for writing APIs. It uses standard Python type hints to validate requests and automatically generates interactive documentation for your endpoints at /docs. Like Flask, it won’t handle your front-end for you, so pair it with any of the recommended front-end frameworks or regular HTML/JS.&lt;br /&gt;
&lt;br /&gt;
=== Other and Database Info ===&lt;br /&gt;
=====MongoDB=====&lt;br /&gt;
Get started [https://docs.mongodb.com/manual/ Here]&lt;br /&gt;
&lt;br /&gt;
MongoDB is a very popular no-sql based database. It may seem like a hassle to learn another type of database querying, but as soon as you realize you don't have to mess with foreign keys and it's all essentially javascript objects, it becomes a pretty attractive option. They also have free cloud databases now which is awesome because you don't have to worry about hosting a database on your computer, but much of the security is handled on their end.&lt;br /&gt;
&lt;br /&gt;
=====Firebase=====&lt;br /&gt;
Get started [https://firebase.google.com/ Here]&lt;br /&gt;
&lt;br /&gt;
Firebase is another cloud-based no-sql option. Firebase provides everything from the database itself to real-time crash reporting and analytics. Very easy to integrate into your app and get started with.&lt;br /&gt;
&lt;br /&gt;
=====Other SQL=====&lt;br /&gt;
[https://www.postgresql.org/ PostgreSQL]&lt;br /&gt;
&lt;br /&gt;
[https://mariadb.org/ MariaDB]&lt;br /&gt;
&lt;br /&gt;
=== Structuring Your Project ===&lt;br /&gt;
Once you've outlined your project and have chosen what languages and frameworks you'd like to use, knowing where to start can be a bit daunting. We recommend a straightforward file structure similar to the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
client/&lt;br /&gt;
server/&lt;br /&gt;
config/&lt;br /&gt;
entrypoint&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Depending on what frameworks you're using, it's easy to just place your full front-end framework in the client folder, all of your back-end files in the server folder, and all of your database and other configurations in the config folder. Your entry point is just a file containing the command(s) to start everything. Whether this is a package.json file, or a python file that starts your Django server, it's nice to keep it in the root of your project.&lt;br /&gt;
&lt;br /&gt;
Have fun!&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3539</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3539"/>
		<updated>2026-11-03T14:59:18Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Go to Developer Settings */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 6]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import logging&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Configure logging to stderr&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    level=logging.INFO,&lt;br /&gt;
    format=&amp;quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;quot;,&lt;br /&gt;
    stream=sys.stderr,&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(&amp;quot;CardinalsMCP-server&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Desktop app''' tab in the left sidebar.&lt;br /&gt;
Then scroll all the way down to Extensions and select '''Manage'''.&lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run the hello tool in the chat of Claude Desktop:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to say hello&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If everything is working correctly, you should see a &amp;quot;Hello CSE 3300 Student&amp;quot; response.&lt;br /&gt;
&lt;br /&gt;
After you have implemented the MCP tools, you can run commands like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3538</id>
		<title>Node.JS</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3538"/>
		<updated>2026-10-21T22:38:56Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Installing Packages */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;AJAX is a useful technology for web applications that need to pull periodic updates from your server. But what do you do when you have thousands of clients all requesting data that changes in real-time? A PHP+AJAX approach could struggle without a lot of effort on your part.&lt;br /&gt;
&lt;br /&gt;
It is situations like these that Node.js comes to the rescue.&lt;br /&gt;
&lt;br /&gt;
Node.js is a '''JavaScript runtime''' (built on Chrome's V8 engine) that lets you run JavaScript outside the browser. Frameworks like Express run ''on top'' of Node.js. &lt;br /&gt;
&lt;br /&gt;
In general, JavaScript frameworks are trendy; it can be hard to keep up with the new cool thing. In fact, there's a joke website https://dayssincelastjavascriptframework.com/ that &amp;quot;tracks&amp;quot; how long it's been since a new framework has come out.&lt;br /&gt;
&lt;br /&gt;
== Non-Blocking I/O ==&lt;br /&gt;
&lt;br /&gt;
You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module. Node.js uses this execution model at its core.&lt;br /&gt;
&lt;br /&gt;
A simple way to think about Node:&lt;br /&gt;
&lt;br /&gt;
* Node is designed to handle lots of connections efficiently.&lt;br /&gt;
* Instead of waiting for slow operations (network, file system, database) to finish, Node starts the work and continues doing other things.&lt;br /&gt;
* When the slow operation finishes, Node runs some code to handle the result.&lt;br /&gt;
&lt;br /&gt;
Historically, the core idea was the use of '''''anonymous functions''''' as ''callbacks'' for processing the results of intensive operations. Consider the pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
var data = database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
print data[0].title;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above pseudo-code queries a database, saves the result of the query in a variable named ''data'', and then prints out the first title in ''data''. The problem arises when multiple clients all want to access the server at the same time.&lt;br /&gt;
&lt;br /&gt;
''In a blocking I/O model, a client needs to wait until all other clients are finished before it can start its process.'' With intensive operations and thousands of clients, this can easily crash your server (or force you to provision much more hardware than you want).&lt;br /&gt;
&lt;br /&gt;
The solution is to use ''non-blocking I/O''. Consider this revised pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Non-Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
database.query(&amp;quot;select * from news&amp;quot;, function(data){&lt;br /&gt;
	print data[0].title;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The revised pseudo-code achieves the same end result as the first version. However, ''it allows other clients to perform tasks while this process is waiting for the database to be queried!''&lt;br /&gt;
&lt;br /&gt;
The code occurs in two parts:&lt;br /&gt;
&lt;br /&gt;
# Start the query.&lt;br /&gt;
# ''When the query is complete,'' run the callback function.&lt;br /&gt;
&lt;br /&gt;
In Node.js, callback functions are usually passed one or more parameters.&lt;br /&gt;
&lt;br /&gt;
=== Modern Node style: Promises and async/await ===&lt;br /&gt;
&lt;br /&gt;
Callbacks are still important to understand (they explain why Node &amp;quot;works&amp;quot;), but modern Node code often uses '''Promises''' and '''async/await''' because they make programs easier to read and debug.&lt;br /&gt;
&lt;br /&gt;
Conceptually:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Promise + async/await pseudo-code&lt;br /&gt;
&lt;br /&gt;
const data = await database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
console.log(data[0].title);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is still non-blocking behind the scenes—it's just nicer syntax for “start work now, continue later when it finishes.”&lt;br /&gt;
&lt;br /&gt;
=== One thread? Concurrency? ===&lt;br /&gt;
&lt;br /&gt;
You will often hear that Node “runs in one thread.” The key point for this course is:&lt;br /&gt;
&lt;br /&gt;
* You can handle many concurrent connections without making a thread per client.&lt;br /&gt;
* The server remains responsive because it avoids blocking on slow I/O.&lt;br /&gt;
&lt;br /&gt;
(There are deeper details in the real Node event loop model, but the above understanding will serve you well for CSE 3300.)&lt;br /&gt;
&lt;br /&gt;
== Installing Node.js ==&lt;br /&gt;
&lt;br /&gt;
We will install Node using '''nvm''' (Node Version Manager). This makes it easy to switch Node versions and consistently use the current LTS version.&lt;br /&gt;
&lt;br /&gt;
Run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash&lt;br /&gt;
$ source ~/.bashrc&lt;br /&gt;
$ nvm install --lts&lt;br /&gt;
$ node -v&lt;br /&gt;
$ npm -v&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' As of October 2026, Node 24 is the Active LTS line. Using `nvm install --lts` means you do not have to hardcode a specific major version in the instructions.&lt;br /&gt;
&lt;br /&gt;
== Your First Node.js Application: Hello World ==&lt;br /&gt;
&lt;br /&gt;
Let's try the classic HTTP server example.&lt;br /&gt;
&lt;br /&gt;
=== Modern version (ES Modules) ===&lt;br /&gt;
&lt;br /&gt;
Create a file named `hello.js`:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
http.createServer((req, res) =&amp;gt; {&lt;br /&gt;
	res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
	res.end(&amp;quot;Hello World\n&amp;quot;);&lt;br /&gt;
}).listen(3456);&lt;br /&gt;
&lt;br /&gt;
console.log(&amp;quot;Server running at http://localhost:3456/&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ node hello.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' If you're running the code from your EC2 instance, you will need to open port 3456.&lt;br /&gt;
&lt;br /&gt;
=== Enabling ES Modules (important!) ===&lt;br /&gt;
&lt;br /&gt;
Modern Node supports both the older '''CommonJS''' module system (require) and the newer JavaScript standard '''ES Modules''' (import/export). ES Modules are the direction modern JavaScript has moved (and they match browser JavaScript).&lt;br /&gt;
&lt;br /&gt;
To tell Node to treat `.js` files as ES Modules, add this to `package.json` in your project directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you do not do this, Node may treat `.js` files as CommonJS by default, and `import ...` may fail.&lt;br /&gt;
&lt;br /&gt;
=== Walkthrough of the Hello World example ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;import http from &amp;quot;node:http&amp;quot;;&amp;lt;/code&amp;gt; enables us to use Node's built-in HTTP server library. Unlike PHP, Node does not automatically load lots of server features for you. You explicitly import what you need.&lt;br /&gt;
* &amp;lt;code&amp;gt;http.createServer((req, res) =&amp;amp;gt; { ... }).listen(3456);&amp;lt;/code&amp;gt; listens for HTTP connections on port 3456. The callback function runs whenever a connection is made.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&amp;lt;/code&amp;gt; sets the HTTP response code and headers.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.end(&amp;quot;Hello World\n&amp;quot;);&amp;lt;/code&amp;gt; writes the response body and ends the response.&lt;br /&gt;
&lt;br /&gt;
Notice the use of non-blocking behavior even in this simple example: ''when a connection is made'', the callback runs with the parameters associated with that connection.&lt;br /&gt;
&lt;br /&gt;
== A Static File Server Using Node.js ==&lt;br /&gt;
&lt;br /&gt;
Node.js does not automatically serve up static files in the way that Apache/PHP does. We need to either:&lt;br /&gt;
&lt;br /&gt;
* use a framework like Express (common in industry), or&lt;br /&gt;
* write a simple static file server ourselves (great for understanding how servers work)&lt;br /&gt;
&lt;br /&gt;
We'll do the second option here.&lt;br /&gt;
&lt;br /&gt;
=== Installing Packages ===&lt;br /&gt;
&lt;br /&gt;
In order for the example code below to work, you need the '''mime''' package (it helps map filenames to Content-Type headers). The `mime` package is actively maintained.&lt;br /&gt;
&lt;br /&gt;
In the directory where you will save your Node.js file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
$ npm install mime&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''npm'' is the package manager for Node.js. It installs dependencies locally (inside `node_modules`) by default, which is a good thing: it lets different projects use different versions of the same dependency.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' When using npm to install local dependencies, '''do not use sudo'''. It can break file permissions in your project directory.&lt;br /&gt;
&lt;br /&gt;
=== Static Fileserver Code (Modern ESM + async/await) ===&lt;br /&gt;
&lt;br /&gt;
The following code makes an HTTP server on port 3456 that serves all static files inside &amp;lt;STATIC DIRECTORY NAME&amp;gt; relative to app.js.&lt;br /&gt;
&lt;br /&gt;
'''Make an attempt to understand this code.''' It's important that you are comfortable with the non-blocking nature, as well as how we handle file I/O.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
import path from &amp;quot;node:path&amp;quot;;&lt;br /&gt;
import { readFile } from &amp;quot;node:fs/promises&amp;quot;;&lt;br /&gt;
import { existsSync } from &amp;quot;node:fs&amp;quot;;&lt;br /&gt;
import mime from &amp;quot;mime&amp;quot;;&lt;br /&gt;
import { fileURLToPath } from &amp;quot;node:url&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const __filename = fileURLToPath(import.meta.url);&lt;br /&gt;
const __dirname = path.dirname(__filename);&lt;br /&gt;
&lt;br /&gt;
const STATIC_DIR = &amp;quot;&amp;lt;STATIC DIRECTORY NAME&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const app = http.createServer(async (req, resp) =&amp;gt; {&lt;br /&gt;
	try {&lt;br /&gt;
		// Parse the path the browser requested&lt;br /&gt;
		const urlPath = new URL(req.url, &amp;quot;http://localhost&amp;quot;).pathname;&lt;br /&gt;
		const filename = path.join(__dirname, STATIC_DIR, decodeURIComponent(urlPath));&lt;br /&gt;
&lt;br /&gt;
		// If file doesn't exist, return 404&lt;br /&gt;
		if (!existsSync(filename)) {&lt;br /&gt;
			resp.writeHead(404, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
			resp.end(&amp;quot;Requested file not found: &amp;quot; + filename);&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Read the file (non-blocking I/O via promises)&lt;br /&gt;
		const data = await readFile(filename);&lt;br /&gt;
&lt;br /&gt;
		// Determine content type&lt;br /&gt;
		const mimetype = mime.getType(filename) || &amp;quot;application/octet-stream&amp;quot;;&lt;br /&gt;
		resp.writeHead(200, { &amp;quot;Content-Type&amp;quot;: mimetype });&lt;br /&gt;
		resp.end(data);&lt;br /&gt;
	} catch (err) {&lt;br /&gt;
		// File exists but isn't readable or some other error occurred&lt;br /&gt;
		resp.writeHead(500, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
		resp.end(&amp;quot;Internal server error: could not read file&amp;quot;);&lt;br /&gt;
	}&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
app.listen(3456);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== A few important notes about the modern version ====&lt;br /&gt;
&lt;br /&gt;
* We use &amp;lt;code&amp;gt;node:fs/promises&amp;lt;/code&amp;gt; so we can write file I/O using async/await.&lt;br /&gt;
* In ES Modules, &amp;lt;code&amp;gt;__dirname&amp;lt;/code&amp;gt; is not automatically defined, so we compute it using &amp;lt;code&amp;gt;import.meta.url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* This server is intentionally simple for instructional purposes.&lt;br /&gt;
&lt;br /&gt;
'''Security note:''' A production static server must protect against directory traversal attacks (paths like &amp;lt;code&amp;gt;../&amp;lt;/code&amp;gt;). For this course example, we focus on learning the mechanics first.&lt;br /&gt;
&lt;br /&gt;
=== Port 3456 ===&lt;br /&gt;
&lt;br /&gt;
We are running our server on port 3456 instead of port 80. We therefore need to open our Node.js application like this:&lt;br /&gt;
&lt;br /&gt;
http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/path.extension&lt;br /&gt;
&lt;br /&gt;
However, this won't work the first time. ''Why?''&lt;br /&gt;
&lt;br /&gt;
We need to open port 3456 on our Amazon EC2 instance. Remember when you opened port 80 back in Module 2? We need to do the same procedure again now.&lt;br /&gt;
&lt;br /&gt;
# Log into your EC2 management console&lt;br /&gt;
# Go to &amp;quot;Security Groups&amp;quot;&lt;br /&gt;
# Select your security group from Module 2&lt;br /&gt;
# Under &amp;quot;Inbound&amp;quot;, create a custom TCP rule for port 3456, and apply your changes.&lt;br /&gt;
&lt;br /&gt;
== Node.js Security ==&lt;br /&gt;
&lt;br /&gt;
Just because we're writing in JavaScript doesn't mean we can forget about the security practices you've been learning in CSE 330. XSS is still a problem, as is the use of ''eval'', CSRF attacks, and so on.&lt;br /&gt;
&lt;br /&gt;
== Node.js Conventions ==&lt;br /&gt;
&lt;br /&gt;
There ought to be a standard way to save metadata about your app, right? Node projects use a &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file for this.&lt;br /&gt;
&lt;br /&gt;
To create one quickly:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One nice feature of &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; is that it automatically handles dependencies. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;dependencies&amp;quot;: {&lt;br /&gt;
  &amp;quot;mime&amp;quot;: &amp;quot;^4.1.0&amp;quot;,&lt;br /&gt;
  &amp;quot;socket.io&amp;quot;: &amp;quot;^4.8.0&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then when you run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm install&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
npm installs the dependencies for you.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Modern npm automatically saves dependencies when you run &amp;lt;code&amp;gt;npm install package-name&amp;lt;/code&amp;gt;. The older &amp;lt;code&amp;gt;--save&amp;lt;/code&amp;gt; flag is no longer necessary.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3537</id>
		<title>Node.JS</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3537"/>
		<updated>2026-10-21T22:38:12Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Installing Node.js */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;AJAX is a useful technology for web applications that need to pull periodic updates from your server. But what do you do when you have thousands of clients all requesting data that changes in real-time? A PHP+AJAX approach could struggle without a lot of effort on your part.&lt;br /&gt;
&lt;br /&gt;
It is situations like these that Node.js comes to the rescue.&lt;br /&gt;
&lt;br /&gt;
Node.js is a '''JavaScript runtime''' (built on Chrome's V8 engine) that lets you run JavaScript outside the browser. Frameworks like Express run ''on top'' of Node.js. &lt;br /&gt;
&lt;br /&gt;
In general, JavaScript frameworks are trendy; it can be hard to keep up with the new cool thing. In fact, there's a joke website https://dayssincelastjavascriptframework.com/ that &amp;quot;tracks&amp;quot; how long it's been since a new framework has come out.&lt;br /&gt;
&lt;br /&gt;
== Non-Blocking I/O ==&lt;br /&gt;
&lt;br /&gt;
You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module. Node.js uses this execution model at its core.&lt;br /&gt;
&lt;br /&gt;
A simple way to think about Node:&lt;br /&gt;
&lt;br /&gt;
* Node is designed to handle lots of connections efficiently.&lt;br /&gt;
* Instead of waiting for slow operations (network, file system, database) to finish, Node starts the work and continues doing other things.&lt;br /&gt;
* When the slow operation finishes, Node runs some code to handle the result.&lt;br /&gt;
&lt;br /&gt;
Historically, the core idea was the use of '''''anonymous functions''''' as ''callbacks'' for processing the results of intensive operations. Consider the pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
var data = database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
print data[0].title;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above pseudo-code queries a database, saves the result of the query in a variable named ''data'', and then prints out the first title in ''data''. The problem arises when multiple clients all want to access the server at the same time.&lt;br /&gt;
&lt;br /&gt;
''In a blocking I/O model, a client needs to wait until all other clients are finished before it can start its process.'' With intensive operations and thousands of clients, this can easily crash your server (or force you to provision much more hardware than you want).&lt;br /&gt;
&lt;br /&gt;
The solution is to use ''non-blocking I/O''. Consider this revised pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Non-Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
database.query(&amp;quot;select * from news&amp;quot;, function(data){&lt;br /&gt;
	print data[0].title;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The revised pseudo-code achieves the same end result as the first version. However, ''it allows other clients to perform tasks while this process is waiting for the database to be queried!''&lt;br /&gt;
&lt;br /&gt;
The code occurs in two parts:&lt;br /&gt;
&lt;br /&gt;
# Start the query.&lt;br /&gt;
# ''When the query is complete,'' run the callback function.&lt;br /&gt;
&lt;br /&gt;
In Node.js, callback functions are usually passed one or more parameters.&lt;br /&gt;
&lt;br /&gt;
=== Modern Node style: Promises and async/await ===&lt;br /&gt;
&lt;br /&gt;
Callbacks are still important to understand (they explain why Node &amp;quot;works&amp;quot;), but modern Node code often uses '''Promises''' and '''async/await''' because they make programs easier to read and debug.&lt;br /&gt;
&lt;br /&gt;
Conceptually:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Promise + async/await pseudo-code&lt;br /&gt;
&lt;br /&gt;
const data = await database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
console.log(data[0].title);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is still non-blocking behind the scenes—it's just nicer syntax for “start work now, continue later when it finishes.”&lt;br /&gt;
&lt;br /&gt;
=== One thread? Concurrency? ===&lt;br /&gt;
&lt;br /&gt;
You will often hear that Node “runs in one thread.” The key point for this course is:&lt;br /&gt;
&lt;br /&gt;
* You can handle many concurrent connections without making a thread per client.&lt;br /&gt;
* The server remains responsive because it avoids blocking on slow I/O.&lt;br /&gt;
&lt;br /&gt;
(There are deeper details in the real Node event loop model, but the above understanding will serve you well for CSE 3300.)&lt;br /&gt;
&lt;br /&gt;
== Installing Node.js ==&lt;br /&gt;
&lt;br /&gt;
We will install Node using '''nvm''' (Node Version Manager). This makes it easy to switch Node versions and consistently use the current LTS version.&lt;br /&gt;
&lt;br /&gt;
Run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash&lt;br /&gt;
$ source ~/.bashrc&lt;br /&gt;
$ nvm install --lts&lt;br /&gt;
$ node -v&lt;br /&gt;
$ npm -v&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' As of October 2026, Node 24 is the Active LTS line. Using `nvm install --lts` means you do not have to hardcode a specific major version in the instructions.&lt;br /&gt;
&lt;br /&gt;
== Your First Node.js Application: Hello World ==&lt;br /&gt;
&lt;br /&gt;
Let's try the classic HTTP server example.&lt;br /&gt;
&lt;br /&gt;
=== Modern version (ES Modules) ===&lt;br /&gt;
&lt;br /&gt;
Create a file named `hello.js`:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
http.createServer((req, res) =&amp;gt; {&lt;br /&gt;
	res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
	res.end(&amp;quot;Hello World\n&amp;quot;);&lt;br /&gt;
}).listen(3456);&lt;br /&gt;
&lt;br /&gt;
console.log(&amp;quot;Server running at http://localhost:3456/&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ node hello.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' If you're running the code from your EC2 instance, you will need to open port 3456.&lt;br /&gt;
&lt;br /&gt;
=== Enabling ES Modules (important!) ===&lt;br /&gt;
&lt;br /&gt;
Modern Node supports both the older '''CommonJS''' module system (require) and the newer JavaScript standard '''ES Modules''' (import/export). ES Modules are the direction modern JavaScript has moved (and they match browser JavaScript).&lt;br /&gt;
&lt;br /&gt;
To tell Node to treat `.js` files as ES Modules, add this to `package.json` in your project directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you do not do this, Node may treat `.js` files as CommonJS by default, and `import ...` may fail.&lt;br /&gt;
&lt;br /&gt;
=== Walkthrough of the Hello World example ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;import http from &amp;quot;node:http&amp;quot;;&amp;lt;/code&amp;gt; enables us to use Node's built-in HTTP server library. Unlike PHP, Node does not automatically load lots of server features for you. You explicitly import what you need.&lt;br /&gt;
* &amp;lt;code&amp;gt;http.createServer((req, res) =&amp;amp;gt; { ... }).listen(3456);&amp;lt;/code&amp;gt; listens for HTTP connections on port 3456. The callback function runs whenever a connection is made.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&amp;lt;/code&amp;gt; sets the HTTP response code and headers.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.end(&amp;quot;Hello World\n&amp;quot;);&amp;lt;/code&amp;gt; writes the response body and ends the response.&lt;br /&gt;
&lt;br /&gt;
Notice the use of non-blocking behavior even in this simple example: ''when a connection is made'', the callback runs with the parameters associated with that connection.&lt;br /&gt;
&lt;br /&gt;
== A Static File Server Using Node.js ==&lt;br /&gt;
&lt;br /&gt;
Node.js does not automatically serve up static files in the way that Apache/PHP does. We need to either:&lt;br /&gt;
&lt;br /&gt;
* use a framework like Express (common in industry), or&lt;br /&gt;
* write a simple static file server ourselves (great for understanding how servers work)&lt;br /&gt;
&lt;br /&gt;
We'll do the second option here.&lt;br /&gt;
&lt;br /&gt;
=== Installing Packages ===&lt;br /&gt;
&lt;br /&gt;
In order for the example code below to work, you need the '''mime''' package (it helps map filenames to Content-Type headers). The `mime` package is actively maintained. :contentReference[oaicite:9]{index=9}&lt;br /&gt;
&lt;br /&gt;
In the directory where you will save your Node.js file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
$ npm install mime&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''npm'' is the package manager for Node.js. It installs dependencies locally (inside `node_modules`) by default, which is a good thing: it lets different projects use different versions of the same dependency.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' When using npm to install local dependencies, '''do not use sudo'''. It can break file permissions in your project directory.&lt;br /&gt;
&lt;br /&gt;
=== Static Fileserver Code (Modern ESM + async/await) ===&lt;br /&gt;
&lt;br /&gt;
The following code makes an HTTP server on port 3456 that serves all static files inside &amp;lt;STATIC DIRECTORY NAME&amp;gt; relative to app.js.&lt;br /&gt;
&lt;br /&gt;
'''Make an attempt to understand this code.''' It's important that you are comfortable with the non-blocking nature, as well as how we handle file I/O.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
import path from &amp;quot;node:path&amp;quot;;&lt;br /&gt;
import { readFile } from &amp;quot;node:fs/promises&amp;quot;;&lt;br /&gt;
import { existsSync } from &amp;quot;node:fs&amp;quot;;&lt;br /&gt;
import mime from &amp;quot;mime&amp;quot;;&lt;br /&gt;
import { fileURLToPath } from &amp;quot;node:url&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const __filename = fileURLToPath(import.meta.url);&lt;br /&gt;
const __dirname = path.dirname(__filename);&lt;br /&gt;
&lt;br /&gt;
const STATIC_DIR = &amp;quot;&amp;lt;STATIC DIRECTORY NAME&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const app = http.createServer(async (req, resp) =&amp;gt; {&lt;br /&gt;
	try {&lt;br /&gt;
		// Parse the path the browser requested&lt;br /&gt;
		const urlPath = new URL(req.url, &amp;quot;http://localhost&amp;quot;).pathname;&lt;br /&gt;
		const filename = path.join(__dirname, STATIC_DIR, decodeURIComponent(urlPath));&lt;br /&gt;
&lt;br /&gt;
		// If file doesn't exist, return 404&lt;br /&gt;
		if (!existsSync(filename)) {&lt;br /&gt;
			resp.writeHead(404, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
			resp.end(&amp;quot;Requested file not found: &amp;quot; + filename);&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Read the file (non-blocking I/O via promises)&lt;br /&gt;
		const data = await readFile(filename);&lt;br /&gt;
&lt;br /&gt;
		// Determine content type&lt;br /&gt;
		const mimetype = mime.getType(filename) || &amp;quot;application/octet-stream&amp;quot;;&lt;br /&gt;
		resp.writeHead(200, { &amp;quot;Content-Type&amp;quot;: mimetype });&lt;br /&gt;
		resp.end(data);&lt;br /&gt;
	} catch (err) {&lt;br /&gt;
		// File exists but isn't readable or some other error occurred&lt;br /&gt;
		resp.writeHead(500, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
		resp.end(&amp;quot;Internal server error: could not read file&amp;quot;);&lt;br /&gt;
	}&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
app.listen(3456);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== A few important notes about the modern version ====&lt;br /&gt;
&lt;br /&gt;
* We use &amp;lt;code&amp;gt;node:fs/promises&amp;lt;/code&amp;gt; so we can write file I/O using async/await.&lt;br /&gt;
* In ES Modules, &amp;lt;code&amp;gt;__dirname&amp;lt;/code&amp;gt; is not automatically defined, so we compute it using &amp;lt;code&amp;gt;import.meta.url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* This server is intentionally simple for instructional purposes.&lt;br /&gt;
&lt;br /&gt;
'''Security note:''' A production static server must protect against directory traversal attacks (paths like &amp;lt;code&amp;gt;../&amp;lt;/code&amp;gt;). For this course example, we focus on learning the mechanics first.&lt;br /&gt;
&lt;br /&gt;
=== Port 3456 ===&lt;br /&gt;
&lt;br /&gt;
We are running our server on port 3456 instead of port 80. We therefore need to open our Node.js application like this:&lt;br /&gt;
&lt;br /&gt;
http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/path.extension&lt;br /&gt;
&lt;br /&gt;
However, this won't work the first time. ''Why?''&lt;br /&gt;
&lt;br /&gt;
We need to open port 3456 on our Amazon EC2 instance. Remember when you opened port 80 back in Module 2? We need to do the same procedure again now.&lt;br /&gt;
&lt;br /&gt;
# Log into your EC2 management console&lt;br /&gt;
# Go to &amp;quot;Security Groups&amp;quot;&lt;br /&gt;
# Select your security group from Module 2&lt;br /&gt;
# Under &amp;quot;Inbound&amp;quot;, create a custom TCP rule for port 3456, and apply your changes.&lt;br /&gt;
&lt;br /&gt;
== Node.js Security ==&lt;br /&gt;
&lt;br /&gt;
Just because we're writing in JavaScript doesn't mean we can forget about the security practices you've been learning in CSE 330. XSS is still a problem, as is the use of ''eval'', CSRF attacks, and so on.&lt;br /&gt;
&lt;br /&gt;
== Node.js Conventions ==&lt;br /&gt;
&lt;br /&gt;
There ought to be a standard way to save metadata about your app, right? Node projects use a &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file for this.&lt;br /&gt;
&lt;br /&gt;
To create one quickly:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One nice feature of &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; is that it automatically handles dependencies. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;dependencies&amp;quot;: {&lt;br /&gt;
  &amp;quot;mime&amp;quot;: &amp;quot;^4.1.0&amp;quot;,&lt;br /&gt;
  &amp;quot;socket.io&amp;quot;: &amp;quot;^4.8.0&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then when you run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm install&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
npm installs the dependencies for you.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Modern npm automatically saves dependencies when you run &amp;lt;code&amp;gt;npm install package-name&amp;lt;/code&amp;gt;. The older &amp;lt;code&amp;gt;--save&amp;lt;/code&amp;gt; flag is no longer necessary.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3536</id>
		<title>Node.JS</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3536"/>
		<updated>2026-10-21T22:36:45Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Installing Node.js */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;AJAX is a useful technology for web applications that need to pull periodic updates from your server. But what do you do when you have thousands of clients all requesting data that changes in real-time? A PHP+AJAX approach could struggle without a lot of effort on your part.&lt;br /&gt;
&lt;br /&gt;
It is situations like these that Node.js comes to the rescue.&lt;br /&gt;
&lt;br /&gt;
Node.js is a '''JavaScript runtime''' (built on Chrome's V8 engine) that lets you run JavaScript outside the browser. Frameworks like Express run ''on top'' of Node.js. &lt;br /&gt;
&lt;br /&gt;
In general, JavaScript frameworks are trendy; it can be hard to keep up with the new cool thing. In fact, there's a joke website https://dayssincelastjavascriptframework.com/ that &amp;quot;tracks&amp;quot; how long it's been since a new framework has come out.&lt;br /&gt;
&lt;br /&gt;
== Non-Blocking I/O ==&lt;br /&gt;
&lt;br /&gt;
You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module. Node.js uses this execution model at its core.&lt;br /&gt;
&lt;br /&gt;
A simple way to think about Node:&lt;br /&gt;
&lt;br /&gt;
* Node is designed to handle lots of connections efficiently.&lt;br /&gt;
* Instead of waiting for slow operations (network, file system, database) to finish, Node starts the work and continues doing other things.&lt;br /&gt;
* When the slow operation finishes, Node runs some code to handle the result.&lt;br /&gt;
&lt;br /&gt;
Historically, the core idea was the use of '''''anonymous functions''''' as ''callbacks'' for processing the results of intensive operations. Consider the pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
var data = database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
print data[0].title;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above pseudo-code queries a database, saves the result of the query in a variable named ''data'', and then prints out the first title in ''data''. The problem arises when multiple clients all want to access the server at the same time.&lt;br /&gt;
&lt;br /&gt;
''In a blocking I/O model, a client needs to wait until all other clients are finished before it can start its process.'' With intensive operations and thousands of clients, this can easily crash your server (or force you to provision much more hardware than you want).&lt;br /&gt;
&lt;br /&gt;
The solution is to use ''non-blocking I/O''. Consider this revised pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Non-Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
database.query(&amp;quot;select * from news&amp;quot;, function(data){&lt;br /&gt;
	print data[0].title;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The revised pseudo-code achieves the same end result as the first version. However, ''it allows other clients to perform tasks while this process is waiting for the database to be queried!''&lt;br /&gt;
&lt;br /&gt;
The code occurs in two parts:&lt;br /&gt;
&lt;br /&gt;
# Start the query.&lt;br /&gt;
# ''When the query is complete,'' run the callback function.&lt;br /&gt;
&lt;br /&gt;
In Node.js, callback functions are usually passed one or more parameters.&lt;br /&gt;
&lt;br /&gt;
=== Modern Node style: Promises and async/await ===&lt;br /&gt;
&lt;br /&gt;
Callbacks are still important to understand (they explain why Node &amp;quot;works&amp;quot;), but modern Node code often uses '''Promises''' and '''async/await''' because they make programs easier to read and debug.&lt;br /&gt;
&lt;br /&gt;
Conceptually:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Promise + async/await pseudo-code&lt;br /&gt;
&lt;br /&gt;
const data = await database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
console.log(data[0].title);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is still non-blocking behind the scenes—it's just nicer syntax for “start work now, continue later when it finishes.”&lt;br /&gt;
&lt;br /&gt;
=== One thread? Concurrency? ===&lt;br /&gt;
&lt;br /&gt;
You will often hear that Node “runs in one thread.” The key point for this course is:&lt;br /&gt;
&lt;br /&gt;
* You can handle many concurrent connections without making a thread per client.&lt;br /&gt;
* The server remains responsive because it avoids blocking on slow I/O.&lt;br /&gt;
&lt;br /&gt;
(There are deeper details in the real Node event loop model, but the above understanding will serve you well for CSE 3300.)&lt;br /&gt;
&lt;br /&gt;
== Installing Node.js ==&lt;br /&gt;
&lt;br /&gt;
We will install Node using '''nvm''' (Node Version Manager). This makes it easy to switch Node versions and consistently use the current LTS version.&lt;br /&gt;
&lt;br /&gt;
Run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash&lt;br /&gt;
$ source ~/.bashrc&lt;br /&gt;
$ nvm install --lts&lt;br /&gt;
$ node -v&lt;br /&gt;
$ npm -v&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' As of February 2026, Node 24 is the Active LTS line. Using `nvm install --lts` means you do not have to hardcode a specific major version in the instructions.&lt;br /&gt;
&lt;br /&gt;
== Your First Node.js Application: Hello World ==&lt;br /&gt;
&lt;br /&gt;
Let's try the classic HTTP server example.&lt;br /&gt;
&lt;br /&gt;
=== Modern version (ES Modules) ===&lt;br /&gt;
&lt;br /&gt;
Create a file named `hello.js`:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
http.createServer((req, res) =&amp;gt; {&lt;br /&gt;
	res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
	res.end(&amp;quot;Hello World\n&amp;quot;);&lt;br /&gt;
}).listen(3456);&lt;br /&gt;
&lt;br /&gt;
console.log(&amp;quot;Server running at http://localhost:3456/&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ node hello.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' If you're running the code from your EC2 instance, you will need to open port 3456.&lt;br /&gt;
&lt;br /&gt;
=== Enabling ES Modules (important!) ===&lt;br /&gt;
&lt;br /&gt;
Modern Node supports both the older '''CommonJS''' module system (require) and the newer JavaScript standard '''ES Modules''' (import/export). ES Modules are the direction modern JavaScript has moved (and they match browser JavaScript).&lt;br /&gt;
&lt;br /&gt;
To tell Node to treat `.js` files as ES Modules, add this to `package.json` in your project directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you do not do this, Node may treat `.js` files as CommonJS by default, and `import ...` may fail.&lt;br /&gt;
&lt;br /&gt;
=== Walkthrough of the Hello World example ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;import http from &amp;quot;node:http&amp;quot;;&amp;lt;/code&amp;gt; enables us to use Node's built-in HTTP server library. Unlike PHP, Node does not automatically load lots of server features for you. You explicitly import what you need.&lt;br /&gt;
* &amp;lt;code&amp;gt;http.createServer((req, res) =&amp;amp;gt; { ... }).listen(3456);&amp;lt;/code&amp;gt; listens for HTTP connections on port 3456. The callback function runs whenever a connection is made.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&amp;lt;/code&amp;gt; sets the HTTP response code and headers.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.end(&amp;quot;Hello World\n&amp;quot;);&amp;lt;/code&amp;gt; writes the response body and ends the response.&lt;br /&gt;
&lt;br /&gt;
Notice the use of non-blocking behavior even in this simple example: ''when a connection is made'', the callback runs with the parameters associated with that connection.&lt;br /&gt;
&lt;br /&gt;
== A Static File Server Using Node.js ==&lt;br /&gt;
&lt;br /&gt;
Node.js does not automatically serve up static files in the way that Apache/PHP does. We need to either:&lt;br /&gt;
&lt;br /&gt;
* use a framework like Express (common in industry), or&lt;br /&gt;
* write a simple static file server ourselves (great for understanding how servers work)&lt;br /&gt;
&lt;br /&gt;
We'll do the second option here.&lt;br /&gt;
&lt;br /&gt;
=== Installing Packages ===&lt;br /&gt;
&lt;br /&gt;
In order for the example code below to work, you need the '''mime''' package (it helps map filenames to Content-Type headers). The `mime` package is actively maintained. :contentReference[oaicite:9]{index=9}&lt;br /&gt;
&lt;br /&gt;
In the directory where you will save your Node.js file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
$ npm install mime&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''npm'' is the package manager for Node.js. It installs dependencies locally (inside `node_modules`) by default, which is a good thing: it lets different projects use different versions of the same dependency.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' When using npm to install local dependencies, '''do not use sudo'''. It can break file permissions in your project directory.&lt;br /&gt;
&lt;br /&gt;
=== Static Fileserver Code (Modern ESM + async/await) ===&lt;br /&gt;
&lt;br /&gt;
The following code makes an HTTP server on port 3456 that serves all static files inside &amp;lt;STATIC DIRECTORY NAME&amp;gt; relative to app.js.&lt;br /&gt;
&lt;br /&gt;
'''Make an attempt to understand this code.''' It's important that you are comfortable with the non-blocking nature, as well as how we handle file I/O.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
import path from &amp;quot;node:path&amp;quot;;&lt;br /&gt;
import { readFile } from &amp;quot;node:fs/promises&amp;quot;;&lt;br /&gt;
import { existsSync } from &amp;quot;node:fs&amp;quot;;&lt;br /&gt;
import mime from &amp;quot;mime&amp;quot;;&lt;br /&gt;
import { fileURLToPath } from &amp;quot;node:url&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const __filename = fileURLToPath(import.meta.url);&lt;br /&gt;
const __dirname = path.dirname(__filename);&lt;br /&gt;
&lt;br /&gt;
const STATIC_DIR = &amp;quot;&amp;lt;STATIC DIRECTORY NAME&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const app = http.createServer(async (req, resp) =&amp;gt; {&lt;br /&gt;
	try {&lt;br /&gt;
		// Parse the path the browser requested&lt;br /&gt;
		const urlPath = new URL(req.url, &amp;quot;http://localhost&amp;quot;).pathname;&lt;br /&gt;
		const filename = path.join(__dirname, STATIC_DIR, decodeURIComponent(urlPath));&lt;br /&gt;
&lt;br /&gt;
		// If file doesn't exist, return 404&lt;br /&gt;
		if (!existsSync(filename)) {&lt;br /&gt;
			resp.writeHead(404, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
			resp.end(&amp;quot;Requested file not found: &amp;quot; + filename);&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Read the file (non-blocking I/O via promises)&lt;br /&gt;
		const data = await readFile(filename);&lt;br /&gt;
&lt;br /&gt;
		// Determine content type&lt;br /&gt;
		const mimetype = mime.getType(filename) || &amp;quot;application/octet-stream&amp;quot;;&lt;br /&gt;
		resp.writeHead(200, { &amp;quot;Content-Type&amp;quot;: mimetype });&lt;br /&gt;
		resp.end(data);&lt;br /&gt;
	} catch (err) {&lt;br /&gt;
		// File exists but isn't readable or some other error occurred&lt;br /&gt;
		resp.writeHead(500, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
		resp.end(&amp;quot;Internal server error: could not read file&amp;quot;);&lt;br /&gt;
	}&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
app.listen(3456);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== A few important notes about the modern version ====&lt;br /&gt;
&lt;br /&gt;
* We use &amp;lt;code&amp;gt;node:fs/promises&amp;lt;/code&amp;gt; so we can write file I/O using async/await.&lt;br /&gt;
* In ES Modules, &amp;lt;code&amp;gt;__dirname&amp;lt;/code&amp;gt; is not automatically defined, so we compute it using &amp;lt;code&amp;gt;import.meta.url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* This server is intentionally simple for instructional purposes.&lt;br /&gt;
&lt;br /&gt;
'''Security note:''' A production static server must protect against directory traversal attacks (paths like &amp;lt;code&amp;gt;../&amp;lt;/code&amp;gt;). For this course example, we focus on learning the mechanics first.&lt;br /&gt;
&lt;br /&gt;
=== Port 3456 ===&lt;br /&gt;
&lt;br /&gt;
We are running our server on port 3456 instead of port 80. We therefore need to open our Node.js application like this:&lt;br /&gt;
&lt;br /&gt;
http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/path.extension&lt;br /&gt;
&lt;br /&gt;
However, this won't work the first time. ''Why?''&lt;br /&gt;
&lt;br /&gt;
We need to open port 3456 on our Amazon EC2 instance. Remember when you opened port 80 back in Module 2? We need to do the same procedure again now.&lt;br /&gt;
&lt;br /&gt;
# Log into your EC2 management console&lt;br /&gt;
# Go to &amp;quot;Security Groups&amp;quot;&lt;br /&gt;
# Select your security group from Module 2&lt;br /&gt;
# Under &amp;quot;Inbound&amp;quot;, create a custom TCP rule for port 3456, and apply your changes.&lt;br /&gt;
&lt;br /&gt;
== Node.js Security ==&lt;br /&gt;
&lt;br /&gt;
Just because we're writing in JavaScript doesn't mean we can forget about the security practices you've been learning in CSE 330. XSS is still a problem, as is the use of ''eval'', CSRF attacks, and so on.&lt;br /&gt;
&lt;br /&gt;
== Node.js Conventions ==&lt;br /&gt;
&lt;br /&gt;
There ought to be a standard way to save metadata about your app, right? Node projects use a &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file for this.&lt;br /&gt;
&lt;br /&gt;
To create one quickly:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One nice feature of &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; is that it automatically handles dependencies. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;dependencies&amp;quot;: {&lt;br /&gt;
  &amp;quot;mime&amp;quot;: &amp;quot;^4.1.0&amp;quot;,&lt;br /&gt;
  &amp;quot;socket.io&amp;quot;: &amp;quot;^4.8.0&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then when you run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm install&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
npm installs the dependencies for you.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Modern npm automatically saves dependencies when you run &amp;lt;code&amp;gt;npm install package-name&amp;lt;/code&amp;gt;. The older &amp;lt;code&amp;gt;--save&amp;lt;/code&amp;gt; flag is no longer necessary.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3535</id>
		<title>Node.JS</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3535"/>
		<updated>2026-10-21T22:34:37Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Enabling ES Modules (important!) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;AJAX is a useful technology for web applications that need to pull periodic updates from your server. But what do you do when you have thousands of clients all requesting data that changes in real-time? A PHP+AJAX approach could struggle without a lot of effort on your part.&lt;br /&gt;
&lt;br /&gt;
It is situations like these that Node.js comes to the rescue.&lt;br /&gt;
&lt;br /&gt;
Node.js is a '''JavaScript runtime''' (built on Chrome's V8 engine) that lets you run JavaScript outside the browser. Frameworks like Express run ''on top'' of Node.js. &lt;br /&gt;
&lt;br /&gt;
In general, JavaScript frameworks are trendy; it can be hard to keep up with the new cool thing. In fact, there's a joke website https://dayssincelastjavascriptframework.com/ that &amp;quot;tracks&amp;quot; how long it's been since a new framework has come out.&lt;br /&gt;
&lt;br /&gt;
== Non-Blocking I/O ==&lt;br /&gt;
&lt;br /&gt;
You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module. Node.js uses this execution model at its core.&lt;br /&gt;
&lt;br /&gt;
A simple way to think about Node:&lt;br /&gt;
&lt;br /&gt;
* Node is designed to handle lots of connections efficiently.&lt;br /&gt;
* Instead of waiting for slow operations (network, file system, database) to finish, Node starts the work and continues doing other things.&lt;br /&gt;
* When the slow operation finishes, Node runs some code to handle the result.&lt;br /&gt;
&lt;br /&gt;
Historically, the core idea was the use of '''''anonymous functions''''' as ''callbacks'' for processing the results of intensive operations. Consider the pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
var data = database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
print data[0].title;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above pseudo-code queries a database, saves the result of the query in a variable named ''data'', and then prints out the first title in ''data''. The problem arises when multiple clients all want to access the server at the same time.&lt;br /&gt;
&lt;br /&gt;
''In a blocking I/O model, a client needs to wait until all other clients are finished before it can start its process.'' With intensive operations and thousands of clients, this can easily crash your server (or force you to provision much more hardware than you want).&lt;br /&gt;
&lt;br /&gt;
The solution is to use ''non-blocking I/O''. Consider this revised pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Non-Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
database.query(&amp;quot;select * from news&amp;quot;, function(data){&lt;br /&gt;
	print data[0].title;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The revised pseudo-code achieves the same end result as the first version. However, ''it allows other clients to perform tasks while this process is waiting for the database to be queried!''&lt;br /&gt;
&lt;br /&gt;
The code occurs in two parts:&lt;br /&gt;
&lt;br /&gt;
# Start the query.&lt;br /&gt;
# ''When the query is complete,'' run the callback function.&lt;br /&gt;
&lt;br /&gt;
In Node.js, callback functions are usually passed one or more parameters.&lt;br /&gt;
&lt;br /&gt;
=== Modern Node style: Promises and async/await ===&lt;br /&gt;
&lt;br /&gt;
Callbacks are still important to understand (they explain why Node &amp;quot;works&amp;quot;), but modern Node code often uses '''Promises''' and '''async/await''' because they make programs easier to read and debug.&lt;br /&gt;
&lt;br /&gt;
Conceptually:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Promise + async/await pseudo-code&lt;br /&gt;
&lt;br /&gt;
const data = await database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
console.log(data[0].title);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is still non-blocking behind the scenes—it's just nicer syntax for “start work now, continue later when it finishes.”&lt;br /&gt;
&lt;br /&gt;
=== One thread? Concurrency? ===&lt;br /&gt;
&lt;br /&gt;
You will often hear that Node “runs in one thread.” The key point for this course is:&lt;br /&gt;
&lt;br /&gt;
* You can handle many concurrent connections without making a thread per client.&lt;br /&gt;
* The server remains responsive because it avoids blocking on slow I/O.&lt;br /&gt;
&lt;br /&gt;
(There are deeper details in the real Node event loop model, but the above understanding will serve you well for CSE 3300.)&lt;br /&gt;
&lt;br /&gt;
== Installing Node.js ==&lt;br /&gt;
&lt;br /&gt;
We will install Node using '''nvm''' (Node Version Manager). This makes it easy to switch Node versions and consistently use the current LTS version. :contentReference[oaicite:4]{index=4}&lt;br /&gt;
&lt;br /&gt;
Run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash&lt;br /&gt;
$ source ~/.bashrc&lt;br /&gt;
$ nvm install --lts&lt;br /&gt;
$ node -v&lt;br /&gt;
$ npm -v&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' As of February 2026, Node 24 is the Active LTS line. Using `nvm install --lts` means you do not have to hardcode a specific major version in the instructions. :contentReference[oaicite:6]{index=6}&lt;br /&gt;
&lt;br /&gt;
== Your First Node.js Application: Hello World ==&lt;br /&gt;
&lt;br /&gt;
Let's try the classic HTTP server example.&lt;br /&gt;
&lt;br /&gt;
=== Modern version (ES Modules) ===&lt;br /&gt;
&lt;br /&gt;
Create a file named `hello.js`:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
http.createServer((req, res) =&amp;gt; {&lt;br /&gt;
	res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
	res.end(&amp;quot;Hello World\n&amp;quot;);&lt;br /&gt;
}).listen(3456);&lt;br /&gt;
&lt;br /&gt;
console.log(&amp;quot;Server running at http://localhost:3456/&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ node hello.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' If you're running the code from your EC2 instance, you will need to open port 3456.&lt;br /&gt;
&lt;br /&gt;
=== Enabling ES Modules (important!) ===&lt;br /&gt;
&lt;br /&gt;
Modern Node supports both the older '''CommonJS''' module system (require) and the newer JavaScript standard '''ES Modules''' (import/export). ES Modules are the direction modern JavaScript has moved (and they match browser JavaScript).&lt;br /&gt;
&lt;br /&gt;
To tell Node to treat `.js` files as ES Modules, add this to `package.json` in your project directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you do not do this, Node may treat `.js` files as CommonJS by default, and `import ...` may fail.&lt;br /&gt;
&lt;br /&gt;
=== Walkthrough of the Hello World example ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;import http from &amp;quot;node:http&amp;quot;;&amp;lt;/code&amp;gt; enables us to use Node's built-in HTTP server library. Unlike PHP, Node does not automatically load lots of server features for you. You explicitly import what you need.&lt;br /&gt;
* &amp;lt;code&amp;gt;http.createServer((req, res) =&amp;amp;gt; { ... }).listen(3456);&amp;lt;/code&amp;gt; listens for HTTP connections on port 3456. The callback function runs whenever a connection is made.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&amp;lt;/code&amp;gt; sets the HTTP response code and headers.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.end(&amp;quot;Hello World\n&amp;quot;);&amp;lt;/code&amp;gt; writes the response body and ends the response.&lt;br /&gt;
&lt;br /&gt;
Notice the use of non-blocking behavior even in this simple example: ''when a connection is made'', the callback runs with the parameters associated with that connection.&lt;br /&gt;
&lt;br /&gt;
== A Static File Server Using Node.js ==&lt;br /&gt;
&lt;br /&gt;
Node.js does not automatically serve up static files in the way that Apache/PHP does. We need to either:&lt;br /&gt;
&lt;br /&gt;
* use a framework like Express (common in industry), or&lt;br /&gt;
* write a simple static file server ourselves (great for understanding how servers work)&lt;br /&gt;
&lt;br /&gt;
We'll do the second option here.&lt;br /&gt;
&lt;br /&gt;
=== Installing Packages ===&lt;br /&gt;
&lt;br /&gt;
In order for the example code below to work, you need the '''mime''' package (it helps map filenames to Content-Type headers). The `mime` package is actively maintained. :contentReference[oaicite:9]{index=9}&lt;br /&gt;
&lt;br /&gt;
In the directory where you will save your Node.js file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
$ npm install mime&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''npm'' is the package manager for Node.js. It installs dependencies locally (inside `node_modules`) by default, which is a good thing: it lets different projects use different versions of the same dependency.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' When using npm to install local dependencies, '''do not use sudo'''. It can break file permissions in your project directory.&lt;br /&gt;
&lt;br /&gt;
=== Static Fileserver Code (Modern ESM + async/await) ===&lt;br /&gt;
&lt;br /&gt;
The following code makes an HTTP server on port 3456 that serves all static files inside &amp;lt;STATIC DIRECTORY NAME&amp;gt; relative to app.js.&lt;br /&gt;
&lt;br /&gt;
'''Make an attempt to understand this code.''' It's important that you are comfortable with the non-blocking nature, as well as how we handle file I/O.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
import path from &amp;quot;node:path&amp;quot;;&lt;br /&gt;
import { readFile } from &amp;quot;node:fs/promises&amp;quot;;&lt;br /&gt;
import { existsSync } from &amp;quot;node:fs&amp;quot;;&lt;br /&gt;
import mime from &amp;quot;mime&amp;quot;;&lt;br /&gt;
import { fileURLToPath } from &amp;quot;node:url&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const __filename = fileURLToPath(import.meta.url);&lt;br /&gt;
const __dirname = path.dirname(__filename);&lt;br /&gt;
&lt;br /&gt;
const STATIC_DIR = &amp;quot;&amp;lt;STATIC DIRECTORY NAME&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const app = http.createServer(async (req, resp) =&amp;gt; {&lt;br /&gt;
	try {&lt;br /&gt;
		// Parse the path the browser requested&lt;br /&gt;
		const urlPath = new URL(req.url, &amp;quot;http://localhost&amp;quot;).pathname;&lt;br /&gt;
		const filename = path.join(__dirname, STATIC_DIR, decodeURIComponent(urlPath));&lt;br /&gt;
&lt;br /&gt;
		// If file doesn't exist, return 404&lt;br /&gt;
		if (!existsSync(filename)) {&lt;br /&gt;
			resp.writeHead(404, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
			resp.end(&amp;quot;Requested file not found: &amp;quot; + filename);&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Read the file (non-blocking I/O via promises)&lt;br /&gt;
		const data = await readFile(filename);&lt;br /&gt;
&lt;br /&gt;
		// Determine content type&lt;br /&gt;
		const mimetype = mime.getType(filename) || &amp;quot;application/octet-stream&amp;quot;;&lt;br /&gt;
		resp.writeHead(200, { &amp;quot;Content-Type&amp;quot;: mimetype });&lt;br /&gt;
		resp.end(data);&lt;br /&gt;
	} catch (err) {&lt;br /&gt;
		// File exists but isn't readable or some other error occurred&lt;br /&gt;
		resp.writeHead(500, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
		resp.end(&amp;quot;Internal server error: could not read file&amp;quot;);&lt;br /&gt;
	}&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
app.listen(3456);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== A few important notes about the modern version ====&lt;br /&gt;
&lt;br /&gt;
* We use &amp;lt;code&amp;gt;node:fs/promises&amp;lt;/code&amp;gt; so we can write file I/O using async/await.&lt;br /&gt;
* In ES Modules, &amp;lt;code&amp;gt;__dirname&amp;lt;/code&amp;gt; is not automatically defined, so we compute it using &amp;lt;code&amp;gt;import.meta.url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* This server is intentionally simple for instructional purposes.&lt;br /&gt;
&lt;br /&gt;
'''Security note:''' A production static server must protect against directory traversal attacks (paths like &amp;lt;code&amp;gt;../&amp;lt;/code&amp;gt;). For this course example, we focus on learning the mechanics first.&lt;br /&gt;
&lt;br /&gt;
=== Port 3456 ===&lt;br /&gt;
&lt;br /&gt;
We are running our server on port 3456 instead of port 80. We therefore need to open our Node.js application like this:&lt;br /&gt;
&lt;br /&gt;
http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/path.extension&lt;br /&gt;
&lt;br /&gt;
However, this won't work the first time. ''Why?''&lt;br /&gt;
&lt;br /&gt;
We need to open port 3456 on our Amazon EC2 instance. Remember when you opened port 80 back in Module 2? We need to do the same procedure again now.&lt;br /&gt;
&lt;br /&gt;
# Log into your EC2 management console&lt;br /&gt;
# Go to &amp;quot;Security Groups&amp;quot;&lt;br /&gt;
# Select your security group from Module 2&lt;br /&gt;
# Under &amp;quot;Inbound&amp;quot;, create a custom TCP rule for port 3456, and apply your changes.&lt;br /&gt;
&lt;br /&gt;
== Node.js Security ==&lt;br /&gt;
&lt;br /&gt;
Just because we're writing in JavaScript doesn't mean we can forget about the security practices you've been learning in CSE 330. XSS is still a problem, as is the use of ''eval'', CSRF attacks, and so on.&lt;br /&gt;
&lt;br /&gt;
== Node.js Conventions ==&lt;br /&gt;
&lt;br /&gt;
There ought to be a standard way to save metadata about your app, right? Node projects use a &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file for this.&lt;br /&gt;
&lt;br /&gt;
To create one quickly:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One nice feature of &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; is that it automatically handles dependencies. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;dependencies&amp;quot;: {&lt;br /&gt;
  &amp;quot;mime&amp;quot;: &amp;quot;^4.1.0&amp;quot;,&lt;br /&gt;
  &amp;quot;socket.io&amp;quot;: &amp;quot;^4.8.0&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then when you run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm install&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
npm installs the dependencies for you.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Modern npm automatically saves dependencies when you run &amp;lt;code&amp;gt;npm install package-name&amp;lt;/code&amp;gt;. The older &amp;lt;code&amp;gt;--save&amp;lt;/code&amp;gt; flag is no longer necessary.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3534</id>
		<title>Node.JS</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3534"/>
		<updated>2026-10-21T22:34:24Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Enabling ES Modules (important!) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;AJAX is a useful technology for web applications that need to pull periodic updates from your server. But what do you do when you have thousands of clients all requesting data that changes in real-time? A PHP+AJAX approach could struggle without a lot of effort on your part.&lt;br /&gt;
&lt;br /&gt;
It is situations like these that Node.js comes to the rescue.&lt;br /&gt;
&lt;br /&gt;
Node.js is a '''JavaScript runtime''' (built on Chrome's V8 engine) that lets you run JavaScript outside the browser. Frameworks like Express run ''on top'' of Node.js. &lt;br /&gt;
&lt;br /&gt;
In general, JavaScript frameworks are trendy; it can be hard to keep up with the new cool thing. In fact, there's a joke website https://dayssincelastjavascriptframework.com/ that &amp;quot;tracks&amp;quot; how long it's been since a new framework has come out.&lt;br /&gt;
&lt;br /&gt;
== Non-Blocking I/O ==&lt;br /&gt;
&lt;br /&gt;
You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module. Node.js uses this execution model at its core.&lt;br /&gt;
&lt;br /&gt;
A simple way to think about Node:&lt;br /&gt;
&lt;br /&gt;
* Node is designed to handle lots of connections efficiently.&lt;br /&gt;
* Instead of waiting for slow operations (network, file system, database) to finish, Node starts the work and continues doing other things.&lt;br /&gt;
* When the slow operation finishes, Node runs some code to handle the result.&lt;br /&gt;
&lt;br /&gt;
Historically, the core idea was the use of '''''anonymous functions''''' as ''callbacks'' for processing the results of intensive operations. Consider the pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
var data = database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
print data[0].title;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above pseudo-code queries a database, saves the result of the query in a variable named ''data'', and then prints out the first title in ''data''. The problem arises when multiple clients all want to access the server at the same time.&lt;br /&gt;
&lt;br /&gt;
''In a blocking I/O model, a client needs to wait until all other clients are finished before it can start its process.'' With intensive operations and thousands of clients, this can easily crash your server (or force you to provision much more hardware than you want).&lt;br /&gt;
&lt;br /&gt;
The solution is to use ''non-blocking I/O''. Consider this revised pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Non-Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
database.query(&amp;quot;select * from news&amp;quot;, function(data){&lt;br /&gt;
	print data[0].title;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The revised pseudo-code achieves the same end result as the first version. However, ''it allows other clients to perform tasks while this process is waiting for the database to be queried!''&lt;br /&gt;
&lt;br /&gt;
The code occurs in two parts:&lt;br /&gt;
&lt;br /&gt;
# Start the query.&lt;br /&gt;
# ''When the query is complete,'' run the callback function.&lt;br /&gt;
&lt;br /&gt;
In Node.js, callback functions are usually passed one or more parameters.&lt;br /&gt;
&lt;br /&gt;
=== Modern Node style: Promises and async/await ===&lt;br /&gt;
&lt;br /&gt;
Callbacks are still important to understand (they explain why Node &amp;quot;works&amp;quot;), but modern Node code often uses '''Promises''' and '''async/await''' because they make programs easier to read and debug.&lt;br /&gt;
&lt;br /&gt;
Conceptually:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Promise + async/await pseudo-code&lt;br /&gt;
&lt;br /&gt;
const data = await database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
console.log(data[0].title);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is still non-blocking behind the scenes—it's just nicer syntax for “start work now, continue later when it finishes.”&lt;br /&gt;
&lt;br /&gt;
=== One thread? Concurrency? ===&lt;br /&gt;
&lt;br /&gt;
You will often hear that Node “runs in one thread.” The key point for this course is:&lt;br /&gt;
&lt;br /&gt;
* You can handle many concurrent connections without making a thread per client.&lt;br /&gt;
* The server remains responsive because it avoids blocking on slow I/O.&lt;br /&gt;
&lt;br /&gt;
(There are deeper details in the real Node event loop model, but the above understanding will serve you well for CSE 3300.)&lt;br /&gt;
&lt;br /&gt;
== Installing Node.js ==&lt;br /&gt;
&lt;br /&gt;
We will install Node using '''nvm''' (Node Version Manager). This makes it easy to switch Node versions and consistently use the current LTS version. :contentReference[oaicite:4]{index=4}&lt;br /&gt;
&lt;br /&gt;
Run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash&lt;br /&gt;
$ source ~/.bashrc&lt;br /&gt;
$ nvm install --lts&lt;br /&gt;
$ node -v&lt;br /&gt;
$ npm -v&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' As of February 2026, Node 24 is the Active LTS line. Using `nvm install --lts` means you do not have to hardcode a specific major version in the instructions. :contentReference[oaicite:6]{index=6}&lt;br /&gt;
&lt;br /&gt;
== Your First Node.js Application: Hello World ==&lt;br /&gt;
&lt;br /&gt;
Let's try the classic HTTP server example.&lt;br /&gt;
&lt;br /&gt;
=== Modern version (ES Modules) ===&lt;br /&gt;
&lt;br /&gt;
Create a file named `hello.js`:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
http.createServer((req, res) =&amp;gt; {&lt;br /&gt;
	res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
	res.end(&amp;quot;Hello World\n&amp;quot;);&lt;br /&gt;
}).listen(3456);&lt;br /&gt;
&lt;br /&gt;
console.log(&amp;quot;Server running at http://localhost:3456/&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ node hello.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' If you're running the code from your EC2 instance, you will need to open port 3456.&lt;br /&gt;
&lt;br /&gt;
=== Enabling ES Modules (important!) ===&lt;br /&gt;
&lt;br /&gt;
Modern Node supports both the older '''CommonJS''' module system (require) and the newer JavaScript standard '''ES Modules''' (import/export). ES Modules are the direction modern JavaScript has moved (and they match browser JavaScript).&lt;br /&gt;
&lt;br /&gt;
To tell Node to treat `.js` files as ES Modules, add this to `package.json` in your project directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
:contentReference[oaicite:8]{index=8}&lt;br /&gt;
&lt;br /&gt;
If you do not do this, Node may treat `.js` files as CommonJS by default, and `import ...` may fail.&lt;br /&gt;
&lt;br /&gt;
=== Walkthrough of the Hello World example ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;import http from &amp;quot;node:http&amp;quot;;&amp;lt;/code&amp;gt; enables us to use Node's built-in HTTP server library. Unlike PHP, Node does not automatically load lots of server features for you. You explicitly import what you need.&lt;br /&gt;
* &amp;lt;code&amp;gt;http.createServer((req, res) =&amp;amp;gt; { ... }).listen(3456);&amp;lt;/code&amp;gt; listens for HTTP connections on port 3456. The callback function runs whenever a connection is made.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&amp;lt;/code&amp;gt; sets the HTTP response code and headers.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.end(&amp;quot;Hello World\n&amp;quot;);&amp;lt;/code&amp;gt; writes the response body and ends the response.&lt;br /&gt;
&lt;br /&gt;
Notice the use of non-blocking behavior even in this simple example: ''when a connection is made'', the callback runs with the parameters associated with that connection.&lt;br /&gt;
&lt;br /&gt;
== A Static File Server Using Node.js ==&lt;br /&gt;
&lt;br /&gt;
Node.js does not automatically serve up static files in the way that Apache/PHP does. We need to either:&lt;br /&gt;
&lt;br /&gt;
* use a framework like Express (common in industry), or&lt;br /&gt;
* write a simple static file server ourselves (great for understanding how servers work)&lt;br /&gt;
&lt;br /&gt;
We'll do the second option here.&lt;br /&gt;
&lt;br /&gt;
=== Installing Packages ===&lt;br /&gt;
&lt;br /&gt;
In order for the example code below to work, you need the '''mime''' package (it helps map filenames to Content-Type headers). The `mime` package is actively maintained. :contentReference[oaicite:9]{index=9}&lt;br /&gt;
&lt;br /&gt;
In the directory where you will save your Node.js file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
$ npm install mime&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''npm'' is the package manager for Node.js. It installs dependencies locally (inside `node_modules`) by default, which is a good thing: it lets different projects use different versions of the same dependency.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' When using npm to install local dependencies, '''do not use sudo'''. It can break file permissions in your project directory.&lt;br /&gt;
&lt;br /&gt;
=== Static Fileserver Code (Modern ESM + async/await) ===&lt;br /&gt;
&lt;br /&gt;
The following code makes an HTTP server on port 3456 that serves all static files inside &amp;lt;STATIC DIRECTORY NAME&amp;gt; relative to app.js.&lt;br /&gt;
&lt;br /&gt;
'''Make an attempt to understand this code.''' It's important that you are comfortable with the non-blocking nature, as well as how we handle file I/O.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
import path from &amp;quot;node:path&amp;quot;;&lt;br /&gt;
import { readFile } from &amp;quot;node:fs/promises&amp;quot;;&lt;br /&gt;
import { existsSync } from &amp;quot;node:fs&amp;quot;;&lt;br /&gt;
import mime from &amp;quot;mime&amp;quot;;&lt;br /&gt;
import { fileURLToPath } from &amp;quot;node:url&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const __filename = fileURLToPath(import.meta.url);&lt;br /&gt;
const __dirname = path.dirname(__filename);&lt;br /&gt;
&lt;br /&gt;
const STATIC_DIR = &amp;quot;&amp;lt;STATIC DIRECTORY NAME&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const app = http.createServer(async (req, resp) =&amp;gt; {&lt;br /&gt;
	try {&lt;br /&gt;
		// Parse the path the browser requested&lt;br /&gt;
		const urlPath = new URL(req.url, &amp;quot;http://localhost&amp;quot;).pathname;&lt;br /&gt;
		const filename = path.join(__dirname, STATIC_DIR, decodeURIComponent(urlPath));&lt;br /&gt;
&lt;br /&gt;
		// If file doesn't exist, return 404&lt;br /&gt;
		if (!existsSync(filename)) {&lt;br /&gt;
			resp.writeHead(404, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
			resp.end(&amp;quot;Requested file not found: &amp;quot; + filename);&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Read the file (non-blocking I/O via promises)&lt;br /&gt;
		const data = await readFile(filename);&lt;br /&gt;
&lt;br /&gt;
		// Determine content type&lt;br /&gt;
		const mimetype = mime.getType(filename) || &amp;quot;application/octet-stream&amp;quot;;&lt;br /&gt;
		resp.writeHead(200, { &amp;quot;Content-Type&amp;quot;: mimetype });&lt;br /&gt;
		resp.end(data);&lt;br /&gt;
	} catch (err) {&lt;br /&gt;
		// File exists but isn't readable or some other error occurred&lt;br /&gt;
		resp.writeHead(500, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
		resp.end(&amp;quot;Internal server error: could not read file&amp;quot;);&lt;br /&gt;
	}&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
app.listen(3456);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== A few important notes about the modern version ====&lt;br /&gt;
&lt;br /&gt;
* We use &amp;lt;code&amp;gt;node:fs/promises&amp;lt;/code&amp;gt; so we can write file I/O using async/await.&lt;br /&gt;
* In ES Modules, &amp;lt;code&amp;gt;__dirname&amp;lt;/code&amp;gt; is not automatically defined, so we compute it using &amp;lt;code&amp;gt;import.meta.url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* This server is intentionally simple for instructional purposes.&lt;br /&gt;
&lt;br /&gt;
'''Security note:''' A production static server must protect against directory traversal attacks (paths like &amp;lt;code&amp;gt;../&amp;lt;/code&amp;gt;). For this course example, we focus on learning the mechanics first.&lt;br /&gt;
&lt;br /&gt;
=== Port 3456 ===&lt;br /&gt;
&lt;br /&gt;
We are running our server on port 3456 instead of port 80. We therefore need to open our Node.js application like this:&lt;br /&gt;
&lt;br /&gt;
http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/path.extension&lt;br /&gt;
&lt;br /&gt;
However, this won't work the first time. ''Why?''&lt;br /&gt;
&lt;br /&gt;
We need to open port 3456 on our Amazon EC2 instance. Remember when you opened port 80 back in Module 2? We need to do the same procedure again now.&lt;br /&gt;
&lt;br /&gt;
# Log into your EC2 management console&lt;br /&gt;
# Go to &amp;quot;Security Groups&amp;quot;&lt;br /&gt;
# Select your security group from Module 2&lt;br /&gt;
# Under &amp;quot;Inbound&amp;quot;, create a custom TCP rule for port 3456, and apply your changes.&lt;br /&gt;
&lt;br /&gt;
== Node.js Security ==&lt;br /&gt;
&lt;br /&gt;
Just because we're writing in JavaScript doesn't mean we can forget about the security practices you've been learning in CSE 330. XSS is still a problem, as is the use of ''eval'', CSRF attacks, and so on.&lt;br /&gt;
&lt;br /&gt;
== Node.js Conventions ==&lt;br /&gt;
&lt;br /&gt;
There ought to be a standard way to save metadata about your app, right? Node projects use a &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file for this.&lt;br /&gt;
&lt;br /&gt;
To create one quickly:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One nice feature of &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; is that it automatically handles dependencies. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;dependencies&amp;quot;: {&lt;br /&gt;
  &amp;quot;mime&amp;quot;: &amp;quot;^4.1.0&amp;quot;,&lt;br /&gt;
  &amp;quot;socket.io&amp;quot;: &amp;quot;^4.8.0&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then when you run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm install&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
npm installs the dependencies for you.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Modern npm automatically saves dependencies when you run &amp;lt;code&amp;gt;npm install package-name&amp;lt;/code&amp;gt;. The older &amp;lt;code&amp;gt;--save&amp;lt;/code&amp;gt; flag is no longer necessary.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3533</id>
		<title>Node.JS</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Node.JS&amp;diff=3533"/>
		<updated>2026-10-21T22:33:58Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Modern Node style: Promises and async/await */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;AJAX is a useful technology for web applications that need to pull periodic updates from your server. But what do you do when you have thousands of clients all requesting data that changes in real-time? A PHP+AJAX approach could struggle without a lot of effort on your part.&lt;br /&gt;
&lt;br /&gt;
It is situations like these that Node.js comes to the rescue.&lt;br /&gt;
&lt;br /&gt;
Node.js is a '''JavaScript runtime''' (built on Chrome's V8 engine) that lets you run JavaScript outside the browser. Frameworks like Express run ''on top'' of Node.js. &lt;br /&gt;
&lt;br /&gt;
In general, JavaScript frameworks are trendy; it can be hard to keep up with the new cool thing. In fact, there's a joke website https://dayssincelastjavascriptframework.com/ that &amp;quot;tracks&amp;quot; how long it's been since a new framework has come out.&lt;br /&gt;
&lt;br /&gt;
== Non-Blocking I/O ==&lt;br /&gt;
&lt;br /&gt;
You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module. Node.js uses this execution model at its core.&lt;br /&gt;
&lt;br /&gt;
A simple way to think about Node:&lt;br /&gt;
&lt;br /&gt;
* Node is designed to handle lots of connections efficiently.&lt;br /&gt;
* Instead of waiting for slow operations (network, file system, database) to finish, Node starts the work and continues doing other things.&lt;br /&gt;
* When the slow operation finishes, Node runs some code to handle the result.&lt;br /&gt;
&lt;br /&gt;
Historically, the core idea was the use of '''''anonymous functions''''' as ''callbacks'' for processing the results of intensive operations. Consider the pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
var data = database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
print data[0].title;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above pseudo-code queries a database, saves the result of the query in a variable named ''data'', and then prints out the first title in ''data''. The problem arises when multiple clients all want to access the server at the same time.&lt;br /&gt;
&lt;br /&gt;
''In a blocking I/O model, a client needs to wait until all other clients are finished before it can start its process.'' With intensive operations and thousands of clients, this can easily crash your server (or force you to provision much more hardware than you want).&lt;br /&gt;
&lt;br /&gt;
The solution is to use ''non-blocking I/O''. Consider this revised pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Non-Blocking I/O Pseudo-Code&lt;br /&gt;
&lt;br /&gt;
database.query(&amp;quot;select * from news&amp;quot;, function(data){&lt;br /&gt;
	print data[0].title;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The revised pseudo-code achieves the same end result as the first version. However, ''it allows other clients to perform tasks while this process is waiting for the database to be queried!''&lt;br /&gt;
&lt;br /&gt;
The code occurs in two parts:&lt;br /&gt;
&lt;br /&gt;
# Start the query.&lt;br /&gt;
# ''When the query is complete,'' run the callback function.&lt;br /&gt;
&lt;br /&gt;
In Node.js, callback functions are usually passed one or more parameters.&lt;br /&gt;
&lt;br /&gt;
=== Modern Node style: Promises and async/await ===&lt;br /&gt;
&lt;br /&gt;
Callbacks are still important to understand (they explain why Node &amp;quot;works&amp;quot;), but modern Node code often uses '''Promises''' and '''async/await''' because they make programs easier to read and debug.&lt;br /&gt;
&lt;br /&gt;
Conceptually:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Promise + async/await pseudo-code&lt;br /&gt;
&lt;br /&gt;
const data = await database.query(&amp;quot;select * from news&amp;quot;);&lt;br /&gt;
console.log(data[0].title);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is still non-blocking behind the scenes—it's just nicer syntax for “start work now, continue later when it finishes.”&lt;br /&gt;
&lt;br /&gt;
=== One thread? Concurrency? ===&lt;br /&gt;
&lt;br /&gt;
You will often hear that Node “runs in one thread.” The key point for this course is:&lt;br /&gt;
&lt;br /&gt;
* You can handle many concurrent connections without making a thread per client.&lt;br /&gt;
* The server remains responsive because it avoids blocking on slow I/O.&lt;br /&gt;
&lt;br /&gt;
(There are deeper details in the real Node event loop model, but the above understanding will serve you well for CSE 3300.)&lt;br /&gt;
&lt;br /&gt;
== Installing Node.js ==&lt;br /&gt;
&lt;br /&gt;
We will install Node using '''nvm''' (Node Version Manager). This makes it easy to switch Node versions and consistently use the current LTS version. :contentReference[oaicite:4]{index=4}&lt;br /&gt;
&lt;br /&gt;
Run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash&lt;br /&gt;
$ source ~/.bashrc&lt;br /&gt;
$ nvm install --lts&lt;br /&gt;
$ node -v&lt;br /&gt;
$ npm -v&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' As of February 2026, Node 24 is the Active LTS line. Using `nvm install --lts` means you do not have to hardcode a specific major version in the instructions. :contentReference[oaicite:6]{index=6}&lt;br /&gt;
&lt;br /&gt;
== Your First Node.js Application: Hello World ==&lt;br /&gt;
&lt;br /&gt;
Let's try the classic HTTP server example.&lt;br /&gt;
&lt;br /&gt;
=== Modern version (ES Modules) ===&lt;br /&gt;
&lt;br /&gt;
Create a file named `hello.js`:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
http.createServer((req, res) =&amp;gt; {&lt;br /&gt;
	res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
	res.end(&amp;quot;Hello World\n&amp;quot;);&lt;br /&gt;
}).listen(3456);&lt;br /&gt;
&lt;br /&gt;
console.log(&amp;quot;Server running at http://localhost:3456/&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ node hello.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' If you're running the code from your EC2 instance, you will need to open port 3456.&lt;br /&gt;
&lt;br /&gt;
=== Enabling ES Modules (important!) ===&lt;br /&gt;
&lt;br /&gt;
Modern Node supports both the older '''CommonJS''' module system (require) and the newer JavaScript standard '''ES Modules''' (import/export). ES Modules are the direction modern JavaScript has moved (and they match browser JavaScript). :contentReference[oaicite:7]{index=7}&lt;br /&gt;
&lt;br /&gt;
To tell Node to treat `.js` files as ES Modules, add this to `package.json` in your project directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
:contentReference[oaicite:8]{index=8}&lt;br /&gt;
&lt;br /&gt;
If you do not do this, Node may treat `.js` files as CommonJS by default, and `import ...` may fail.&lt;br /&gt;
&lt;br /&gt;
=== Walkthrough of the Hello World example ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;import http from &amp;quot;node:http&amp;quot;;&amp;lt;/code&amp;gt; enables us to use Node's built-in HTTP server library. Unlike PHP, Node does not automatically load lots of server features for you. You explicitly import what you need.&lt;br /&gt;
* &amp;lt;code&amp;gt;http.createServer((req, res) =&amp;amp;gt; { ... }).listen(3456);&amp;lt;/code&amp;gt; listens for HTTP connections on port 3456. The callback function runs whenever a connection is made.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.writeHead(200, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&amp;lt;/code&amp;gt; sets the HTTP response code and headers.&lt;br /&gt;
* &amp;lt;code&amp;gt;res.end(&amp;quot;Hello World\n&amp;quot;);&amp;lt;/code&amp;gt; writes the response body and ends the response.&lt;br /&gt;
&lt;br /&gt;
Notice the use of non-blocking behavior even in this simple example: ''when a connection is made'', the callback runs with the parameters associated with that connection.&lt;br /&gt;
&lt;br /&gt;
== A Static File Server Using Node.js ==&lt;br /&gt;
&lt;br /&gt;
Node.js does not automatically serve up static files in the way that Apache/PHP does. We need to either:&lt;br /&gt;
&lt;br /&gt;
* use a framework like Express (common in industry), or&lt;br /&gt;
* write a simple static file server ourselves (great for understanding how servers work)&lt;br /&gt;
&lt;br /&gt;
We'll do the second option here.&lt;br /&gt;
&lt;br /&gt;
=== Installing Packages ===&lt;br /&gt;
&lt;br /&gt;
In order for the example code below to work, you need the '''mime''' package (it helps map filenames to Content-Type headers). The `mime` package is actively maintained. :contentReference[oaicite:9]{index=9}&lt;br /&gt;
&lt;br /&gt;
In the directory where you will save your Node.js file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
$ npm install mime&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''npm'' is the package manager for Node.js. It installs dependencies locally (inside `node_modules`) by default, which is a good thing: it lets different projects use different versions of the same dependency.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' When using npm to install local dependencies, '''do not use sudo'''. It can break file permissions in your project directory.&lt;br /&gt;
&lt;br /&gt;
=== Static Fileserver Code (Modern ESM + async/await) ===&lt;br /&gt;
&lt;br /&gt;
The following code makes an HTTP server on port 3456 that serves all static files inside &amp;lt;STATIC DIRECTORY NAME&amp;gt; relative to app.js.&lt;br /&gt;
&lt;br /&gt;
'''Make an attempt to understand this code.''' It's important that you are comfortable with the non-blocking nature, as well as how we handle file I/O.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import http from &amp;quot;node:http&amp;quot;;&lt;br /&gt;
import path from &amp;quot;node:path&amp;quot;;&lt;br /&gt;
import { readFile } from &amp;quot;node:fs/promises&amp;quot;;&lt;br /&gt;
import { existsSync } from &amp;quot;node:fs&amp;quot;;&lt;br /&gt;
import mime from &amp;quot;mime&amp;quot;;&lt;br /&gt;
import { fileURLToPath } from &amp;quot;node:url&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const __filename = fileURLToPath(import.meta.url);&lt;br /&gt;
const __dirname = path.dirname(__filename);&lt;br /&gt;
&lt;br /&gt;
const STATIC_DIR = &amp;quot;&amp;lt;STATIC DIRECTORY NAME&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const app = http.createServer(async (req, resp) =&amp;gt; {&lt;br /&gt;
	try {&lt;br /&gt;
		// Parse the path the browser requested&lt;br /&gt;
		const urlPath = new URL(req.url, &amp;quot;http://localhost&amp;quot;).pathname;&lt;br /&gt;
		const filename = path.join(__dirname, STATIC_DIR, decodeURIComponent(urlPath));&lt;br /&gt;
&lt;br /&gt;
		// If file doesn't exist, return 404&lt;br /&gt;
		if (!existsSync(filename)) {&lt;br /&gt;
			resp.writeHead(404, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
			resp.end(&amp;quot;Requested file not found: &amp;quot; + filename);&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Read the file (non-blocking I/O via promises)&lt;br /&gt;
		const data = await readFile(filename);&lt;br /&gt;
&lt;br /&gt;
		// Determine content type&lt;br /&gt;
		const mimetype = mime.getType(filename) || &amp;quot;application/octet-stream&amp;quot;;&lt;br /&gt;
		resp.writeHead(200, { &amp;quot;Content-Type&amp;quot;: mimetype });&lt;br /&gt;
		resp.end(data);&lt;br /&gt;
	} catch (err) {&lt;br /&gt;
		// File exists but isn't readable or some other error occurred&lt;br /&gt;
		resp.writeHead(500, { &amp;quot;Content-Type&amp;quot;: &amp;quot;text/plain&amp;quot; });&lt;br /&gt;
		resp.end(&amp;quot;Internal server error: could not read file&amp;quot;);&lt;br /&gt;
	}&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
app.listen(3456);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== A few important notes about the modern version ====&lt;br /&gt;
&lt;br /&gt;
* We use &amp;lt;code&amp;gt;node:fs/promises&amp;lt;/code&amp;gt; so we can write file I/O using async/await.&lt;br /&gt;
* In ES Modules, &amp;lt;code&amp;gt;__dirname&amp;lt;/code&amp;gt; is not automatically defined, so we compute it using &amp;lt;code&amp;gt;import.meta.url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* This server is intentionally simple for instructional purposes.&lt;br /&gt;
&lt;br /&gt;
'''Security note:''' A production static server must protect against directory traversal attacks (paths like &amp;lt;code&amp;gt;../&amp;lt;/code&amp;gt;). For this course example, we focus on learning the mechanics first.&lt;br /&gt;
&lt;br /&gt;
=== Port 3456 ===&lt;br /&gt;
&lt;br /&gt;
We are running our server on port 3456 instead of port 80. We therefore need to open our Node.js application like this:&lt;br /&gt;
&lt;br /&gt;
http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/path.extension&lt;br /&gt;
&lt;br /&gt;
However, this won't work the first time. ''Why?''&lt;br /&gt;
&lt;br /&gt;
We need to open port 3456 on our Amazon EC2 instance. Remember when you opened port 80 back in Module 2? We need to do the same procedure again now.&lt;br /&gt;
&lt;br /&gt;
# Log into your EC2 management console&lt;br /&gt;
# Go to &amp;quot;Security Groups&amp;quot;&lt;br /&gt;
# Select your security group from Module 2&lt;br /&gt;
# Under &amp;quot;Inbound&amp;quot;, create a custom TCP rule for port 3456, and apply your changes.&lt;br /&gt;
&lt;br /&gt;
== Node.js Security ==&lt;br /&gt;
&lt;br /&gt;
Just because we're writing in JavaScript doesn't mean we can forget about the security practices you've been learning in CSE 330. XSS is still a problem, as is the use of ''eval'', CSRF attacks, and so on.&lt;br /&gt;
&lt;br /&gt;
== Node.js Conventions ==&lt;br /&gt;
&lt;br /&gt;
There ought to be a standard way to save metadata about your app, right? Node projects use a &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file for this.&lt;br /&gt;
&lt;br /&gt;
To create one quickly:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm init -y&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One nice feature of &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; is that it automatically handles dependencies. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;dependencies&amp;quot;: {&lt;br /&gt;
  &amp;quot;mime&amp;quot;: &amp;quot;^4.1.0&amp;quot;,&lt;br /&gt;
  &amp;quot;socket.io&amp;quot;: &amp;quot;^4.8.0&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then when you run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ npm install&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
npm installs the dependencies for you.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Modern npm automatically saves dependencies when you run &amp;lt;code&amp;gt;npm install package-name&amp;lt;/code&amp;gt;. The older &amp;lt;code&amp;gt;--save&amp;lt;/code&amp;gt; flag is no longer necessary.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3532</id>
		<title>Module 4</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3532"/>
		<updated>2026-10-04T20:23:57Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Connect Four Debugging Exercise */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
In Module 4, you will learn JavaScript, the dominant client-side web language.  JavaScript is the second programming language you will learn in CSE 3300.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 4.&lt;br /&gt;
&lt;br /&gt;
'''Warning:'''&lt;br /&gt;
&lt;br /&gt;
''This module takes students a while. It is strongly recommended that you start early and ask questions as they come up.''&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* JavaScript&lt;br /&gt;
* DOM Events&lt;br /&gt;
* AJAX&lt;br /&gt;
* JSON&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[JavaScript]]&lt;br /&gt;
* [[AJAX and JSON]]&lt;br /&gt;
* [[jQuery]]&lt;br /&gt;
* [[Web Application Security, Part 3]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
'''''IMPORTANT:'' The individual assignments must be completed ''without'' using a JavaScript library like jQuery.'''  However, you may use jQuery for the group portion.&lt;br /&gt;
&lt;br /&gt;
=== JavaScript Calculator ===&lt;br /&gt;
&lt;br /&gt;
In Module 2, you made a calculator using PHP.  Now you will be making one using JavaScript.&lt;br /&gt;
&lt;br /&gt;
* 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).&lt;br /&gt;
* JavaScript should monitor all three fields and display the current result whenever the user changes any value in any field, without refreshing the page.&lt;br /&gt;
*: '''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.&lt;br /&gt;
*: '''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!)&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' You can embed JavaScript code into an HTML document like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
// your code here&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
'''Important:''' It is best practice to fully separate the content, appearance, and behavior.  In previous modules, you may have lost points for using HTML tags that change the appearance of things &amp;lt;nowiki&amp;gt;(like &amp;lt;center&amp;gt; and &amp;lt;font&amp;gt;).&amp;lt;/nowiki&amp;gt;  In this module, make sure that you define your event listeners in JavaScript as in &amp;lt;code&amp;gt;document.getElementById(&amp;quot;xyz&amp;quot;).addEventListener(...)&amp;lt;/code&amp;gt; and '''''not''''' in the HTML like &amp;lt;code&amp;gt;&amp;lt;button onclick=&amp;quot;badExample();&amp;quot;&amp;gt;Bad Example&amp;lt;/button&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Weather Widget ===&lt;br /&gt;
&lt;br /&gt;
In this section, you will make a web page that displays the weather forecast using AJAX requests to a weather server.&lt;br /&gt;
&lt;br /&gt;
# Make an empty HTML document; name it '''weather.html'''&lt;br /&gt;
#: Refer to the [[HTML and CSS]] guide for the skeleton of an HTML document&lt;br /&gt;
# Define a function in JavaScript; call it '''fetchWeather()'''.  You may write your JavaScript in an embedded script in your HTML document.&lt;br /&gt;
# Inside your '''fetchWeather()''' function, make an AJAX request to the weather server.&lt;br /&gt;
#: We have a server that outputs the current weather in JSON format.  The format is documented in the [[#JSON Structure]] section below.&lt;br /&gt;
&lt;br /&gt;
#: '''URL:'''  https://classes.engineering.wustl.edu/cse330/content/weather_json.php&lt;br /&gt;
#* '''IMPORTANT:''' Under normal circumstances, AJAX requests ''cannot'' be performed cross-domain for security reasons.  We have set the &amp;lt;code&amp;gt;Access-Control-Allow-Origin&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
# In your callback, process the JSON and use JavaScript to manipulate the HTML DOM to display the following information on your page:&lt;br /&gt;
#* Location&lt;br /&gt;
#** City, in a &amp;lt;nowiki&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/nowiki&amp;gt; tag&lt;br /&gt;
#** State, not in any tag&lt;br /&gt;
#* Humidity&lt;br /&gt;
#* Current Temperature&lt;br /&gt;
#* Image for Tomorrow's Forecast (see [[#Weather Condition Images]] below for more information)&lt;br /&gt;
#* Image for the Day After Tomorrow's Forecast&lt;br /&gt;
# Finally, bind '''fetchWeather()''' to the DOMContentLoaded event so that your weather widget is automatically initialized when the page is loaded:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, fetchWeather, false);&amp;lt;/source&amp;gt;&lt;br /&gt;
# In addition, add a button that runs your ''fetchWeather'' function when clicked.  This should update your widget with the current weather conditions.&lt;br /&gt;
&lt;br /&gt;
Use the following HTML:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;weather&amp;quot; id=&amp;quot;weatherWidget&amp;quot;&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-loc&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-humidity&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-temp&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-tomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-dayaftertomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Include the CSS file from here: https://classes.engineering.wustl.edu/cse330/content/weather.css&lt;br /&gt;
&lt;br /&gt;
When everything is working, the weather widget should look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:WeatherWidget.png]]&lt;br /&gt;
&lt;br /&gt;
'''Important:''' The widget in this section needs to work in only Firefox and Chrome.  It does '''''not''''' need to work Internet Explorer.&lt;br /&gt;
&lt;br /&gt;
==== Tips ====&lt;br /&gt;
&lt;br /&gt;
===== JSON Structure =====&lt;br /&gt;
&lt;br /&gt;
The JSON from our server looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;updated&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;,&lt;br /&gt;
   &amp;quot;location&amp;quot;: {&lt;br /&gt;
      &amp;quot;city&amp;quot;: &amp;quot;St. Louis&amp;quot;,&lt;br /&gt;
      &amp;quot;state&amp;quot;: &amp;quot;MO&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;wind&amp;quot;: {&lt;br /&gt;
      &amp;quot;chill&amp;quot;: &amp;quot;62&amp;quot;,&lt;br /&gt;
      &amp;quot;direction&amp;quot;: &amp;quot;150&amp;quot;,&lt;br /&gt;
      &amp;quot;speed&amp;quot;: &amp;quot;3 mph&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;atmosphere&amp;quot;: {&lt;br /&gt;
      &amp;quot;humidity&amp;quot;: &amp;quot;50&amp;quot;,&lt;br /&gt;
      &amp;quot;visibility&amp;quot;: &amp;quot;10&amp;quot;,&lt;br /&gt;
      &amp;quot;pressure&amp;quot;: &amp;quot;30.12 in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;current&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;28&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Mostly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;temp&amp;quot;: &amp;quot;62°F&amp;quot;,&lt;br /&gt;
      &amp;quot;date&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;tomorrow&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;29&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Clouds Early/Clearing Late&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;45°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;61°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;dayafter&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;30&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Partly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;53°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;65°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;credit&amp;quot;: &amp;quot;http://us.rd.yahoo.com/dailynews/rss/weather/St._Louis__MO/*http://weather.yahoo.com/forecast/USMO0170_f.html&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Weather Condition Images =====&lt;br /&gt;
&lt;br /&gt;
Each day's forecast has a code.  There are images associated with these codes.&lt;br /&gt;
&lt;br /&gt;
One place to get the images is from here: &amp;lt;nowiki&amp;gt;http://us.yimg.com/i/us/nws/weather/gr/&amp;lt;/nowiki&amp;gt;'''##'''&amp;lt;nowiki&amp;gt;ds.png&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
=== Connect Four Debugging Exercise ===&lt;br /&gt;
&lt;br /&gt;
As a short debugging exercise for Module 4, you will be given a broken Connect Four implementation (located in the repository and named `connect4`). You may also download the files [https://research.engineering.wustl.edu/~todd/cse3300-wiki/connect4/ here]. &lt;br /&gt;
&lt;br /&gt;
The goals of this assignment are to complete the following:&lt;br /&gt;
&lt;br /&gt;
* Run the provided program and observe failures or incorrect behavior.&lt;br /&gt;
* Produce a list of all bugs you find. For each bug include:&lt;br /&gt;
** The file and function (or approximate line) where it appears&lt;br /&gt;
** A short description of why it is a bug and what symptom it causes (crash, wrong output, missing feature)&lt;br /&gt;
** A suggested fix (one or two sentences)&lt;br /&gt;
* Use GitHub Copilot in &amp;quot;Agent&amp;quot; mode to assist you in writing and testing fixes. When you use Copilot Agent, document the prompts you issued, any code the agent generated, and what changes you applied or rejected after code review.&lt;br /&gt;
* Produce a corrected version of the program that can demonstrate the following:&lt;br /&gt;
** A horizontal, vertical, or diagonal win is detected correctly&lt;br /&gt;
** A draw (full-board) is detected correctly&lt;br /&gt;
&lt;br /&gt;
A video of the working Connect Four app can be found &lt;br /&gt;
[https://wustl.app.box.com/s/e3t8layy3vqp4wm75dcxn9yyip9dsbdp here]&lt;br /&gt;
&lt;br /&gt;
Documentation to include in your repository:&lt;br /&gt;
* The bug list (as described above) is inserted as a comment at the top of the connect4.js file&lt;br /&gt;
* The Copilot Agent prompts you used and a short evaluation of the suggestions (what was helpful, what you changed) in the AI Reflection &lt;br /&gt;
* The corrected file(s) committed to your GitHub repo&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To receive full credit, you must complete the following:&lt;br /&gt;
* Identifying and explaining each real bug&lt;br /&gt;
* Correct fixes that produce a working game (wins + draw)&lt;br /&gt;
* Proper documentation of Copilot Agent usage and critical review &lt;br /&gt;
&lt;br /&gt;
This exercise is intended to teach debugging, careful code review, and responsible use of AI coding assistants (reviewing and accepting/rejecting suggestions). Keep your commits small and descriptive so the instructor can follow your reasoning.&lt;br /&gt;
&lt;br /&gt;
== Group Project ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Calendar ===&lt;br /&gt;
&lt;br /&gt;
Build a simple calendar that allows users to add and remove events dynamically.&lt;br /&gt;
&lt;br /&gt;
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, so long as that JavaScript library does not defeat the purpose of the assignment. If you have questions about this, ask. In particular, libraries like FullCalendar are unacceptable.&lt;br /&gt;
&lt;br /&gt;
Your application should utilize AJAX to run server-side scripts that query your database to save and retrieve information, including user accounts and events.&lt;br /&gt;
&lt;br /&gt;
We have also written some functions to help you get started: [[JavaScript Calendar Library]]&lt;br /&gt;
&lt;br /&gt;
==== Examples ====&lt;br /&gt;
&lt;br /&gt;
* https://calendar.google.com/&lt;br /&gt;
* http://www.zoho.com/calendar/&lt;br /&gt;
&lt;br /&gt;
==== Server Side Language ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
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 you are free to explore other databases if you like.&lt;br /&gt;
==== Calendar Requirements ====&lt;br /&gt;
&lt;br /&gt;
* Support a month-by-month view of the calendar.&lt;br /&gt;
*: Show one month at a time, with buttons to move forward or backward.&lt;br /&gt;
*: There should be no limit to how far forward or backward the user can go.&lt;br /&gt;
* Users can register and log in to the website.&lt;br /&gt;
*: You may leverage your MySQL project code and database from module 3 to get started.&lt;br /&gt;
* Unregistered users should see no events on the calendar.&lt;br /&gt;
* Registered users can add events.&lt;br /&gt;
*: All events should have a date and time, but do not need to have a duration.&lt;br /&gt;
*: You do ''not'' need to support recurring events (where you add an event that repeats, for example, every Monday).&lt;br /&gt;
* Registered users see only events that they have added.&lt;br /&gt;
*: 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?&lt;br /&gt;
* Registered users can edit and delete their own events, but not the events of others.&lt;br /&gt;
*: Again, the server should edit and delete events based on the username present in the session, not based on a username or event ID provided by the user.  (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.)&lt;br /&gt;
* All user and event data should be kept in a database.&lt;br /&gt;
* At no time should the main page need to be reloaded.&lt;br /&gt;
*: User registration, user authentication, event addition, and event deletion should all be handled by JavaScript and AJAX requests to your server.&lt;br /&gt;
* Your page needs to work in the versions of Firefox and Chrome installed on the lab computers.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Run your database schema by a TA before implementing it.&lt;br /&gt;
&lt;br /&gt;
=== Web Security and Validation ===&lt;br /&gt;
&lt;br /&gt;
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]]&lt;br /&gt;
&lt;br /&gt;
In particular:&lt;br /&gt;
&lt;br /&gt;
* '''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.)&lt;br /&gt;
* '''Perform precautionary measures to prevent session hijacking attacks.'''&lt;br /&gt;
*: 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.&lt;br /&gt;
&lt;br /&gt;
You should continue the practices that you have learned in past weeks:&lt;br /&gt;
&lt;br /&gt;
* Pass tokens in forms to prevent CSRF attacks.&lt;br /&gt;
*: '''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.&lt;br /&gt;
* Use prepared queries to prevent SQL Injection attacks.&lt;br /&gt;
* If storing passwords in a database, always store them salted and encrypted.&lt;br /&gt;
* Your page should validate with no errors or warnings through the W3C validator.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== AI Security Audit with GitHub Copilot ===&lt;br /&gt;
&lt;br /&gt;
For this module, you will use GitHub Copilot to perform a security audit of your calendar application. This exercise is designed to help you learn how to leverage AI tools for code security analysis while maintaining critical thinking about the results.&lt;br /&gt;
&lt;br /&gt;
* Code Generation: Use GitHub Copilot to help implement core calendar features if needed&lt;br /&gt;
* Security Review: Use Copilot's code analysis capabilities to:&lt;br /&gt;
** Identify potential security vulnerabilities in your JavaScript and PHP code&lt;br /&gt;
** Check for common web security issues (XSS, CSRF, SQL injection, etc.)&lt;br /&gt;
**Review authentication and authorization logic&lt;br /&gt;
*Vulnerability Assessment: Ask Copilot to analyze specific security aspects:&lt;br /&gt;
**Input validation and output escaping&lt;br /&gt;
**Session management security&lt;br /&gt;
**AJAX request security&lt;br /&gt;
**Database query safety&lt;br /&gt;
&lt;br /&gt;
Required Tasks:&lt;br /&gt;
#Use GitHub Copilot to generate at least 3 security-focused code reviews for different parts of your application&lt;br /&gt;
#Implement fixes for at least 2 identified security issues (real or potential)&lt;br /&gt;
#Document the security audit process and findings&lt;br /&gt;
&lt;br /&gt;
* Documentation Requirements&lt;br /&gt;
**Examples of security-focused prompts you used with Copilot&lt;br /&gt;
**Security issues identified (both real and false positives)&lt;br /&gt;
**Changes made based on the security audit&lt;br /&gt;
**Your assessment of Copilot's effectiveness as a security review tool&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 125 points in total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
If you do not include a link to your individual and group portion running on your instance in the respective README.md, you will receive a 0 for the missing portion&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''JavaScript Calculator (10 Points):'''&lt;br /&gt;
#* Calculator successfully adds, subtracts, multiplies, and divides numbers (3 points)&lt;br /&gt;
#* The result is automatically computed when a value in either field is changed (3 points)&lt;br /&gt;
#* Calculator is written entirely in pure, conventional JavaScript with no external libraries (4 points)*&lt;br /&gt;
#*: ''Note: be sure to fully separate content, appearance, and behavior.  This means that you should '''not''' use the event attributes on the HTML elements like onclick, onchange, and onkeyup.''&lt;br /&gt;
# '''Weather Widget (15 Points):'''&lt;br /&gt;
#* Widget performs an AJAX request to fetch the current weather (4 points)&lt;br /&gt;
#* The HTML template is filled with the correct information, including an image (4 points)&lt;br /&gt;
#* Button to reload the weather widget (2 points)&lt;br /&gt;
#* Widget is written entirely in pure, conventional JavaScript with no external libraries (5 points)*&lt;br /&gt;
# '''Connect Four Debugging Exercise (15 Points):'''&lt;br /&gt;
#* Identify and explain bugs in the provided `connect4.js` (5 points)&lt;br /&gt;
#** For each bug, include location (function/lines), description of the symptom, and the root cause.&lt;br /&gt;
#* Correct the program so it reliably detects wins and draws and handles invalid input (5 points)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#**Include the prompts used, what Copilot suggested, and what you changed or rejected.&lt;br /&gt;
# '''AJAX Calendar (60 Points):'''&lt;br /&gt;
#* '''''Calendar View (10 Points):'''''&lt;br /&gt;
#** 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)&lt;br /&gt;
#** The user can view different months as far in the past or future as desired (5 points)&lt;br /&gt;
#* '''''User and Event Management (25 Points):'''''&lt;br /&gt;
#** Events can be added, modified, and deleted (5 points)&lt;br /&gt;
#** Events have a title, date, and time (2 points)&lt;br /&gt;
#** Users can log into the site, and they cannot view or manipulate events associated with other users (8 points)&lt;br /&gt;
#**: ''Don't fall into the Abuse of Functionality trap!  Check user credentials on the server side as well as on the client side.''&lt;br /&gt;
#** All actions are performed over AJAX, without ever needing to reload the page (7 points)&lt;br /&gt;
#** Refreshing the page does not log a user out (3 points)&lt;br /&gt;
#* '''''Best Practices (20 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** If storing passwords, they are stored salted and hashed (2 points)&lt;br /&gt;
#** All AJAX requests that either contain sensitive information or modify something on the server are performed via POST, not GET (3 points)&lt;br /&gt;
#** Safe from XSS attacks; that is, all content is escaped on output (3 points)&lt;br /&gt;
#** Safe from SQL Injection attacks (2 points)&lt;br /&gt;
#** CSRF tokens are passed when adding/editing/deleting events (3 points)&lt;br /&gt;
#** Session cookie is HTTP-Only (3 points)&lt;br /&gt;
#** Page passes the W3C validator (2 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Site is intuitive to use and navigate (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
# '''Creative Portion &amp;amp; AI Security Audit (25 Points)'''&lt;br /&gt;
#* '''''AI Security Audit Implementation (15 Points):'''''&lt;br /&gt;
#** Used GitHub Copilot for security analysis of at least 3 different code components (4 points)&lt;br /&gt;
#** Documented security audit process and findings in README.md (3 points)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#** Implemented fixes for identified security issues (3 points)&lt;br /&gt;
#** We recommend you to use &amp;quot;Agent&amp;quot; Mode of Copilot to implement this feature&lt;br /&gt;
#** Be sure to create a new branch before implement this in case any of your original code being changed. &lt;br /&gt;
#* '''Additional Calendars Features (worth 10 points):''' Develop some additional features for the calendar, a few examples are provided below.&lt;br /&gt;
#**  Users can tag an event with a particular category and enable/disable those tags in the calendar view. (5 points)&lt;br /&gt;
#**  Users can share their calendar with additional users. (5 points)&lt;br /&gt;
#**  Users can create group events that display on multiple users calendars (5 points)&lt;br /&gt;
#** Feature enhances user experience and calendar functionality&lt;br /&gt;
#* '''''Make sure to save a description of your creative portion, AI security audit process, and a link to your server in your README.md file.'''''&lt;br /&gt;
&lt;br /&gt;
''* 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 &amp;quot;free.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 4]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3531</id>
		<title>Module 4</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3531"/>
		<updated>2026-10-04T20:21:29Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Connect Four Debugging Exercise */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
In Module 4, you will learn JavaScript, the dominant client-side web language.  JavaScript is the second programming language you will learn in CSE 3300.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 4.&lt;br /&gt;
&lt;br /&gt;
'''Warning:'''&lt;br /&gt;
&lt;br /&gt;
''This module takes students a while. It is strongly recommended that you start early and ask questions as they come up.''&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* JavaScript&lt;br /&gt;
* DOM Events&lt;br /&gt;
* AJAX&lt;br /&gt;
* JSON&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[JavaScript]]&lt;br /&gt;
* [[AJAX and JSON]]&lt;br /&gt;
* [[jQuery]]&lt;br /&gt;
* [[Web Application Security, Part 3]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
'''''IMPORTANT:'' The individual assignments must be completed ''without'' using a JavaScript library like jQuery.'''  However, you may use jQuery for the group portion.&lt;br /&gt;
&lt;br /&gt;
=== JavaScript Calculator ===&lt;br /&gt;
&lt;br /&gt;
In Module 2, you made a calculator using PHP.  Now you will be making one using JavaScript.&lt;br /&gt;
&lt;br /&gt;
* 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).&lt;br /&gt;
* JavaScript should monitor all three fields and display the current result whenever the user changes any value in any field, without refreshing the page.&lt;br /&gt;
*: '''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.&lt;br /&gt;
*: '''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!)&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' You can embed JavaScript code into an HTML document like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
// your code here&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
'''Important:''' It is best practice to fully separate the content, appearance, and behavior.  In previous modules, you may have lost points for using HTML tags that change the appearance of things &amp;lt;nowiki&amp;gt;(like &amp;lt;center&amp;gt; and &amp;lt;font&amp;gt;).&amp;lt;/nowiki&amp;gt;  In this module, make sure that you define your event listeners in JavaScript as in &amp;lt;code&amp;gt;document.getElementById(&amp;quot;xyz&amp;quot;).addEventListener(...)&amp;lt;/code&amp;gt; and '''''not''''' in the HTML like &amp;lt;code&amp;gt;&amp;lt;button onclick=&amp;quot;badExample();&amp;quot;&amp;gt;Bad Example&amp;lt;/button&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Weather Widget ===&lt;br /&gt;
&lt;br /&gt;
In this section, you will make a web page that displays the weather forecast using AJAX requests to a weather server.&lt;br /&gt;
&lt;br /&gt;
# Make an empty HTML document; name it '''weather.html'''&lt;br /&gt;
#: Refer to the [[HTML and CSS]] guide for the skeleton of an HTML document&lt;br /&gt;
# Define a function in JavaScript; call it '''fetchWeather()'''.  You may write your JavaScript in an embedded script in your HTML document.&lt;br /&gt;
# Inside your '''fetchWeather()''' function, make an AJAX request to the weather server.&lt;br /&gt;
#: We have a server that outputs the current weather in JSON format.  The format is documented in the [[#JSON Structure]] section below.&lt;br /&gt;
&lt;br /&gt;
#: '''URL:'''  https://classes.engineering.wustl.edu/cse330/content/weather_json.php&lt;br /&gt;
#* '''IMPORTANT:''' Under normal circumstances, AJAX requests ''cannot'' be performed cross-domain for security reasons.  We have set the &amp;lt;code&amp;gt;Access-Control-Allow-Origin&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
# In your callback, process the JSON and use JavaScript to manipulate the HTML DOM to display the following information on your page:&lt;br /&gt;
#* Location&lt;br /&gt;
#** City, in a &amp;lt;nowiki&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/nowiki&amp;gt; tag&lt;br /&gt;
#** State, not in any tag&lt;br /&gt;
#* Humidity&lt;br /&gt;
#* Current Temperature&lt;br /&gt;
#* Image for Tomorrow's Forecast (see [[#Weather Condition Images]] below for more information)&lt;br /&gt;
#* Image for the Day After Tomorrow's Forecast&lt;br /&gt;
# Finally, bind '''fetchWeather()''' to the DOMContentLoaded event so that your weather widget is automatically initialized when the page is loaded:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, fetchWeather, false);&amp;lt;/source&amp;gt;&lt;br /&gt;
# In addition, add a button that runs your ''fetchWeather'' function when clicked.  This should update your widget with the current weather conditions.&lt;br /&gt;
&lt;br /&gt;
Use the following HTML:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;weather&amp;quot; id=&amp;quot;weatherWidget&amp;quot;&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-loc&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-humidity&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-temp&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-tomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-dayaftertomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Include the CSS file from here: https://classes.engineering.wustl.edu/cse330/content/weather.css&lt;br /&gt;
&lt;br /&gt;
When everything is working, the weather widget should look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:WeatherWidget.png]]&lt;br /&gt;
&lt;br /&gt;
'''Important:''' The widget in this section needs to work in only Firefox and Chrome.  It does '''''not''''' need to work Internet Explorer.&lt;br /&gt;
&lt;br /&gt;
==== Tips ====&lt;br /&gt;
&lt;br /&gt;
===== JSON Structure =====&lt;br /&gt;
&lt;br /&gt;
The JSON from our server looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;updated&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;,&lt;br /&gt;
   &amp;quot;location&amp;quot;: {&lt;br /&gt;
      &amp;quot;city&amp;quot;: &amp;quot;St. Louis&amp;quot;,&lt;br /&gt;
      &amp;quot;state&amp;quot;: &amp;quot;MO&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;wind&amp;quot;: {&lt;br /&gt;
      &amp;quot;chill&amp;quot;: &amp;quot;62&amp;quot;,&lt;br /&gt;
      &amp;quot;direction&amp;quot;: &amp;quot;150&amp;quot;,&lt;br /&gt;
      &amp;quot;speed&amp;quot;: &amp;quot;3 mph&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;atmosphere&amp;quot;: {&lt;br /&gt;
      &amp;quot;humidity&amp;quot;: &amp;quot;50&amp;quot;,&lt;br /&gt;
      &amp;quot;visibility&amp;quot;: &amp;quot;10&amp;quot;,&lt;br /&gt;
      &amp;quot;pressure&amp;quot;: &amp;quot;30.12 in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;current&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;28&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Mostly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;temp&amp;quot;: &amp;quot;62°F&amp;quot;,&lt;br /&gt;
      &amp;quot;date&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;tomorrow&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;29&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Clouds Early/Clearing Late&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;45°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;61°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;dayafter&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;30&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Partly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;53°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;65°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;credit&amp;quot;: &amp;quot;http://us.rd.yahoo.com/dailynews/rss/weather/St._Louis__MO/*http://weather.yahoo.com/forecast/USMO0170_f.html&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Weather Condition Images =====&lt;br /&gt;
&lt;br /&gt;
Each day's forecast has a code.  There are images associated with these codes.&lt;br /&gt;
&lt;br /&gt;
One place to get the images is from here: &amp;lt;nowiki&amp;gt;http://us.yimg.com/i/us/nws/weather/gr/&amp;lt;/nowiki&amp;gt;'''##'''&amp;lt;nowiki&amp;gt;ds.png&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
=== Connect Four Debugging Exercise ===&lt;br /&gt;
&lt;br /&gt;
As a short debugging exercise for Module 4, you will be given a broken Connect Four implementation (located in the repository and named `connect4`). You may also download the files [https://research.engineering.wustl.edu/~todd/cse3300-wiki/connect4/ here]. &lt;br /&gt;
&lt;br /&gt;
The goals of this assignment are to complete the following:&lt;br /&gt;
&lt;br /&gt;
* Run the provided program and observe failures or incorrect behavior.&lt;br /&gt;
* Produce a list of all bugs you find. For each bug include:&lt;br /&gt;
** The file and function (or approximate line) where it appears&lt;br /&gt;
** A short description of why it is a bug and what symptom it causes (crash, wrong output, missing feature)&lt;br /&gt;
** A suggested fix (one or two sentences)&lt;br /&gt;
* Use GitHub Copilot in &amp;quot;Agent&amp;quot; mode to assist you in writing and testing fixes. When you use Copilot Agent, document the prompts you issued, any code the agent generated, and what changes you applied or rejected after code review.&lt;br /&gt;
* Produce a corrected version of the program that can demonstrate the following:&lt;br /&gt;
** A horizontal, vertical, or diagonal win is detected correctly&lt;br /&gt;
** A draw (full-board) is detected correctly&lt;br /&gt;
&lt;br /&gt;
A video of the working Connect Four app can be found &lt;br /&gt;
[https://wustl.box.com/shared/static/e3t8layy3vqp4wm75dcxn9yyip9dsbdp.mp4 here]&lt;br /&gt;
&lt;br /&gt;
Documentation to include in your repository:&lt;br /&gt;
* The bug list (as described above) is inserted as a comment at the top of the connect4.js file&lt;br /&gt;
* The Copilot Agent prompts you used and a short evaluation of the suggestions (what was helpful, what you changed) in the AI Reflection &lt;br /&gt;
* The corrected file(s) committed to your GitHub repo&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To receive full credit, you must complete the following:&lt;br /&gt;
* Identifying and explaining each real bug&lt;br /&gt;
* Correct fixes that produce a working game (wins + draw)&lt;br /&gt;
* Proper documentation of Copilot Agent usage and critical review &lt;br /&gt;
&lt;br /&gt;
This exercise is intended to teach debugging, careful code review, and responsible use of AI coding assistants (reviewing and accepting/rejecting suggestions). Keep your commits small and descriptive so the instructor can follow your reasoning.&lt;br /&gt;
&lt;br /&gt;
== Group Project ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Calendar ===&lt;br /&gt;
&lt;br /&gt;
Build a simple calendar that allows users to add and remove events dynamically.&lt;br /&gt;
&lt;br /&gt;
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, so long as that JavaScript library does not defeat the purpose of the assignment. If you have questions about this, ask. In particular, libraries like FullCalendar are unacceptable.&lt;br /&gt;
&lt;br /&gt;
Your application should utilize AJAX to run server-side scripts that query your database to save and retrieve information, including user accounts and events.&lt;br /&gt;
&lt;br /&gt;
We have also written some functions to help you get started: [[JavaScript Calendar Library]]&lt;br /&gt;
&lt;br /&gt;
==== Examples ====&lt;br /&gt;
&lt;br /&gt;
* https://calendar.google.com/&lt;br /&gt;
* http://www.zoho.com/calendar/&lt;br /&gt;
&lt;br /&gt;
==== Server Side Language ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
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 you are free to explore other databases if you like.&lt;br /&gt;
==== Calendar Requirements ====&lt;br /&gt;
&lt;br /&gt;
* Support a month-by-month view of the calendar.&lt;br /&gt;
*: Show one month at a time, with buttons to move forward or backward.&lt;br /&gt;
*: There should be no limit to how far forward or backward the user can go.&lt;br /&gt;
* Users can register and log in to the website.&lt;br /&gt;
*: You may leverage your MySQL project code and database from module 3 to get started.&lt;br /&gt;
* Unregistered users should see no events on the calendar.&lt;br /&gt;
* Registered users can add events.&lt;br /&gt;
*: All events should have a date and time, but do not need to have a duration.&lt;br /&gt;
*: You do ''not'' need to support recurring events (where you add an event that repeats, for example, every Monday).&lt;br /&gt;
* Registered users see only events that they have added.&lt;br /&gt;
*: 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?&lt;br /&gt;
* Registered users can edit and delete their own events, but not the events of others.&lt;br /&gt;
*: Again, the server should edit and delete events based on the username present in the session, not based on a username or event ID provided by the user.  (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.)&lt;br /&gt;
* All user and event data should be kept in a database.&lt;br /&gt;
* At no time should the main page need to be reloaded.&lt;br /&gt;
*: User registration, user authentication, event addition, and event deletion should all be handled by JavaScript and AJAX requests to your server.&lt;br /&gt;
* Your page needs to work in the versions of Firefox and Chrome installed on the lab computers.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Run your database schema by a TA before implementing it.&lt;br /&gt;
&lt;br /&gt;
=== Web Security and Validation ===&lt;br /&gt;
&lt;br /&gt;
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]]&lt;br /&gt;
&lt;br /&gt;
In particular:&lt;br /&gt;
&lt;br /&gt;
* '''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.)&lt;br /&gt;
* '''Perform precautionary measures to prevent session hijacking attacks.'''&lt;br /&gt;
*: 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.&lt;br /&gt;
&lt;br /&gt;
You should continue the practices that you have learned in past weeks:&lt;br /&gt;
&lt;br /&gt;
* Pass tokens in forms to prevent CSRF attacks.&lt;br /&gt;
*: '''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.&lt;br /&gt;
* Use prepared queries to prevent SQL Injection attacks.&lt;br /&gt;
* If storing passwords in a database, always store them salted and encrypted.&lt;br /&gt;
* Your page should validate with no errors or warnings through the W3C validator.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== AI Security Audit with GitHub Copilot ===&lt;br /&gt;
&lt;br /&gt;
For this module, you will use GitHub Copilot to perform a security audit of your calendar application. This exercise is designed to help you learn how to leverage AI tools for code security analysis while maintaining critical thinking about the results.&lt;br /&gt;
&lt;br /&gt;
* Code Generation: Use GitHub Copilot to help implement core calendar features if needed&lt;br /&gt;
* Security Review: Use Copilot's code analysis capabilities to:&lt;br /&gt;
** Identify potential security vulnerabilities in your JavaScript and PHP code&lt;br /&gt;
** Check for common web security issues (XSS, CSRF, SQL injection, etc.)&lt;br /&gt;
**Review authentication and authorization logic&lt;br /&gt;
*Vulnerability Assessment: Ask Copilot to analyze specific security aspects:&lt;br /&gt;
**Input validation and output escaping&lt;br /&gt;
**Session management security&lt;br /&gt;
**AJAX request security&lt;br /&gt;
**Database query safety&lt;br /&gt;
&lt;br /&gt;
Required Tasks:&lt;br /&gt;
#Use GitHub Copilot to generate at least 3 security-focused code reviews for different parts of your application&lt;br /&gt;
#Implement fixes for at least 2 identified security issues (real or potential)&lt;br /&gt;
#Document the security audit process and findings&lt;br /&gt;
&lt;br /&gt;
* Documentation Requirements&lt;br /&gt;
**Examples of security-focused prompts you used with Copilot&lt;br /&gt;
**Security issues identified (both real and false positives)&lt;br /&gt;
**Changes made based on the security audit&lt;br /&gt;
**Your assessment of Copilot's effectiveness as a security review tool&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 125 points in total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
If you do not include a link to your individual and group portion running on your instance in the respective README.md, you will receive a 0 for the missing portion&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''JavaScript Calculator (10 Points):'''&lt;br /&gt;
#* Calculator successfully adds, subtracts, multiplies, and divides numbers (3 points)&lt;br /&gt;
#* The result is automatically computed when a value in either field is changed (3 points)&lt;br /&gt;
#* Calculator is written entirely in pure, conventional JavaScript with no external libraries (4 points)*&lt;br /&gt;
#*: ''Note: be sure to fully separate content, appearance, and behavior.  This means that you should '''not''' use the event attributes on the HTML elements like onclick, onchange, and onkeyup.''&lt;br /&gt;
# '''Weather Widget (15 Points):'''&lt;br /&gt;
#* Widget performs an AJAX request to fetch the current weather (4 points)&lt;br /&gt;
#* The HTML template is filled with the correct information, including an image (4 points)&lt;br /&gt;
#* Button to reload the weather widget (2 points)&lt;br /&gt;
#* Widget is written entirely in pure, conventional JavaScript with no external libraries (5 points)*&lt;br /&gt;
# '''Connect Four Debugging Exercise (15 Points):'''&lt;br /&gt;
#* Identify and explain bugs in the provided `connect4.js` (5 points)&lt;br /&gt;
#** For each bug, include location (function/lines), description of the symptom, and the root cause.&lt;br /&gt;
#* Correct the program so it reliably detects wins and draws and handles invalid input (5 points)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#**Include the prompts used, what Copilot suggested, and what you changed or rejected.&lt;br /&gt;
# '''AJAX Calendar (60 Points):'''&lt;br /&gt;
#* '''''Calendar View (10 Points):'''''&lt;br /&gt;
#** 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)&lt;br /&gt;
#** The user can view different months as far in the past or future as desired (5 points)&lt;br /&gt;
#* '''''User and Event Management (25 Points):'''''&lt;br /&gt;
#** Events can be added, modified, and deleted (5 points)&lt;br /&gt;
#** Events have a title, date, and time (2 points)&lt;br /&gt;
#** Users can log into the site, and they cannot view or manipulate events associated with other users (8 points)&lt;br /&gt;
#**: ''Don't fall into the Abuse of Functionality trap!  Check user credentials on the server side as well as on the client side.''&lt;br /&gt;
#** All actions are performed over AJAX, without ever needing to reload the page (7 points)&lt;br /&gt;
#** Refreshing the page does not log a user out (3 points)&lt;br /&gt;
#* '''''Best Practices (20 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** If storing passwords, they are stored salted and hashed (2 points)&lt;br /&gt;
#** All AJAX requests that either contain sensitive information or modify something on the server are performed via POST, not GET (3 points)&lt;br /&gt;
#** Safe from XSS attacks; that is, all content is escaped on output (3 points)&lt;br /&gt;
#** Safe from SQL Injection attacks (2 points)&lt;br /&gt;
#** CSRF tokens are passed when adding/editing/deleting events (3 points)&lt;br /&gt;
#** Session cookie is HTTP-Only (3 points)&lt;br /&gt;
#** Page passes the W3C validator (2 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Site is intuitive to use and navigate (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
# '''Creative Portion &amp;amp; AI Security Audit (25 Points)'''&lt;br /&gt;
#* '''''AI Security Audit Implementation (15 Points):'''''&lt;br /&gt;
#** Used GitHub Copilot for security analysis of at least 3 different code components (4 points)&lt;br /&gt;
#** Documented security audit process and findings in README.md (3 points)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#** Implemented fixes for identified security issues (3 points)&lt;br /&gt;
#** We recommend you to use &amp;quot;Agent&amp;quot; Mode of Copilot to implement this feature&lt;br /&gt;
#** Be sure to create a new branch before implement this in case any of your original code being changed. &lt;br /&gt;
#* '''Additional Calendars Features (worth 10 points):''' Develop some additional features for the calendar, a few examples are provided below.&lt;br /&gt;
#**  Users can tag an event with a particular category and enable/disable those tags in the calendar view. (5 points)&lt;br /&gt;
#**  Users can share their calendar with additional users. (5 points)&lt;br /&gt;
#**  Users can create group events that display on multiple users calendars (5 points)&lt;br /&gt;
#** Feature enhances user experience and calendar functionality&lt;br /&gt;
#* '''''Make sure to save a description of your creative portion, AI security audit process, and a link to your server in your README.md file.'''''&lt;br /&gt;
&lt;br /&gt;
''* 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 &amp;quot;free.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 4]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_2&amp;diff=3530</id>
		<title>Module 2</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_2&amp;diff=3530"/>
		<updated>2026-09-14T17:28:27Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Simple File Sharing Site */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__ &lt;br /&gt;
Module 2 introduces you to Linux, a command-line environment, the Apache web server, and PHP.  You will create and configure your own cloud instance, install Apache and related software, and then form groups to make a simple file sharing site.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:blue;&amp;quot;&amp;gt;'''DO NOT WAIT UNTIL THE LAST MINUTE TO START ON THIS (or any) MODULE!'''  The most common reason students perform poorly in this class is procrastination.  You have been warned.&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Linux user management&lt;br /&gt;
* SSH&lt;br /&gt;
* Apache&lt;br /&gt;
* PHP fundamentals&lt;br /&gt;
* Web security (FIEO)&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
''To jump right into setting up your EC2 Instance, Begin with [[SSH]]''&lt;br /&gt;
&lt;br /&gt;
* [[Linux]]&lt;br /&gt;
* [[Bash]]&lt;br /&gt;
* [[Amazon Web Services]]&lt;br /&gt;
* [[SSH]]&lt;br /&gt;
* [[Apache]]&lt;br /&gt;
* [[PHP]]&lt;br /&gt;
* [[Workflow]]&lt;br /&gt;
* [[VS Code]]&lt;br /&gt;
* [[Web Application Security, Part 1]]&lt;br /&gt;
* [[FAQ - Mod 2]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;There are a lot of wiki pages that you need to read in order to complete the individual portion of Module 2.  The wiki pages contain an assortment of required steps, optional steps, and information.  To make things easier, we have highlighted the ''required steps'' with a red bar, like you see on the left of this message.  Do not skip anything marked with a red bar, or else you will not have a perfectly configured instance.&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Learn About Linux ===&lt;br /&gt;
&lt;br /&gt;
Linux is an open-source operating system based on UNIX. Linux is highly versatile and is used in a wide range of applications.&lt;br /&gt;
&lt;br /&gt;
If you are not a Linux guru, read the Linux guide to get started: [[Linux]]&lt;br /&gt;
&lt;br /&gt;
=== Windows Users Only: Enable Windows Subsystem for Linux (WSL) or Install Cygwin ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You will need WSL or Cygwin in order to log into and configure your EC2 instance. Cygwin is already installed on the CEC machines. If you're using Windows 11, you can use WSL, which is an officially-supported way of accessing a Linux terminal on Windows. If not, you'll want to use Git Bash or Cygwin. Instructions for installing either on your personal machine are located in [[Workflow#Cygwin|the Workflow guide]].&lt;br /&gt;
&lt;br /&gt;
Mac OS X users should located the &amp;quot;Terminal&amp;quot; application, which is installed by default; you will need this in the coming steps.&lt;br /&gt;
&lt;br /&gt;
=== Create an AWS EC2 Instance ===&lt;br /&gt;
&lt;br /&gt;
Once you understand [[Linux]], you need to set up your Amazon EC2 Instance.  Use the AWS article to guide you through the process: [[Amazon Web Services]]&lt;br /&gt;
&lt;br /&gt;
=== Configure SSH ===&lt;br /&gt;
&lt;br /&gt;
Follow the instructions in [[SSH#SSH Configuration|the SSH guide]] to do the following:&lt;br /&gt;
&lt;br /&gt;
# Create your own SSH key pair&lt;br /&gt;
# Log in as the default user to your EC2 instance&lt;br /&gt;
# Create a new user&lt;br /&gt;
# Give the new user your SSH public key&lt;br /&gt;
&lt;br /&gt;
From now on, when you need to log into your EC2 instance as an administrator, just pop up a terminal and run:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 $ ssh &amp;lt;your-instance-username&amp;gt;@&amp;lt;ec2-xx-xx-xx-xx.compute-1.amazonaws.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To save from typing this command in every time you want to access your server, you can set an '''alias''' in your terminal.&lt;br /&gt;
&lt;br /&gt;
In the '''Mac''' terminal, vim ~/.zshrc and input the following command:&lt;br /&gt;
 $ alias aws='ssh &amp;lt;your-instance-username&amp;gt;@&amp;lt;ec2-xx-xx-xx-xx.compute-1.amazonaws.com&amp;gt;'&lt;br /&gt;
Now, whenever you type aws in your terminal, it will type your ssh command.&lt;br /&gt;
&lt;br /&gt;
In '''Cygwin''', vim ~/.bash_profile and input the following:&lt;br /&gt;
 export aws=&amp;quot;ssh &amp;lt;your-instance-username&amp;gt;@&amp;lt;ec2-xx-xx-xx-xx.compute-1.amazonaws.com&amp;gt;&amp;quot;&lt;br /&gt;
And you run this command by typing '''$aws'''&lt;br /&gt;
Also note that you may have to restart your terminal for this to take effect, or put the command in your ~/.bashrc file&lt;br /&gt;
&lt;br /&gt;
=== Install Essential Packages ===&lt;br /&gt;
&lt;br /&gt;
Recall that software installation in Linux instances is usually handled through a package manager, like '''yum'''.  For more information, refer to [[Linux#Repository-Based Package Managers|the Linux guide]].&lt;br /&gt;
&lt;br /&gt;
To make sure everything is working properly, '''install your first package(s) according to the ''Essential Packages'' subsection in the Linux guide.'''&lt;br /&gt;
&lt;br /&gt;
=== Set the Timezone ===&lt;br /&gt;
&lt;br /&gt;
'''Set your server to use US Central time (America/Chicago).'''  Instructions are in the Linux guide: [[Linux#Synchronizing Date and Time]]&lt;br /&gt;
&lt;br /&gt;
=== Set Up the Apache Web Server ===&lt;br /&gt;
&lt;br /&gt;
You need to '''install the Apache web server on your EC2 instance'''.  Refer to [[Apache|the Apache guide]].&lt;br /&gt;
&lt;br /&gt;
In order for your web server to be accessible, you need to '''open up Port 80 on your EC2 instance'''.  Instructions are in the AWS guide: [[Amazon Web Services#Enabling Web Access to your EC2 Instance]]&lt;br /&gt;
&lt;br /&gt;
'''Enable the UserDir module in Apache.'''  Instructions are in the Apache guide: [[Apache#The UserDir Module]]&lt;br /&gt;
&lt;br /&gt;
=== Install PHP ===&lt;br /&gt;
&lt;br /&gt;
You need to '''install PHP on your EC2 server'''.  Instructions are at the top of the PHP guide: [[PHP]]&lt;br /&gt;
&lt;br /&gt;
You need to '''configure PHP to show errors'''.  Instructions are immediately below installation in the PHP guide.&lt;br /&gt;
&lt;br /&gt;
=== Learn about the PHP Language ===&lt;br /&gt;
&lt;br /&gt;
Before you can continue, you will need to make yourself acquainted with PHP language components.  Read the PHP guide for an overview: [[PHP#PHP Language Components]]&lt;br /&gt;
&lt;br /&gt;
=== Set Up GitHub Copilot ===&lt;br /&gt;
&lt;br /&gt;
You will be using GitHub Copilot in this module to help you generate PHPUnit tests.  &lt;br /&gt;
 &lt;br /&gt;
# Install the GitHub Copilot extension in VS Code (see the [[AI Tools]] guide for setup).  &lt;br /&gt;
# Sign in with your GitHub account to enable Copilot.  &lt;br /&gt;
# Verify that Copilot is active by checking the status icon in the VS Code status bar.  &lt;br /&gt;
&lt;br /&gt;
Copilot will suggest code completions as you type. You can use these suggestions to accelerate writing your test cases, but you must still review and adjust its output for correctness. See [[AI Best Practices]].&lt;br /&gt;
&lt;br /&gt;
=== Set Up your Workspace ===&lt;br /&gt;
&lt;br /&gt;
You are now ready to set up the workspace that you will be using for the rest of the semester.  Follow the instructions in [[Workflow]] to:&lt;br /&gt;
&lt;br /&gt;
# Make an account on GitHub&lt;br /&gt;
# Setup a repo using GitHub Classroom&lt;br /&gt;
# Clone your CSE 330 repo from GitHub&lt;br /&gt;
# Install a text editor such as '''VSCode''' to write your assignments with&lt;br /&gt;
# Either install an SFTP extension in VSCode (recommended) or use SFTP through command line, to transfer files from your local machine to your instance&lt;br /&gt;
&lt;br /&gt;
=== Calculator ===&lt;br /&gt;
&lt;br /&gt;
'''Build a calculator using PHP and an HTML form.'''&lt;br /&gt;
The form should include two number inputs and a radio button group for addition, subtraction, multiplication, and division. Submit the form with a GET request, either to the same page or to a separate results page. Be sure to handle edge cases (division by zero, empty fields, non-numeric input, floating point values).&lt;br /&gt;
&lt;br /&gt;
'''GitHub Copilot''' installed &lt;br /&gt;
Use Copilot to help you write and refine your calculator logic and to create simple edge-case tests (for example, a small PHP script that calls your calculator with sample inputs).&lt;br /&gt;
&lt;br /&gt;
Finally, include in your README.md:&lt;br /&gt;
&lt;br /&gt;
* A working link to your calculator page on your EC2 instance&lt;br /&gt;
* A brief note describing how you used Copilot to write your tests (for example, which suggestions you accepted or modified) as described in [[AI Best Practices]]&lt;br /&gt;
&lt;br /&gt;
== Group Project ==&lt;br /&gt;
&lt;br /&gt;
You will work in pairs (that means you and one other person) on this project.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:blue;&amp;quot;&amp;gt;In this module, and all future modules, the group portion builds off of material you learn in the individual portion.  You should therefore complete the individual portions prior to working on the group portions.  (Trust us: it will make your life easier!)&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Important Reminder:''' frequently commit your work to your repository as a backup!&lt;br /&gt;
&lt;br /&gt;
=== Simple File Sharing Site ===&lt;br /&gt;
&lt;br /&gt;
You will be making a simple file sharing site that supports uploading, viewing, and deleting files associated with various users.  Details:&lt;br /&gt;
&lt;br /&gt;
* You should have a file named '''users.txt''' stored in a secure place on your filesystem.  It should have at least three usernames, with one username per line.&lt;br /&gt;
*: Passwords add an extra layer of complexity (encryption) that we will cover in Module 3.  You will not earn credit for implementing passwords as your creative portion for this module.&lt;br /&gt;
* Users of the file sharing site should be able to enter their username and then log in.&lt;br /&gt;
*: You may implement logins using session variables, or you may implement them using a GET parameter that is passed between pages.  You will need to always use sessions starting in Module 3.&lt;br /&gt;
* Users should see a list of all files associated with their username after the log in.&lt;br /&gt;
* Users should be able to view, upload, and delete files associated with their username.&lt;br /&gt;
* The URI should NOT reveal the internal file structure of your web site.&lt;br /&gt;
** Example of what not to do:  http://www.example.com/download.php?file=/home/paul/file.txt&lt;br /&gt;
** Another bad example:  http://www.example.com/~user/file.txt&lt;br /&gt;
** Good example: http://www.example.com/~user/view.php?user=music&amp;amp;name=file.txt&lt;br /&gt;
*: In general, don't reveal any internal information about the site.&lt;br /&gt;
*: '''Food for thought:''' Given that Apache processes (with the exception of the main process) do not run as root, but rather as the user apache, how can you allow both your user account and the php scripts to read and write the &lt;br /&gt;
*:necessary files?&lt;br /&gt;
*:: ''Hint:'' You shouldn't be storing the user-uploaded files in public_html (that is only the place for your own web-page files), &lt;br /&gt;
*::''Hint number 2:''  you will need to use chown to change permissions to let user apache have permissions on the user-files (that '''are not''' in the public_html folder). If you don't know what this does or how to do this, look at the [[Apache]] page&lt;br /&gt;
* You should customize your file sharing site by implementing an additional feature or two as part of the '''creative portion'''.&lt;br /&gt;
&lt;br /&gt;
You will probably find the PHP guide on this wiki to be helpful: [[PHP#Other PHP Tips]]&lt;br /&gt;
&lt;br /&gt;
=== Web Security and Validation ===&lt;br /&gt;
&lt;br /&gt;
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 1]]&lt;br /&gt;
&lt;br /&gt;
In particular:&lt;br /&gt;
&lt;br /&gt;
* '''Your application needs to follow the conventions of FIEO (Filter Input and Escape Output)'''.  Things you should filter include file names, usernames, and so on.&lt;br /&gt;
* '''Your project must pass the W3C Validator with no errors or warnings'''.  For more information, see the HTML and CSS guide: [[HTML and CSS#Validation]]&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 105 points total. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''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. &lt;br /&gt;
&lt;br /&gt;
_____________&lt;br /&gt;
&lt;br /&gt;
# '''Cloud Instance and Web Server (25 Points):'''&lt;br /&gt;
#* You can SSH into your instance using your own custom username and SSH key. Put a screenshot in your repository of your terminal SSH-ing into your instance (10 points)&lt;br /&gt;
#* Your custom username is able to perform SUDO commands. Put a screenshot in your repository of you successfully executing a command with sudo (5 points)&lt;br /&gt;
#* The time zone is correct on your instance. Put a screenshot in your repository of the results of the `date` command (5 points)&lt;br /&gt;
#* The UserDir Apache module is working. Place a file in /home/&amp;lt;username&amp;gt;/public_html and link to the file in your README.md (5 points)&lt;br /&gt;
# '''PHP Calculator (25 Points):'''&lt;br /&gt;
#* The calculator is able to perform all four functions with floating point numbers (2 points each, 8 points in total)&lt;br /&gt;
#* Each function is given as a radio button choice (4 points)&lt;br /&gt;
#* The calculator page passes the W3C HTML validator (3 points)&lt;br /&gt;
#* '''''Make sure you have a README.md file in your individual repo with the link to your calculator on your instance.''''' See the Creative Portion section for more information about .md files.&lt;br /&gt;
#* At least three unit tests (written with GitHub Copilot)(5 points). &lt;br /&gt;
#** We recommend you to use &amp;quot;Ask&amp;quot; Mode of Copilot to implement this feature&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
# '''File Sharing Site (45 Points):'''&lt;br /&gt;
#* '''''File Management (25 Points):'''''&lt;br /&gt;
#** Users should not be able to see any files until they enter a username and log in (4 points)&lt;br /&gt;
#**: ''Remember that users.txt should be stored in a secure location on your filesystem.  That is, '''you should not be able to type any URL into your browser and see the raw users.txt file!'''''&lt;br /&gt;
#** Users can see a list of all files they have uploaded (4 points)&lt;br /&gt;
#** Users can open files they have previously uploaded (5 points)&lt;br /&gt;
#**: ''Note: Users should be able to open not only plain text files but also other file formats: images, spreadsheets, etc.''&lt;br /&gt;
#** Users can upload files (4 points)&lt;br /&gt;
#**: ''Note: Like users.txt, uploaded files should be stored in a secure location on your filesystem.  That is, '''do not keep your uploads directory underneath a directory served by Apache!'''''&lt;br /&gt;
#** Users can delete files.  If a file is &amp;quot;deleted&amp;quot;, it should actually be removed from the filesystem (4 points)&lt;br /&gt;
#** The directory structure is hidden. Users should not be able to access or view files by manipulating a URL. (2 points)&lt;br /&gt;
#** Users can log out (2 points)&lt;br /&gt;
#**: ''Note: If using session variables, you must ''actually'' log out the user by destroying their session; i.e., don't just redirect them to the login screen.''&lt;br /&gt;
#* '''''Best Practices (15 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (4 points)&lt;br /&gt;
#** The site follows the FIEO philosophy (3 points)&lt;br /&gt;
#** All pages pass the W3C validator (3 points)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Site is intuitive to use and navigate (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
# '''Creative Portion (10 Points)''' (see below)&lt;br /&gt;
#* '''''Make sure you have a README.md file in your group repo with the following:'''''&lt;br /&gt;
#** An .md file is a plain text file called a markdown file  [https://guides.github.com/features/mastering-markdown/ ] .  &lt;br /&gt;
#***The contents of your README.md file are shown on your repo, making it easier for us to grade&lt;br /&gt;
#*** You may style the page with bolding, hyperlings, images, block quotes and lists&lt;br /&gt;
#*** [http://dillinger.io/ This online markdown editor] makes it easy to learn this format  &lt;br /&gt;
#** The link to your file sharing site. '''''We need this to grade your work.'''''&lt;br /&gt;
#** A brief description of what you did for your creative portion&lt;br /&gt;
#** Any additional login details needed for the TA&lt;br /&gt;
&lt;br /&gt;
=== Creative Portion ===&lt;br /&gt;
&lt;br /&gt;
This module and all future modules will require that you invest some time into creating additional features for your group project.  Plan to invest at the very least 60 minutes of your time into the creative portion.  In the above rubric, viewing files is worth five and uploading is worth four. The creative portion is worth 10 points; it should be a very sizable part of your project.&lt;br /&gt;
&lt;br /&gt;
* The creative portion is an opportunity for you to learn the material of your own interest.&lt;br /&gt;
* You will not earn credit for a creative-portion feature that simply rehashes something you've already done in a previous module.&lt;br /&gt;
&lt;br /&gt;
If you need ideas for a creative portion, or if you want to know whether or not your creative portion idea is &amp;quot;hard enough&amp;quot;, ask a TA.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 2]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=SSH&amp;diff=3529</id>
		<title>SSH</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=SSH&amp;diff=3529"/>
		<updated>2026-09-02T18:44:20Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* SSH Configuration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Secure SHell, or '''SSH''', is the leading interface for connecting as an administrator from your local computer to something on the cloud.  You will be using exclusively SSH to execute commands and upload files to your cloud instance.  If you use a Git hosting service like BitBucket or Github, you will need SSH in order to save your changes there as well.&lt;br /&gt;
&lt;br /&gt;
SSH access requires that the ''sshd'' daemon is running on the remote machine.  By default, SSH is preinstalled on your EC2 instance.  If you are not using an EC2 instance, simply install '''ssh''' from yum or apt on the remote machine.&lt;br /&gt;
&lt;br /&gt;
== SSH Keys ==&lt;br /&gt;
&lt;br /&gt;
SSH can work using password-based authentication, but it is more common nowadays, and also more secure, to use ''public and private keys'', also known as a ''key pair''.&lt;br /&gt;
&lt;br /&gt;
Here's how it works.  You have a private copy of your key, called your '''private key'''.  Servers to which you want to connect have an analog to your private key called your '''public key'''.  When you attempt to connect to a server using your private key, the server checks to see if any of its public keys &amp;quot;fits&amp;quot; your private key, and if it finds a match, it lets you in.&lt;br /&gt;
&lt;br /&gt;
You can think of the ''private key'' as the &amp;quot;key&amp;quot; and the ''public key'' as the &amp;quot;lock&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== SSH Configuration ==&lt;br /&gt;
&lt;br /&gt;
This section discusses how to set up your computer and your remote instance for SSH.  You need to do this only once.&lt;br /&gt;
&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
=== Creating an SSH Key Pair ===&lt;br /&gt;
&lt;br /&gt;
You should create your own SSH Key Pair.  You will keep the private key on your own computer, and you will upload the public key to all places to which you need to connect.&lt;br /&gt;
&lt;br /&gt;
To generate a key pair, open a terminal ([[Workflow#Installing_a_Terminal_Emulator|on Windows, we recommend using Git Bash]] and run the following command:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;# Run on Local Machine in Terminal or Cygwin&lt;br /&gt;
$ ssh-keygen -t rsa -b 4096&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Important:''' Don't name the file unless you are using a CEC machine. If you are using a CEC machine, name the file ''/cygdrive/h/.ssh/id_rsa''&lt;br /&gt;
&lt;br /&gt;
For the purposes of this course, you do not need to enter a passphrase, although you would probably want one in real life.&lt;br /&gt;
&lt;br /&gt;
Your keys should have been created in ~/.ssh.  Check to make sure they exist using the ''ls'' command:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ls ~/.ssh&lt;br /&gt;
id_rsa  id_rsa.pub&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''id_rsa'' is your private key, and ''id_rsa.pub'' is the corresponding public key.  Let's look at the public signature of your key pair:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ cat ~/.ssh/id_rsa.pub&lt;br /&gt;
ssh-rsa AAAA3Bblahblahblahblah yourname@somedomain&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
We will need this later.  Let's now add our key to the ''ssh-agent''.&lt;br /&gt;
&lt;br /&gt;
==== SSH Agent: OS X ====&lt;br /&gt;
&lt;br /&gt;
Run the following command to add your key to the SSH agent in Mac OS X.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;# Non-Cygwin&lt;br /&gt;
$ ssh-add&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== SSH Agent: WSL or Cygwin ====&lt;br /&gt;
&lt;br /&gt;
In order to make the SSH agent run when you start your terminal, you need to run a couple more commands (these will add a line to your .bashrc file, which runs automatically on startup):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Cygwin only&lt;br /&gt;
$ echo &amp;quot;eval \`ssh-agent -s\`; ssh-add&amp;quot; &amp;gt;&amp;gt; ~/.bashrc&lt;br /&gt;
$ source ~/.bashrc # reload the Bash shell&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you get this scary-looking error after you run the second command…&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;br /&gt;
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @&lt;br /&gt;
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
…then you need to do the following to fix the permissions:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ chgrp Users &amp;lt;/path/to/id_rsa&amp;gt;&lt;br /&gt;
$ chmod 600 &amp;lt;/path/to/id_rsa&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Amazon EC2 SSH Configuration ===&lt;br /&gt;
&lt;br /&gt;
When you first created your EC2 instance, you downloaded a *.pem file.  This is the ''private key'' for the default user ('''ec2-user''' on AMI).  We need use this key to connect to your instance using the default user, at which point we will create a new custom user.  We will give that new user the public key that you created in the previous step.  You will then be able to log into your custom user using your personal key pair, so you won't need the default user again (although you should keep the *.pem file around just in case).&lt;br /&gt;
&lt;br /&gt;
==== Logging In as the Default User ====&lt;br /&gt;
&lt;br /&gt;
To connect to the default user on your Amazon EC2 instance, run this command in your terminal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run on Local Machine&lt;br /&gt;
$ ssh -i &amp;lt;/path/to/default-privatekey.pem&amp;gt; ec2-user@&amp;lt;ec2-xx-xx-xx-xx.compute-1.amazonaws.com&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Important:''' As soon as you are able to successfully log in, commands you type into your prompt will be executed on the remote instance, ''not'' your local machine.&lt;br /&gt;
&lt;br /&gt;
If you get this scary-looking warning:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;br /&gt;
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @&lt;br /&gt;
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
…then you need to correctly set the permissions on your *.pem file.  You should know how to do this from the Linux guide, but in case you didn't catch that detail, run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run on Local Machine&lt;br /&gt;
&lt;br /&gt;
# Cygwin Only:&lt;br /&gt;
&lt;br /&gt;
 $ chgrp Users &amp;lt;/path/to/default-privatekey.pem&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# For Everyone:&lt;br /&gt;
$ chmod 600 &amp;lt;/path/to/default-privatekey.pem&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Food for Thought:''' Why you wouldn't want other people viewing your private key file?&lt;br /&gt;
&lt;br /&gt;
==== Creating a New User with SSH Permissions ====&lt;br /&gt;
&lt;br /&gt;
First, you need to create your new user with sudo and SSH permissions.  Again, the instructions for this are in the Linux guide, but to keep everything in one place, the commands are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run these on your instance, not on your local machine!&lt;br /&gt;
&lt;br /&gt;
# Don't forget to customize `&amp;lt;My Full Name&amp;gt;` and `&amp;lt;usernameHere&amp;gt;`:&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
$ sudo passwd &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# You need to add your username to the sudoers list.  Refer to the Linux guide for details.&lt;br /&gt;
# Look around line 101.&lt;br /&gt;
$ sudo visudo &lt;br /&gt;
&lt;br /&gt;
# Restart the SSH server:&lt;br /&gt;
$ sudo service sshd restart&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Okay, cool: we have created our custom username on our remote instance.  Now, we need to enable this account to accept our SSH key when authenticating.  To do this, we need to add our public key to a file named '''authorized_keys''', which is stored in your remote username's SSH configuration directory.&lt;br /&gt;
&lt;br /&gt;
To create the directory and edit the file, run on your instance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Switch to our own user to ensure that permissions are set correctly:&lt;br /&gt;
$ su &amp;lt;username here&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Change the working directory to our user's home directory:&lt;br /&gt;
$ cd&lt;br /&gt;
&lt;br /&gt;
# Make the nonexistent SSH configuration directory and set its permissions:&lt;br /&gt;
$ mkdir .ssh&lt;br /&gt;
$ chmod 700 .ssh&lt;br /&gt;
&lt;br /&gt;
# Create the `authorized_keys` file and set the permissions&lt;br /&gt;
$ touch .ssh/authorized_keys&lt;br /&gt;
$ chmod 600 .ssh/authorized_keys&lt;br /&gt;
&lt;br /&gt;
# Edit the `authorized_keys` file&lt;br /&gt;
$ nano .ssh/authorized_keys&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For more detail on editing files on the command line, see [[Linux#File Editors|the Linux guide]].&lt;br /&gt;
&lt;br /&gt;
Inside of the `authorized_keys` file, paste in your public key.  Your public key is the content of `~/.ssh/id_rsa.pub` '''on your local machine''' (it starts with &amp;quot;ssh-rsa&amp;quot;).  There are a few ways to get it onto your clipboard:&lt;br /&gt;
&lt;br /&gt;
# '''OS X Only:''' Open a new Terminal and run: &amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;cat ~/.ssh/id_rsa.pub | pbcopy&amp;lt;/source&amp;gt; This immediately copies the contents of your public key to your clipboard.&lt;br /&gt;
# '''Windows Only:''' Navigate to id_rsa.pub manually, open it in your favorite text editor (like Notepad++), select all, and copy.  The location of id_rsa.pub might be: ''C:\cygwin\home\username\.ssh\id_rsa.pub''&lt;br /&gt;
# '''&amp;amp;#42;NIX Only:''' Open a new Terminal and copy the output of the following command: &amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;cat ~/.ssh/id_rsa.pub&amp;lt;/source&amp;gt; Note that this won't work as advertised in Cygwin because Cygwin adds extra line breaks when you try to copy out of it.&lt;br /&gt;
&lt;br /&gt;
When finished, save and close `authorized_keys`.&lt;br /&gt;
&lt;br /&gt;
'''Food for Thought:''' How would you enable your remote user to authenticate over SSH using the same default key instead of the one you created using ''ssh-keygen''?&lt;br /&gt;
&lt;br /&gt;
==== Logging In as your New User ====&lt;br /&gt;
&lt;br /&gt;
Let's test whether we can log into our instance using our own username and our own key pair. Open up a new Terminal or Cygwin window '''on your local machine''' (not from ec2) and run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ssh &amp;lt;username here&amp;gt;@&amp;lt;ec2-xx-xx-xx-xx.amazonaws.com&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is how you will be SSHing into your instances for the remainder of the semester.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Troubleshooting ===&lt;br /&gt;
&lt;br /&gt;
Almost everyone receives the following error at some point in their career: &amp;lt;code&amp;gt;Permission denied (publickey)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Here are some possible causes for the error.&lt;br /&gt;
&lt;br /&gt;
==== The Authorized Keys File ====&lt;br /&gt;
&lt;br /&gt;
Make sure that your remote ''authorized_keys'' file contains ''exactly'' your public signature from ''id_rsa.pub''.&lt;br /&gt;
&lt;br /&gt;
===== Pasting into Vi =====&lt;br /&gt;
&lt;br /&gt;
If you used vi to edit your file and you forgot to enter &amp;quot;insert&amp;quot; mode before pasting in your key, it is possible that you lost the first &amp;quot;s&amp;quot; in &amp;quot;ssh-rsa&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Check the contents of authorized_keys via &amp;lt;code&amp;gt;cat ~/.ssh/authorized_keys&amp;lt;/code&amp;gt; on your server.  If the output starts with &amp;quot;sh-rsa&amp;quot; instead of &amp;quot;ssh-rsa&amp;quot;, edit the authorized_keys file and add the second '''s'''.&lt;br /&gt;
&lt;br /&gt;
===== Unintentional Line Breaks =====&lt;br /&gt;
&lt;br /&gt;
If you got your key via a &amp;lt;code&amp;gt;cat ~/.ssh/id_rsa.pub&amp;lt;/code&amp;gt; and then copied it from the terminal, it's possible that you unintentionally introduced line breaks.  Ensure that the key is represented by only one line of text.&lt;br /&gt;
&lt;br /&gt;
==== Permissions ====&lt;br /&gt;
&lt;br /&gt;
They need to be correct on both the client side and on the server side.&lt;br /&gt;
&lt;br /&gt;
* Local ''.ssh'' = 700&lt;br /&gt;
* Local ''id_rsa'' = 600&lt;br /&gt;
* Remote ''.ssh'' = 700&lt;br /&gt;
* Remote ''authorized_keys'' = 600&lt;br /&gt;
&lt;br /&gt;
If you are in Cygwin and the permissions don't seem to change, you might need to add the troubled file to a usergroup, like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ chgrp Users .ssh/id_rsa&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
==== The SSH Agent ====&lt;br /&gt;
&lt;br /&gt;
You need to be sending the correct key along with your SSH Agent.  Run the following command, and you should see some output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ssh-add -l&lt;br /&gt;
2048 ab:cd:ef:ab:cd:ef:ab:cd:ef:ab:cd:ef:ab:cd:ef:ab:cd /path/to/.ssh/id_rsa (RSA)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don't see any output, or if the output does not contain your ''id_rsa'', then you need to run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ssh-add &amp;lt;/path/to/.ssh/id_rsa&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
=== More SSH Server Configurations ===&lt;br /&gt;
&lt;br /&gt;
Up to this point, we have been using all of the default SSH configurations.  Like most things in Linux, SSH can be customized.&lt;br /&gt;
&lt;br /&gt;
For the purposes of CSE 330, the additional configurations in this short section are optional; this serves only as a reference.&lt;br /&gt;
&lt;br /&gt;
==== Disabling Root Access ====&lt;br /&gt;
&lt;br /&gt;
It is almost always a good idea to disable root access over ssh. This could be done by editing ''/etc/ssh/sshd_config'' and setting  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;PermitRootLogin no&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will need to restart the SSH server for changes to take effect:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo service sshd restart&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Enabling Password-Based Authentication ====&lt;br /&gt;
&lt;br /&gt;
Up to this point, we have been using key pairs for SSH.  A public/private key pair is generally considered to be more secure, but it requires that you always have access to your private key file when you want to log into your remote machine.&lt;br /&gt;
&lt;br /&gt;
By default, EC2 instances allow only public/private key pair authentication.  You can enable password-based authentication by setting the PaswordAuthentication option in ''/etc/ssh/sshd_config'' to '''yes''':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;PasswordAuthentication yes&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There may be a line with this option that you can un-comment.  For me, it is line 25.  You will need to restart the server for changes to take effect.&lt;br /&gt;
&lt;br /&gt;
If possible, however, you should restrict yourself to using private and public keys.&lt;br /&gt;
&lt;br /&gt;
== Using SSH ==&lt;br /&gt;
&lt;br /&gt;
Once you have completed the [[#SSH Configuration]] section above, all you need to do in order to SSH into your instance is to open a terminal and run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ssh &amp;lt;username here&amp;gt;@&amp;lt;ec2-xx-xx-xx-xx.compute-1.amazonaws.com&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PuTTY ===&lt;br /&gt;
&lt;br /&gt;
'''This short section about PuTTY serves only as a reference.'''&lt;br /&gt;
&lt;br /&gt;
If you have Cygwin available, you should use it to SSH into your instance.  However, if you are using a different computer, you might not have Cygwin at your fingertips.  In this case, a lighter-weight SSH client called '''PuTTY''' is available for Windows.&lt;br /&gt;
&lt;br /&gt;
You can download PuTTY from: http://www.chiark.greenend.org.uk/~sgtatham/putty/&lt;br /&gt;
&lt;br /&gt;
Amazon provides [http://docs.amazonwebservices.com/AWSEC2/latest/GettingStartedGuide/ConnectToInstanceLinux.html#connect-from-windows-machine a great tutorial on how to connect to a virtual machine from Windows].&lt;br /&gt;
&lt;br /&gt;
PuTTY is fairly simple and straight forward with one caveat: Amazon's ''*.pem'' files are SSH private key files, and they need to be converted to PuTTY's own '''.ppk''' format.  To do this, use the ''puttygen.exe'' utility available from the same page as PuTTY.  Select &amp;quot;Import&amp;quot; under the conversions menu, load the amazon ''*.pem'' key file, and press the &amp;quot;Save Private Key&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
Copy and paste works similarly to the X Window System in Unix. You use the left mouse button to select text in the PuTTY window. The act of selection automatically copies the text to the clipboard: there is no need to press Ctrl-Ins or Ctrl-C or anything else. In fact, pressing Ctrl-C will send a Ctrl-C character to the other end of your connection (just like it does the rest of the time), which may have unpleasant effects. The only thing you need to do, to copy text to the clipboard, is to select it.&lt;br /&gt;
&lt;br /&gt;
To paste the clipboard contents into a PuTTY window, by default you click the right mouse button. If you have a three-button mouse and are used to X applications, you can configure pasting to be done by the middle button instead, but this is not the default because most Windows users don't have a middle button at all. &lt;br /&gt;
&lt;br /&gt;
Here is a good PuTTY tutorial that you might find useful to get started: http://kb.mediatemple.net/questions/1595/Using+SSH+in+Putty+%28Windows%29&lt;br /&gt;
&lt;br /&gt;
== SSHFS and SFTP ==&lt;br /&gt;
&lt;br /&gt;
In addition to just SSH, your SSH server also supports '''SSHFS''', which enables you to mount your remote instance as a disk on your local computer, and '''SFTP''', which is a file transfer protocol that enables you to upload files directly to your instance.&lt;br /&gt;
&lt;br /&gt;
=== SSHFS ===&lt;br /&gt;
&lt;br /&gt;
SSHFS is a filesystem client which allows secure mounting of remote file systems. While there are other ways to mount remote file systems, SSHFS has the advantage of being able to mount a file system located on any host that has an SSH daemon running without any host side installation or configuration. This means that you can easily access and edit your files using all of your local applications including IDEs. &lt;br /&gt;
&lt;br /&gt;
As you may have inferred from the name, the underlying implementation utilizes SSH File Transfer Protocol in combination with FUSE, a package now included in the kernel that allows unprivileged users to easily create their own file systems in userspace (see the wikipedia entry for more information [http://en.wikipedia.org/wiki/Filesystem_in_Userspace]).&lt;br /&gt;
&lt;br /&gt;
To mount a share using password based authentication, the command is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sshfs &amp;lt;user&amp;gt;@&amp;lt;domain&amp;gt;:&amp;lt;/path/to/remote/directory&amp;gt; &amp;lt;/path/to/local/mountpoint&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For example, to mount the directory /home/joe/myfiles in the user ''joe'''s home directory for a machine with the domain schmoesfiles.org using SSHFS you would enter the command&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sshfs joe@www.schmoesfiles.org:myfiles&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if you are using public key authentication, the command to mount the remote filesystem may need to be slightly different:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sshfs -o IdentityFile=&amp;lt;/path/to/private/key&amp;gt; &amp;lt;user&amp;gt;@&amp;lt;domain&amp;gt;:&amp;lt;/path/to/remote/directory&amp;gt; &amp;lt;/path/to/local/mountpoint&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To unmount the filesystem, you can use the following command:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ fusermount -u &amp;lt;/path/to/local/mountpoint&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SFTP ===&lt;br /&gt;
&lt;br /&gt;
Any server running an SSH server is also compatible with '''SFTP''' or Secure File Transfer Protocol.  (Compare to FTP, or File Transfer Protocol.)  SFTP is a convenient way to edit files on your computer and then upload them to your server in just a few clicks.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 2]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Workflow&amp;diff=3528</id>
		<title>Workflow</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Workflow&amp;diff=3528"/>
		<updated>2026-08-20T14:03:37Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Installing a Terminal Emulator */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The few minutes it takes to optimize your workflow will be the best few minutes you will spend in CSE 330 and your career.  It is remarkable how many hours you can save just by using the best interface for text editing and deployment.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
There are going to be several aspects to our workflow in CSE 3300, and we will be switching between multiple different programs.  Here is how they all fit together.&lt;br /&gt;
&lt;br /&gt;
=== Running Commands ===&lt;br /&gt;
&lt;br /&gt;
''Mac OS X:'' You will be running commands on your local machine using '''Terminal''', which you can find in your Applications -&amp;gt; Utilities folder.  You will be connecting to your server via SSH.&lt;br /&gt;
&lt;br /&gt;
''Windows:'' You will be running commands on your local machine using '''Git Bash''', which you will install in the next section.  You will be connecting to your server via SSH.&lt;br /&gt;
&lt;br /&gt;
=== Version Control ===&lt;br /&gt;
&lt;br /&gt;
You will be using '''Git''' as your version control system.  Git enables you to keep track of changes to your source code.  If you're using VS Code, the Git integration makes is very user friendly.&lt;br /&gt;
&lt;br /&gt;
=== Text Editing ===&lt;br /&gt;
&lt;br /&gt;
Although we highly recommend '''VS Code''', you may use '''Sublime Text''', '''Komodo Edit''', '''Notepad++''' (for Windows), and '''TextWrangler''' (for Mac). You can also use an IDE, like '''PHPStorm''', if you prefer.&lt;br /&gt;
&lt;br /&gt;
=== File Transfer ===&lt;br /&gt;
&lt;br /&gt;
In order to deploy your projects to your EC2 instance, you need some sort of method to transfer files from your local machine to your instance using SFTP. One option is using '''FileZilla''', an FTP client, to transport files from your local computer to your server.&lt;br /&gt;
&lt;br /&gt;
=== Flowchart ===&lt;br /&gt;
&lt;br /&gt;
If you are more visual, this flowchart might help:&lt;br /&gt;
&lt;br /&gt;
[[File:CSE-330-Workflow.png]]&lt;br /&gt;
&lt;br /&gt;
The flowchart illustrates 3 methods to copy files to your cloud instance: via your text editor, via an external SFTP client, and by cloning your remote Git repository.  We will be primarily using '''Option 2''' in CSE 330 this semester.&lt;br /&gt;
&lt;br /&gt;
In the flowchart, your ''Git Client'' is SourceTree and your ''Text Editor'' is Komodo.  The ''SFTP Client'' could be FileZilla.&lt;br /&gt;
&lt;br /&gt;
== Installing a Terminal Emulator ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
Windows is based on a fundamentally different architecture than Linux, meaning that, if you want to run Linux programs on Windows, you'll have to enable or install a special terminal emulator for it. Mac users do not need to install a terminal emulator since MacOs itself is a derivative of Unix, which is an ancestor of Linux.  The Terminal utility is all you need.&lt;br /&gt;
&lt;br /&gt;
=== Git Bash ===&lt;br /&gt;
&lt;br /&gt;
Git Bash is a software application for Windows that gives you a Bash shell terminal and the Git command-line tool. It lets Windows users run Linux-style commands and manage code using Git. You can download it from [https://git-scm.com/install/windows Git for Windows].&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Github ==&lt;br /&gt;
&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
'''Github''' is a service that enables you to store your code for free in the cloud.&lt;br /&gt;
&lt;br /&gt;
=== Creating a Github Account ===&lt;br /&gt;
&lt;br /&gt;
Go to [https://github.org/ Github.org] to create an account.&lt;br /&gt;
&lt;br /&gt;
'''Important:''' Use your @wustl.edu e-mail address to create your account.  This will put your account on the &amp;quot;educational plan&amp;quot;, which removes some limitations of the free account.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Adding Your Key to Github ===&lt;br /&gt;
'''Not uploading your key is perfectly fine, and in many cases easier.  Here are the instructions though, if you'd like'''&lt;br /&gt;
&lt;br /&gt;
# First make sure that you've finished the [[SSH]] guide configuration instructions.&lt;br /&gt;
#: We will use the same key for connecting to your remote server as for connecting to Github.  (This is completely valid practice.)&lt;br /&gt;
# Go to the &amp;quot;SSH keys&amp;quot; section of the settings panel (Manage Account -&amp;gt; SSH Keys)&lt;br /&gt;
# Add a new key.&lt;br /&gt;
#* I recommend naming it after the computer you're using; if you're in the CEC, for example, you could name it &amp;quot;Warehouse Cygwin&amp;quot;.&lt;br /&gt;
#* The contents of your key is the contents of your ''id_rsa.pub''.  It looks like &amp;lt;code&amp;gt;ssh-rsa AAAAB3blahblahblahblah yourname@somedomain&amp;lt;/code&amp;gt;.  It is exactly the same thing that you had to paste into the ''authorized_keys'' file on your EC2 instance.&lt;br /&gt;
&lt;br /&gt;
You will now be able to push code to your Github repository using your personal SSH key.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== FileZilla ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use SFTP from the command line, or you can use any GUI file transfer client.  All FTP clients I have seen also support SFTP.  One popular FTP client is [http://www.filezilla-project.org/ Filezilla].&lt;br /&gt;
&lt;br /&gt;
# Download and install FileZilla from http://filezilla-project.org/download.php?type=client&lt;br /&gt;
#: If you are using a lab computer, you can save it to your H drive.&lt;br /&gt;
# When you launch FileZilla, go to Edit→Settings or FileZilla→Preferences, and go to the SFTP options (under Connection).  Click &amp;quot;Add keyfile…&amp;quot;.&lt;br /&gt;
#* Choose your ''id_rsa'' file.   '''OS X Tip:''' Press Shift+Command+Period to reveal hidden files in the file chooser window.&lt;br /&gt;
#* FileZilla will want to make a *.ppk file.  A good place to save it would be the .ssh directory as ''id_rsa.ppk''.&lt;br /&gt;
#: When finished, press '''OK''' (not the red ×) to save your changes.&lt;br /&gt;
# Back on the main FileZilla screen, there are four fields: Host, Username, Password, and Port.  Fill them in as follows:&lt;br /&gt;
#* '''Hostname:''' sftp://&amp;lt;ec2-xxx-xx-xx-xxx.compute-1.amazonaws.com/&amp;gt;&lt;br /&gt;
#* '''Username:''' The username you created on your server.&lt;br /&gt;
#* '''Password:''' Since you are using a key, you may leave this blank.&lt;br /&gt;
#*: If you changed your SSH settings to allow password-based authentication, then you could put your password in here.&lt;br /&gt;
#* '''Port:''' 22&lt;br /&gt;
# Then click Quickconnect.  If everything is configured correctly, FileZilla should log into your server.&lt;br /&gt;
&lt;br /&gt;
[[File:FileZilla.png]]&lt;br /&gt;
&lt;br /&gt;
On the left and right of the FileZilla window, you can drag files between your computer and your server.  Thus, you can edit a file in a text editor like Notepad++ on your decktop, and then upload it to your server by simply dragging it from the left pane to the right pane in FileZilla.&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Workflow&amp;diff=3527</id>
		<title>Workflow</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Workflow&amp;diff=3527"/>
		<updated>2026-08-20T14:03:30Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Installing a Terminal Emulator */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The few minutes it takes to optimize your workflow will be the best few minutes you will spend in CSE 330 and your career.  It is remarkable how many hours you can save just by using the best interface for text editing and deployment.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
There are going to be several aspects to our workflow in CSE 3300, and we will be switching between multiple different programs.  Here is how they all fit together.&lt;br /&gt;
&lt;br /&gt;
=== Running Commands ===&lt;br /&gt;
&lt;br /&gt;
''Mac OS X:'' You will be running commands on your local machine using '''Terminal''', which you can find in your Applications -&amp;gt; Utilities folder.  You will be connecting to your server via SSH.&lt;br /&gt;
&lt;br /&gt;
''Windows:'' You will be running commands on your local machine using '''Git Bash''', which you will install in the next section.  You will be connecting to your server via SSH.&lt;br /&gt;
&lt;br /&gt;
=== Version Control ===&lt;br /&gt;
&lt;br /&gt;
You will be using '''Git''' as your version control system.  Git enables you to keep track of changes to your source code.  If you're using VS Code, the Git integration makes is very user friendly.&lt;br /&gt;
&lt;br /&gt;
=== Text Editing ===&lt;br /&gt;
&lt;br /&gt;
Although we highly recommend '''VS Code''', you may use '''Sublime Text''', '''Komodo Edit''', '''Notepad++''' (for Windows), and '''TextWrangler''' (for Mac). You can also use an IDE, like '''PHPStorm''', if you prefer.&lt;br /&gt;
&lt;br /&gt;
=== File Transfer ===&lt;br /&gt;
&lt;br /&gt;
In order to deploy your projects to your EC2 instance, you need some sort of method to transfer files from your local machine to your instance using SFTP. One option is using '''FileZilla''', an FTP client, to transport files from your local computer to your server.&lt;br /&gt;
&lt;br /&gt;
=== Flowchart ===&lt;br /&gt;
&lt;br /&gt;
If you are more visual, this flowchart might help:&lt;br /&gt;
&lt;br /&gt;
[[File:CSE-330-Workflow.png]]&lt;br /&gt;
&lt;br /&gt;
The flowchart illustrates 3 methods to copy files to your cloud instance: via your text editor, via an external SFTP client, and by cloning your remote Git repository.  We will be primarily using '''Option 2''' in CSE 330 this semester.&lt;br /&gt;
&lt;br /&gt;
In the flowchart, your ''Git Client'' is SourceTree and your ''Text Editor'' is Komodo.  The ''SFTP Client'' could be FileZilla.&lt;br /&gt;
&lt;br /&gt;
== Installing a Terminal Emulator ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
Windows is based on a fundamentally different architecture than Linux, meaning that, if you want to run Linux programs on Windows, you'll have to enable or install a special terminal emulator for it. Mac users do not need to install a terminal emulator since MacOs itself is a derivative of Unix, which is an ancestor of Linux.  The Terminal utility is all you need.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Git Bash ===&lt;br /&gt;
&lt;br /&gt;
Git Bash is a software application for Windows that gives you a Bash shell terminal and the Git command-line tool. It lets Windows users run Linux-style commands and manage code using Git. You can download it from [https://git-scm.com/install/windows Git for Windows].&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Github ==&lt;br /&gt;
&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
'''Github''' is a service that enables you to store your code for free in the cloud.&lt;br /&gt;
&lt;br /&gt;
=== Creating a Github Account ===&lt;br /&gt;
&lt;br /&gt;
Go to [https://github.org/ Github.org] to create an account.&lt;br /&gt;
&lt;br /&gt;
'''Important:''' Use your @wustl.edu e-mail address to create your account.  This will put your account on the &amp;quot;educational plan&amp;quot;, which removes some limitations of the free account.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Adding Your Key to Github ===&lt;br /&gt;
'''Not uploading your key is perfectly fine, and in many cases easier.  Here are the instructions though, if you'd like'''&lt;br /&gt;
&lt;br /&gt;
# First make sure that you've finished the [[SSH]] guide configuration instructions.&lt;br /&gt;
#: We will use the same key for connecting to your remote server as for connecting to Github.  (This is completely valid practice.)&lt;br /&gt;
# Go to the &amp;quot;SSH keys&amp;quot; section of the settings panel (Manage Account -&amp;gt; SSH Keys)&lt;br /&gt;
# Add a new key.&lt;br /&gt;
#* I recommend naming it after the computer you're using; if you're in the CEC, for example, you could name it &amp;quot;Warehouse Cygwin&amp;quot;.&lt;br /&gt;
#* The contents of your key is the contents of your ''id_rsa.pub''.  It looks like &amp;lt;code&amp;gt;ssh-rsa AAAAB3blahblahblahblah yourname@somedomain&amp;lt;/code&amp;gt;.  It is exactly the same thing that you had to paste into the ''authorized_keys'' file on your EC2 instance.&lt;br /&gt;
&lt;br /&gt;
You will now be able to push code to your Github repository using your personal SSH key.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== FileZilla ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use SFTP from the command line, or you can use any GUI file transfer client.  All FTP clients I have seen also support SFTP.  One popular FTP client is [http://www.filezilla-project.org/ Filezilla].&lt;br /&gt;
&lt;br /&gt;
# Download and install FileZilla from http://filezilla-project.org/download.php?type=client&lt;br /&gt;
#: If you are using a lab computer, you can save it to your H drive.&lt;br /&gt;
# When you launch FileZilla, go to Edit→Settings or FileZilla→Preferences, and go to the SFTP options (under Connection).  Click &amp;quot;Add keyfile…&amp;quot;.&lt;br /&gt;
#* Choose your ''id_rsa'' file.   '''OS X Tip:''' Press Shift+Command+Period to reveal hidden files in the file chooser window.&lt;br /&gt;
#* FileZilla will want to make a *.ppk file.  A good place to save it would be the .ssh directory as ''id_rsa.ppk''.&lt;br /&gt;
#: When finished, press '''OK''' (not the red ×) to save your changes.&lt;br /&gt;
# Back on the main FileZilla screen, there are four fields: Host, Username, Password, and Port.  Fill them in as follows:&lt;br /&gt;
#* '''Hostname:''' sftp://&amp;lt;ec2-xxx-xx-xx-xxx.compute-1.amazonaws.com/&amp;gt;&lt;br /&gt;
#* '''Username:''' The username you created on your server.&lt;br /&gt;
#* '''Password:''' Since you are using a key, you may leave this blank.&lt;br /&gt;
#*: If you changed your SSH settings to allow password-based authentication, then you could put your password in here.&lt;br /&gt;
#* '''Port:''' 22&lt;br /&gt;
# Then click Quickconnect.  If everything is configured correctly, FileZilla should log into your server.&lt;br /&gt;
&lt;br /&gt;
[[File:FileZilla.png]]&lt;br /&gt;
&lt;br /&gt;
On the left and right of the FileZilla window, you can drag files between your computer and your server.  Thus, you can edit a file in a text editor like Notepad++ on your decktop, and then upload it to your server by simply dragging it from the left pane to the right pane in FileZilla.&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Workflow&amp;diff=3526</id>
		<title>Workflow</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Workflow&amp;diff=3526"/>
		<updated>2026-08-20T14:00:47Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Running Commands */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The few minutes it takes to optimize your workflow will be the best few minutes you will spend in CSE 330 and your career.  It is remarkable how many hours you can save just by using the best interface for text editing and deployment.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
There are going to be several aspects to our workflow in CSE 3300, and we will be switching between multiple different programs.  Here is how they all fit together.&lt;br /&gt;
&lt;br /&gt;
=== Running Commands ===&lt;br /&gt;
&lt;br /&gt;
''Mac OS X:'' You will be running commands on your local machine using '''Terminal''', which you can find in your Applications -&amp;gt; Utilities folder.  You will be connecting to your server via SSH.&lt;br /&gt;
&lt;br /&gt;
''Windows:'' You will be running commands on your local machine using '''Git Bash''', which you will install in the next section.  You will be connecting to your server via SSH.&lt;br /&gt;
&lt;br /&gt;
=== Version Control ===&lt;br /&gt;
&lt;br /&gt;
You will be using '''Git''' as your version control system.  Git enables you to keep track of changes to your source code.  If you're using VS Code, the Git integration makes is very user friendly.&lt;br /&gt;
&lt;br /&gt;
=== Text Editing ===&lt;br /&gt;
&lt;br /&gt;
Although we highly recommend '''VS Code''', you may use '''Sublime Text''', '''Komodo Edit''', '''Notepad++''' (for Windows), and '''TextWrangler''' (for Mac). You can also use an IDE, like '''PHPStorm''', if you prefer.&lt;br /&gt;
&lt;br /&gt;
=== File Transfer ===&lt;br /&gt;
&lt;br /&gt;
In order to deploy your projects to your EC2 instance, you need some sort of method to transfer files from your local machine to your instance using SFTP. One option is using '''FileZilla''', an FTP client, to transport files from your local computer to your server.&lt;br /&gt;
&lt;br /&gt;
=== Flowchart ===&lt;br /&gt;
&lt;br /&gt;
If you are more visual, this flowchart might help:&lt;br /&gt;
&lt;br /&gt;
[[File:CSE-330-Workflow.png]]&lt;br /&gt;
&lt;br /&gt;
The flowchart illustrates 3 methods to copy files to your cloud instance: via your text editor, via an external SFTP client, and by cloning your remote Git repository.  We will be primarily using '''Option 2''' in CSE 330 this semester.&lt;br /&gt;
&lt;br /&gt;
In the flowchart, your ''Git Client'' is SourceTree and your ''Text Editor'' is Komodo.  The ''SFTP Client'' could be FileZilla.&lt;br /&gt;
&lt;br /&gt;
== Installing a Terminal Emulator ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
Windows is based on a fundamentally different architecture than Linux, meaning that, if you want to run Linux programs on Windows, you'll have to enable or install a special terminal emulator for it. Mac users do not need to install a terminal emulator since MacOs itself is a derivative of Unix, which is an ancestor of Linux.  The Terminal utility is all you need.&lt;br /&gt;
&lt;br /&gt;
=== Windows Subsystem for Linux ===&lt;br /&gt;
&lt;br /&gt;
Starting in Windows 10, Windows has the ability to emulate Linux, meaning that you can run Linux commands on Windows, without having to install Cygwin. To enable WSL, follow [https://msdn.microsoft.com/en-us/commandline/wsl/install_guide these instructions]. Once those instructions are complete, you can open a shell by clicking on the Start menu, typing &amp;quot;Bash&amp;quot;, then clicking on the entry labeled &amp;quot;Bash on Ubuntu on Windows&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Cygwin ===&lt;br /&gt;
&lt;br /&gt;
'''Cygwin''' is a Linux Terminal emulator for Windows.&lt;br /&gt;
&lt;br /&gt;
==== Installation ====&lt;br /&gt;
&lt;br /&gt;
The CEC lab machines already have Cygwin installed.  Follow these instructions to get Cygwin up and running on your personal PC.&lt;br /&gt;
&lt;br /&gt;
# Run ''setup.exe'' from http://www.cygwin.com/&lt;br /&gt;
# Keep the Cygwin directory as the default, ''C:\cygwin''.  Choose a place like ''C:\cygwin\downloads'' to download the installation files.  Choose any download site, although probably one with a .com suffix is best when you're in the US.&lt;br /&gt;
# In the &amp;quot;Select Packages&amp;quot; screen, select the following packages:&lt;br /&gt;
#* Net -&amp;gt; openssh&lt;br /&gt;
#* Net -&amp;gt; openssl&lt;br /&gt;
#* Python -&amp;gt; python3 (you will need this for Module 4)&lt;br /&gt;
#* Devel -&amp;gt; git (version control system)&lt;br /&gt;
#* One or more of the following:&lt;br /&gt;
#** Editors -&amp;gt; vim&lt;br /&gt;
#** Editors -&amp;gt; emacs&lt;br /&gt;
#** Editors -&amp;gt; nano&lt;br /&gt;
# Allow the installation to complete.&lt;br /&gt;
&lt;br /&gt;
Throughout the rest of this course, when we say to &amp;quot;open a terminal&amp;quot;, if you are on Windows, we mean to &amp;quot;open WSL/Cygwin&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Github ==&lt;br /&gt;
&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
'''Github''' is a service that enables you to store your code for free in the cloud.&lt;br /&gt;
&lt;br /&gt;
=== Creating a Github Account ===&lt;br /&gt;
&lt;br /&gt;
Go to [https://github.org/ Github.org] to create an account.&lt;br /&gt;
&lt;br /&gt;
'''Important:''' Use your @wustl.edu e-mail address to create your account.  This will put your account on the &amp;quot;educational plan&amp;quot;, which removes some limitations of the free account.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Adding Your Key to Github ===&lt;br /&gt;
'''Not uploading your key is perfectly fine, and in many cases easier.  Here are the instructions though, if you'd like'''&lt;br /&gt;
&lt;br /&gt;
# First make sure that you've finished the [[SSH]] guide configuration instructions.&lt;br /&gt;
#: We will use the same key for connecting to your remote server as for connecting to Github.  (This is completely valid practice.)&lt;br /&gt;
# Go to the &amp;quot;SSH keys&amp;quot; section of the settings panel (Manage Account -&amp;gt; SSH Keys)&lt;br /&gt;
# Add a new key.&lt;br /&gt;
#* I recommend naming it after the computer you're using; if you're in the CEC, for example, you could name it &amp;quot;Warehouse Cygwin&amp;quot;.&lt;br /&gt;
#* The contents of your key is the contents of your ''id_rsa.pub''.  It looks like &amp;lt;code&amp;gt;ssh-rsa AAAAB3blahblahblahblah yourname@somedomain&amp;lt;/code&amp;gt;.  It is exactly the same thing that you had to paste into the ''authorized_keys'' file on your EC2 instance.&lt;br /&gt;
&lt;br /&gt;
You will now be able to push code to your Github repository using your personal SSH key.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== FileZilla ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use SFTP from the command line, or you can use any GUI file transfer client.  All FTP clients I have seen also support SFTP.  One popular FTP client is [http://www.filezilla-project.org/ Filezilla].&lt;br /&gt;
&lt;br /&gt;
# Download and install FileZilla from http://filezilla-project.org/download.php?type=client&lt;br /&gt;
#: If you are using a lab computer, you can save it to your H drive.&lt;br /&gt;
# When you launch FileZilla, go to Edit→Settings or FileZilla→Preferences, and go to the SFTP options (under Connection).  Click &amp;quot;Add keyfile…&amp;quot;.&lt;br /&gt;
#* Choose your ''id_rsa'' file.   '''OS X Tip:''' Press Shift+Command+Period to reveal hidden files in the file chooser window.&lt;br /&gt;
#* FileZilla will want to make a *.ppk file.  A good place to save it would be the .ssh directory as ''id_rsa.ppk''.&lt;br /&gt;
#: When finished, press '''OK''' (not the red ×) to save your changes.&lt;br /&gt;
# Back on the main FileZilla screen, there are four fields: Host, Username, Password, and Port.  Fill them in as follows:&lt;br /&gt;
#* '''Hostname:''' sftp://&amp;lt;ec2-xxx-xx-xx-xxx.compute-1.amazonaws.com/&amp;gt;&lt;br /&gt;
#* '''Username:''' The username you created on your server.&lt;br /&gt;
#* '''Password:''' Since you are using a key, you may leave this blank.&lt;br /&gt;
#*: If you changed your SSH settings to allow password-based authentication, then you could put your password in here.&lt;br /&gt;
#* '''Port:''' 22&lt;br /&gt;
# Then click Quickconnect.  If everything is configured correctly, FileZilla should log into your server.&lt;br /&gt;
&lt;br /&gt;
[[File:FileZilla.png]]&lt;br /&gt;
&lt;br /&gt;
On the left and right of the FileZilla window, you can drag files between your computer and your server.  Thus, you can edit a file in a text editor like Notepad++ on your decktop, and then upload it to your server by simply dragging it from the left pane to the right pane in FileZilla.&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=SSH&amp;diff=3525</id>
		<title>SSH</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=SSH&amp;diff=3525"/>
		<updated>2026-08-20T13:59:37Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* SSH Configuration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Secure SHell, or '''SSH''', is the leading interface for connecting as an administrator from your local computer to something on the cloud.  You will be using exclusively SSH to execute commands and upload files to your cloud instance.  If you use a Git hosting service like BitBucket or Github, you will need SSH in order to save your changes there as well.&lt;br /&gt;
&lt;br /&gt;
SSH access requires that the ''sshd'' daemon is running on the remote machine.  By default, SSH is preinstalled on your EC2 instance.  If you are not using an EC2 instance, simply install '''ssh''' from yum or apt on the remote machine.&lt;br /&gt;
&lt;br /&gt;
== SSH Keys ==&lt;br /&gt;
&lt;br /&gt;
SSH can work using password-based authentication, but it is more common nowadays, and also more secure, to use ''public and private keys'', also known as a ''key pair''.&lt;br /&gt;
&lt;br /&gt;
Here's how it works.  You have a private copy of your key, called your '''private key'''.  Servers to which you want to connect have an analog to your private key called your '''public key'''.  When you attempt to connect to a server using your private key, the server checks to see if any of its public keys &amp;quot;fits&amp;quot; your private key, and if it finds a match, it lets you in.&lt;br /&gt;
&lt;br /&gt;
You can think of the ''private key'' as the &amp;quot;key&amp;quot; and the ''public key'' as the &amp;quot;lock&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== SSH Configuration ==&lt;br /&gt;
&lt;br /&gt;
This section discusses how to set up your computer and your remote instance for SSH.  You need to do this only once.&lt;br /&gt;
&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
&lt;br /&gt;
=== Creating an SSH Key Pair ===&lt;br /&gt;
&lt;br /&gt;
You should create your own SSH Key Pair.  You will keep the private key on your own computer, and you will upload the public key to all places to which you need to connect.&lt;br /&gt;
&lt;br /&gt;
To generate a key pair, open a terminal ([[Workflow#Installing_a_Terminal_Emulator|on Windows, you'll need to have enabled WSL or installed Cygwin]] and run the following command:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;# Run on Local Machine in Terminal or Cygwin&lt;br /&gt;
$ ssh-keygen -t rsa -b 4096&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Important:''' Don't name the file unless you are using a CEC machine. If you are using a CEC machine, name the file ''/cygdrive/h/.ssh/id_rsa''&lt;br /&gt;
&lt;br /&gt;
For the purposes of this course, you do not need to enter a passphrase, although you would probably want one in real life.&lt;br /&gt;
&lt;br /&gt;
Your keys should have been created in ~/.ssh.  Check to make sure they exist using the ''ls'' command:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ls ~/.ssh&lt;br /&gt;
id_rsa  id_rsa.pub&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''id_rsa'' is your private key, and ''id_rsa.pub'' is the corresponding public key.  Let's look at the public signature of your key pair:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ cat ~/.ssh/id_rsa.pub&lt;br /&gt;
ssh-rsa AAAA3Bblahblahblahblah yourname@somedomain&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
We will need this later.  Let's now add our key to the ''ssh-agent''.&lt;br /&gt;
&lt;br /&gt;
==== SSH Agent: OS X ====&lt;br /&gt;
&lt;br /&gt;
Run the following command to add your key to the SSH agent in Mac OS X.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;# Non-Cygwin&lt;br /&gt;
$ ssh-add&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== SSH Agent: WSL or Cygwin ====&lt;br /&gt;
&lt;br /&gt;
In order to make the SSH agent run when you start your terminal, you need to run a couple more commands (these will add a line to your .bashrc file, which runs automatically on startup):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Cygwin only&lt;br /&gt;
$ echo &amp;quot;eval \`ssh-agent -s\`; ssh-add&amp;quot; &amp;gt;&amp;gt; ~/.bashrc&lt;br /&gt;
$ source ~/.bashrc # reload the Bash shell&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you get this scary-looking error after you run the second command…&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;br /&gt;
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @&lt;br /&gt;
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
…then you need to do the following to fix the permissions:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ chgrp Users &amp;lt;/path/to/id_rsa&amp;gt;&lt;br /&gt;
$ chmod 600 &amp;lt;/path/to/id_rsa&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Amazon EC2 SSH Configuration ===&lt;br /&gt;
&lt;br /&gt;
When you first created your EC2 instance, you downloaded a *.pem file.  This is the ''private key'' for the default user ('''ec2-user''' on AMI).  We need use this key to connect to your instance using the default user, at which point we will create a new custom user.  We will give that new user the public key that you created in the previous step.  You will then be able to log into your custom user using your personal key pair, so you won't need the default user again (although you should keep the *.pem file around just in case).&lt;br /&gt;
&lt;br /&gt;
==== Logging In as the Default User ====&lt;br /&gt;
&lt;br /&gt;
To connect to the default user on your Amazon EC2 instance, run this command in your terminal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run on Local Machine&lt;br /&gt;
$ ssh -i &amp;lt;/path/to/default-privatekey.pem&amp;gt; ec2-user@&amp;lt;ec2-xx-xx-xx-xx.compute-1.amazonaws.com&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Important:''' As soon as you are able to successfully log in, commands you type into your prompt will be executed on the remote instance, ''not'' your local machine.&lt;br /&gt;
&lt;br /&gt;
If you get this scary-looking warning:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;br /&gt;
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @&lt;br /&gt;
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
…then you need to correctly set the permissions on your *.pem file.  You should know how to do this from the Linux guide, but in case you didn't catch that detail, run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run on Local Machine&lt;br /&gt;
&lt;br /&gt;
# Cygwin Only:&lt;br /&gt;
&lt;br /&gt;
 $ chgrp Users &amp;lt;/path/to/default-privatekey.pem&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# For Everyone:&lt;br /&gt;
$ chmod 600 &amp;lt;/path/to/default-privatekey.pem&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Food for Thought:''' Why you wouldn't want other people viewing your private key file?&lt;br /&gt;
&lt;br /&gt;
==== Creating a New User with SSH Permissions ====&lt;br /&gt;
&lt;br /&gt;
First, you need to create your new user with sudo and SSH permissions.  Again, the instructions for this are in the Linux guide, but to keep everything in one place, the commands are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run these on your instance, not on your local machine!&lt;br /&gt;
&lt;br /&gt;
# Don't forget to customize `&amp;lt;My Full Name&amp;gt;` and `&amp;lt;usernameHere&amp;gt;`:&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
$ sudo passwd &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# You need to add your username to the sudoers list.  Refer to the Linux guide for details.&lt;br /&gt;
# Look around line 101.&lt;br /&gt;
$ sudo visudo &lt;br /&gt;
&lt;br /&gt;
# Restart the SSH server:&lt;br /&gt;
$ sudo service sshd restart&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Okay, cool: we have created our custom username on our remote instance.  Now, we need to enable this account to accept our SSH key when authenticating.  To do this, we need to add our public key to a file named '''authorized_keys''', which is stored in your remote username's SSH configuration directory.&lt;br /&gt;
&lt;br /&gt;
To create the directory and edit the file, run on your instance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Switch to our own user to ensure that permissions are set correctly:&lt;br /&gt;
$ su &amp;lt;username here&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Change the working directory to our user's home directory:&lt;br /&gt;
$ cd&lt;br /&gt;
&lt;br /&gt;
# Make the nonexistent SSH configuration directory and set its permissions:&lt;br /&gt;
$ mkdir .ssh&lt;br /&gt;
$ chmod 700 .ssh&lt;br /&gt;
&lt;br /&gt;
# Create the `authorized_keys` file and set the permissions&lt;br /&gt;
$ touch .ssh/authorized_keys&lt;br /&gt;
$ chmod 600 .ssh/authorized_keys&lt;br /&gt;
&lt;br /&gt;
# Edit the `authorized_keys` file&lt;br /&gt;
$ nano .ssh/authorized_keys&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For more detail on editing files on the command line, see [[Linux#File Editors|the Linux guide]].&lt;br /&gt;
&lt;br /&gt;
Inside of the `authorized_keys` file, paste in your public key.  Your public key is the content of `~/.ssh/id_rsa.pub` '''on your local machine''' (it starts with &amp;quot;ssh-rsa&amp;quot;).  There are a few ways to get it onto your clipboard:&lt;br /&gt;
&lt;br /&gt;
# '''OS X Only:''' Open a new Terminal and run: &amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;cat ~/.ssh/id_rsa.pub | pbcopy&amp;lt;/source&amp;gt; This immediately copies the contents of your public key to your clipboard.&lt;br /&gt;
# '''Windows Only:''' Navigate to id_rsa.pub manually, open it in your favorite text editor (like Notepad++), select all, and copy.  The location of id_rsa.pub might be: ''C:\cygwin\home\username\.ssh\id_rsa.pub''&lt;br /&gt;
# '''&amp;amp;#42;NIX Only:''' Open a new Terminal and copy the output of the following command: &amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;cat ~/.ssh/id_rsa.pub&amp;lt;/source&amp;gt; Note that this won't work as advertised in Cygwin because Cygwin adds extra line breaks when you try to copy out of it.&lt;br /&gt;
&lt;br /&gt;
When finished, save and close `authorized_keys`.&lt;br /&gt;
&lt;br /&gt;
'''Food for Thought:''' How would you enable your remote user to authenticate over SSH using the same default key instead of the one you created using ''ssh-keygen''?&lt;br /&gt;
&lt;br /&gt;
==== Logging In as your New User ====&lt;br /&gt;
&lt;br /&gt;
Let's test whether we can log into our instance using our own username and our own key pair. Open up a new Terminal or Cygwin window '''on your local machine''' (not from ec2) and run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ssh &amp;lt;username here&amp;gt;@&amp;lt;ec2-xx-xx-xx-xx.amazonaws.com&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is how you will be SSHing into your instances for the remainder of the semester.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Troubleshooting ===&lt;br /&gt;
&lt;br /&gt;
Almost everyone receives the following error at some point in their career: &amp;lt;code&amp;gt;Permission denied (publickey)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Here are some possible causes for the error.&lt;br /&gt;
&lt;br /&gt;
==== The Authorized Keys File ====&lt;br /&gt;
&lt;br /&gt;
Make sure that your remote ''authorized_keys'' file contains ''exactly'' your public signature from ''id_rsa.pub''.&lt;br /&gt;
&lt;br /&gt;
===== Pasting into Vi =====&lt;br /&gt;
&lt;br /&gt;
If you used vi to edit your file and you forgot to enter &amp;quot;insert&amp;quot; mode before pasting in your key, it is possible that you lost the first &amp;quot;s&amp;quot; in &amp;quot;ssh-rsa&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Check the contents of authorized_keys via &amp;lt;code&amp;gt;cat ~/.ssh/authorized_keys&amp;lt;/code&amp;gt; on your server.  If the output starts with &amp;quot;sh-rsa&amp;quot; instead of &amp;quot;ssh-rsa&amp;quot;, edit the authorized_keys file and add the second '''s'''.&lt;br /&gt;
&lt;br /&gt;
===== Unintentional Line Breaks =====&lt;br /&gt;
&lt;br /&gt;
If you got your key via a &amp;lt;code&amp;gt;cat ~/.ssh/id_rsa.pub&amp;lt;/code&amp;gt; and then copied it from the terminal, it's possible that you unintentionally introduced line breaks.  Ensure that the key is represented by only one line of text.&lt;br /&gt;
&lt;br /&gt;
==== Permissions ====&lt;br /&gt;
&lt;br /&gt;
They need to be correct on both the client side and on the server side.&lt;br /&gt;
&lt;br /&gt;
* Local ''.ssh'' = 700&lt;br /&gt;
* Local ''id_rsa'' = 600&lt;br /&gt;
* Remote ''.ssh'' = 700&lt;br /&gt;
* Remote ''authorized_keys'' = 600&lt;br /&gt;
&lt;br /&gt;
If you are in Cygwin and the permissions don't seem to change, you might need to add the troubled file to a usergroup, like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ chgrp Users .ssh/id_rsa&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
==== The SSH Agent ====&lt;br /&gt;
&lt;br /&gt;
You need to be sending the correct key along with your SSH Agent.  Run the following command, and you should see some output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ssh-add -l&lt;br /&gt;
2048 ab:cd:ef:ab:cd:ef:ab:cd:ef:ab:cd:ef:ab:cd:ef:ab:cd /path/to/.ssh/id_rsa (RSA)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don't see any output, or if the output does not contain your ''id_rsa'', then you need to run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ssh-add &amp;lt;/path/to/.ssh/id_rsa&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
=== More SSH Server Configurations ===&lt;br /&gt;
&lt;br /&gt;
Up to this point, we have been using all of the default SSH configurations.  Like most things in Linux, SSH can be customized.&lt;br /&gt;
&lt;br /&gt;
For the purposes of CSE 330, the additional configurations in this short section are optional; this serves only as a reference.&lt;br /&gt;
&lt;br /&gt;
==== Disabling Root Access ====&lt;br /&gt;
&lt;br /&gt;
It is almost always a good idea to disable root access over ssh. This could be done by editing ''/etc/ssh/sshd_config'' and setting  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;PermitRootLogin no&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will need to restart the SSH server for changes to take effect:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo service sshd restart&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Enabling Password-Based Authentication ====&lt;br /&gt;
&lt;br /&gt;
Up to this point, we have been using key pairs for SSH.  A public/private key pair is generally considered to be more secure, but it requires that you always have access to your private key file when you want to log into your remote machine.&lt;br /&gt;
&lt;br /&gt;
By default, EC2 instances allow only public/private key pair authentication.  You can enable password-based authentication by setting the PaswordAuthentication option in ''/etc/ssh/sshd_config'' to '''yes''':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;PasswordAuthentication yes&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There may be a line with this option that you can un-comment.  For me, it is line 25.  You will need to restart the server for changes to take effect.&lt;br /&gt;
&lt;br /&gt;
If possible, however, you should restrict yourself to using private and public keys.&lt;br /&gt;
&lt;br /&gt;
== Using SSH ==&lt;br /&gt;
&lt;br /&gt;
Once you have completed the [[#SSH Configuration]] section above, all you need to do in order to SSH into your instance is to open a terminal and run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ssh &amp;lt;username here&amp;gt;@&amp;lt;ec2-xx-xx-xx-xx.compute-1.amazonaws.com&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PuTTY ===&lt;br /&gt;
&lt;br /&gt;
'''This short section about PuTTY serves only as a reference.'''&lt;br /&gt;
&lt;br /&gt;
If you have Cygwin available, you should use it to SSH into your instance.  However, if you are using a different computer, you might not have Cygwin at your fingertips.  In this case, a lighter-weight SSH client called '''PuTTY''' is available for Windows.&lt;br /&gt;
&lt;br /&gt;
You can download PuTTY from: http://www.chiark.greenend.org.uk/~sgtatham/putty/&lt;br /&gt;
&lt;br /&gt;
Amazon provides [http://docs.amazonwebservices.com/AWSEC2/latest/GettingStartedGuide/ConnectToInstanceLinux.html#connect-from-windows-machine a great tutorial on how to connect to a virtual machine from Windows].&lt;br /&gt;
&lt;br /&gt;
PuTTY is fairly simple and straight forward with one caveat: Amazon's ''*.pem'' files are SSH private key files, and they need to be converted to PuTTY's own '''.ppk''' format.  To do this, use the ''puttygen.exe'' utility available from the same page as PuTTY.  Select &amp;quot;Import&amp;quot; under the conversions menu, load the amazon ''*.pem'' key file, and press the &amp;quot;Save Private Key&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
Copy and paste works similarly to the X Window System in Unix. You use the left mouse button to select text in the PuTTY window. The act of selection automatically copies the text to the clipboard: there is no need to press Ctrl-Ins or Ctrl-C or anything else. In fact, pressing Ctrl-C will send a Ctrl-C character to the other end of your connection (just like it does the rest of the time), which may have unpleasant effects. The only thing you need to do, to copy text to the clipboard, is to select it.&lt;br /&gt;
&lt;br /&gt;
To paste the clipboard contents into a PuTTY window, by default you click the right mouse button. If you have a three-button mouse and are used to X applications, you can configure pasting to be done by the middle button instead, but this is not the default because most Windows users don't have a middle button at all. &lt;br /&gt;
&lt;br /&gt;
Here is a good PuTTY tutorial that you might find useful to get started: http://kb.mediatemple.net/questions/1595/Using+SSH+in+Putty+%28Windows%29&lt;br /&gt;
&lt;br /&gt;
== SSHFS and SFTP ==&lt;br /&gt;
&lt;br /&gt;
In addition to just SSH, your SSH server also supports '''SSHFS''', which enables you to mount your remote instance as a disk on your local computer, and '''SFTP''', which is a file transfer protocol that enables you to upload files directly to your instance.&lt;br /&gt;
&lt;br /&gt;
=== SSHFS ===&lt;br /&gt;
&lt;br /&gt;
SSHFS is a filesystem client which allows secure mounting of remote file systems. While there are other ways to mount remote file systems, SSHFS has the advantage of being able to mount a file system located on any host that has an SSH daemon running without any host side installation or configuration. This means that you can easily access and edit your files using all of your local applications including IDEs. &lt;br /&gt;
&lt;br /&gt;
As you may have inferred from the name, the underlying implementation utilizes SSH File Transfer Protocol in combination with FUSE, a package now included in the kernel that allows unprivileged users to easily create their own file systems in userspace (see the wikipedia entry for more information [http://en.wikipedia.org/wiki/Filesystem_in_Userspace]).&lt;br /&gt;
&lt;br /&gt;
To mount a share using password based authentication, the command is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sshfs &amp;lt;user&amp;gt;@&amp;lt;domain&amp;gt;:&amp;lt;/path/to/remote/directory&amp;gt; &amp;lt;/path/to/local/mountpoint&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For example, to mount the directory /home/joe/myfiles in the user ''joe'''s home directory for a machine with the domain schmoesfiles.org using SSHFS you would enter the command&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sshfs joe@www.schmoesfiles.org:myfiles&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if you are using public key authentication, the command to mount the remote filesystem may need to be slightly different:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sshfs -o IdentityFile=&amp;lt;/path/to/private/key&amp;gt; &amp;lt;user&amp;gt;@&amp;lt;domain&amp;gt;:&amp;lt;/path/to/remote/directory&amp;gt; &amp;lt;/path/to/local/mountpoint&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To unmount the filesystem, you can use the following command:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ fusermount -u &amp;lt;/path/to/local/mountpoint&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SFTP ===&lt;br /&gt;
&lt;br /&gt;
Any server running an SSH server is also compatible with '''SFTP''' or Secure File Transfer Protocol.  (Compare to FTP, or File Transfer Protocol.)  SFTP is a convenient way to edit files on your computer and then upload them to your server in just a few clicks.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 2]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=AJAX_and_JSON&amp;diff=3524</id>
		<title>AJAX and JSON</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=AJAX_and_JSON&amp;diff=3524"/>
		<updated>2026-08-12T14:18:58Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Javascript Fetch API */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Asynchronous Javascript And XML, or '''''AJAX''''', is a group of related web development techniques, standards, and technologies used on the client (web browser) side of the standard web server-client communication model.&lt;br /&gt;
&lt;br /&gt;
Here's the general concept: JavaScript is used to send requests to the web server in the background and retrieve data, without the need to refresh the page.  The result: dynamic, fast, user-friendly web pages.&lt;br /&gt;
&lt;br /&gt;
The requests are performed ''asynchronously'': this means that the end user can continue interacting with your web page while the server data is being loaded.&lt;br /&gt;
&lt;br /&gt;
The key idea is that a large fraction of the work happens at the web browser, and larger requests happen behind the scenes, keeping waiting times for data to come back over the network to a minimum.  AJAX is generally also associated with higher-level APIs and libraries that aid in producing cleaner user interfaces for web applications.&lt;br /&gt;
&lt;br /&gt;
== The XMLHttpRequest Object ==&lt;br /&gt;
&lt;br /&gt;
The JavaScript specification defines the '''XMLHttpRequest''' object (sometimes abbreviated as '''XHR'''), which is used to exchange data with the web servers without needing to reload the entire web page in the browser.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' For security reasons, you cannot perform AJAX requests to other domains, unless that domain has a special security policy set up to enable such requests.&lt;br /&gt;
=== Javascript Fetch API ===&lt;br /&gt;
&lt;br /&gt;
While creating an xhr object works, it's messy and hard to cleanly organize JavaScript's asynchronous nature.&lt;br /&gt;
&lt;br /&gt;
Javascript has a [https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch Fetch API] that you should be using. The Fetch API uses a concept in JavaScript known as a [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise Promise] to represent the completion or failure of an asynchronous operation.&lt;br /&gt;
&lt;br /&gt;
=== Sending Data ===&lt;br /&gt;
&lt;br /&gt;
==== GET Data ====&lt;br /&gt;
&lt;br /&gt;
To send GET data, create a fetch object with method &amp;quot;GET&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
fetch('/path/to/php/file', {&lt;br /&gt;
    method: &amp;quot;GET&amp;quot;&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can build your url to include GET variables if you want to send data.&lt;br /&gt;
ex: '/path/to/php/file?x='hi'&lt;br /&gt;
&lt;br /&gt;
==== POST Data ====&lt;br /&gt;
&lt;br /&gt;
To send POST data, create a fetch object with method &amp;quot;POST&amp;quot;.  You need to create an object of the data you want to send.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const data = { x: 'hi', y: 'hello' };&lt;br /&gt;
fetch('/path/to/php/file', {&lt;br /&gt;
    method: &amp;quot;POST&amp;quot;,&lt;br /&gt;
    body: JSON.stringify(data)&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Listening for a Response ===&lt;br /&gt;
&lt;br /&gt;
When expecting a response from your php script, javascript's fetch api has a clean promise-based method of handling this.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const pathToPhpFile = 'test.php';&lt;br /&gt;
const data = { x: 'hi', y: 'hello' };&lt;br /&gt;
fetch(pathToPhpFile, {&lt;br /&gt;
    method: &amp;quot;POST&amp;quot;,&lt;br /&gt;
    body: JSON.stringify(data)&lt;br /&gt;
  })&lt;br /&gt;
  .then(res =&amp;gt; res.json())&lt;br /&gt;
  .then(response =&amp;gt; console.log('Success:', JSON.stringify(response)))&lt;br /&gt;
  .catch(error =&amp;gt; console.error('Error:',error))&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You'll notice unfamiliar syntax here with the .then() methods attached.&lt;br /&gt;
These are attaching event listeners to the end of your fetch request, and will trigger a function when your php script returns a response.&lt;br /&gt;
&lt;br /&gt;
You should '''always''' have a catch block at the end, in case an error occurs in your response. In the near future, Javascript will ignore any requests that do not include a .catch() block.&lt;br /&gt;
&lt;br /&gt;
==== Browser Support ====&lt;br /&gt;
&lt;br /&gt;
Many examples that you find when Googling for &amp;quot;AJAX&amp;quot; use the '''onreadystatechange''' property of the XMLHttpRequest instance to react on the completion of the request, rather than using an event listener.  This approach works, and it is required for comprehensive browser support, but it is no longer recommended for future use in modern web applications.  The implementation of XMLHttpRequest documented here (so-called &amp;quot;XHR level 2&amp;quot;) works in the following browsers:&lt;br /&gt;
* Firefox &lt;br /&gt;
* Safari &lt;br /&gt;
* Chrome &lt;br /&gt;
* Opera&lt;br /&gt;
&lt;br /&gt;
=== Response Formats ===&lt;br /&gt;
&lt;br /&gt;
More often than not, a plaintext response form the server is not very helpful: you want to transmit more information than simply a single string of text.  There are two good options to get more information from a single AJAX request: '''XML''' (the original format, thus the ''X'' in ''AJAX'') and '''JSON'''.&lt;br /&gt;
&lt;br /&gt;
==== XML ====&lt;br /&gt;
&lt;br /&gt;
Your server can respond with an XML document (whence, ''XMLHttpRequest'').  An '''XML document''' uses syntax that is extremely close to HTML  For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; standalone=&amp;quot;yes&amp;quot; ?&amp;gt;&lt;br /&gt;
&amp;lt;note&amp;gt;&lt;br /&gt;
  &amp;lt;date year=&amp;quot;2023&amp;quot; month=&amp;quot;02&amp;quot; day=&amp;quot;14&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;to&amp;gt;Sally&amp;lt;/to&amp;gt;&lt;br /&gt;
  &amp;lt;message&amp;gt;Happy Valentine's Day!&amp;lt;/message&amp;gt;&lt;br /&gt;
  &amp;lt;from&amp;gt;Your Secret Admirer&amp;lt;/from&amp;gt;&lt;br /&gt;
&amp;lt;/note&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Like HTML, XML has a DOM that can be traversed in JavaScript.&lt;br /&gt;
&lt;br /&gt;
If you make an AJAX request to a script generating an XML document, the root XML node is accessible in the '''responseXML''' property.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function ajaxCallback(event){&lt;br /&gt;
	var xmlDocument = event.target.responseXML;&lt;br /&gt;
	&lt;br /&gt;
	alert(xmlDocument.getElementsByTagName(&amp;quot;to&amp;quot;)[0].textContent);  // Sally (in the case of the example XML above)&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== JSON ====&lt;br /&gt;
&lt;br /&gt;
JavaScript Object Notation, or '''JSON''', is a rising standard interface format because of its lightweight syntax that makes it especially useful for AJAX requests.&lt;br /&gt;
&lt;br /&gt;
The JSON syntax closely follows the syntax for [[JavaScript#Object Literals|object literals]].  For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;apple&amp;quot;: {&lt;br /&gt;
      &amp;quot;color&amp;quot;: &amp;quot;red&amp;quot;,&lt;br /&gt;
      &amp;quot;flavor&amp;quot;: &amp;quot;sweet&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;lemon&amp;quot;: {&lt;br /&gt;
      &amp;quot;color&amp;quot;: &amp;quot;yellow&amp;quot;,&lt;br /&gt;
      &amp;quot;flavor&amp;quot;: &amp;quot;sour&amp;quot;&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Usually when JSON is transferred over the internet, it is condensed down to one line, like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&amp;quot;apple&amp;quot;:{&amp;quot;color&amp;quot;:&amp;quot;red&amp;quot;,&amp;quot;flavor&amp;quot;:&amp;quot;sweet&amp;quot;},&amp;quot;lemon&amp;quot;:{&amp;quot;color&amp;quot;:&amp;quot;yellow&amp;quot;,&amp;quot;flavor&amp;quot;:&amp;quot;sour&amp;quot;}}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In JavaScript, you can convert a JSON string to an object using '''JSON.parse()'''.  For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var jsonData = JSON.parse('{&amp;quot;apple&amp;quot;:{&amp;quot;color&amp;quot;:&amp;quot;red&amp;quot;,&amp;quot;flavor&amp;quot;:&amp;quot;sweet&amp;quot;},&amp;quot;lemon&amp;quot;:{&amp;quot;color&amp;quot;:&amp;quot;yellow&amp;quot;,&amp;quot;flavor&amp;quot;:&amp;quot;sour&amp;quot;}}');&lt;br /&gt;
&lt;br /&gt;
alert(jsonData.apple.color);  // red&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here's how to use it in conjunction with your AJAX response callback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function ajaxCallback(event){&lt;br /&gt;
	var jsonData = JSON.parse(event.target.responseText);&lt;br /&gt;
	&lt;br /&gt;
	alert(jsonData.lemon.flavor);  // sour (in the case of the example JSON above)&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here is a graphic to illustrate the JSON process.&lt;br /&gt;
&lt;br /&gt;
[[File:JSON-Flowchart.png‎]]&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Logging In a User ===&lt;br /&gt;
&lt;br /&gt;
Suppose you had the following PHP script:&lt;br /&gt;
Please note the difference in accessing POST data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// login_ajax.php&lt;br /&gt;
&lt;br /&gt;
header(&amp;quot;Content-Type: application/json&amp;quot;); // Since we are sending a JSON response here (not an HTML document), set the MIME Type to application/json&lt;br /&gt;
&lt;br /&gt;
//Because you are posting the data via fetch(), php has to retrieve it elsewhere.&lt;br /&gt;
$json_str = file_get_contents('php://input');&lt;br /&gt;
//This will store the data into an associative array&lt;br /&gt;
$json_obj = json_decode($json_str, true);&lt;br /&gt;
&lt;br /&gt;
//Variables can be accessed as such:&lt;br /&gt;
$username = $json_obj['username'];&lt;br /&gt;
$password = $json_obj['password'];&lt;br /&gt;
//This is equivalent to what you previously did with $_POST['username'] and $_POST['password']&lt;br /&gt;
&lt;br /&gt;
// Check to see if the username and password are valid.  (You learned how to do this in Module 3.)&lt;br /&gt;
&lt;br /&gt;
if( /* valid username and password */ ){&lt;br /&gt;
	session_start();&lt;br /&gt;
	$_SESSION['username'] = $username;&lt;br /&gt;
	$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32)); &lt;br /&gt;
&lt;br /&gt;
	echo json_encode(array(&lt;br /&gt;
		&amp;quot;success&amp;quot; =&amp;gt; true&lt;br /&gt;
	));&lt;br /&gt;
	exit;&lt;br /&gt;
}else{&lt;br /&gt;
	echo json_encode(array(&lt;br /&gt;
		&amp;quot;success&amp;quot; =&amp;gt; false,&lt;br /&gt;
		&amp;quot;message&amp;quot; =&amp;gt; &amp;quot;Incorrect Username or Password&amp;quot;&lt;br /&gt;
	));&lt;br /&gt;
	exit;&lt;br /&gt;
}&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' This example uses PHP's very handy '''json_encode()''' function to translate an associative array into a JSON object.  This means that you can access in JavaScript the same structure that you made in PHP!&lt;br /&gt;
&lt;br /&gt;
And suppose you had the following HTML content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;username&amp;quot; placeholder=&amp;quot;Username&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;password&amp;quot; id=&amp;quot;password&amp;quot; placeholder=&amp;quot;Password&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;button id=&amp;quot;login_btn&amp;quot;&amp;gt;Log In&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;ajax.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;!-- load the JavaScript file --&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You could use the following JavaScript code to prepare, send, and receive a request from the server to see if the user is logged in, without ever having to refresh the page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// ajax.js&lt;br /&gt;
&lt;br /&gt;
function loginAjax(event) {&lt;br /&gt;
    const username = document.getElementById(&amp;quot;username&amp;quot;).value; // Get the username from the form&lt;br /&gt;
    const password = document.getElementById(&amp;quot;password&amp;quot;).value; // Get the password from the form&lt;br /&gt;
&lt;br /&gt;
    // Make a URL-encoded string for passing POST data:&lt;br /&gt;
    const data = { 'username': username, 'password': password };&lt;br /&gt;
&lt;br /&gt;
    fetch(&amp;quot;login_ajax.php&amp;quot;, {&lt;br /&gt;
            method: 'POST',&lt;br /&gt;
            body: JSON.stringify(data),&lt;br /&gt;
            headers: { 'content-type': 'application/json' }&lt;br /&gt;
        })&lt;br /&gt;
        .then(response =&amp;gt; response.json())&lt;br /&gt;
        .then(data =&amp;gt; console.log(data.success ? &amp;quot;You've been logged in!&amp;quot; : `You were not logged in ${data.message}`))&lt;br /&gt;
        .catch(err =&amp;gt; console.error(err));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
document.getElementById(&amp;quot;login_btn&amp;quot;).addEventListener(&amp;quot;click&amp;quot;, loginAjax, false); // Bind the AJAX call to button click&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' The above example uses an '''anonymous function''' (a function without a name that is declared in an attribute) as the callback.  If you are using a certain callback function only once in your program, this is a perfectly valid approach to reduce the amount of code you need to write.&lt;br /&gt;
&lt;br /&gt;
=== Loading Data from an XML File for Display in HTML ===&lt;br /&gt;
&lt;br /&gt;
Suppose you have a file '''note.xml''' that contains the following information:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; standalone=&amp;quot;yes&amp;quot; ?&amp;gt;&lt;br /&gt;
&amp;lt;note&amp;gt;&lt;br /&gt;
  &amp;lt;date year=&amp;quot;2023&amp;quot; month=&amp;quot;02&amp;quot; day=&amp;quot;14&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;to&amp;gt;Sally&amp;lt;/to&amp;gt;&lt;br /&gt;
  &amp;lt;message&amp;gt;Happy Valentine's Day!&amp;lt;/message&amp;gt;&lt;br /&gt;
  &amp;lt;from&amp;gt;Your Secret Admirer&amp;lt;/from&amp;gt;&lt;br /&gt;
&amp;lt;/note&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Further, suppose you had an HTML element with ID '''note''':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;note&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You could load this XML file and then display it inside of your HTML document like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function getNoteAjax(event){&lt;br /&gt;
	// The XMLHttpRequest is simple this time:&lt;br /&gt;
	var xmlHttp = new XMLHttpRequest();&lt;br /&gt;
	xmlHttp.open(&amp;quot;GET&amp;quot;, &amp;quot;note.xml&amp;quot;, true);&lt;br /&gt;
	xmlHttp.addEventListener(&amp;quot;load&amp;quot;, getNoteCallback, false);&lt;br /&gt;
	xmlHttp.send(null);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getNoteCallback(event){&lt;br /&gt;
	var htmlParent = document.getElementById(&amp;quot;note&amp;quot;); // Get the HTML element into which we want to write the note&lt;br /&gt;
	var xmlDocument = event.target.responseXML; // Get the XML root node from the response&lt;br /&gt;
	&lt;br /&gt;
	// Find the &amp;lt;date/&amp;gt; element from the XML response.  We expect only 1 such element, so we can call&lt;br /&gt;
	// xmlDocument.getElementsByTagName() and then get the first (and only) element in the array all in&lt;br /&gt;
	// one step.  This is called chaining.&lt;br /&gt;
	var xmlDateElement = xmlDocument.getElementsByTagName(&amp;quot;date&amp;quot;)[0];&lt;br /&gt;
	&lt;br /&gt;
	// Put together a YYYY-MM-DD type date into a string based on the attributes of the &amp;lt;date/&amp;gt; element.&lt;br /&gt;
	var dateString = xmlDateElement.getAttribute(&amp;quot;year&amp;quot;) + &amp;quot;-&amp;quot; + xmlDateElement.getAttribute(&amp;quot;month&amp;quot;)&lt;br /&gt;
		+ &amp;quot;-&amp;quot; + xmlDateElement.getAttribute(&amp;quot;day&amp;quot;);&lt;br /&gt;
	&lt;br /&gt;
	// Get the content of the &amp;lt;to&amp;gt;, &amp;lt;message&amp;gt;, and &amp;lt;from&amp;gt; tags.  Again, you can use a chain to do this in one line for each.&lt;br /&gt;
	var to = xmlDocument.getElementsByTagName(&amp;quot;to&amp;quot;)[0].textContent;&lt;br /&gt;
	var message = xmlDocument.getElementsByTagName(&amp;quot;message&amp;quot;)[0].textContent;&lt;br /&gt;
	var from = xmlDocument.getElementsByTagName(&amp;quot;from&amp;quot;)[0].textContent;&lt;br /&gt;
	&lt;br /&gt;
	// Make a &amp;lt;strong&amp;gt; tag, append the date string as its content, and then append it to the HTML element.&lt;br /&gt;
	var htmlDateObj = document.createElement(&amp;quot;strong&amp;quot;);&lt;br /&gt;
	htmlDateObj.appendChild(document.createTextNode(dateString));&lt;br /&gt;
	htmlParent.appendChild(htmlDateObj);&lt;br /&gt;
	&lt;br /&gt;
	// Make a &amp;lt;p&amp;gt; tag to contain the note.&lt;br /&gt;
	var htmlParagraphObj = document.createElement(&amp;quot;p&amp;quot;);&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createTextNode(&amp;quot;Dear &amp;quot;+to+&amp;quot;,&amp;quot;)); // First append the greeting&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createElement(&amp;quot;br&amp;quot;)); // Append a couple line feeds&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createElement(&amp;quot;br&amp;quot;));&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createTextNode(message)); // Write the message itself&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createElement(&amp;quot;br&amp;quot;)); // Append a couple more line feeds&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createElement(&amp;quot;br&amp;quot;));&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createTextNode(&amp;quot;Love, &amp;quot;+from)); // Finally, append the salutation&lt;br /&gt;
	htmlParent.appendChild(htmlParagraphObj); // Append the newly engineered &amp;lt;p&amp;gt; to the HTML element.&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, getNoteAjax, false); // Bind the AJAX call to page load&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example uses a lot of techniques documented in the [[JavaScript#Document Object Model]] section above.  The resulting DOM will be something like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;strong&amp;gt;2023-02-14&amp;lt;/strong&amp;gt;&lt;br /&gt;
  &amp;lt;p&amp;gt;Dear Sally,&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;Happy Valentine's Day!&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;Love, Your Secret Admirer&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Debugging AJAX and JSON ==&lt;br /&gt;
&lt;br /&gt;
It is helpful when building your application to look at the AJAX requests and responses as they happen in real time.  Fortunately, there are many tools to help.&lt;br /&gt;
&lt;br /&gt;
In your favorite web inspector (Chrome Dev Tools, Mozilla Dev Tools, Firebug, Dragonfly, and so on), go to the &amp;quot;Network&amp;quot; tab.  This is where you can see all assets loaded by the application: images, stylesheets, and, most importantly for us, AJAX files.  Some inspectors let you filter requests by type; for example, the Chrome inspector lets you click &amp;quot;XHR&amp;quot; to only show XMLHttpRequest (AJAX) requests.&lt;br /&gt;
&lt;br /&gt;
You can also view the JSON sent by your server.  Double-clicking the entries in the web inspector opens the source URL in a new tab.&lt;br /&gt;
&lt;br /&gt;
If you find minified JSON hard to read, we recommend that you install the JSONView browser extension or one of its close relatives:&lt;br /&gt;
* [https://addons.mozilla.org/en-us/firefox/addon/jsonview/ Firefox]&lt;br /&gt;
* [https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc?hl=en Chrome]&lt;br /&gt;
* [https://addons.opera.com/en/extensions/details/jsonviewer/ Opera]&lt;br /&gt;
* [https://github.com/rfletcher/safari-json-formatter Safari]&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3523</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3523"/>
		<updated>2026-07-25T18:54:54Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Dockerfile */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 6]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import logging&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Configure logging to stderr&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    level=logging.INFO,&lt;br /&gt;
    format=&amp;quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;quot;,&lt;br /&gt;
    stream=sys.stderr,&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(&amp;quot;CardinalsMCP-server&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run the hello tool in the chat of Claude Desktop:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to say hello&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If everything is working correctly, you should see a &amp;quot;Hello CSE 3300 Student&amp;quot; response.&lt;br /&gt;
&lt;br /&gt;
After you have implemented the MCP tools, you can run commands like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3522</id>
		<title>Module 5</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3522"/>
		<updated>2026-07-07T13:31:29Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Submission Instructions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
Module 5 builds upon the JavaScript skills you learned in Module 4 and introduces server-side JavaScript using Node.js, along with real-time communication using Socket.IO.&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the class wiki contain information that will help you complete the assignments:&lt;br /&gt;
&lt;br /&gt;
* [[Node.JS]]&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Install Node.js ===&lt;br /&gt;
&lt;br /&gt;
Install Node.js using the instructions in the [[Node.JS]] guide (use the current LTS version via nvm).&lt;br /&gt;
&lt;br /&gt;
=== Static File Server in Node.js ===&lt;br /&gt;
&lt;br /&gt;
The individual portion is intentionally short so you have sufficient time for the group project.&lt;br /&gt;
&lt;br /&gt;
Follow these steps carefully.&lt;br /&gt;
&lt;br /&gt;
# Copy the static file server example from the [[Node.JS]] page into a file named &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt;. Save it on your EC2 instance in a directory that is NOT served by Apache.&lt;br /&gt;
# Ensure that you understand what the static file server script is doing.&lt;br /&gt;
# Create a directory parallel to &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt; named &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Confirm that your server’s &amp;lt;code&amp;gt;STATIC_DIR&amp;lt;/code&amp;gt; variable matches this directory name.&lt;br /&gt;
# Create the following files inside the &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt; directory:&lt;br /&gt;
#* &amp;lt;code&amp;gt;hello.txt&amp;lt;/code&amp;gt; — a text file containing: &amp;lt;b&amp;gt;Hello World&amp;lt;/b&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt; — download from: http://classes.engineering.wustl.edu/cse330/content/brookings.jpg&lt;br /&gt;
#* &amp;lt;code&amp;gt;college.html&amp;lt;/code&amp;gt; — an HTML page based on the [[HTML and CSS#Quick and Easy Page Layout|Quick and Easy HTML Layout]] that includes an &amp;lt;code&amp;gt;&amp;amp;lt;img&amp;amp;gt;&amp;lt;/code&amp;gt; referencing &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; — a PHP file containing:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;php&amp;quot;&amp;gt;&amp;lt;?php phpinfo(); ?&amp;gt;&amp;lt;/source&amp;gt;&lt;br /&gt;
# Start your Node.js server and load all four files in your browser.&lt;br /&gt;
&lt;br /&gt;
Questions to consider:&lt;br /&gt;
&lt;br /&gt;
* Which files work and which do not?&lt;br /&gt;
* Why does &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; behave differently when served by Node instead of Apache?&lt;br /&gt;
&lt;br /&gt;
== Group Project: Multi-Room Chat Server ==&lt;br /&gt;
&lt;br /&gt;
In the group portion of Module 5, you will build a multi-room chat system using Node.js and Socket.IO.&lt;br /&gt;
&lt;br /&gt;
The system must include:&lt;br /&gt;
&lt;br /&gt;
* A main lobby where users select a nickname&lt;br /&gt;
* Ability to create chat rooms&lt;br /&gt;
* Ability to join chat rooms&lt;br /&gt;
* A list of available rooms&lt;br /&gt;
* A list of users currently in the room&lt;br /&gt;
* The entire application displayed on a single webpage&lt;br /&gt;
&lt;br /&gt;
The single-room example is provided here:&lt;br /&gt;
&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
&lt;br /&gt;
== AI Agent Extension ==&lt;br /&gt;
&lt;br /&gt;
You will enhance your chat server with an AI assistant.&lt;br /&gt;
&lt;br /&gt;
Users can message the AI by typing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;/agent What is the capital of France?&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The server detects messages that begin with &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt;, sends the prompt to an AI model, and broadcasts the response as &amp;lt;b&amp;gt;Agent&amp;lt;/b&amp;gt; in the same room.&lt;br /&gt;
&lt;br /&gt;
You may use:&lt;br /&gt;
&lt;br /&gt;
* Ollama (recommended — runs locally and free)&lt;br /&gt;
* OpenAI&lt;br /&gt;
* Gemini&lt;br /&gt;
* HuggingFace&lt;br /&gt;
* Any approved model&lt;br /&gt;
&lt;br /&gt;
If using an API, store your key in a file named &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; at the root of your project:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=your_key_here&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Load it securely in your server:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import 'dotenv/config';&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Refer to your chosen model’s documentation for setup details.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 100 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. '''&lt;br /&gt;
&lt;br /&gt;
'''Additionally, for this module, the TA should be able to simply download your repo and start up your file server, without having to copy files or create additional subdirectories.  This applies to both the individual and the creative portion'''&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''Individual Portion (30 Points):'''&lt;br /&gt;
#* Node.JS is installed on your EC2 instance (5 points)&lt;br /&gt;
#*: ''Take a screenshot of your browser visiting your EC2 instance serving up the college.html web page on port 3456, make sure the URL is visible in the screenshot.''&lt;br /&gt;
#* The ''hello.txt'', ''brookings.jpg'', and ''college.html'' files all load successfully (4 points each)&lt;br /&gt;
#* Visiting a file that does not exist inside the ''static'' directory results in a 404 (4 points)&lt;br /&gt;
#* Discuss in the README.md why ''phpinfo.php'' behaves the way it does when loaded through Node.JS (4 points)&lt;br /&gt;
#* Make sure all of your individual portion files are pushed to Github (include your node.js files, hello.txt, brookings.jpg, college.html, and phpinfo.php)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
# '''Multi-room Chat Server (70 Points):'''&lt;br /&gt;
#* '''''Administration of user created chat rooms (25 Points):'''''&lt;br /&gt;
#** Users can create chat rooms with an arbitrary room name(5 points)&lt;br /&gt;
#** Users can join an arbitrary chat room (5 points)&lt;br /&gt;
#** The chat room displays all users currently in the room (5 points)&lt;br /&gt;
#** A private room can be created that is password protected (5 points)&lt;br /&gt;
#** Creators of chat rooms can temporarily kick others out of the room (3 points)&lt;br /&gt;
#** Creators of chat rooms can permanently ban users from joining that particular room (2 points)&lt;br /&gt;
#* '''''Messaging (5 Points):'''''&lt;br /&gt;
#** A user's message shows their username and is sent to everyone in the room (1 point)&lt;br /&gt;
#** Users can send private messages to another user in the same room (4 points)&lt;br /&gt;
#* '''''Best Practices (5 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** Code passes HTML validation (2 points)&lt;br /&gt;
#** node_modules folder is ignored by version control (1 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Communicating with others and joining rooms is easy and intuitive (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
#* '''AI Agent and Reflection (20 Points)'''&lt;br /&gt;
#** Implement an AI assistant accessible via the command \agent ... that replies in the current room (10 points)&lt;br /&gt;
#** Handles basic errors gracefully and does not crash the server (5 point)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
#* '''Creative Portion (10 Points)'''&lt;br /&gt;
&amp;lt;!-- #** Make sure to create a README.md file pointing to your server --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Submission Instructions ==&lt;br /&gt;
&lt;br /&gt;
To ensure your project can be graded quickly and reliably, follow these submission requirements carefully.&lt;br /&gt;
&lt;br /&gt;
All students must submit a working GitHub repository. In addition, your project must be verifiable by the TA using one of the two methods below.&lt;br /&gt;
&lt;br /&gt;
=== Required for ALL Submissions ===&lt;br /&gt;
&lt;br /&gt;
Your GitHub repository must include:&lt;br /&gt;
&lt;br /&gt;
* All source code for both the individual and group portions&lt;br /&gt;
* A complete &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file&lt;br /&gt;
* A &amp;lt;code&amp;gt;README.md&amp;lt;/code&amp;gt; containing:&lt;br /&gt;
** How to run your server&lt;br /&gt;
** Which AI model you used (Ollama, OpenAI, Gemini, etc.)&lt;br /&gt;
** Any special setup instructions&lt;br /&gt;
* A &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file that excludes:&lt;br /&gt;
** &amp;lt;code&amp;gt;node_modules/&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Do NOT commit your API keys.'''&lt;br /&gt;
&lt;br /&gt;
If your project uses environment variables, include a file named:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
.env.example&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Your server must not crash if the API key is missing. It should fail gracefully.&lt;br /&gt;
&lt;br /&gt;
=== Option A - Running on EC2 ===&lt;br /&gt;
&lt;br /&gt;
Deploy your chat server on your EC2 instance and keep it running using [[#Running Tmux|tmux]].&lt;br /&gt;
&lt;br /&gt;
Your README must include:&lt;br /&gt;
&lt;br /&gt;
* Public EC2 URL&lt;br /&gt;
* Port number (e.g., 3456)&lt;br /&gt;
* Any login instructions (if applicable)&lt;br /&gt;
&lt;br /&gt;
The TA will:&lt;br /&gt;
&lt;br /&gt;
* Visit your running server&lt;br /&gt;
* Test chat functionality&lt;br /&gt;
* Test multi-room behavior&lt;br /&gt;
* Test the &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; AI command&lt;br /&gt;
&lt;br /&gt;
This is the fastest and preferred grading method.&lt;br /&gt;
&lt;br /&gt;
=== Option B — Local Execution + Demo Video ===&lt;br /&gt;
&lt;br /&gt;
If your server is NOT running on EC2, you must provide a short demonstration video.&lt;br /&gt;
&lt;br /&gt;
The video must show:&lt;br /&gt;
&lt;br /&gt;
* Starting the server&lt;br /&gt;
* Two browser windows connected&lt;br /&gt;
* Chat working between users&lt;br /&gt;
* The &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; command working&lt;br /&gt;
&lt;br /&gt;
Video length: approximately 1–2 minutes.&lt;br /&gt;
&lt;br /&gt;
The TA may still run your code locally if needed.&lt;br /&gt;
&lt;br /&gt;
=== Server Requirements ===&lt;br /&gt;
&lt;br /&gt;
Your submission must:&lt;br /&gt;
&lt;br /&gt;
* Start successfully with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm install&lt;br /&gt;
node chat-server.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(or equivalent script)&lt;br /&gt;
&lt;br /&gt;
* Not require manual file copying or directory creation&lt;br /&gt;
* Not require editing source code to run&lt;br /&gt;
* Handle missing AI keys gracefully&lt;br /&gt;
* Not crash during normal use&lt;br /&gt;
&lt;br /&gt;
=== Common Causes of Lost Points ===&lt;br /&gt;
&lt;br /&gt;
* Missing README instructions&lt;br /&gt;
* API key committed to GitHub&lt;br /&gt;
* Server crashes when AI key missing&lt;br /&gt;
* Broken EC2 deployment (port closed)&lt;br /&gt;
* Cannot run after &amp;lt;code&amp;gt;npm install&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;node_modules&amp;lt;/code&amp;gt; committed to repo&lt;br /&gt;
* Hardcoded absolute file paths&lt;br /&gt;
* Missing AI demonstration&lt;br /&gt;
&lt;br /&gt;
Following these instructions ensures your project can be graded quickly and fairly.&lt;br /&gt;
&lt;br /&gt;
=== Running Tmux ===&lt;br /&gt;
&lt;br /&gt;
To keep your Node.js apps running after you disconnect from your EC2 instance, you can use a tool called tmux. Normally, when you SSH into a server and start a program, it stops running as soon as you close the terminal. tmux lets your programs continue running in the background even after you disconnect.&lt;br /&gt;
&lt;br /&gt;
First, install tmux on your EC2 instance by running:&lt;br /&gt;
sudo yum install tmux&lt;br /&gt;
&lt;br /&gt;
To start using tmux, create a session by typing:&lt;br /&gt;
tmux&lt;br /&gt;
&lt;br /&gt;
This opens a new terminal session managed by tmux. You can now start your Node.js server inside this session (for example, node server.js).&lt;br /&gt;
&lt;br /&gt;
If you want to disconnect from the server but keep your program running, detach from tmux by pressing:&lt;br /&gt;
Control + b, then press d&lt;br /&gt;
&lt;br /&gt;
Your Node.js app will continue running even after you disconnect from SSH.&lt;br /&gt;
&lt;br /&gt;
Later, when you SSH back into your EC2 instance, you can return to the same tmux session by running:&lt;br /&gt;
tmux attach&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3521</id>
		<title>Module 5</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3521"/>
		<updated>2026-07-07T13:26:31Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Submission Instructions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
Module 5 builds upon the JavaScript skills you learned in Module 4 and introduces server-side JavaScript using Node.js, along with real-time communication using Socket.IO.&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the class wiki contain information that will help you complete the assignments:&lt;br /&gt;
&lt;br /&gt;
* [[Node.JS]]&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Install Node.js ===&lt;br /&gt;
&lt;br /&gt;
Install Node.js using the instructions in the [[Node.JS]] guide (use the current LTS version via nvm).&lt;br /&gt;
&lt;br /&gt;
=== Static File Server in Node.js ===&lt;br /&gt;
&lt;br /&gt;
The individual portion is intentionally short so you have sufficient time for the group project.&lt;br /&gt;
&lt;br /&gt;
Follow these steps carefully.&lt;br /&gt;
&lt;br /&gt;
# Copy the static file server example from the [[Node.JS]] page into a file named &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt;. Save it on your EC2 instance in a directory that is NOT served by Apache.&lt;br /&gt;
# Ensure that you understand what the static file server script is doing.&lt;br /&gt;
# Create a directory parallel to &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt; named &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Confirm that your server’s &amp;lt;code&amp;gt;STATIC_DIR&amp;lt;/code&amp;gt; variable matches this directory name.&lt;br /&gt;
# Create the following files inside the &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt; directory:&lt;br /&gt;
#* &amp;lt;code&amp;gt;hello.txt&amp;lt;/code&amp;gt; — a text file containing: &amp;lt;b&amp;gt;Hello World&amp;lt;/b&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt; — download from: http://classes.engineering.wustl.edu/cse330/content/brookings.jpg&lt;br /&gt;
#* &amp;lt;code&amp;gt;college.html&amp;lt;/code&amp;gt; — an HTML page based on the [[HTML and CSS#Quick and Easy Page Layout|Quick and Easy HTML Layout]] that includes an &amp;lt;code&amp;gt;&amp;amp;lt;img&amp;amp;gt;&amp;lt;/code&amp;gt; referencing &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; — a PHP file containing:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;php&amp;quot;&amp;gt;&amp;lt;?php phpinfo(); ?&amp;gt;&amp;lt;/source&amp;gt;&lt;br /&gt;
# Start your Node.js server and load all four files in your browser.&lt;br /&gt;
&lt;br /&gt;
Questions to consider:&lt;br /&gt;
&lt;br /&gt;
* Which files work and which do not?&lt;br /&gt;
* Why does &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; behave differently when served by Node instead of Apache?&lt;br /&gt;
&lt;br /&gt;
== Group Project: Multi-Room Chat Server ==&lt;br /&gt;
&lt;br /&gt;
In the group portion of Module 5, you will build a multi-room chat system using Node.js and Socket.IO.&lt;br /&gt;
&lt;br /&gt;
The system must include:&lt;br /&gt;
&lt;br /&gt;
* A main lobby where users select a nickname&lt;br /&gt;
* Ability to create chat rooms&lt;br /&gt;
* Ability to join chat rooms&lt;br /&gt;
* A list of available rooms&lt;br /&gt;
* A list of users currently in the room&lt;br /&gt;
* The entire application displayed on a single webpage&lt;br /&gt;
&lt;br /&gt;
The single-room example is provided here:&lt;br /&gt;
&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
&lt;br /&gt;
== AI Agent Extension ==&lt;br /&gt;
&lt;br /&gt;
You will enhance your chat server with an AI assistant.&lt;br /&gt;
&lt;br /&gt;
Users can message the AI by typing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;/agent What is the capital of France?&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The server detects messages that begin with &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt;, sends the prompt to an AI model, and broadcasts the response as &amp;lt;b&amp;gt;Agent&amp;lt;/b&amp;gt; in the same room.&lt;br /&gt;
&lt;br /&gt;
You may use:&lt;br /&gt;
&lt;br /&gt;
* Ollama (recommended — runs locally and free)&lt;br /&gt;
* OpenAI&lt;br /&gt;
* Gemini&lt;br /&gt;
* HuggingFace&lt;br /&gt;
* Any approved model&lt;br /&gt;
&lt;br /&gt;
If using an API, store your key in a file named &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; at the root of your project:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=your_key_here&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Load it securely in your server:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import 'dotenv/config';&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Refer to your chosen model’s documentation for setup details.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 100 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. '''&lt;br /&gt;
&lt;br /&gt;
'''Additionally, for this module, the TA should be able to simply download your repo and start up your file server, without having to copy files or create additional subdirectories.  This applies to both the individual and the creative portion'''&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''Individual Portion (30 Points):'''&lt;br /&gt;
#* Node.JS is installed on your EC2 instance (5 points)&lt;br /&gt;
#*: ''Take a screenshot of your browser visiting your EC2 instance serving up the college.html web page on port 3456, make sure the URL is visible in the screenshot.''&lt;br /&gt;
#* The ''hello.txt'', ''brookings.jpg'', and ''college.html'' files all load successfully (4 points each)&lt;br /&gt;
#* Visiting a file that does not exist inside the ''static'' directory results in a 404 (4 points)&lt;br /&gt;
#* Discuss in the README.md why ''phpinfo.php'' behaves the way it does when loaded through Node.JS (4 points)&lt;br /&gt;
#* Make sure all of your individual portion files are pushed to Github (include your node.js files, hello.txt, brookings.jpg, college.html, and phpinfo.php)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
# '''Multi-room Chat Server (70 Points):'''&lt;br /&gt;
#* '''''Administration of user created chat rooms (25 Points):'''''&lt;br /&gt;
#** Users can create chat rooms with an arbitrary room name(5 points)&lt;br /&gt;
#** Users can join an arbitrary chat room (5 points)&lt;br /&gt;
#** The chat room displays all users currently in the room (5 points)&lt;br /&gt;
#** A private room can be created that is password protected (5 points)&lt;br /&gt;
#** Creators of chat rooms can temporarily kick others out of the room (3 points)&lt;br /&gt;
#** Creators of chat rooms can permanently ban users from joining that particular room (2 points)&lt;br /&gt;
#* '''''Messaging (5 Points):'''''&lt;br /&gt;
#** A user's message shows their username and is sent to everyone in the room (1 point)&lt;br /&gt;
#** Users can send private messages to another user in the same room (4 points)&lt;br /&gt;
#* '''''Best Practices (5 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** Code passes HTML validation (2 points)&lt;br /&gt;
#** node_modules folder is ignored by version control (1 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Communicating with others and joining rooms is easy and intuitive (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
#* '''AI Agent and Reflection (20 Points)'''&lt;br /&gt;
#** Implement an AI assistant accessible via the command \agent ... that replies in the current room (10 points)&lt;br /&gt;
#** Handles basic errors gracefully and does not crash the server (5 point)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
#* '''Creative Portion (10 Points)'''&lt;br /&gt;
&amp;lt;!-- #** Make sure to create a README.md file pointing to your server --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Submission Instructions ==&lt;br /&gt;
&lt;br /&gt;
To ensure your project can be graded quickly and reliably, follow these submission requirements carefully.&lt;br /&gt;
&lt;br /&gt;
All students must submit a working GitHub repository. In addition, your project must be verifiable by the TA using one of the two methods below.&lt;br /&gt;
&lt;br /&gt;
=== Required for ALL Submissions ===&lt;br /&gt;
&lt;br /&gt;
Your GitHub repository must include:&lt;br /&gt;
&lt;br /&gt;
* All source code for both the individual and group portions&lt;br /&gt;
* A complete &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file&lt;br /&gt;
* A &amp;lt;code&amp;gt;README.md&amp;lt;/code&amp;gt; containing:&lt;br /&gt;
** How to run your server&lt;br /&gt;
** Which AI model you used (Ollama, OpenAI, Gemini, etc.)&lt;br /&gt;
** Any special setup instructions&lt;br /&gt;
* A &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file that excludes:&lt;br /&gt;
** &amp;lt;code&amp;gt;node_modules/&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Do NOT commit your API keys.'''&lt;br /&gt;
&lt;br /&gt;
If your project uses environment variables, include a file named:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
.env.example&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Your server must not crash if the API key is missing. It should fail gracefully.&lt;br /&gt;
&lt;br /&gt;
=== Option A - Running on EC2 ===&lt;br /&gt;
&lt;br /&gt;
Deploy your chat server on your EC2 instance and keep it running using tmux.&lt;br /&gt;
&lt;br /&gt;
Your README must include:&lt;br /&gt;
&lt;br /&gt;
* Public EC2 URL&lt;br /&gt;
* Port number (e.g., 3456)&lt;br /&gt;
* Any login instructions (if applicable)&lt;br /&gt;
&lt;br /&gt;
The TA will:&lt;br /&gt;
&lt;br /&gt;
* Visit your running server&lt;br /&gt;
* Test chat functionality&lt;br /&gt;
* Test multi-room behavior&lt;br /&gt;
* Test the &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; AI command&lt;br /&gt;
&lt;br /&gt;
This is the fastest and preferred grading method.&lt;br /&gt;
&lt;br /&gt;
=== Option B — Local Execution + Demo Video ===&lt;br /&gt;
&lt;br /&gt;
If your server is NOT running on EC2, you must provide a short demonstration video.&lt;br /&gt;
&lt;br /&gt;
The video must show:&lt;br /&gt;
&lt;br /&gt;
* Starting the server&lt;br /&gt;
* Two browser windows connected&lt;br /&gt;
* Chat working between users&lt;br /&gt;
* The &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; command working&lt;br /&gt;
&lt;br /&gt;
Video length: approximately 1–2 minutes.&lt;br /&gt;
&lt;br /&gt;
The TA may still run your code locally if needed.&lt;br /&gt;
&lt;br /&gt;
=== Server Requirements ===&lt;br /&gt;
&lt;br /&gt;
Your submission must:&lt;br /&gt;
&lt;br /&gt;
* Start successfully with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm install&lt;br /&gt;
node chat-server.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(or equivalent script)&lt;br /&gt;
&lt;br /&gt;
* Not require manual file copying or directory creation&lt;br /&gt;
* Not require editing source code to run&lt;br /&gt;
* Handle missing AI keys gracefully&lt;br /&gt;
* Not crash during normal use&lt;br /&gt;
&lt;br /&gt;
=== Common Causes of Lost Points ===&lt;br /&gt;
&lt;br /&gt;
* Missing README instructions&lt;br /&gt;
* API key committed to GitHub&lt;br /&gt;
* Server crashes when AI key missing&lt;br /&gt;
* Broken EC2 deployment (port closed)&lt;br /&gt;
* Cannot run after &amp;lt;code&amp;gt;npm install&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;node_modules&amp;lt;/code&amp;gt; committed to repo&lt;br /&gt;
* Hardcoded absolute file paths&lt;br /&gt;
* Missing AI demonstration&lt;br /&gt;
&lt;br /&gt;
Following these instructions ensures your project can be graded quickly and fairly.&lt;br /&gt;
&lt;br /&gt;
=== Running Tmux ===&lt;br /&gt;
&lt;br /&gt;
To keep your Node.js apps running after you disconnect from your EC2 instance, you can use a tool called tmux. Normally, when you SSH into a server and start a program, it stops running as soon as you close the terminal. tmux lets your programs continue running in the background even after you disconnect.&lt;br /&gt;
&lt;br /&gt;
First, install tmux on your EC2 instance by running:&lt;br /&gt;
sudo yum install tmux&lt;br /&gt;
&lt;br /&gt;
To start using tmux, create a session by typing:&lt;br /&gt;
tmux&lt;br /&gt;
&lt;br /&gt;
This opens a new terminal session managed by tmux. You can now start your Node.js server inside this session (for example, node server.js).&lt;br /&gt;
&lt;br /&gt;
If you want to disconnect from the server but keep your program running, detach from tmux by pressing:&lt;br /&gt;
Control + b, then press d&lt;br /&gt;
&lt;br /&gt;
Your Node.js app will continue running even after you disconnect from SSH.&lt;br /&gt;
&lt;br /&gt;
Later, when you SSH back into your EC2 instance, you can return to the same tmux session by running:&lt;br /&gt;
tmux attach&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3520</id>
		<title>Module 5</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3520"/>
		<updated>2026-07-07T13:25:34Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Option A - Running on EC2 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
Module 5 builds upon the JavaScript skills you learned in Module 4 and introduces server-side JavaScript using Node.js, along with real-time communication using Socket.IO.&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the class wiki contain information that will help you complete the assignments:&lt;br /&gt;
&lt;br /&gt;
* [[Node.JS]]&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Install Node.js ===&lt;br /&gt;
&lt;br /&gt;
Install Node.js using the instructions in the [[Node.JS]] guide (use the current LTS version via nvm).&lt;br /&gt;
&lt;br /&gt;
=== Static File Server in Node.js ===&lt;br /&gt;
&lt;br /&gt;
The individual portion is intentionally short so you have sufficient time for the group project.&lt;br /&gt;
&lt;br /&gt;
Follow these steps carefully.&lt;br /&gt;
&lt;br /&gt;
# Copy the static file server example from the [[Node.JS]] page into a file named &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt;. Save it on your EC2 instance in a directory that is NOT served by Apache.&lt;br /&gt;
# Ensure that you understand what the static file server script is doing.&lt;br /&gt;
# Create a directory parallel to &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt; named &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Confirm that your server’s &amp;lt;code&amp;gt;STATIC_DIR&amp;lt;/code&amp;gt; variable matches this directory name.&lt;br /&gt;
# Create the following files inside the &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt; directory:&lt;br /&gt;
#* &amp;lt;code&amp;gt;hello.txt&amp;lt;/code&amp;gt; — a text file containing: &amp;lt;b&amp;gt;Hello World&amp;lt;/b&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt; — download from: http://classes.engineering.wustl.edu/cse330/content/brookings.jpg&lt;br /&gt;
#* &amp;lt;code&amp;gt;college.html&amp;lt;/code&amp;gt; — an HTML page based on the [[HTML and CSS#Quick and Easy Page Layout|Quick and Easy HTML Layout]] that includes an &amp;lt;code&amp;gt;&amp;amp;lt;img&amp;amp;gt;&amp;lt;/code&amp;gt; referencing &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; — a PHP file containing:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;php&amp;quot;&amp;gt;&amp;lt;?php phpinfo(); ?&amp;gt;&amp;lt;/source&amp;gt;&lt;br /&gt;
# Start your Node.js server and load all four files in your browser.&lt;br /&gt;
&lt;br /&gt;
Questions to consider:&lt;br /&gt;
&lt;br /&gt;
* Which files work and which do not?&lt;br /&gt;
* Why does &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; behave differently when served by Node instead of Apache?&lt;br /&gt;
&lt;br /&gt;
== Group Project: Multi-Room Chat Server ==&lt;br /&gt;
&lt;br /&gt;
In the group portion of Module 5, you will build a multi-room chat system using Node.js and Socket.IO.&lt;br /&gt;
&lt;br /&gt;
The system must include:&lt;br /&gt;
&lt;br /&gt;
* A main lobby where users select a nickname&lt;br /&gt;
* Ability to create chat rooms&lt;br /&gt;
* Ability to join chat rooms&lt;br /&gt;
* A list of available rooms&lt;br /&gt;
* A list of users currently in the room&lt;br /&gt;
* The entire application displayed on a single webpage&lt;br /&gt;
&lt;br /&gt;
The single-room example is provided here:&lt;br /&gt;
&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
&lt;br /&gt;
== AI Agent Extension ==&lt;br /&gt;
&lt;br /&gt;
You will enhance your chat server with an AI assistant.&lt;br /&gt;
&lt;br /&gt;
Users can message the AI by typing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;/agent What is the capital of France?&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The server detects messages that begin with &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt;, sends the prompt to an AI model, and broadcasts the response as &amp;lt;b&amp;gt;Agent&amp;lt;/b&amp;gt; in the same room.&lt;br /&gt;
&lt;br /&gt;
You may use:&lt;br /&gt;
&lt;br /&gt;
* Ollama (recommended — runs locally and free)&lt;br /&gt;
* OpenAI&lt;br /&gt;
* Gemini&lt;br /&gt;
* HuggingFace&lt;br /&gt;
* Any approved model&lt;br /&gt;
&lt;br /&gt;
If using an API, store your key in a file named &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; at the root of your project:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=your_key_here&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Load it securely in your server:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import 'dotenv/config';&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Refer to your chosen model’s documentation for setup details.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 100 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. '''&lt;br /&gt;
&lt;br /&gt;
'''Additionally, for this module, the TA should be able to simply download your repo and start up your file server, without having to copy files or create additional subdirectories.  This applies to both the individual and the creative portion'''&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''Individual Portion (30 Points):'''&lt;br /&gt;
#* Node.JS is installed on your EC2 instance (5 points)&lt;br /&gt;
#*: ''Take a screenshot of your browser visiting your EC2 instance serving up the college.html web page on port 3456, make sure the URL is visible in the screenshot.''&lt;br /&gt;
#* The ''hello.txt'', ''brookings.jpg'', and ''college.html'' files all load successfully (4 points each)&lt;br /&gt;
#* Visiting a file that does not exist inside the ''static'' directory results in a 404 (4 points)&lt;br /&gt;
#* Discuss in the README.md why ''phpinfo.php'' behaves the way it does when loaded through Node.JS (4 points)&lt;br /&gt;
#* Make sure all of your individual portion files are pushed to Github (include your node.js files, hello.txt, brookings.jpg, college.html, and phpinfo.php)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
# '''Multi-room Chat Server (70 Points):'''&lt;br /&gt;
#* '''''Administration of user created chat rooms (25 Points):'''''&lt;br /&gt;
#** Users can create chat rooms with an arbitrary room name(5 points)&lt;br /&gt;
#** Users can join an arbitrary chat room (5 points)&lt;br /&gt;
#** The chat room displays all users currently in the room (5 points)&lt;br /&gt;
#** A private room can be created that is password protected (5 points)&lt;br /&gt;
#** Creators of chat rooms can temporarily kick others out of the room (3 points)&lt;br /&gt;
#** Creators of chat rooms can permanently ban users from joining that particular room (2 points)&lt;br /&gt;
#* '''''Messaging (5 Points):'''''&lt;br /&gt;
#** A user's message shows their username and is sent to everyone in the room (1 point)&lt;br /&gt;
#** Users can send private messages to another user in the same room (4 points)&lt;br /&gt;
#* '''''Best Practices (5 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** Code passes HTML validation (2 points)&lt;br /&gt;
#** node_modules folder is ignored by version control (1 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Communicating with others and joining rooms is easy and intuitive (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
#* '''AI Agent and Reflection (20 Points)'''&lt;br /&gt;
#** Implement an AI assistant accessible via the command \agent ... that replies in the current room (10 points)&lt;br /&gt;
#** Handles basic errors gracefully and does not crash the server (5 point)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
#* '''Creative Portion (10 Points)'''&lt;br /&gt;
&amp;lt;!-- #** Make sure to create a README.md file pointing to your server --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Submission Instructions ==&lt;br /&gt;
&lt;br /&gt;
To ensure your project can be graded quickly and reliably, follow these submission requirements carefully.&lt;br /&gt;
&lt;br /&gt;
All students must submit a working GitHub repository. In addition, your project must be verifiable by the TA using one of the two methods below.&lt;br /&gt;
&lt;br /&gt;
=== Required for ALL Submissions ===&lt;br /&gt;
&lt;br /&gt;
Your GitHub repository must include:&lt;br /&gt;
&lt;br /&gt;
* All source code for both the individual and group portions&lt;br /&gt;
* A complete &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file&lt;br /&gt;
* A &amp;lt;code&amp;gt;README.md&amp;lt;/code&amp;gt; containing:&lt;br /&gt;
** How to run your server&lt;br /&gt;
** Which AI model you used (Ollama, OpenAI, Gemini, etc.)&lt;br /&gt;
** Any special setup instructions&lt;br /&gt;
* A &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file that excludes:&lt;br /&gt;
** &amp;lt;code&amp;gt;node_modules/&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Do NOT commit your API keys.'''&lt;br /&gt;
&lt;br /&gt;
If your project uses environment variables, include a file named:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
.env.example&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Your server must not crash if the API key is missing. It should fail gracefully.&lt;br /&gt;
&lt;br /&gt;
=== Option A - Running on EC2 ===&lt;br /&gt;
&lt;br /&gt;
Deploy your chat server on your EC2 instance and keep it running using tmux.&lt;br /&gt;
&lt;br /&gt;
Your README must include:&lt;br /&gt;
&lt;br /&gt;
* Public EC2 URL&lt;br /&gt;
* Port number (e.g., 3456)&lt;br /&gt;
* Any login instructions (if applicable)&lt;br /&gt;
&lt;br /&gt;
The TA will:&lt;br /&gt;
&lt;br /&gt;
* Visit your running server&lt;br /&gt;
* Test chat functionality&lt;br /&gt;
* Test multi-room behavior&lt;br /&gt;
* Test the &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; AI command&lt;br /&gt;
&lt;br /&gt;
This is the fastest and preferred grading method.&lt;br /&gt;
&lt;br /&gt;
=== Option B — Local Execution + Demo Video ===&lt;br /&gt;
&lt;br /&gt;
If your server is NOT running on EC2, you must provide a short demonstration video.&lt;br /&gt;
&lt;br /&gt;
The video must show:&lt;br /&gt;
&lt;br /&gt;
* Starting the server&lt;br /&gt;
* Two browser windows connected&lt;br /&gt;
* Chat working between users&lt;br /&gt;
* The &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; command working&lt;br /&gt;
&lt;br /&gt;
Video length: approximately 1–2 minutes.&lt;br /&gt;
&lt;br /&gt;
The TA may still run your code locally if needed.&lt;br /&gt;
&lt;br /&gt;
=== Server Requirements ===&lt;br /&gt;
&lt;br /&gt;
Your submission must:&lt;br /&gt;
&lt;br /&gt;
* Start successfully with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm install&lt;br /&gt;
node chat-server.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(or equivalent script)&lt;br /&gt;
&lt;br /&gt;
* Not require manual file copying or directory creation&lt;br /&gt;
* Not require editing source code to run&lt;br /&gt;
* Handle missing AI keys gracefully&lt;br /&gt;
* Not crash during normal use&lt;br /&gt;
&lt;br /&gt;
=== Common Causes of Lost Points ===&lt;br /&gt;
&lt;br /&gt;
* Missing README instructions&lt;br /&gt;
* API key committed to GitHub&lt;br /&gt;
* Server crashes when AI key missing&lt;br /&gt;
* Broken EC2 deployment (port closed)&lt;br /&gt;
* Cannot run after &amp;lt;code&amp;gt;npm install&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;node_modules&amp;lt;/code&amp;gt; committed to repo&lt;br /&gt;
* Hardcoded absolute file paths&lt;br /&gt;
* Missing AI demonstration&lt;br /&gt;
&lt;br /&gt;
Following these instructions ensures your project can be graded quickly and fairly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=AJAX_and_JSON&amp;diff=3519</id>
		<title>AJAX and JSON</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=AJAX_and_JSON&amp;diff=3519"/>
		<updated>2026-07-06T18:26:48Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Javascript Fetch API */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Asynchronous Javascript And XML, or '''''AJAX''''', is a group of related web development techniques, standards, and technologies used on the client (web browser) side of the standard web server-client communication model.&lt;br /&gt;
&lt;br /&gt;
Here's the general concept: JavaScript is used to send requests to the web server in the background and retrieve data, without the need to refresh the page.  The result: dynamic, fast, user-friendly web pages.&lt;br /&gt;
&lt;br /&gt;
The requests are performed ''asynchronously'': this means that the end user can continue interacting with your web page while the server data is being loaded.&lt;br /&gt;
&lt;br /&gt;
The key idea is that a large fraction of the work happens at the web browser, and larger requests happen behind the scenes, keeping waiting times for data to come back over the network to a minimum.  AJAX is generally also associated with higher-level APIs and libraries that aid in producing cleaner user interfaces for web applications.&lt;br /&gt;
&lt;br /&gt;
== The XMLHttpRequest Object ==&lt;br /&gt;
&lt;br /&gt;
The JavaScript specification defines the '''XMLHttpRequest''' object (sometimes abbreviated as '''XHR'''), which is used to exchange data with the web servers without needing to reload the entire web page in the browser.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' For security reasons, you cannot perform AJAX requests to other domains, unless that domain has a special security policy set up to enable such requests.&lt;br /&gt;
=== Javascript Fetch API ===&lt;br /&gt;
&lt;br /&gt;
While creating an xhr object works, it's messy and hard to cleanly organize JavaScript's asynchronous nature.&lt;br /&gt;
&lt;br /&gt;
Javascript has a [https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch Fetch API] that you should be using. The Fetch API uses a concept in JavaScript known as a [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise Promise] to represent the completion or failure of an asynchronous operation.&lt;br /&gt;
&lt;br /&gt;
An interesting discussion of XMLHttpRequest vs Fetch can be found [https://web.archive.org/web/20220421025435/https://blog.openreplay.com/ajax-battle-xmlhttprequest-vs-the-fetch-api here].&lt;br /&gt;
&lt;br /&gt;
=== Sending Data ===&lt;br /&gt;
&lt;br /&gt;
==== GET Data ====&lt;br /&gt;
&lt;br /&gt;
To send GET data, create a fetch object with method &amp;quot;GET&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
fetch('/path/to/php/file', {&lt;br /&gt;
    method: &amp;quot;GET&amp;quot;&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can build your url to include GET variables if you want to send data.&lt;br /&gt;
ex: '/path/to/php/file?x='hi'&lt;br /&gt;
&lt;br /&gt;
==== POST Data ====&lt;br /&gt;
&lt;br /&gt;
To send POST data, create a fetch object with method &amp;quot;POST&amp;quot;.  You need to create an object of the data you want to send.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const data = { x: 'hi', y: 'hello' };&lt;br /&gt;
fetch('/path/to/php/file', {&lt;br /&gt;
    method: &amp;quot;POST&amp;quot;,&lt;br /&gt;
    body: JSON.stringify(data)&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Listening for a Response ===&lt;br /&gt;
&lt;br /&gt;
When expecting a response from your php script, javascript's fetch api has a clean promise-based method of handling this.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const pathToPhpFile = 'test.php';&lt;br /&gt;
const data = { x: 'hi', y: 'hello' };&lt;br /&gt;
fetch(pathToPhpFile, {&lt;br /&gt;
    method: &amp;quot;POST&amp;quot;,&lt;br /&gt;
    body: JSON.stringify(data)&lt;br /&gt;
  })&lt;br /&gt;
  .then(res =&amp;gt; res.json())&lt;br /&gt;
  .then(response =&amp;gt; console.log('Success:', JSON.stringify(response)))&lt;br /&gt;
  .catch(error =&amp;gt; console.error('Error:',error))&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You'll notice unfamiliar syntax here with the .then() methods attached.&lt;br /&gt;
These are attaching event listeners to the end of your fetch request, and will trigger a function when your php script returns a response.&lt;br /&gt;
&lt;br /&gt;
You should '''always''' have a catch block at the end, in case an error occurs in your response. In the near future, Javascript will ignore any requests that do not include a .catch() block.&lt;br /&gt;
&lt;br /&gt;
==== Browser Support ====&lt;br /&gt;
&lt;br /&gt;
Many examples that you find when Googling for &amp;quot;AJAX&amp;quot; use the '''onreadystatechange''' property of the XMLHttpRequest instance to react on the completion of the request, rather than using an event listener.  This approach works, and it is required for comprehensive browser support, but it is no longer recommended for future use in modern web applications.  The implementation of XMLHttpRequest documented here (so-called &amp;quot;XHR level 2&amp;quot;) works in the following browsers:&lt;br /&gt;
* Firefox &lt;br /&gt;
* Safari &lt;br /&gt;
* Chrome &lt;br /&gt;
* Opera&lt;br /&gt;
&lt;br /&gt;
=== Response Formats ===&lt;br /&gt;
&lt;br /&gt;
More often than not, a plaintext response form the server is not very helpful: you want to transmit more information than simply a single string of text.  There are two good options to get more information from a single AJAX request: '''XML''' (the original format, thus the ''X'' in ''AJAX'') and '''JSON'''.&lt;br /&gt;
&lt;br /&gt;
==== XML ====&lt;br /&gt;
&lt;br /&gt;
Your server can respond with an XML document (whence, ''XMLHttpRequest'').  An '''XML document''' uses syntax that is extremely close to HTML  For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; standalone=&amp;quot;yes&amp;quot; ?&amp;gt;&lt;br /&gt;
&amp;lt;note&amp;gt;&lt;br /&gt;
  &amp;lt;date year=&amp;quot;2023&amp;quot; month=&amp;quot;02&amp;quot; day=&amp;quot;14&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;to&amp;gt;Sally&amp;lt;/to&amp;gt;&lt;br /&gt;
  &amp;lt;message&amp;gt;Happy Valentine's Day!&amp;lt;/message&amp;gt;&lt;br /&gt;
  &amp;lt;from&amp;gt;Your Secret Admirer&amp;lt;/from&amp;gt;&lt;br /&gt;
&amp;lt;/note&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Like HTML, XML has a DOM that can be traversed in JavaScript.&lt;br /&gt;
&lt;br /&gt;
If you make an AJAX request to a script generating an XML document, the root XML node is accessible in the '''responseXML''' property.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function ajaxCallback(event){&lt;br /&gt;
	var xmlDocument = event.target.responseXML;&lt;br /&gt;
	&lt;br /&gt;
	alert(xmlDocument.getElementsByTagName(&amp;quot;to&amp;quot;)[0].textContent);  // Sally (in the case of the example XML above)&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== JSON ====&lt;br /&gt;
&lt;br /&gt;
JavaScript Object Notation, or '''JSON''', is a rising standard interface format because of its lightweight syntax that makes it especially useful for AJAX requests.&lt;br /&gt;
&lt;br /&gt;
The JSON syntax closely follows the syntax for [[JavaScript#Object Literals|object literals]].  For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;apple&amp;quot;: {&lt;br /&gt;
      &amp;quot;color&amp;quot;: &amp;quot;red&amp;quot;,&lt;br /&gt;
      &amp;quot;flavor&amp;quot;: &amp;quot;sweet&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;lemon&amp;quot;: {&lt;br /&gt;
      &amp;quot;color&amp;quot;: &amp;quot;yellow&amp;quot;,&lt;br /&gt;
      &amp;quot;flavor&amp;quot;: &amp;quot;sour&amp;quot;&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Usually when JSON is transferred over the internet, it is condensed down to one line, like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&amp;quot;apple&amp;quot;:{&amp;quot;color&amp;quot;:&amp;quot;red&amp;quot;,&amp;quot;flavor&amp;quot;:&amp;quot;sweet&amp;quot;},&amp;quot;lemon&amp;quot;:{&amp;quot;color&amp;quot;:&amp;quot;yellow&amp;quot;,&amp;quot;flavor&amp;quot;:&amp;quot;sour&amp;quot;}}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In JavaScript, you can convert a JSON string to an object using '''JSON.parse()'''.  For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var jsonData = JSON.parse('{&amp;quot;apple&amp;quot;:{&amp;quot;color&amp;quot;:&amp;quot;red&amp;quot;,&amp;quot;flavor&amp;quot;:&amp;quot;sweet&amp;quot;},&amp;quot;lemon&amp;quot;:{&amp;quot;color&amp;quot;:&amp;quot;yellow&amp;quot;,&amp;quot;flavor&amp;quot;:&amp;quot;sour&amp;quot;}}');&lt;br /&gt;
&lt;br /&gt;
alert(jsonData.apple.color);  // red&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here's how to use it in conjunction with your AJAX response callback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function ajaxCallback(event){&lt;br /&gt;
	var jsonData = JSON.parse(event.target.responseText);&lt;br /&gt;
	&lt;br /&gt;
	alert(jsonData.lemon.flavor);  // sour (in the case of the example JSON above)&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here is a graphic to illustrate the JSON process.&lt;br /&gt;
&lt;br /&gt;
[[File:JSON-Flowchart.png‎]]&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Logging In a User ===&lt;br /&gt;
&lt;br /&gt;
Suppose you had the following PHP script:&lt;br /&gt;
Please note the difference in accessing POST data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
// login_ajax.php&lt;br /&gt;
&lt;br /&gt;
header(&amp;quot;Content-Type: application/json&amp;quot;); // Since we are sending a JSON response here (not an HTML document), set the MIME Type to application/json&lt;br /&gt;
&lt;br /&gt;
//Because you are posting the data via fetch(), php has to retrieve it elsewhere.&lt;br /&gt;
$json_str = file_get_contents('php://input');&lt;br /&gt;
//This will store the data into an associative array&lt;br /&gt;
$json_obj = json_decode($json_str, true);&lt;br /&gt;
&lt;br /&gt;
//Variables can be accessed as such:&lt;br /&gt;
$username = $json_obj['username'];&lt;br /&gt;
$password = $json_obj['password'];&lt;br /&gt;
//This is equivalent to what you previously did with $_POST['username'] and $_POST['password']&lt;br /&gt;
&lt;br /&gt;
// Check to see if the username and password are valid.  (You learned how to do this in Module 3.)&lt;br /&gt;
&lt;br /&gt;
if( /* valid username and password */ ){&lt;br /&gt;
	session_start();&lt;br /&gt;
	$_SESSION['username'] = $username;&lt;br /&gt;
	$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32)); &lt;br /&gt;
&lt;br /&gt;
	echo json_encode(array(&lt;br /&gt;
		&amp;quot;success&amp;quot; =&amp;gt; true&lt;br /&gt;
	));&lt;br /&gt;
	exit;&lt;br /&gt;
}else{&lt;br /&gt;
	echo json_encode(array(&lt;br /&gt;
		&amp;quot;success&amp;quot; =&amp;gt; false,&lt;br /&gt;
		&amp;quot;message&amp;quot; =&amp;gt; &amp;quot;Incorrect Username or Password&amp;quot;&lt;br /&gt;
	));&lt;br /&gt;
	exit;&lt;br /&gt;
}&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' This example uses PHP's very handy '''json_encode()''' function to translate an associative array into a JSON object.  This means that you can access in JavaScript the same structure that you made in PHP!&lt;br /&gt;
&lt;br /&gt;
And suppose you had the following HTML content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;username&amp;quot; placeholder=&amp;quot;Username&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;password&amp;quot; id=&amp;quot;password&amp;quot; placeholder=&amp;quot;Password&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;button id=&amp;quot;login_btn&amp;quot;&amp;gt;Log In&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;ajax.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;!-- load the JavaScript file --&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You could use the following JavaScript code to prepare, send, and receive a request from the server to see if the user is logged in, without ever having to refresh the page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// ajax.js&lt;br /&gt;
&lt;br /&gt;
function loginAjax(event) {&lt;br /&gt;
    const username = document.getElementById(&amp;quot;username&amp;quot;).value; // Get the username from the form&lt;br /&gt;
    const password = document.getElementById(&amp;quot;password&amp;quot;).value; // Get the password from the form&lt;br /&gt;
&lt;br /&gt;
    // Make a URL-encoded string for passing POST data:&lt;br /&gt;
    const data = { 'username': username, 'password': password };&lt;br /&gt;
&lt;br /&gt;
    fetch(&amp;quot;login_ajax.php&amp;quot;, {&lt;br /&gt;
            method: 'POST',&lt;br /&gt;
            body: JSON.stringify(data),&lt;br /&gt;
            headers: { 'content-type': 'application/json' }&lt;br /&gt;
        })&lt;br /&gt;
        .then(response =&amp;gt; response.json())&lt;br /&gt;
        .then(data =&amp;gt; console.log(data.success ? &amp;quot;You've been logged in!&amp;quot; : `You were not logged in ${data.message}`))&lt;br /&gt;
        .catch(err =&amp;gt; console.error(err));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
document.getElementById(&amp;quot;login_btn&amp;quot;).addEventListener(&amp;quot;click&amp;quot;, loginAjax, false); // Bind the AJAX call to button click&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Note:''' The above example uses an '''anonymous function''' (a function without a name that is declared in an attribute) as the callback.  If you are using a certain callback function only once in your program, this is a perfectly valid approach to reduce the amount of code you need to write.&lt;br /&gt;
&lt;br /&gt;
=== Loading Data from an XML File for Display in HTML ===&lt;br /&gt;
&lt;br /&gt;
Suppose you have a file '''note.xml''' that contains the following information:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; standalone=&amp;quot;yes&amp;quot; ?&amp;gt;&lt;br /&gt;
&amp;lt;note&amp;gt;&lt;br /&gt;
  &amp;lt;date year=&amp;quot;2023&amp;quot; month=&amp;quot;02&amp;quot; day=&amp;quot;14&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;to&amp;gt;Sally&amp;lt;/to&amp;gt;&lt;br /&gt;
  &amp;lt;message&amp;gt;Happy Valentine's Day!&amp;lt;/message&amp;gt;&lt;br /&gt;
  &amp;lt;from&amp;gt;Your Secret Admirer&amp;lt;/from&amp;gt;&lt;br /&gt;
&amp;lt;/note&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Further, suppose you had an HTML element with ID '''note''':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;note&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You could load this XML file and then display it inside of your HTML document like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function getNoteAjax(event){&lt;br /&gt;
	// The XMLHttpRequest is simple this time:&lt;br /&gt;
	var xmlHttp = new XMLHttpRequest();&lt;br /&gt;
	xmlHttp.open(&amp;quot;GET&amp;quot;, &amp;quot;note.xml&amp;quot;, true);&lt;br /&gt;
	xmlHttp.addEventListener(&amp;quot;load&amp;quot;, getNoteCallback, false);&lt;br /&gt;
	xmlHttp.send(null);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getNoteCallback(event){&lt;br /&gt;
	var htmlParent = document.getElementById(&amp;quot;note&amp;quot;); // Get the HTML element into which we want to write the note&lt;br /&gt;
	var xmlDocument = event.target.responseXML; // Get the XML root node from the response&lt;br /&gt;
	&lt;br /&gt;
	// Find the &amp;lt;date/&amp;gt; element from the XML response.  We expect only 1 such element, so we can call&lt;br /&gt;
	// xmlDocument.getElementsByTagName() and then get the first (and only) element in the array all in&lt;br /&gt;
	// one step.  This is called chaining.&lt;br /&gt;
	var xmlDateElement = xmlDocument.getElementsByTagName(&amp;quot;date&amp;quot;)[0];&lt;br /&gt;
	&lt;br /&gt;
	// Put together a YYYY-MM-DD type date into a string based on the attributes of the &amp;lt;date/&amp;gt; element.&lt;br /&gt;
	var dateString = xmlDateElement.getAttribute(&amp;quot;year&amp;quot;) + &amp;quot;-&amp;quot; + xmlDateElement.getAttribute(&amp;quot;month&amp;quot;)&lt;br /&gt;
		+ &amp;quot;-&amp;quot; + xmlDateElement.getAttribute(&amp;quot;day&amp;quot;);&lt;br /&gt;
	&lt;br /&gt;
	// Get the content of the &amp;lt;to&amp;gt;, &amp;lt;message&amp;gt;, and &amp;lt;from&amp;gt; tags.  Again, you can use a chain to do this in one line for each.&lt;br /&gt;
	var to = xmlDocument.getElementsByTagName(&amp;quot;to&amp;quot;)[0].textContent;&lt;br /&gt;
	var message = xmlDocument.getElementsByTagName(&amp;quot;message&amp;quot;)[0].textContent;&lt;br /&gt;
	var from = xmlDocument.getElementsByTagName(&amp;quot;from&amp;quot;)[0].textContent;&lt;br /&gt;
	&lt;br /&gt;
	// Make a &amp;lt;strong&amp;gt; tag, append the date string as its content, and then append it to the HTML element.&lt;br /&gt;
	var htmlDateObj = document.createElement(&amp;quot;strong&amp;quot;);&lt;br /&gt;
	htmlDateObj.appendChild(document.createTextNode(dateString));&lt;br /&gt;
	htmlParent.appendChild(htmlDateObj);&lt;br /&gt;
	&lt;br /&gt;
	// Make a &amp;lt;p&amp;gt; tag to contain the note.&lt;br /&gt;
	var htmlParagraphObj = document.createElement(&amp;quot;p&amp;quot;);&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createTextNode(&amp;quot;Dear &amp;quot;+to+&amp;quot;,&amp;quot;)); // First append the greeting&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createElement(&amp;quot;br&amp;quot;)); // Append a couple line feeds&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createElement(&amp;quot;br&amp;quot;));&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createTextNode(message)); // Write the message itself&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createElement(&amp;quot;br&amp;quot;)); // Append a couple more line feeds&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createElement(&amp;quot;br&amp;quot;));&lt;br /&gt;
	htmlParagraphObj.appendChild(document.createTextNode(&amp;quot;Love, &amp;quot;+from)); // Finally, append the salutation&lt;br /&gt;
	htmlParent.appendChild(htmlParagraphObj); // Append the newly engineered &amp;lt;p&amp;gt; to the HTML element.&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, getNoteAjax, false); // Bind the AJAX call to page load&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example uses a lot of techniques documented in the [[JavaScript#Document Object Model]] section above.  The resulting DOM will be something like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;strong&amp;gt;2023-02-14&amp;lt;/strong&amp;gt;&lt;br /&gt;
  &amp;lt;p&amp;gt;Dear Sally,&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;Happy Valentine's Day!&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;Love, Your Secret Admirer&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Debugging AJAX and JSON ==&lt;br /&gt;
&lt;br /&gt;
It is helpful when building your application to look at the AJAX requests and responses as they happen in real time.  Fortunately, there are many tools to help.&lt;br /&gt;
&lt;br /&gt;
In your favorite web inspector (Chrome Dev Tools, Mozilla Dev Tools, Firebug, Dragonfly, and so on), go to the &amp;quot;Network&amp;quot; tab.  This is where you can see all assets loaded by the application: images, stylesheets, and, most importantly for us, AJAX files.  Some inspectors let you filter requests by type; for example, the Chrome inspector lets you click &amp;quot;XHR&amp;quot; to only show XMLHttpRequest (AJAX) requests.&lt;br /&gt;
&lt;br /&gt;
You can also view the JSON sent by your server.  Double-clicking the entries in the web inspector opens the source URL in a new tab.&lt;br /&gt;
&lt;br /&gt;
If you find minified JSON hard to read, we recommend that you install the JSONView browser extension or one of its close relatives:&lt;br /&gt;
* [https://addons.mozilla.org/en-us/firefox/addon/jsonview/ Firefox]&lt;br /&gt;
* [https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc?hl=en Chrome]&lt;br /&gt;
* [https://addons.opera.com/en/extensions/details/jsonviewer/ Opera]&lt;br /&gt;
* [https://github.com/rfletcher/safari-json-formatter Safari]&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3518</id>
		<title>Module 4</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3518"/>
		<updated>2026-06-29T23:13:39Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Connect Four Debugging Exercise */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
In Module 4, you will learn JavaScript, the dominant client-side web language.  JavaScript is the second programming language you will learn in CSE 3300.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 4.&lt;br /&gt;
&lt;br /&gt;
'''Warning:'''&lt;br /&gt;
&lt;br /&gt;
''This module takes students a while. It is strongly recommended that you start early and ask questions as they come up.''&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* JavaScript&lt;br /&gt;
* DOM Events&lt;br /&gt;
* AJAX&lt;br /&gt;
* JSON&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[JavaScript]]&lt;br /&gt;
* [[AJAX and JSON]]&lt;br /&gt;
* [[jQuery]]&lt;br /&gt;
* [[Web Application Security, Part 3]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
'''''IMPORTANT:'' The individual assignments must be completed ''without'' using a JavaScript library like jQuery.'''  However, you may use jQuery for the group portion.&lt;br /&gt;
&lt;br /&gt;
=== JavaScript Calculator ===&lt;br /&gt;
&lt;br /&gt;
In Module 2, you made a calculator using PHP.  Now you will be making one using JavaScript.&lt;br /&gt;
&lt;br /&gt;
* 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).&lt;br /&gt;
* JavaScript should monitor all three fields and display the current result whenever the user changes any value in any field, without refreshing the page.&lt;br /&gt;
*: '''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.&lt;br /&gt;
*: '''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!)&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' You can embed JavaScript code into an HTML document like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
// your code here&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
'''Important:''' It is best practice to fully separate the content, appearance, and behavior.  In previous modules, you may have lost points for using HTML tags that change the appearance of things &amp;lt;nowiki&amp;gt;(like &amp;lt;center&amp;gt; and &amp;lt;font&amp;gt;).&amp;lt;/nowiki&amp;gt;  In this module, make sure that you define your event listeners in JavaScript as in &amp;lt;code&amp;gt;document.getElementById(&amp;quot;xyz&amp;quot;).addEventListener(...)&amp;lt;/code&amp;gt; and '''''not''''' in the HTML like &amp;lt;code&amp;gt;&amp;lt;button onclick=&amp;quot;badExample();&amp;quot;&amp;gt;Bad Example&amp;lt;/button&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Weather Widget ===&lt;br /&gt;
&lt;br /&gt;
In this section, you will make a web page that displays the weather forecast using AJAX requests to a weather server.&lt;br /&gt;
&lt;br /&gt;
# Make an empty HTML document; name it '''weather.html'''&lt;br /&gt;
#: Refer to the [[HTML and CSS]] guide for the skeleton of an HTML document&lt;br /&gt;
# Define a function in JavaScript; call it '''fetchWeather()'''.  You may write your JavaScript in an embedded script in your HTML document.&lt;br /&gt;
# Inside your '''fetchWeather()''' function, make an AJAX request to the weather server.&lt;br /&gt;
#: We have a server that outputs the current weather in JSON format.  The format is documented in the [[#JSON Structure]] section below.&lt;br /&gt;
&lt;br /&gt;
#: '''URL:'''  https://classes.engineering.wustl.edu/cse330/content/weather_json.php&lt;br /&gt;
#* '''IMPORTANT:''' Under normal circumstances, AJAX requests ''cannot'' be performed cross-domain for security reasons.  We have set the &amp;lt;code&amp;gt;Access-Control-Allow-Origin&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
# In your callback, process the JSON and use JavaScript to manipulate the HTML DOM to display the following information on your page:&lt;br /&gt;
#* Location&lt;br /&gt;
#** City, in a &amp;lt;nowiki&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/nowiki&amp;gt; tag&lt;br /&gt;
#** State, not in any tag&lt;br /&gt;
#* Humidity&lt;br /&gt;
#* Current Temperature&lt;br /&gt;
#* Image for Tomorrow's Forecast (see [[#Weather Condition Images]] below for more information)&lt;br /&gt;
#* Image for the Day After Tomorrow's Forecast&lt;br /&gt;
# Finally, bind '''fetchWeather()''' to the DOMContentLoaded event so that your weather widget is automatically initialized when the page is loaded:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, fetchWeather, false);&amp;lt;/source&amp;gt;&lt;br /&gt;
# In addition, add a button that runs your ''fetchWeather'' function when clicked.  This should update your widget with the current weather conditions.&lt;br /&gt;
&lt;br /&gt;
Use the following HTML:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;weather&amp;quot; id=&amp;quot;weatherWidget&amp;quot;&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-loc&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-humidity&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-temp&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-tomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-dayaftertomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Include the CSS file from here: https://classes.engineering.wustl.edu/cse330/content/weather.css&lt;br /&gt;
&lt;br /&gt;
When everything is working, the weather widget should look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:WeatherWidget.png]]&lt;br /&gt;
&lt;br /&gt;
'''Important:''' The widget in this section needs to work in only Firefox and Chrome.  It does '''''not''''' need to work Internet Explorer.&lt;br /&gt;
&lt;br /&gt;
==== Tips ====&lt;br /&gt;
&lt;br /&gt;
===== JSON Structure =====&lt;br /&gt;
&lt;br /&gt;
The JSON from our server looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;updated&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;,&lt;br /&gt;
   &amp;quot;location&amp;quot;: {&lt;br /&gt;
      &amp;quot;city&amp;quot;: &amp;quot;St. Louis&amp;quot;,&lt;br /&gt;
      &amp;quot;state&amp;quot;: &amp;quot;MO&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;wind&amp;quot;: {&lt;br /&gt;
      &amp;quot;chill&amp;quot;: &amp;quot;62&amp;quot;,&lt;br /&gt;
      &amp;quot;direction&amp;quot;: &amp;quot;150&amp;quot;,&lt;br /&gt;
      &amp;quot;speed&amp;quot;: &amp;quot;3 mph&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;atmosphere&amp;quot;: {&lt;br /&gt;
      &amp;quot;humidity&amp;quot;: &amp;quot;50&amp;quot;,&lt;br /&gt;
      &amp;quot;visibility&amp;quot;: &amp;quot;10&amp;quot;,&lt;br /&gt;
      &amp;quot;pressure&amp;quot;: &amp;quot;30.12 in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;current&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;28&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Mostly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;temp&amp;quot;: &amp;quot;62°F&amp;quot;,&lt;br /&gt;
      &amp;quot;date&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;tomorrow&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;29&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Clouds Early/Clearing Late&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;45°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;61°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;dayafter&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;30&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Partly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;53°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;65°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;credit&amp;quot;: &amp;quot;http://us.rd.yahoo.com/dailynews/rss/weather/St._Louis__MO/*http://weather.yahoo.com/forecast/USMO0170_f.html&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Weather Condition Images =====&lt;br /&gt;
&lt;br /&gt;
Each day's forecast has a code.  There are images associated with these codes.&lt;br /&gt;
&lt;br /&gt;
One place to get the images is from here: &amp;lt;nowiki&amp;gt;http://us.yimg.com/i/us/nws/weather/gr/&amp;lt;/nowiki&amp;gt;'''##'''&amp;lt;nowiki&amp;gt;ds.png&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
=== Connect Four Debugging Exercise ===&lt;br /&gt;
&lt;br /&gt;
As a short debugging exercise for Module 4, you will be given a broken Connect Four implementation (located in the repository and named `connect4`). You may also download the files [https://research.engineering.wustl.edu/~todd/cse3300-wiki/connect4/ here]. &lt;br /&gt;
&lt;br /&gt;
The goals of this assignment are to complete the following:&lt;br /&gt;
&lt;br /&gt;
* Run the provided program and observe failures or incorrect behavior.&lt;br /&gt;
* Produce a list of all bugs you find. For each bug include:&lt;br /&gt;
** The file and function (or approximate line) where it appears&lt;br /&gt;
** A short description of why it is a bug and what symptom it causes (crash, wrong output, missing feature)&lt;br /&gt;
** A suggested fix (one or two sentences)&lt;br /&gt;
* Use GitHub Copilot in &amp;quot;Agent&amp;quot; mode to assist you in writing and testing fixes. When you use Copilot Agent, document the prompts you issued, any code the agent generated, and what changes you applied or rejected after code review.&lt;br /&gt;
* Produce a corrected version of the program that can demonstrate the following:&lt;br /&gt;
** A horizontal, vertical, or diagonal win is detected correctly&lt;br /&gt;
** A draw (full-board) is detected correctly&lt;br /&gt;
&lt;br /&gt;
A video of the working Connect Four app can be found &lt;br /&gt;
[https://wustl.box.com/s/4u4h0rx9z7umrunmgdv0fq73rhhz8p99 here]&lt;br /&gt;
&lt;br /&gt;
Documentation to include in your repository:&lt;br /&gt;
* The bug list (as described above) is inserted as a comment at the top of the connect4.js file&lt;br /&gt;
* The Copilot Agent prompts you used and a short evaluation of the suggestions (what was helpful, what you changed) in the AI Reflection &lt;br /&gt;
* The corrected file(s) committed to your GitHub repo&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To receive full credit, you must complete the following:&lt;br /&gt;
* Identifying and explaining each real bug&lt;br /&gt;
* Correct fixes that produce a working game (wins + draw)&lt;br /&gt;
* Proper documentation of Copilot Agent usage and critical review &lt;br /&gt;
&lt;br /&gt;
This exercise is intended to teach debugging, careful code review, and responsible use of AI coding assistants (reviewing and accepting/rejecting suggestions). Keep your commits small and descriptive so the instructor can follow your reasoning.&lt;br /&gt;
&lt;br /&gt;
== Group Project ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Calendar ===&lt;br /&gt;
&lt;br /&gt;
Build a simple calendar that allows users to add and remove events dynamically.&lt;br /&gt;
&lt;br /&gt;
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, so long as that JavaScript library does not defeat the purpose of the assignment. If you have questions about this, ask. In particular, libraries like FullCalendar are unacceptable.&lt;br /&gt;
&lt;br /&gt;
Your application should utilize AJAX to run server-side scripts that query your database to save and retrieve information, including user accounts and events.&lt;br /&gt;
&lt;br /&gt;
We have also written some functions to help you get started: [[JavaScript Calendar Library]]&lt;br /&gt;
&lt;br /&gt;
==== Examples ====&lt;br /&gt;
&lt;br /&gt;
* https://calendar.google.com/&lt;br /&gt;
* http://www.zoho.com/calendar/&lt;br /&gt;
&lt;br /&gt;
==== Server Side Language ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
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 you are free to explore other databases if you like.&lt;br /&gt;
==== Calendar Requirements ====&lt;br /&gt;
&lt;br /&gt;
* Support a month-by-month view of the calendar.&lt;br /&gt;
*: Show one month at a time, with buttons to move forward or backward.&lt;br /&gt;
*: There should be no limit to how far forward or backward the user can go.&lt;br /&gt;
* Users can register and log in to the website.&lt;br /&gt;
*: You may leverage your MySQL project code and database from module 3 to get started.&lt;br /&gt;
* Unregistered users should see no events on the calendar.&lt;br /&gt;
* Registered users can add events.&lt;br /&gt;
*: All events should have a date and time, but do not need to have a duration.&lt;br /&gt;
*: You do ''not'' need to support recurring events (where you add an event that repeats, for example, every Monday).&lt;br /&gt;
* Registered users see only events that they have added.&lt;br /&gt;
*: 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?&lt;br /&gt;
* Registered users can edit and delete their own events, but not the events of others.&lt;br /&gt;
*: Again, the server should edit and delete events based on the username present in the session, not based on a username or event ID provided by the user.  (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.)&lt;br /&gt;
* All user and event data should be kept in a database.&lt;br /&gt;
* At no time should the main page need to be reloaded.&lt;br /&gt;
*: User registration, user authentication, event addition, and event deletion should all be handled by JavaScript and AJAX requests to your server.&lt;br /&gt;
* Your page needs to work in the versions of Firefox and Chrome installed on the lab computers.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Run your database schema by a TA before implementing it.&lt;br /&gt;
&lt;br /&gt;
=== Web Security and Validation ===&lt;br /&gt;
&lt;br /&gt;
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]]&lt;br /&gt;
&lt;br /&gt;
In particular:&lt;br /&gt;
&lt;br /&gt;
* '''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.)&lt;br /&gt;
* '''Perform precautionary measures to prevent session hijacking attacks.'''&lt;br /&gt;
*: 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.&lt;br /&gt;
&lt;br /&gt;
You should continue the practices that you have learned in past weeks:&lt;br /&gt;
&lt;br /&gt;
* Pass tokens in forms to prevent CSRF attacks.&lt;br /&gt;
*: '''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.&lt;br /&gt;
* Use prepared queries to prevent SQL Injection attacks.&lt;br /&gt;
* If storing passwords in a database, always store them salted and encrypted.&lt;br /&gt;
* Your page should validate with no errors or warnings through the W3C validator.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== AI Security Audit with GitHub Copilot ===&lt;br /&gt;
&lt;br /&gt;
For this module, you will use GitHub Copilot to perform a security audit of your calendar application. This exercise is designed to help you learn how to leverage AI tools for code security analysis while maintaining critical thinking about the results.&lt;br /&gt;
&lt;br /&gt;
* Code Generation: Use GitHub Copilot to help implement core calendar features if needed&lt;br /&gt;
* Security Review: Use Copilot's code analysis capabilities to:&lt;br /&gt;
** Identify potential security vulnerabilities in your JavaScript and PHP code&lt;br /&gt;
** Check for common web security issues (XSS, CSRF, SQL injection, etc.)&lt;br /&gt;
**Review authentication and authorization logic&lt;br /&gt;
*Vulnerability Assessment: Ask Copilot to analyze specific security aspects:&lt;br /&gt;
**Input validation and output escaping&lt;br /&gt;
**Session management security&lt;br /&gt;
**AJAX request security&lt;br /&gt;
**Database query safety&lt;br /&gt;
&lt;br /&gt;
Required Tasks:&lt;br /&gt;
#Use GitHub Copilot to generate at least 3 security-focused code reviews for different parts of your application&lt;br /&gt;
#Implement fixes for at least 2 identified security issues (real or potential)&lt;br /&gt;
#Document the security audit process and findings&lt;br /&gt;
&lt;br /&gt;
* Documentation Requirements&lt;br /&gt;
**Examples of security-focused prompts you used with Copilot&lt;br /&gt;
**Security issues identified (both real and false positives)&lt;br /&gt;
**Changes made based on the security audit&lt;br /&gt;
**Your assessment of Copilot's effectiveness as a security review tool&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 125 points in total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
If you do not include a link to your individual and group portion running on your instance in the respective README.md, you will receive a 0 for the missing portion&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''JavaScript Calculator (10 Points):'''&lt;br /&gt;
#* Calculator successfully adds, subtracts, multiplies, and divides numbers (3 points)&lt;br /&gt;
#* The result is automatically computed when a value in either field is changed (3 points)&lt;br /&gt;
#* Calculator is written entirely in pure, conventional JavaScript with no external libraries (4 points)*&lt;br /&gt;
#*: ''Note: be sure to fully separate content, appearance, and behavior.  This means that you should '''not''' use the event attributes on the HTML elements like onclick, onchange, and onkeyup.''&lt;br /&gt;
# '''Weather Widget (15 Points):'''&lt;br /&gt;
#* Widget performs an AJAX request to fetch the current weather (4 points)&lt;br /&gt;
#* The HTML template is filled with the correct information, including an image (4 points)&lt;br /&gt;
#* Button to reload the weather widget (2 points)&lt;br /&gt;
#* Widget is written entirely in pure, conventional JavaScript with no external libraries (5 points)*&lt;br /&gt;
# '''Connect Four Debugging Exercise (15 Points):'''&lt;br /&gt;
#* Identify and explain bugs in the provided `connect4.js` (5 points)&lt;br /&gt;
#** For each bug, include location (function/lines), description of the symptom, and the root cause.&lt;br /&gt;
#* Correct the program so it reliably detects wins and draws and handles invalid input (5 points)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#**Include the prompts used, what Copilot suggested, and what you changed or rejected.&lt;br /&gt;
# '''AJAX Calendar (60 Points):'''&lt;br /&gt;
#* '''''Calendar View (10 Points):'''''&lt;br /&gt;
#** 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)&lt;br /&gt;
#** The user can view different months as far in the past or future as desired (5 points)&lt;br /&gt;
#* '''''User and Event Management (25 Points):'''''&lt;br /&gt;
#** Events can be added, modified, and deleted (5 points)&lt;br /&gt;
#** Events have a title, date, and time (2 points)&lt;br /&gt;
#** Users can log into the site, and they cannot view or manipulate events associated with other users (8 points)&lt;br /&gt;
#**: ''Don't fall into the Abuse of Functionality trap!  Check user credentials on the server side as well as on the client side.''&lt;br /&gt;
#** All actions are performed over AJAX, without ever needing to reload the page (7 points)&lt;br /&gt;
#** Refreshing the page does not log a user out (3 points)&lt;br /&gt;
#* '''''Best Practices (20 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** If storing passwords, they are stored salted and hashed (2 points)&lt;br /&gt;
#** All AJAX requests that either contain sensitive information or modify something on the server are performed via POST, not GET (3 points)&lt;br /&gt;
#** Safe from XSS attacks; that is, all content is escaped on output (3 points)&lt;br /&gt;
#** Safe from SQL Injection attacks (2 points)&lt;br /&gt;
#** CSRF tokens are passed when adding/editing/deleting events (3 points)&lt;br /&gt;
#** Session cookie is HTTP-Only (3 points)&lt;br /&gt;
#** Page passes the W3C validator (2 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Site is intuitive to use and navigate (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
# '''Creative Portion &amp;amp; AI Security Audit (25 Points)'''&lt;br /&gt;
#* '''''AI Security Audit Implementation (15 Points):'''''&lt;br /&gt;
#** Used GitHub Copilot for security analysis of at least 3 different code components (4 points)&lt;br /&gt;
#** Documented security audit process and findings in README.md (3 points)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#** Implemented fixes for identified security issues (3 points)&lt;br /&gt;
#** We recommend you to use &amp;quot;Agent&amp;quot; Mode of Copilot to implement this feature&lt;br /&gt;
#** Be sure to create a new branch before implement this in case any of your original code being changed. &lt;br /&gt;
#* '''Additional Calendars Features (worth 10 points):''' Develop some additional features for the calendar, a few examples are provided below.&lt;br /&gt;
#**  Users can tag an event with a particular category and enable/disable those tags in the calendar view. (5 points)&lt;br /&gt;
#**  Users can share their calendar with additional users. (5 points)&lt;br /&gt;
#**  Users can create group events that display on multiple users calendars (5 points)&lt;br /&gt;
#** Feature enhances user experience and calendar functionality&lt;br /&gt;
#* '''''Make sure to save a description of your creative portion, AI security audit process, and a link to your server in your README.md file.'''''&lt;br /&gt;
&lt;br /&gt;
''* 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 &amp;quot;free.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 4]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3517</id>
		<title>Module 4</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3517"/>
		<updated>2026-06-29T20:20:32Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Weather Widget */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
In Module 4, you will learn JavaScript, the dominant client-side web language.  JavaScript is the second programming language you will learn in CSE 3300.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 4.&lt;br /&gt;
&lt;br /&gt;
'''Warning:'''&lt;br /&gt;
&lt;br /&gt;
''This module takes students a while. It is strongly recommended that you start early and ask questions as they come up.''&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* JavaScript&lt;br /&gt;
* DOM Events&lt;br /&gt;
* AJAX&lt;br /&gt;
* JSON&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[JavaScript]]&lt;br /&gt;
* [[AJAX and JSON]]&lt;br /&gt;
* [[jQuery]]&lt;br /&gt;
* [[Web Application Security, Part 3]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
'''''IMPORTANT:'' The individual assignments must be completed ''without'' using a JavaScript library like jQuery.'''  However, you may use jQuery for the group portion.&lt;br /&gt;
&lt;br /&gt;
=== JavaScript Calculator ===&lt;br /&gt;
&lt;br /&gt;
In Module 2, you made a calculator using PHP.  Now you will be making one using JavaScript.&lt;br /&gt;
&lt;br /&gt;
* 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).&lt;br /&gt;
* JavaScript should monitor all three fields and display the current result whenever the user changes any value in any field, without refreshing the page.&lt;br /&gt;
*: '''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.&lt;br /&gt;
*: '''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!)&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' You can embed JavaScript code into an HTML document like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
// your code here&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
'''Important:''' It is best practice to fully separate the content, appearance, and behavior.  In previous modules, you may have lost points for using HTML tags that change the appearance of things &amp;lt;nowiki&amp;gt;(like &amp;lt;center&amp;gt; and &amp;lt;font&amp;gt;).&amp;lt;/nowiki&amp;gt;  In this module, make sure that you define your event listeners in JavaScript as in &amp;lt;code&amp;gt;document.getElementById(&amp;quot;xyz&amp;quot;).addEventListener(...)&amp;lt;/code&amp;gt; and '''''not''''' in the HTML like &amp;lt;code&amp;gt;&amp;lt;button onclick=&amp;quot;badExample();&amp;quot;&amp;gt;Bad Example&amp;lt;/button&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Weather Widget ===&lt;br /&gt;
&lt;br /&gt;
In this section, you will make a web page that displays the weather forecast using AJAX requests to a weather server.&lt;br /&gt;
&lt;br /&gt;
# Make an empty HTML document; name it '''weather.html'''&lt;br /&gt;
#: Refer to the [[HTML and CSS]] guide for the skeleton of an HTML document&lt;br /&gt;
# Define a function in JavaScript; call it '''fetchWeather()'''.  You may write your JavaScript in an embedded script in your HTML document.&lt;br /&gt;
# Inside your '''fetchWeather()''' function, make an AJAX request to the weather server.&lt;br /&gt;
#: We have a server that outputs the current weather in JSON format.  The format is documented in the [[#JSON Structure]] section below.&lt;br /&gt;
&lt;br /&gt;
#: '''URL:'''  https://classes.engineering.wustl.edu/cse330/content/weather_json.php&lt;br /&gt;
#* '''IMPORTANT:''' Under normal circumstances, AJAX requests ''cannot'' be performed cross-domain for security reasons.  We have set the &amp;lt;code&amp;gt;Access-Control-Allow-Origin&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
# In your callback, process the JSON and use JavaScript to manipulate the HTML DOM to display the following information on your page:&lt;br /&gt;
#* Location&lt;br /&gt;
#** City, in a &amp;lt;nowiki&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/nowiki&amp;gt; tag&lt;br /&gt;
#** State, not in any tag&lt;br /&gt;
#* Humidity&lt;br /&gt;
#* Current Temperature&lt;br /&gt;
#* Image for Tomorrow's Forecast (see [[#Weather Condition Images]] below for more information)&lt;br /&gt;
#* Image for the Day After Tomorrow's Forecast&lt;br /&gt;
# Finally, bind '''fetchWeather()''' to the DOMContentLoaded event so that your weather widget is automatically initialized when the page is loaded:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, fetchWeather, false);&amp;lt;/source&amp;gt;&lt;br /&gt;
# In addition, add a button that runs your ''fetchWeather'' function when clicked.  This should update your widget with the current weather conditions.&lt;br /&gt;
&lt;br /&gt;
Use the following HTML:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;weather&amp;quot; id=&amp;quot;weatherWidget&amp;quot;&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-loc&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-humidity&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-temp&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-tomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-dayaftertomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Include the CSS file from here: https://classes.engineering.wustl.edu/cse330/content/weather.css&lt;br /&gt;
&lt;br /&gt;
When everything is working, the weather widget should look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:WeatherWidget.png]]&lt;br /&gt;
&lt;br /&gt;
'''Important:''' The widget in this section needs to work in only Firefox and Chrome.  It does '''''not''''' need to work Internet Explorer.&lt;br /&gt;
&lt;br /&gt;
==== Tips ====&lt;br /&gt;
&lt;br /&gt;
===== JSON Structure =====&lt;br /&gt;
&lt;br /&gt;
The JSON from our server looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;updated&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;,&lt;br /&gt;
   &amp;quot;location&amp;quot;: {&lt;br /&gt;
      &amp;quot;city&amp;quot;: &amp;quot;St. Louis&amp;quot;,&lt;br /&gt;
      &amp;quot;state&amp;quot;: &amp;quot;MO&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;wind&amp;quot;: {&lt;br /&gt;
      &amp;quot;chill&amp;quot;: &amp;quot;62&amp;quot;,&lt;br /&gt;
      &amp;quot;direction&amp;quot;: &amp;quot;150&amp;quot;,&lt;br /&gt;
      &amp;quot;speed&amp;quot;: &amp;quot;3 mph&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;atmosphere&amp;quot;: {&lt;br /&gt;
      &amp;quot;humidity&amp;quot;: &amp;quot;50&amp;quot;,&lt;br /&gt;
      &amp;quot;visibility&amp;quot;: &amp;quot;10&amp;quot;,&lt;br /&gt;
      &amp;quot;pressure&amp;quot;: &amp;quot;30.12 in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;current&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;28&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Mostly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;temp&amp;quot;: &amp;quot;62°F&amp;quot;,&lt;br /&gt;
      &amp;quot;date&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;tomorrow&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;29&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Clouds Early/Clearing Late&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;45°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;61°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;dayafter&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;30&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Partly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;53°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;65°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;credit&amp;quot;: &amp;quot;http://us.rd.yahoo.com/dailynews/rss/weather/St._Louis__MO/*http://weather.yahoo.com/forecast/USMO0170_f.html&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Weather Condition Images =====&lt;br /&gt;
&lt;br /&gt;
Each day's forecast has a code.  There are images associated with these codes.&lt;br /&gt;
&lt;br /&gt;
One place to get the images is from here: &amp;lt;nowiki&amp;gt;http://us.yimg.com/i/us/nws/weather/gr/&amp;lt;/nowiki&amp;gt;'''##'''&amp;lt;nowiki&amp;gt;ds.png&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
=== Connect Four Debugging Exercise ===&lt;br /&gt;
&lt;br /&gt;
As a short debugging exercise for Module 4, you will be given a broken Connect Four implementation (located in the repository and named `connect4`). You may also download the files [https://research.engineering.wustl.edu/~todd/cse3300-wiki/connect4/ here]. &lt;br /&gt;
&lt;br /&gt;
The goals of this assignment are to complete the following:&lt;br /&gt;
&lt;br /&gt;
* Run the provided program and observe failures or incorrect behavior.&lt;br /&gt;
* Produce a list of all bugs you find. For each bug include:&lt;br /&gt;
** The file and function (or approximate line) where it appears&lt;br /&gt;
** A short description of why it is a bug and what symptom it causes (crash, wrong output, missing feature)&lt;br /&gt;
** A suggested fix (one or two sentences)&lt;br /&gt;
* Use GitHub Copilot in &amp;quot;Agent&amp;quot; mode to assist you in writing and testing fixes. When you use Copilot Agent, document the prompts you issued, any code the agent generated, and what changes you applied or rejected after code review.&lt;br /&gt;
* Produce a corrected version of the program that can demonstrate the following:&lt;br /&gt;
** A horizontal, vertical, or diagonal win is detected correctly&lt;br /&gt;
** A draw (full-board) is detected correctly&lt;br /&gt;
&lt;br /&gt;
A video of the working Connect Four app can be found [https://wustl.box.com/s/jjafn5x0ffcjki8irfni1ko1e4jickqh here]&lt;br /&gt;
&lt;br /&gt;
Documentation to include in your repository:&lt;br /&gt;
* The bug list (as described above) is inserted as a comment at the top of the connect4.js file&lt;br /&gt;
* The Copilot Agent prompts you used and a short evaluation of the suggestions (what was helpful, what you changed) in the AI Reflection &lt;br /&gt;
* The corrected file(s) committed to your GitHub repo&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To receive full credit, you must complete the following:&lt;br /&gt;
* Identifying and explaining each real bug&lt;br /&gt;
* Correct fixes that produce a working game (wins + draw)&lt;br /&gt;
* Proper documentation of Copilot Agent usage and critical review &lt;br /&gt;
&lt;br /&gt;
This exercise is intended to teach debugging, careful code review, and responsible use of AI coding assistants (reviewing and accepting/rejecting suggestions). Keep your commits small and descriptive so the instructor can follow your reasoning.&lt;br /&gt;
&lt;br /&gt;
== Group Project ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Calendar ===&lt;br /&gt;
&lt;br /&gt;
Build a simple calendar that allows users to add and remove events dynamically.&lt;br /&gt;
&lt;br /&gt;
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, so long as that JavaScript library does not defeat the purpose of the assignment. If you have questions about this, ask. In particular, libraries like FullCalendar are unacceptable.&lt;br /&gt;
&lt;br /&gt;
Your application should utilize AJAX to run server-side scripts that query your database to save and retrieve information, including user accounts and events.&lt;br /&gt;
&lt;br /&gt;
We have also written some functions to help you get started: [[JavaScript Calendar Library]]&lt;br /&gt;
&lt;br /&gt;
==== Examples ====&lt;br /&gt;
&lt;br /&gt;
* https://calendar.google.com/&lt;br /&gt;
* http://www.zoho.com/calendar/&lt;br /&gt;
&lt;br /&gt;
==== Server Side Language ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
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 you are free to explore other databases if you like.&lt;br /&gt;
==== Calendar Requirements ====&lt;br /&gt;
&lt;br /&gt;
* Support a month-by-month view of the calendar.&lt;br /&gt;
*: Show one month at a time, with buttons to move forward or backward.&lt;br /&gt;
*: There should be no limit to how far forward or backward the user can go.&lt;br /&gt;
* Users can register and log in to the website.&lt;br /&gt;
*: You may leverage your MySQL project code and database from module 3 to get started.&lt;br /&gt;
* Unregistered users should see no events on the calendar.&lt;br /&gt;
* Registered users can add events.&lt;br /&gt;
*: All events should have a date and time, but do not need to have a duration.&lt;br /&gt;
*: You do ''not'' need to support recurring events (where you add an event that repeats, for example, every Monday).&lt;br /&gt;
* Registered users see only events that they have added.&lt;br /&gt;
*: 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?&lt;br /&gt;
* Registered users can edit and delete their own events, but not the events of others.&lt;br /&gt;
*: Again, the server should edit and delete events based on the username present in the session, not based on a username or event ID provided by the user.  (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.)&lt;br /&gt;
* All user and event data should be kept in a database.&lt;br /&gt;
* At no time should the main page need to be reloaded.&lt;br /&gt;
*: User registration, user authentication, event addition, and event deletion should all be handled by JavaScript and AJAX requests to your server.&lt;br /&gt;
* Your page needs to work in the versions of Firefox and Chrome installed on the lab computers.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Run your database schema by a TA before implementing it.&lt;br /&gt;
&lt;br /&gt;
=== Web Security and Validation ===&lt;br /&gt;
&lt;br /&gt;
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]]&lt;br /&gt;
&lt;br /&gt;
In particular:&lt;br /&gt;
&lt;br /&gt;
* '''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.)&lt;br /&gt;
* '''Perform precautionary measures to prevent session hijacking attacks.'''&lt;br /&gt;
*: 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.&lt;br /&gt;
&lt;br /&gt;
You should continue the practices that you have learned in past weeks:&lt;br /&gt;
&lt;br /&gt;
* Pass tokens in forms to prevent CSRF attacks.&lt;br /&gt;
*: '''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.&lt;br /&gt;
* Use prepared queries to prevent SQL Injection attacks.&lt;br /&gt;
* If storing passwords in a database, always store them salted and encrypted.&lt;br /&gt;
* Your page should validate with no errors or warnings through the W3C validator.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== AI Security Audit with GitHub Copilot ===&lt;br /&gt;
&lt;br /&gt;
For this module, you will use GitHub Copilot to perform a security audit of your calendar application. This exercise is designed to help you learn how to leverage AI tools for code security analysis while maintaining critical thinking about the results.&lt;br /&gt;
&lt;br /&gt;
* Code Generation: Use GitHub Copilot to help implement core calendar features if needed&lt;br /&gt;
* Security Review: Use Copilot's code analysis capabilities to:&lt;br /&gt;
** Identify potential security vulnerabilities in your JavaScript and PHP code&lt;br /&gt;
** Check for common web security issues (XSS, CSRF, SQL injection, etc.)&lt;br /&gt;
**Review authentication and authorization logic&lt;br /&gt;
*Vulnerability Assessment: Ask Copilot to analyze specific security aspects:&lt;br /&gt;
**Input validation and output escaping&lt;br /&gt;
**Session management security&lt;br /&gt;
**AJAX request security&lt;br /&gt;
**Database query safety&lt;br /&gt;
&lt;br /&gt;
Required Tasks:&lt;br /&gt;
#Use GitHub Copilot to generate at least 3 security-focused code reviews for different parts of your application&lt;br /&gt;
#Implement fixes for at least 2 identified security issues (real or potential)&lt;br /&gt;
#Document the security audit process and findings&lt;br /&gt;
&lt;br /&gt;
* Documentation Requirements&lt;br /&gt;
**Examples of security-focused prompts you used with Copilot&lt;br /&gt;
**Security issues identified (both real and false positives)&lt;br /&gt;
**Changes made based on the security audit&lt;br /&gt;
**Your assessment of Copilot's effectiveness as a security review tool&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 125 points in total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
If you do not include a link to your individual and group portion running on your instance in the respective README.md, you will receive a 0 for the missing portion&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''JavaScript Calculator (10 Points):'''&lt;br /&gt;
#* Calculator successfully adds, subtracts, multiplies, and divides numbers (3 points)&lt;br /&gt;
#* The result is automatically computed when a value in either field is changed (3 points)&lt;br /&gt;
#* Calculator is written entirely in pure, conventional JavaScript with no external libraries (4 points)*&lt;br /&gt;
#*: ''Note: be sure to fully separate content, appearance, and behavior.  This means that you should '''not''' use the event attributes on the HTML elements like onclick, onchange, and onkeyup.''&lt;br /&gt;
# '''Weather Widget (15 Points):'''&lt;br /&gt;
#* Widget performs an AJAX request to fetch the current weather (4 points)&lt;br /&gt;
#* The HTML template is filled with the correct information, including an image (4 points)&lt;br /&gt;
#* Button to reload the weather widget (2 points)&lt;br /&gt;
#* Widget is written entirely in pure, conventional JavaScript with no external libraries (5 points)*&lt;br /&gt;
# '''Connect Four Debugging Exercise (15 Points):'''&lt;br /&gt;
#* Identify and explain bugs in the provided `connect4.js` (5 points)&lt;br /&gt;
#** For each bug, include location (function/lines), description of the symptom, and the root cause.&lt;br /&gt;
#* Correct the program so it reliably detects wins and draws and handles invalid input (5 points)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#**Include the prompts used, what Copilot suggested, and what you changed or rejected.&lt;br /&gt;
# '''AJAX Calendar (60 Points):'''&lt;br /&gt;
#* '''''Calendar View (10 Points):'''''&lt;br /&gt;
#** 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)&lt;br /&gt;
#** The user can view different months as far in the past or future as desired (5 points)&lt;br /&gt;
#* '''''User and Event Management (25 Points):'''''&lt;br /&gt;
#** Events can be added, modified, and deleted (5 points)&lt;br /&gt;
#** Events have a title, date, and time (2 points)&lt;br /&gt;
#** Users can log into the site, and they cannot view or manipulate events associated with other users (8 points)&lt;br /&gt;
#**: ''Don't fall into the Abuse of Functionality trap!  Check user credentials on the server side as well as on the client side.''&lt;br /&gt;
#** All actions are performed over AJAX, without ever needing to reload the page (7 points)&lt;br /&gt;
#** Refreshing the page does not log a user out (3 points)&lt;br /&gt;
#* '''''Best Practices (20 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** If storing passwords, they are stored salted and hashed (2 points)&lt;br /&gt;
#** All AJAX requests that either contain sensitive information or modify something on the server are performed via POST, not GET (3 points)&lt;br /&gt;
#** Safe from XSS attacks; that is, all content is escaped on output (3 points)&lt;br /&gt;
#** Safe from SQL Injection attacks (2 points)&lt;br /&gt;
#** CSRF tokens are passed when adding/editing/deleting events (3 points)&lt;br /&gt;
#** Session cookie is HTTP-Only (3 points)&lt;br /&gt;
#** Page passes the W3C validator (2 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Site is intuitive to use and navigate (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
# '''Creative Portion &amp;amp; AI Security Audit (25 Points)'''&lt;br /&gt;
#* '''''AI Security Audit Implementation (15 Points):'''''&lt;br /&gt;
#** Used GitHub Copilot for security analysis of at least 3 different code components (4 points)&lt;br /&gt;
#** Documented security audit process and findings in README.md (3 points)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#** Implemented fixes for identified security issues (3 points)&lt;br /&gt;
#** We recommend you to use &amp;quot;Agent&amp;quot; Mode of Copilot to implement this feature&lt;br /&gt;
#** Be sure to create a new branch before implement this in case any of your original code being changed. &lt;br /&gt;
#* '''Additional Calendars Features (worth 10 points):''' Develop some additional features for the calendar, a few examples are provided below.&lt;br /&gt;
#**  Users can tag an event with a particular category and enable/disable those tags in the calendar view. (5 points)&lt;br /&gt;
#**  Users can share their calendar with additional users. (5 points)&lt;br /&gt;
#**  Users can create group events that display on multiple users calendars (5 points)&lt;br /&gt;
#** Feature enhances user experience and calendar functionality&lt;br /&gt;
#* '''''Make sure to save a description of your creative portion, AI security audit process, and a link to your server in your README.md file.'''''&lt;br /&gt;
&lt;br /&gt;
''* 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 &amp;quot;free.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 4]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3516</id>
		<title>Module 4</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_4&amp;diff=3516"/>
		<updated>2026-06-29T15:09:22Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Weather Widget */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
In Module 4, you will learn JavaScript, the dominant client-side web language.  JavaScript is the second programming language you will learn in CSE 3300.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 4.&lt;br /&gt;
&lt;br /&gt;
'''Warning:'''&lt;br /&gt;
&lt;br /&gt;
''This module takes students a while. It is strongly recommended that you start early and ask questions as they come up.''&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* JavaScript&lt;br /&gt;
* DOM Events&lt;br /&gt;
* AJAX&lt;br /&gt;
* JSON&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[JavaScript]]&lt;br /&gt;
* [[AJAX and JSON]]&lt;br /&gt;
* [[jQuery]]&lt;br /&gt;
* [[Web Application Security, Part 3]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
'''''IMPORTANT:'' The individual assignments must be completed ''without'' using a JavaScript library like jQuery.'''  However, you may use jQuery for the group portion.&lt;br /&gt;
&lt;br /&gt;
=== JavaScript Calculator ===&lt;br /&gt;
&lt;br /&gt;
In Module 2, you made a calculator using PHP.  Now you will be making one using JavaScript.&lt;br /&gt;
&lt;br /&gt;
* 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).&lt;br /&gt;
* JavaScript should monitor all three fields and display the current result whenever the user changes any value in any field, without refreshing the page.&lt;br /&gt;
*: '''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.&lt;br /&gt;
*: '''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!)&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' You can embed JavaScript code into an HTML document like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
// your code here&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
'''Important:''' It is best practice to fully separate the content, appearance, and behavior.  In previous modules, you may have lost points for using HTML tags that change the appearance of things &amp;lt;nowiki&amp;gt;(like &amp;lt;center&amp;gt; and &amp;lt;font&amp;gt;).&amp;lt;/nowiki&amp;gt;  In this module, make sure that you define your event listeners in JavaScript as in &amp;lt;code&amp;gt;document.getElementById(&amp;quot;xyz&amp;quot;).addEventListener(...)&amp;lt;/code&amp;gt; and '''''not''''' in the HTML like &amp;lt;code&amp;gt;&amp;lt;button onclick=&amp;quot;badExample();&amp;quot;&amp;gt;Bad Example&amp;lt;/button&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Weather Widget ===&lt;br /&gt;
&lt;br /&gt;
In this section, you will make a web page that displays the weather forecast using AJAX requests to a weather server.&lt;br /&gt;
&lt;br /&gt;
# Make an empty HTML document; name it '''weather.html'''&lt;br /&gt;
#: Refer to the [[HTML and CSS]] guide for the skeleton of an HTML document&lt;br /&gt;
# Define a function in JavaScript; call it '''fetchWeather()'''.  You may write your JavaScript in an embedded script in your HTML document.&lt;br /&gt;
# Inside your '''fetchWeather()''' function, make an AJAX request to the weather server.&lt;br /&gt;
#: We have a server that outputs the current weather in JSON format.  The format is documented in the [[#JSON Structure]] section below.&lt;br /&gt;
#: '''URL:''' http://ec2-100-52-167-82.compute-1.amazonaws.com/weather_json.php&lt;br /&gt;
#* '''IMPORTANT:''' Under normal circumstances, AJAX requests ''cannot'' be performed cross-domain for security reasons.  We have set the &amp;lt;code&amp;gt;Access-Control-Allow-Origin&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
# In your callback, process the JSON and use JavaScript to manipulate the HTML DOM to display the following information on your page:&lt;br /&gt;
#* Location&lt;br /&gt;
#** City, in a &amp;lt;nowiki&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/nowiki&amp;gt; tag&lt;br /&gt;
#** State, not in any tag&lt;br /&gt;
#* Humidity&lt;br /&gt;
#* Current Temperature&lt;br /&gt;
#* Image for Tomorrow's Forecast (see [[#Weather Condition Images]] below for more information)&lt;br /&gt;
#* Image for the Day After Tomorrow's Forecast&lt;br /&gt;
# Finally, bind '''fetchWeather()''' to the DOMContentLoaded event so that your weather widget is automatically initialized when the page is loaded:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, fetchWeather, false);&amp;lt;/source&amp;gt;&lt;br /&gt;
# In addition, add a button that runs your ''fetchWeather'' function when clicked.  This should update your widget with the current weather conditions.&lt;br /&gt;
&lt;br /&gt;
Use the following HTML:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;weather&amp;quot; id=&amp;quot;weatherWidget&amp;quot;&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-loc&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-humidity&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div class=&amp;quot;weather-temp&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-tomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
	&amp;lt;img class=&amp;quot;weather-dayaftertomorrow&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Include the CSS file from here: https://classes.engineering.wustl.edu/cse330/content/weather.css&lt;br /&gt;
&lt;br /&gt;
When everything is working, the weather widget should look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:WeatherWidget.png]]&lt;br /&gt;
&lt;br /&gt;
'''Important:''' The widget in this section needs to work in only Firefox and Chrome.  It does '''''not''''' need to work Internet Explorer.&lt;br /&gt;
&lt;br /&gt;
==== Tips ====&lt;br /&gt;
&lt;br /&gt;
===== JSON Structure =====&lt;br /&gt;
&lt;br /&gt;
The JSON from our server looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;updated&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;,&lt;br /&gt;
   &amp;quot;location&amp;quot;: {&lt;br /&gt;
      &amp;quot;city&amp;quot;: &amp;quot;St. Louis&amp;quot;,&lt;br /&gt;
      &amp;quot;state&amp;quot;: &amp;quot;MO&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;wind&amp;quot;: {&lt;br /&gt;
      &amp;quot;chill&amp;quot;: &amp;quot;62&amp;quot;,&lt;br /&gt;
      &amp;quot;direction&amp;quot;: &amp;quot;150&amp;quot;,&lt;br /&gt;
      &amp;quot;speed&amp;quot;: &amp;quot;3 mph&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;atmosphere&amp;quot;: {&lt;br /&gt;
      &amp;quot;humidity&amp;quot;: &amp;quot;50&amp;quot;,&lt;br /&gt;
      &amp;quot;visibility&amp;quot;: &amp;quot;10&amp;quot;,&lt;br /&gt;
      &amp;quot;pressure&amp;quot;: &amp;quot;30.12 in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;current&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;28&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Mostly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;temp&amp;quot;: &amp;quot;62°F&amp;quot;,&lt;br /&gt;
      &amp;quot;date&amp;quot;: &amp;quot;Thu, 11 Oct 2025 5:54 pm CDT&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;tomorrow&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;29&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Clouds Early/Clearing Late&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;45°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;61°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;dayafter&amp;quot;: {&lt;br /&gt;
      &amp;quot;code&amp;quot;: &amp;quot;30&amp;quot;,&lt;br /&gt;
      &amp;quot;text&amp;quot;: &amp;quot;Partly Cloudy&amp;quot;,&lt;br /&gt;
      &amp;quot;low&amp;quot;: &amp;quot;53°F&amp;quot;,&lt;br /&gt;
      &amp;quot;high&amp;quot;: &amp;quot;65°F&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;credit&amp;quot;: &amp;quot;http://us.rd.yahoo.com/dailynews/rss/weather/St._Louis__MO/*http://weather.yahoo.com/forecast/USMO0170_f.html&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Weather Condition Images =====&lt;br /&gt;
&lt;br /&gt;
Each day's forecast has a code.  There are images associated with these codes.&lt;br /&gt;
&lt;br /&gt;
One place to get the images is from here: &amp;lt;nowiki&amp;gt;http://us.yimg.com/i/us/nws/weather/gr/&amp;lt;/nowiki&amp;gt;'''##'''&amp;lt;nowiki&amp;gt;ds.png&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
=== Connect Four Debugging Exercise ===&lt;br /&gt;
&lt;br /&gt;
As a short debugging exercise for Module 4, you will be given a broken Connect Four implementation (located in the repository and named `connect4`). You may also download the files [https://research.engineering.wustl.edu/~todd/cse3300-wiki/connect4/ here]. &lt;br /&gt;
&lt;br /&gt;
The goals of this assignment are to complete the following:&lt;br /&gt;
&lt;br /&gt;
* Run the provided program and observe failures or incorrect behavior.&lt;br /&gt;
* Produce a list of all bugs you find. For each bug include:&lt;br /&gt;
** The file and function (or approximate line) where it appears&lt;br /&gt;
** A short description of why it is a bug and what symptom it causes (crash, wrong output, missing feature)&lt;br /&gt;
** A suggested fix (one or two sentences)&lt;br /&gt;
* Use GitHub Copilot in &amp;quot;Agent&amp;quot; mode to assist you in writing and testing fixes. When you use Copilot Agent, document the prompts you issued, any code the agent generated, and what changes you applied or rejected after code review.&lt;br /&gt;
* Produce a corrected version of the program that can demonstrate the following:&lt;br /&gt;
** A horizontal, vertical, or diagonal win is detected correctly&lt;br /&gt;
** A draw (full-board) is detected correctly&lt;br /&gt;
&lt;br /&gt;
A video of the working Connect Four app can be found [https://wustl.box.com/s/jjafn5x0ffcjki8irfni1ko1e4jickqh here]&lt;br /&gt;
&lt;br /&gt;
Documentation to include in your repository:&lt;br /&gt;
* The bug list (as described above) is inserted as a comment at the top of the connect4.js file&lt;br /&gt;
* The Copilot Agent prompts you used and a short evaluation of the suggestions (what was helpful, what you changed) in the AI Reflection &lt;br /&gt;
* The corrected file(s) committed to your GitHub repo&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To receive full credit, you must complete the following:&lt;br /&gt;
* Identifying and explaining each real bug&lt;br /&gt;
* Correct fixes that produce a working game (wins + draw)&lt;br /&gt;
* Proper documentation of Copilot Agent usage and critical review &lt;br /&gt;
&lt;br /&gt;
This exercise is intended to teach debugging, careful code review, and responsible use of AI coding assistants (reviewing and accepting/rejecting suggestions). Keep your commits small and descriptive so the instructor can follow your reasoning.&lt;br /&gt;
&lt;br /&gt;
== Group Project ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Calendar ===&lt;br /&gt;
&lt;br /&gt;
Build a simple calendar that allows users to add and remove events dynamically.&lt;br /&gt;
&lt;br /&gt;
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, so long as that JavaScript library does not defeat the purpose of the assignment. If you have questions about this, ask. In particular, libraries like FullCalendar are unacceptable.&lt;br /&gt;
&lt;br /&gt;
Your application should utilize AJAX to run server-side scripts that query your database to save and retrieve information, including user accounts and events.&lt;br /&gt;
&lt;br /&gt;
We have also written some functions to help you get started: [[JavaScript Calendar Library]]&lt;br /&gt;
&lt;br /&gt;
==== Examples ====&lt;br /&gt;
&lt;br /&gt;
* https://calendar.google.com/&lt;br /&gt;
* http://www.zoho.com/calendar/&lt;br /&gt;
&lt;br /&gt;
==== Server Side Language ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
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 you are free to explore other databases if you like.&lt;br /&gt;
==== Calendar Requirements ====&lt;br /&gt;
&lt;br /&gt;
* Support a month-by-month view of the calendar.&lt;br /&gt;
*: Show one month at a time, with buttons to move forward or backward.&lt;br /&gt;
*: There should be no limit to how far forward or backward the user can go.&lt;br /&gt;
* Users can register and log in to the website.&lt;br /&gt;
*: You may leverage your MySQL project code and database from module 3 to get started.&lt;br /&gt;
* Unregistered users should see no events on the calendar.&lt;br /&gt;
* Registered users can add events.&lt;br /&gt;
*: All events should have a date and time, but do not need to have a duration.&lt;br /&gt;
*: You do ''not'' need to support recurring events (where you add an event that repeats, for example, every Monday).&lt;br /&gt;
* Registered users see only events that they have added.&lt;br /&gt;
*: 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?&lt;br /&gt;
* Registered users can edit and delete their own events, but not the events of others.&lt;br /&gt;
*: Again, the server should edit and delete events based on the username present in the session, not based on a username or event ID provided by the user.  (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.)&lt;br /&gt;
* All user and event data should be kept in a database.&lt;br /&gt;
* At no time should the main page need to be reloaded.&lt;br /&gt;
*: User registration, user authentication, event addition, and event deletion should all be handled by JavaScript and AJAX requests to your server.&lt;br /&gt;
* Your page needs to work in the versions of Firefox and Chrome installed on the lab computers.&lt;br /&gt;
&lt;br /&gt;
'''Tip:''' Run your database schema by a TA before implementing it.&lt;br /&gt;
&lt;br /&gt;
=== Web Security and Validation ===&lt;br /&gt;
&lt;br /&gt;
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]]&lt;br /&gt;
&lt;br /&gt;
In particular:&lt;br /&gt;
&lt;br /&gt;
* '''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.)&lt;br /&gt;
* '''Perform precautionary measures to prevent session hijacking attacks.'''&lt;br /&gt;
*: 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.&lt;br /&gt;
&lt;br /&gt;
You should continue the practices that you have learned in past weeks:&lt;br /&gt;
&lt;br /&gt;
* Pass tokens in forms to prevent CSRF attacks.&lt;br /&gt;
*: '''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.&lt;br /&gt;
* Use prepared queries to prevent SQL Injection attacks.&lt;br /&gt;
* If storing passwords in a database, always store them salted and encrypted.&lt;br /&gt;
* Your page should validate with no errors or warnings through the W3C validator.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== AI Security Audit with GitHub Copilot ===&lt;br /&gt;
&lt;br /&gt;
For this module, you will use GitHub Copilot to perform a security audit of your calendar application. This exercise is designed to help you learn how to leverage AI tools for code security analysis while maintaining critical thinking about the results.&lt;br /&gt;
&lt;br /&gt;
* Code Generation: Use GitHub Copilot to help implement core calendar features if needed&lt;br /&gt;
* Security Review: Use Copilot's code analysis capabilities to:&lt;br /&gt;
** Identify potential security vulnerabilities in your JavaScript and PHP code&lt;br /&gt;
** Check for common web security issues (XSS, CSRF, SQL injection, etc.)&lt;br /&gt;
**Review authentication and authorization logic&lt;br /&gt;
*Vulnerability Assessment: Ask Copilot to analyze specific security aspects:&lt;br /&gt;
**Input validation and output escaping&lt;br /&gt;
**Session management security&lt;br /&gt;
**AJAX request security&lt;br /&gt;
**Database query safety&lt;br /&gt;
&lt;br /&gt;
Required Tasks:&lt;br /&gt;
#Use GitHub Copilot to generate at least 3 security-focused code reviews for different parts of your application&lt;br /&gt;
#Implement fixes for at least 2 identified security issues (real or potential)&lt;br /&gt;
#Document the security audit process and findings&lt;br /&gt;
&lt;br /&gt;
* Documentation Requirements&lt;br /&gt;
**Examples of security-focused prompts you used with Copilot&lt;br /&gt;
**Security issues identified (both real and false positives)&lt;br /&gt;
**Changes made based on the security audit&lt;br /&gt;
**Your assessment of Copilot's effectiveness as a security review tool&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 125 points in total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
If you do not include a link to your individual and group portion running on your instance in the respective README.md, you will receive a 0 for the missing portion&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''JavaScript Calculator (10 Points):'''&lt;br /&gt;
#* Calculator successfully adds, subtracts, multiplies, and divides numbers (3 points)&lt;br /&gt;
#* The result is automatically computed when a value in either field is changed (3 points)&lt;br /&gt;
#* Calculator is written entirely in pure, conventional JavaScript with no external libraries (4 points)*&lt;br /&gt;
#*: ''Note: be sure to fully separate content, appearance, and behavior.  This means that you should '''not''' use the event attributes on the HTML elements like onclick, onchange, and onkeyup.''&lt;br /&gt;
# '''Weather Widget (15 Points):'''&lt;br /&gt;
#* Widget performs an AJAX request to fetch the current weather (4 points)&lt;br /&gt;
#* The HTML template is filled with the correct information, including an image (4 points)&lt;br /&gt;
#* Button to reload the weather widget (2 points)&lt;br /&gt;
#* Widget is written entirely in pure, conventional JavaScript with no external libraries (5 points)*&lt;br /&gt;
# '''Connect Four Debugging Exercise (15 Points):'''&lt;br /&gt;
#* Identify and explain bugs in the provided `connect4.js` (5 points)&lt;br /&gt;
#** For each bug, include location (function/lines), description of the symptom, and the root cause.&lt;br /&gt;
#* Correct the program so it reliably detects wins and draws and handles invalid input (5 points)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#**Include the prompts used, what Copilot suggested, and what you changed or rejected.&lt;br /&gt;
# '''AJAX Calendar (60 Points):'''&lt;br /&gt;
#* '''''Calendar View (10 Points):'''''&lt;br /&gt;
#** 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)&lt;br /&gt;
#** The user can view different months as far in the past or future as desired (5 points)&lt;br /&gt;
#* '''''User and Event Management (25 Points):'''''&lt;br /&gt;
#** Events can be added, modified, and deleted (5 points)&lt;br /&gt;
#** Events have a title, date, and time (2 points)&lt;br /&gt;
#** Users can log into the site, and they cannot view or manipulate events associated with other users (8 points)&lt;br /&gt;
#**: ''Don't fall into the Abuse of Functionality trap!  Check user credentials on the server side as well as on the client side.''&lt;br /&gt;
#** All actions are performed over AJAX, without ever needing to reload the page (7 points)&lt;br /&gt;
#** Refreshing the page does not log a user out (3 points)&lt;br /&gt;
#* '''''Best Practices (20 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** If storing passwords, they are stored salted and hashed (2 points)&lt;br /&gt;
#** All AJAX requests that either contain sensitive information or modify something on the server are performed via POST, not GET (3 points)&lt;br /&gt;
#** Safe from XSS attacks; that is, all content is escaped on output (3 points)&lt;br /&gt;
#** Safe from SQL Injection attacks (2 points)&lt;br /&gt;
#** CSRF tokens are passed when adding/editing/deleting events (3 points)&lt;br /&gt;
#** Session cookie is HTTP-Only (3 points)&lt;br /&gt;
#** Page passes the W3C validator (2 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Site is intuitive to use and navigate (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
# '''Creative Portion &amp;amp; AI Security Audit (25 Points)'''&lt;br /&gt;
#* '''''AI Security Audit Implementation (15 Points):'''''&lt;br /&gt;
#** Used GitHub Copilot for security analysis of at least 3 different code components (4 points)&lt;br /&gt;
#** Documented security audit process and findings in README.md (3 points)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the AI Best Practices (5 points)&lt;br /&gt;
#** Implemented fixes for identified security issues (3 points)&lt;br /&gt;
#** We recommend you to use &amp;quot;Agent&amp;quot; Mode of Copilot to implement this feature&lt;br /&gt;
#** Be sure to create a new branch before implement this in case any of your original code being changed. &lt;br /&gt;
#* '''Additional Calendars Features (worth 10 points):''' Develop some additional features for the calendar, a few examples are provided below.&lt;br /&gt;
#**  Users can tag an event with a particular category and enable/disable those tags in the calendar view. (5 points)&lt;br /&gt;
#**  Users can share their calendar with additional users. (5 points)&lt;br /&gt;
#**  Users can create group events that display on multiple users calendars (5 points)&lt;br /&gt;
#** Feature enhances user experience and calendar functionality&lt;br /&gt;
#* '''''Make sure to save a description of your creative portion, AI security audit process, and a link to your server in your README.md file.'''''&lt;br /&gt;
&lt;br /&gt;
''* 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 &amp;quot;free.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 4]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_1&amp;diff=3515</id>
		<title>Module 1</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_1&amp;diff=3515"/>
		<updated>2026-06-23T13:07:28Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Make a Portal to a DuckDuckGo Search */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__ &lt;br /&gt;
Module 1 introduces the fundamentals of HTML and CSS.  You will first make a static document using HTML and CSS, and then you will make a portal to a DuckDuckGo search.  This module is worth 80 points.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
&lt;br /&gt;
* HTML&lt;br /&gt;
* CSS&lt;br /&gt;
* Git&lt;br /&gt;
* VS Code&lt;br /&gt;
* HTML Forms&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignment.&lt;br /&gt;
&lt;br /&gt;
* [[Git]]&lt;br /&gt;
* [[HTML and CSS]]&lt;br /&gt;
* [[HTML Forms]]&lt;br /&gt;
* [[VS Code]]&lt;br /&gt;
* [[FAQ - Mod 1]]&lt;br /&gt;
&lt;br /&gt;
== Assignments ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Write a Birthday Card ===&lt;br /&gt;
&lt;br /&gt;
'''Write a birthday card to a friend or family member using HTML and CSS.'''  Use HTML to define the content of the card (like the headings and the paragraphs), and use CSS to define the appearance.  Some ideas:&lt;br /&gt;
&lt;br /&gt;
* Make the background-color of the page baby blue and use purple ink to write a card to a little boy&lt;br /&gt;
* Make the text big and include pictures of hearts&lt;br /&gt;
* Use a font with text shadows (more advanced)&lt;br /&gt;
&lt;br /&gt;
Use this opportunity to experiment with the plethora of CSS properties.  Remember that you're in a computer science class (not an art class), so we don't care if your card looks ugly.  The main thing is that you learn HTML and CSS and use them appropriately.&lt;br /&gt;
&lt;br /&gt;
==== AI-Powered Animation Requirement ====&lt;br /&gt;
&lt;br /&gt;
For this assignment, you are required to use an AI tool (e.g., ChatGPT, GitHub Copilot) to generate one creative CSS animation for your birthday card. This component is worth 10 points of the overall grade, split as follows:&lt;br /&gt;
&lt;br /&gt;
* '''Implementation (5 points):''' Integrate the AI-generated CSS animation into your birthday card so that it runs correctly.  &lt;br /&gt;
* '''Documentation (5 points):''' In your project's README file, document the AI tool you used, the prompt you gave, and how you applied the generated code.  &lt;br /&gt;
&lt;br /&gt;
Example prompts you could use include:&lt;br /&gt;
* Generate CSS code to make a heart icon pulse using keyframes.&lt;br /&gt;
* How can I create a bouncing animation for text with CSS?&lt;br /&gt;
* Provide code for a smooth color-changing background animation.&lt;br /&gt;
&lt;br /&gt;
You must implement the AI-generated code, ensure it works, and document its use in your project's README. &lt;br /&gt;
&lt;br /&gt;
==== How to Edit Your File ====&lt;br /&gt;
&lt;br /&gt;
Throughout this course we '''highly''' recommend using VS Code https://code.visualstudio.com/download, and have included tips for each module specific to VS Code.&lt;br /&gt;
Visit the wiki page [[VS Code]] and check out the Module 1 section&lt;br /&gt;
&lt;br /&gt;
'''Important Note:''' Writing your web page in a word processor like Microsoft Word or even WordPad or TextEdit will ''not'' work.  You need a plain text editor.&lt;br /&gt;
&lt;br /&gt;
==== How to View Your HTML Document ====&lt;br /&gt;
&lt;br /&gt;
Once you have ''birthday.html'', you can open it using your favorite web browser (like Firefox).  For the purposes of this exercise, it suffices to right-click the file and tell it to &amp;quot;open with&amp;quot; Firefox.  When the file opens, it should be under the file:/// protocol.&lt;br /&gt;
&lt;br /&gt;
In Module 2, we will install a web server that serves up pages on the http:// and https:// protocols.&lt;br /&gt;
&lt;br /&gt;
==== Validation ====&lt;br /&gt;
&lt;br /&gt;
'''Your birthday card and search portal pages must pass the W3C Validators for both HTML and CSS with no errors or warnings.'''  You can upload your HTML to [https://validator.w3.org/ the HTML validator] and your CSS to [https://jigsaw.w3.org/css-validator/ the CSS validator].&lt;br /&gt;
&lt;br /&gt;
For more information on validation, see the HTML and CSS guide: [[HTML and CSS#Validation]]&lt;br /&gt;
&lt;br /&gt;
=== Make a Portal to a DuckDuckGo Search ===&lt;br /&gt;
&lt;br /&gt;
'''Write an HTML document containing a form that searches DuckDuckGo's web site.'''  You should be able to type in a search term and have your form load the DuckDuckGo search page. In this HTML document you must also include this course's regrade policy to ensure that you have read it and understand it. [https://www.arl.wustl.edu/~todd/cse3300/regrade.html Regrade Policy]&lt;br /&gt;
&lt;br /&gt;
# Visit [https://www.duckduckgo.com/ www.duckduckgo.com].  Search for something easy to spot in the URL, like &amp;quot;QUERY&amp;quot;.&lt;br /&gt;
# Look at the URL of the search page.   What is the query variable?&lt;br /&gt;
# Make an HTML document containing a text field and a submit button.  Typing a search query into the text field and pressing the submit button should load the DuckDuckGo search page.&lt;br /&gt;
&lt;br /&gt;
- Additionally, we want you to copy/paste this course's regrade policy somewhere into this html document to ensure your familiarity.&lt;br /&gt;
&lt;br /&gt;
=== Git Tutorial ===&lt;br /&gt;
&lt;br /&gt;
Complete all of the levels of the Main and Remote sections of [https://learngitbranching.js.org/ this Git tutorial]. After you finish the tutorial, take two screenshots showing the completion of all of the levels of the Main and Remote sections. Place these two screenshots in your repo.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 80 points total.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''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. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# '''Birthday Card (35 Points):'''&lt;br /&gt;
#* Correct usage of HTML and CSS in your birthday card (10 points).&lt;br /&gt;
#*: ''Make sure that you use HTML for &amp;quot;content&amp;quot; and CSS for &amp;quot;appearance.&amp;quot;  Don't write inline styles: use a separate stylesheet and link your HTML file to it.''&lt;br /&gt;
#* Experimenting with at least 5 different CSS properties in your birthday card (10 points).&lt;br /&gt;
#* Implementation of an AI-generated CSS animation (5 points).&lt;br /&gt;
#* Birthday card and search portal pages pass the W3C validation for HTML 5 and CSS. (10 points).&lt;br /&gt;
# '''Search Portal (10 Points):'''&lt;br /&gt;
#* DuckDuckGo search portal is functional (9 points).&lt;br /&gt;
#* Your search portal page includes the text of this course's regrade policy (1 point).&lt;br /&gt;
# '''Account Creation and Version Control (10 Points):'''&lt;br /&gt;
#* GitHub account created (2 Points)&lt;br /&gt;
#* Module 1 repo created using the link provided on Piazza (2 Points)&lt;br /&gt;
#* Account created on Amazon Web Services and an image file containing a screenshot of your account page pushed to GitHub. (4 Points)&lt;br /&gt;
#* Files pushed to repo on GitHub using Git (2 Points)&lt;br /&gt;
# '''Git Tutorial (20 Points):'''&lt;br /&gt;
#* Two screenshots showing the completion of all of the levels of the Main and Remote sections placed in your individual assignment repository.&lt;br /&gt;
# '''AI Reflection (5 Points):'''&lt;br /&gt;
#* Document use of AI for this assignment in the AI Reflections as described in [[AI Best Practices]] (5 Points)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 1]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_1&amp;diff=3514</id>
		<title>Module 1</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_1&amp;diff=3514"/>
		<updated>2026-06-08T19:59:39Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Make a Portal to a DuckDuckGo Search */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__ &lt;br /&gt;
Module 1 introduces the fundamentals of HTML and CSS.  You will first make a static document using HTML and CSS, and then you will make a portal to a DuckDuckGo search.  This module is worth 80 points.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
&lt;br /&gt;
* HTML&lt;br /&gt;
* CSS&lt;br /&gt;
* Git&lt;br /&gt;
* VS Code&lt;br /&gt;
* HTML Forms&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignment.&lt;br /&gt;
&lt;br /&gt;
* [[Git]]&lt;br /&gt;
* [[HTML and CSS]]&lt;br /&gt;
* [[HTML Forms]]&lt;br /&gt;
* [[VS Code]]&lt;br /&gt;
* [[FAQ - Mod 1]]&lt;br /&gt;
&lt;br /&gt;
== Assignments ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Write a Birthday Card ===&lt;br /&gt;
&lt;br /&gt;
'''Write a birthday card to a friend or family member using HTML and CSS.'''  Use HTML to define the content of the card (like the headings and the paragraphs), and use CSS to define the appearance.  Some ideas:&lt;br /&gt;
&lt;br /&gt;
* Make the background-color of the page baby blue and use purple ink to write a card to a little boy&lt;br /&gt;
* Make the text big and include pictures of hearts&lt;br /&gt;
* Use a font with text shadows (more advanced)&lt;br /&gt;
&lt;br /&gt;
Use this opportunity to experiment with the plethora of CSS properties.  Remember that you're in a computer science class (not an art class), so we don't care if your card looks ugly.  The main thing is that you learn HTML and CSS and use them appropriately.&lt;br /&gt;
&lt;br /&gt;
==== AI-Powered Animation Requirement ====&lt;br /&gt;
&lt;br /&gt;
For this assignment, you are required to use an AI tool (e.g., ChatGPT, GitHub Copilot) to generate one creative CSS animation for your birthday card. This component is worth 10 points of the overall grade, split as follows:&lt;br /&gt;
&lt;br /&gt;
* '''Implementation (5 points):''' Integrate the AI-generated CSS animation into your birthday card so that it runs correctly.  &lt;br /&gt;
* '''Documentation (5 points):''' In your project's README file, document the AI tool you used, the prompt you gave, and how you applied the generated code.  &lt;br /&gt;
&lt;br /&gt;
Example prompts you could use include:&lt;br /&gt;
* Generate CSS code to make a heart icon pulse using keyframes.&lt;br /&gt;
* How can I create a bouncing animation for text with CSS?&lt;br /&gt;
* Provide code for a smooth color-changing background animation.&lt;br /&gt;
&lt;br /&gt;
You must implement the AI-generated code, ensure it works, and document its use in your project's README. &lt;br /&gt;
&lt;br /&gt;
==== How to Edit Your File ====&lt;br /&gt;
&lt;br /&gt;
Throughout this course we '''highly''' recommend using VS Code https://code.visualstudio.com/download, and have included tips for each module specific to VS Code.&lt;br /&gt;
Visit the wiki page [[VS Code]] and check out the Module 1 section&lt;br /&gt;
&lt;br /&gt;
'''Important Note:''' Writing your web page in a word processor like Microsoft Word or even WordPad or TextEdit will ''not'' work.  You need a plain text editor.&lt;br /&gt;
&lt;br /&gt;
==== How to View Your HTML Document ====&lt;br /&gt;
&lt;br /&gt;
Once you have ''birthday.html'', you can open it using your favorite web browser (like Firefox).  For the purposes of this exercise, it suffices to right-click the file and tell it to &amp;quot;open with&amp;quot; Firefox.  When the file opens, it should be under the file:/// protocol.&lt;br /&gt;
&lt;br /&gt;
In Module 2, we will install a web server that serves up pages on the http:// and https:// protocols.&lt;br /&gt;
&lt;br /&gt;
==== Validation ====&lt;br /&gt;
&lt;br /&gt;
'''Your birthday card and search portal pages must pass the W3C Validators for both HTML and CSS with no errors or warnings.'''  You can upload your HTML to [https://validator.w3.org/ the HTML validator] and your CSS to [https://jigsaw.w3.org/css-validator/ the CSS validator].&lt;br /&gt;
&lt;br /&gt;
For more information on validation, see the HTML and CSS guide: [[HTML and CSS#Validation]]&lt;br /&gt;
&lt;br /&gt;
=== Make a Portal to a DuckDuckGo Search ===&lt;br /&gt;
&lt;br /&gt;
'''Write an HTML document containing a form that searches DuckDuckGo's web site.'''  You should be able to type in a search term and have your form load the DuckDuckGo search page. In this HTML document you must also include this course's regrade policy to ensure that you have read it and understand it. [https://www.arl.wustl.edu/~todd/cse3300/info.html Regrade Policy]&lt;br /&gt;
&lt;br /&gt;
# Visit [https://www.duckduckgo.com/ www.duckduckgo.com].  Search for something easy to spot in the URL, like &amp;quot;QUERY&amp;quot;.&lt;br /&gt;
# Look at the URL of the search page.   What is the query variable?&lt;br /&gt;
# Make an HTML document containing a text field and a submit button.  Typing a search query into the text field and pressing the submit button should load the DuckDuckGo search page.&lt;br /&gt;
&lt;br /&gt;
- Additionally, we want you to copy/paste this course's regrade policy somewhere into this html document to ensure your familiarity.&lt;br /&gt;
&lt;br /&gt;
=== Git Tutorial ===&lt;br /&gt;
&lt;br /&gt;
Complete all of the levels of the Main and Remote sections of [https://learngitbranching.js.org/ this Git tutorial]. After you finish the tutorial, take two screenshots showing the completion of all of the levels of the Main and Remote sections. Place these two screenshots in your repo.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 80 points total.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''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. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# '''Birthday Card (35 Points):'''&lt;br /&gt;
#* Correct usage of HTML and CSS in your birthday card (10 points).&lt;br /&gt;
#*: ''Make sure that you use HTML for &amp;quot;content&amp;quot; and CSS for &amp;quot;appearance.&amp;quot;  Don't write inline styles: use a separate stylesheet and link your HTML file to it.''&lt;br /&gt;
#* Experimenting with at least 5 different CSS properties in your birthday card (10 points).&lt;br /&gt;
#* Implementation of an AI-generated CSS animation (5 points).&lt;br /&gt;
#* Birthday card and search portal pages pass the W3C validation for HTML 5 and CSS. (10 points).&lt;br /&gt;
# '''Search Portal (10 Points):'''&lt;br /&gt;
#* DuckDuckGo search portal is functional (9 points).&lt;br /&gt;
#* Your search portal page includes the text of this course's regrade policy (1 point).&lt;br /&gt;
# '''Account Creation and Version Control (10 Points):'''&lt;br /&gt;
#* GitHub account created (2 Points)&lt;br /&gt;
#* Module 1 repo created using the link provided on Piazza (2 Points)&lt;br /&gt;
#* Account created on Amazon Web Services and an image file containing a screenshot of your account page pushed to GitHub. (4 Points)&lt;br /&gt;
#* Files pushed to repo on GitHub using Git (2 Points)&lt;br /&gt;
# '''Git Tutorial (20 Points):'''&lt;br /&gt;
#* Two screenshots showing the completion of all of the levels of the Main and Remote sections placed in your individual assignment repository.&lt;br /&gt;
# '''AI Reflection (5 Points):'''&lt;br /&gt;
#* Document use of AI for this assignment in the AI Reflections as described in [[AI Best Practices]] (5 Points)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 1]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Tools_for_Performance_Evaluation_in_Linux&amp;diff=3513</id>
		<title>Tools for Performance Evaluation in Linux</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Tools_for_Performance_Evaluation_in_Linux&amp;diff=3513"/>
		<updated>2026-04-22T17:43:17Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Additional Performance Evaluation Resources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Learning how to evaluate the performance of your system is critical to scaling up your web services to a large number of users. This article will discuss a few tools that will help you with evaluating the performance of your Linux server and web applications.&lt;br /&gt;
&lt;br /&gt;
== Linux Command Line Tools ==&lt;br /&gt;
The Linux command line offers an excellent set of tools to help you monitor the performance of your server.  When evaluating the performance of your web application you need to understand the impact your application has on the server you've deployed it on.  It is important to understand where a potential bottleneck exists for your application.  &lt;br /&gt;
&lt;br /&gt;
There are many different factors that influence an applications performance. A good place to start is to investigate the CPU utilization and IO (disk and network) of your server.  Below are a few examples of tools to start to dissect performance issues in Linux.  &lt;br /&gt;
 &lt;br /&gt;
=== top ===&lt;br /&gt;
The Linux command ''top'' provides real-time view of the running system. The command is helpful for displaying high utilizing resources.&lt;br /&gt;
To run the command simply type the name:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ top&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output of the screen will look something like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
top - 18:38:09 up  2:13,  3 users,  load average: 0.57, 0.13, 0.04&lt;br /&gt;
Tasks:  83 total,   2 running,  81 sleeping,   0 stopped,   0 zombie&lt;br /&gt;
Cpu(s): 49.7%us, 39.3%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi, 11.0%si,  0.0%st&lt;br /&gt;
Mem:   1019452k total,   186040k used,   833412k free,    12000k buffers&lt;br /&gt;
Swap:        0k total,        0k used,        0k free,    99032k cached&lt;br /&gt;
&lt;br /&gt;
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND           &lt;br /&gt;
 3854 ec2-user  20   0 88776 8012 4956 R 21.0  0.8   0:03.69 ab                 &lt;br /&gt;
 3871 apache    20   0  334m  10m 6252 S 14.3  1.1   0:00.55 httpd              &lt;br /&gt;
 3868 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3869 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3870 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3873 apache    20   0  334m  10m 6252 S  5.7  1.1   0:00.17 httpd              &lt;br /&gt;
 3874 apache    20   0  334m  10m 6252 S  0.7  1.1   0:00.02 httpd              &lt;br /&gt;
 3875 apache    20   0  334m  10m 6252 S  0.7  1.1   0:00.02 httpd              &lt;br /&gt;
    1 root      20   0 19612 2416 2092 S  0.0  0.2   0:00.78 init               &lt;br /&gt;
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kthreadd           &lt;br /&gt;
    3 root      20   0     0    0    0 S  0.0  0.0   0:00.03 ksoftirqd/0        &lt;br /&gt;
    5 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 kworker/0:0H       &lt;br /&gt;
    6 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kworker/u30:0      &lt;br /&gt;
    7 root      20   0     0    0    0 S  0.0  0.0   0:00.07 rcu_sched          &lt;br /&gt;
    8 root      20   0     0    0    0 S  0.0  0.0   0:00.00 rcu_bh             &lt;br /&gt;
    9 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 migration/0        &lt;br /&gt;
   10 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 khelper            &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explanation of Columns:&lt;br /&gt;
* PID – process ID&lt;br /&gt;
* USER – username for the owner of each process&lt;br /&gt;
* PR – process priority (RT means a Real Time priority class – used for system processes)&lt;br /&gt;
* NI – priority set by nice utility&lt;br /&gt;
* VIRT – the amount of virtual memory used by a process: code, data and shared libraries plus pages that have been swapped out&lt;br /&gt;
* RES – the resident part of a process – how much of it resides in the physical memory (non-swapped memory)&lt;br /&gt;
* SHR – shows you the size of potentially shared memory segments for a process&lt;br /&gt;
* S – the current state of each process&lt;br /&gt;
* %CPU – percentage of the time shares CPU spends running a particular process&lt;br /&gt;
* %MEM – percentage of the physical memory of your system which is used by each process&lt;br /&gt;
* %TIME+ – total time CPUs spent running each process&lt;br /&gt;
* COMMAND – a command used to initiate each process.&lt;br /&gt;
&lt;br /&gt;
This ''top'' interactive mode allows you to control the output of top and even send signals to kill a process.&lt;br /&gt;
&lt;br /&gt;
From this example, we can see that the process 'ab' (which is the Apache benchmark tool) is utilizing 21% of the CPU and 0.8% of the memory. &lt;br /&gt;
=== vmstat ===&lt;br /&gt;
&lt;br /&gt;
''vmstat'' (virtual memory statistics) is a monitoring utility, which also provides information about block IO and CPU activity in addition to memory.&lt;br /&gt;
&lt;br /&gt;
vmstat provides a number of values and is typically called with one or two parameters. For example:&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ vmstat 1 5&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first argument specifies a sampling period of 1 second, the second argument instructs vmstat to stop after 5 measurements.&lt;br /&gt;
&lt;br /&gt;
The first line of the report below will contain the average values since the last reboot. The additional lines provide information based on the sampling period.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[ec2-user@ip-172-30-0-240 ~]$ vmstat 1 5&lt;br /&gt;
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----&lt;br /&gt;
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st&lt;br /&gt;
 1  0      0 1869728  11612 112776    0    0    16     2    8   19  0  0 99  0  0	&lt;br /&gt;
 0  0      0 1869756  11612 112776    0    0     0     0   13   22  1  0 99  0  0	&lt;br /&gt;
 0  0      0 1869752  11612 112776    0    0     0     0    9   11  0  0 100  0  0	&lt;br /&gt;
 0  0      0 1869748  11612 112776    0    0     0     0   12   18  0  0 100  0  0	&lt;br /&gt;
 1  0      0 1869748  11612 112776    0    0     0     0    9    8  0  0 100  0  0	&lt;br /&gt;
[ec2-user@ip-172-30-0-240 ~]$ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explanation of Individual Values:&lt;br /&gt;
(Source &amp;lt;code&amp;gt;man vmstat&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Procs&lt;br /&gt;
** r: The number of processes waiting for run time.&lt;br /&gt;
** b: The number of processes in uninterruptible sleep.&lt;br /&gt;
* Memory&lt;br /&gt;
** swpd: the amount of virtual memory used.&lt;br /&gt;
** free: the amount of idle memory.&lt;br /&gt;
** buff: the amount of memory used as buffers.&lt;br /&gt;
** cache: the amount of memory used as cache.&lt;br /&gt;
** inact: the amount of inactive memory. (-a option)&lt;br /&gt;
** active: the amount of active memory. (-a option)&lt;br /&gt;
* Swap&lt;br /&gt;
** si: Amount of memory swapped in from disk (/s).&lt;br /&gt;
** so: Amount of memory swapped to disk (/s).&lt;br /&gt;
* IO&lt;br /&gt;
** bi: Blocks received from a block device (blocks/s).&lt;br /&gt;
** bo: Blocks sent to a block device (blocks/s).&lt;br /&gt;
* System&lt;br /&gt;
** in: The number of interrupts per second, including the clock.&lt;br /&gt;
** cs: The number of context switches per second.&lt;br /&gt;
* CPU&lt;br /&gt;
** These are percentages of total CPU time.&lt;br /&gt;
** us: Time spent running non-kernel code. (user time, including nice time)&lt;br /&gt;
** sy: Time spent running kernel code. (system time)&lt;br /&gt;
** id: Time spent idle. Prior to Linux 2.5.41, this includes IO-wait time.&lt;br /&gt;
** wa: Time spent waiting for IO. Prior to Linux 2.5.41, included in idle.&lt;br /&gt;
** st: Time stolen from a virtual machine.&lt;br /&gt;
&lt;br /&gt;
A detailed discussion of using vmstat to diagnose Linux performance can be found here [https://www.thomas-krenn.com/en/wiki/Linux_Performance_Measurements_using_vmstat Linux]. &lt;br /&gt;
&lt;br /&gt;
=== Additional System Tools ===&lt;br /&gt;
A comprehensive list of tools can be found here [http://www.brendangregg.com/linuxperf.html].  Depending on what aspect of the Linux OS you want to explore, an appropriate tool exists.&lt;br /&gt;
&lt;br /&gt;
== Application Benchmarks ==&lt;br /&gt;
&lt;br /&gt;
Below is a list of common application benchmark tools.  &lt;br /&gt;
&lt;br /&gt;
=== Apache Benchmark Tool ===&lt;br /&gt;
&lt;br /&gt;
The Apache Benchmark ab [https://httpd.apache.org/docs/2.2/programs/ab.html] provides a convenient way to stress test your web server. The tool is included with the default installation of Apache.&lt;br /&gt;
&lt;br /&gt;
An example of using ab to send 1000 requests, 10 at a time to the URL http://example.com/ is shown below:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ab -c 10 –n 1000 http://example.com/&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output of the tool will look similar to this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Document Path:          /&lt;br /&gt;
Document Length:        25951 bytes&lt;br /&gt;
&lt;br /&gt;
Concurrency Level:      10&lt;br /&gt;
Time taken for tests:   11.072 seconds&lt;br /&gt;
Complete requests:      1000&lt;br /&gt;
Failed requests:        0&lt;br /&gt;
Write errors:           0&lt;br /&gt;
Total transferred:      26253056 bytes&lt;br /&gt;
HTML transferred:       25995542 bytes&lt;br /&gt;
Requests per second:    90.32 [#/sec] (mean)&lt;br /&gt;
Time per request:       110.722 [ms] (mean)&lt;br /&gt;
Time per request:       11.072 [ms] (mean, across all concurrent requests)&lt;br /&gt;
Transfer rate:          2315.51 [Kbytes/sec] received&lt;br /&gt;
&lt;br /&gt;
Connection Times (ms)&lt;br /&gt;
              min  mean[+/-sd] median   max&lt;br /&gt;
Connect:       32   33   0.4     33      39&lt;br /&gt;
Processing:    67   77  22.5     68     141&lt;br /&gt;
Waiting:       34   35   0.9     35      44&lt;br /&gt;
Total:        100  110  22.4    101     174&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From the tests, we see that Apache is able to serve 90 requests per second to the machine running the ab tool. We also have statistics on the min, mean, median, and max time to send a request.&lt;br /&gt;
&lt;br /&gt;
In order to effectively measure the performance of your server we should execute the ab tool from a separate machine. The ab tool can utilize a decent amount of CPU, which may interfere with the performance of your web server.  &lt;br /&gt;
&lt;br /&gt;
==== Using ab in AWS ====&lt;br /&gt;
When using ab in AWS with multiple instances, we should take advantage of Amazon's Virtual Private Cloud (VPC) [https://aws.amazon.com/vpc/getting-started/ VPC]. You actually have a VPC setup by default and to communicate with other instances, you simply use the private IP address assigned to it. Each instance by default will have a public IP address (which you ssh into) and the private IP address that is only accessible from inside the VPC.  When load testing your server on AWS you should always communicate with the private IP address of your server, to avoid contention from other networks.&lt;br /&gt;
&lt;br /&gt;
=== Additional Application Testing Tools ===&lt;br /&gt;
*Web Server Tests&lt;br /&gt;
**Siege [https://www.joedog.org/siege-home/]&lt;br /&gt;
**JMeter [http://jmeter.apache.org/]&lt;br /&gt;
**weighttp [http://redmine.lighttpd.net/projects/weighttp/wiki]&lt;br /&gt;
**httperf [http://sourceforge.net/projects/httperf/]&lt;br /&gt;
** Bees with Machine Guns [http://blog.bluemalkin.net/load-testing-on-ec2-using-bees-with-machine-guns/] &lt;br /&gt;
&lt;br /&gt;
*Database&lt;br /&gt;
**MySQL Benchmark Tools [https://dev.mysql.com/downloads/benchmarks.html]&lt;br /&gt;
**MongoDB Benchmark  [https://github.com/mongodb/mongo-perf] [https://docs.mongodb.org/manual/reference/program/mongoperf/]&lt;br /&gt;
&lt;br /&gt;
*JavaScript&lt;br /&gt;
**Visualizing Client-Side JavaScript Performance [https://developers.google.com/web/tools/chrome-devtools/profile/rendering-tools/js-execution] [https://developer.mozilla.org/en-US/docs/Tools/Performance/Call_Tree] [http://www.smashingmagazine.com/2012/06/javascript-profiling-chrome-developer-tools/]&lt;br /&gt;
** Node.js Profiling [https://nodejs.org/en/docs/guides/simple-profiling/]&lt;br /&gt;
&lt;br /&gt;
== Additional Performance Evaluation Resources ==&lt;br /&gt;
&lt;br /&gt;
[http://alvinalexander.com/linux/unix-linux-top-command-cpu-memory Linux Top Command]&lt;br /&gt;
&lt;br /&gt;
[https://www.thomas-krenn.com/en/wiki/Linux_Performance_Measurements_using_vmstat Linux Performance Measurements using vmstat]&lt;br /&gt;
&lt;br /&gt;
[https://www.datadoghq.com/blog/understanding-aws-stolen-cpu-and-how-it-affects-your-apps/ CPU Steal Time] &lt;br /&gt;
&lt;br /&gt;
[https://aws.amazon.com/blogs/aws/choosing-the-right-ec2-instance-type-for-your-application/ Choosing the Right EC2 Instance Type]&lt;br /&gt;
&lt;br /&gt;
[https://developers.google.com/speed/pagespeed/insights/ Google PageSpeed Insights]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Tools_for_Performance_Evaluation_in_Linux&amp;diff=3512</id>
		<title>Tools for Performance Evaluation in Linux</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Tools_for_Performance_Evaluation_in_Linux&amp;diff=3512"/>
		<updated>2026-04-22T17:29:55Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Additional Performance Evaluation Resources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Learning how to evaluate the performance of your system is critical to scaling up your web services to a large number of users. This article will discuss a few tools that will help you with evaluating the performance of your Linux server and web applications.&lt;br /&gt;
&lt;br /&gt;
== Linux Command Line Tools ==&lt;br /&gt;
The Linux command line offers an excellent set of tools to help you monitor the performance of your server.  When evaluating the performance of your web application you need to understand the impact your application has on the server you've deployed it on.  It is important to understand where a potential bottleneck exists for your application.  &lt;br /&gt;
&lt;br /&gt;
There are many different factors that influence an applications performance. A good place to start is to investigate the CPU utilization and IO (disk and network) of your server.  Below are a few examples of tools to start to dissect performance issues in Linux.  &lt;br /&gt;
 &lt;br /&gt;
=== top ===&lt;br /&gt;
The Linux command ''top'' provides real-time view of the running system. The command is helpful for displaying high utilizing resources.&lt;br /&gt;
To run the command simply type the name:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ top&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output of the screen will look something like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
top - 18:38:09 up  2:13,  3 users,  load average: 0.57, 0.13, 0.04&lt;br /&gt;
Tasks:  83 total,   2 running,  81 sleeping,   0 stopped,   0 zombie&lt;br /&gt;
Cpu(s): 49.7%us, 39.3%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi, 11.0%si,  0.0%st&lt;br /&gt;
Mem:   1019452k total,   186040k used,   833412k free,    12000k buffers&lt;br /&gt;
Swap:        0k total,        0k used,        0k free,    99032k cached&lt;br /&gt;
&lt;br /&gt;
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND           &lt;br /&gt;
 3854 ec2-user  20   0 88776 8012 4956 R 21.0  0.8   0:03.69 ab                 &lt;br /&gt;
 3871 apache    20   0  334m  10m 6252 S 14.3  1.1   0:00.55 httpd              &lt;br /&gt;
 3868 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3869 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3870 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3873 apache    20   0  334m  10m 6252 S  5.7  1.1   0:00.17 httpd              &lt;br /&gt;
 3874 apache    20   0  334m  10m 6252 S  0.7  1.1   0:00.02 httpd              &lt;br /&gt;
 3875 apache    20   0  334m  10m 6252 S  0.7  1.1   0:00.02 httpd              &lt;br /&gt;
    1 root      20   0 19612 2416 2092 S  0.0  0.2   0:00.78 init               &lt;br /&gt;
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kthreadd           &lt;br /&gt;
    3 root      20   0     0    0    0 S  0.0  0.0   0:00.03 ksoftirqd/0        &lt;br /&gt;
    5 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 kworker/0:0H       &lt;br /&gt;
    6 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kworker/u30:0      &lt;br /&gt;
    7 root      20   0     0    0    0 S  0.0  0.0   0:00.07 rcu_sched          &lt;br /&gt;
    8 root      20   0     0    0    0 S  0.0  0.0   0:00.00 rcu_bh             &lt;br /&gt;
    9 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 migration/0        &lt;br /&gt;
   10 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 khelper            &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explanation of Columns:&lt;br /&gt;
* PID – process ID&lt;br /&gt;
* USER – username for the owner of each process&lt;br /&gt;
* PR – process priority (RT means a Real Time priority class – used for system processes)&lt;br /&gt;
* NI – priority set by nice utility&lt;br /&gt;
* VIRT – the amount of virtual memory used by a process: code, data and shared libraries plus pages that have been swapped out&lt;br /&gt;
* RES – the resident part of a process – how much of it resides in the physical memory (non-swapped memory)&lt;br /&gt;
* SHR – shows you the size of potentially shared memory segments for a process&lt;br /&gt;
* S – the current state of each process&lt;br /&gt;
* %CPU – percentage of the time shares CPU spends running a particular process&lt;br /&gt;
* %MEM – percentage of the physical memory of your system which is used by each process&lt;br /&gt;
* %TIME+ – total time CPUs spent running each process&lt;br /&gt;
* COMMAND – a command used to initiate each process.&lt;br /&gt;
&lt;br /&gt;
This ''top'' interactive mode allows you to control the output of top and even send signals to kill a process.&lt;br /&gt;
&lt;br /&gt;
From this example, we can see that the process 'ab' (which is the Apache benchmark tool) is utilizing 21% of the CPU and 0.8% of the memory. &lt;br /&gt;
=== vmstat ===&lt;br /&gt;
&lt;br /&gt;
''vmstat'' (virtual memory statistics) is a monitoring utility, which also provides information about block IO and CPU activity in addition to memory.&lt;br /&gt;
&lt;br /&gt;
vmstat provides a number of values and is typically called with one or two parameters. For example:&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ vmstat 1 5&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first argument specifies a sampling period of 1 second, the second argument instructs vmstat to stop after 5 measurements.&lt;br /&gt;
&lt;br /&gt;
The first line of the report below will contain the average values since the last reboot. The additional lines provide information based on the sampling period.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[ec2-user@ip-172-30-0-240 ~]$ vmstat 1 5&lt;br /&gt;
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----&lt;br /&gt;
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st&lt;br /&gt;
 1  0      0 1869728  11612 112776    0    0    16     2    8   19  0  0 99  0  0	&lt;br /&gt;
 0  0      0 1869756  11612 112776    0    0     0     0   13   22  1  0 99  0  0	&lt;br /&gt;
 0  0      0 1869752  11612 112776    0    0     0     0    9   11  0  0 100  0  0	&lt;br /&gt;
 0  0      0 1869748  11612 112776    0    0     0     0   12   18  0  0 100  0  0	&lt;br /&gt;
 1  0      0 1869748  11612 112776    0    0     0     0    9    8  0  0 100  0  0	&lt;br /&gt;
[ec2-user@ip-172-30-0-240 ~]$ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explanation of Individual Values:&lt;br /&gt;
(Source &amp;lt;code&amp;gt;man vmstat&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Procs&lt;br /&gt;
** r: The number of processes waiting for run time.&lt;br /&gt;
** b: The number of processes in uninterruptible sleep.&lt;br /&gt;
* Memory&lt;br /&gt;
** swpd: the amount of virtual memory used.&lt;br /&gt;
** free: the amount of idle memory.&lt;br /&gt;
** buff: the amount of memory used as buffers.&lt;br /&gt;
** cache: the amount of memory used as cache.&lt;br /&gt;
** inact: the amount of inactive memory. (-a option)&lt;br /&gt;
** active: the amount of active memory. (-a option)&lt;br /&gt;
* Swap&lt;br /&gt;
** si: Amount of memory swapped in from disk (/s).&lt;br /&gt;
** so: Amount of memory swapped to disk (/s).&lt;br /&gt;
* IO&lt;br /&gt;
** bi: Blocks received from a block device (blocks/s).&lt;br /&gt;
** bo: Blocks sent to a block device (blocks/s).&lt;br /&gt;
* System&lt;br /&gt;
** in: The number of interrupts per second, including the clock.&lt;br /&gt;
** cs: The number of context switches per second.&lt;br /&gt;
* CPU&lt;br /&gt;
** These are percentages of total CPU time.&lt;br /&gt;
** us: Time spent running non-kernel code. (user time, including nice time)&lt;br /&gt;
** sy: Time spent running kernel code. (system time)&lt;br /&gt;
** id: Time spent idle. Prior to Linux 2.5.41, this includes IO-wait time.&lt;br /&gt;
** wa: Time spent waiting for IO. Prior to Linux 2.5.41, included in idle.&lt;br /&gt;
** st: Time stolen from a virtual machine.&lt;br /&gt;
&lt;br /&gt;
A detailed discussion of using vmstat to diagnose Linux performance can be found here [https://www.thomas-krenn.com/en/wiki/Linux_Performance_Measurements_using_vmstat Linux]. &lt;br /&gt;
&lt;br /&gt;
=== Additional System Tools ===&lt;br /&gt;
A comprehensive list of tools can be found here [http://www.brendangregg.com/linuxperf.html].  Depending on what aspect of the Linux OS you want to explore, an appropriate tool exists.&lt;br /&gt;
&lt;br /&gt;
== Application Benchmarks ==&lt;br /&gt;
&lt;br /&gt;
Below is a list of common application benchmark tools.  &lt;br /&gt;
&lt;br /&gt;
=== Apache Benchmark Tool ===&lt;br /&gt;
&lt;br /&gt;
The Apache Benchmark ab [https://httpd.apache.org/docs/2.2/programs/ab.html] provides a convenient way to stress test your web server. The tool is included with the default installation of Apache.&lt;br /&gt;
&lt;br /&gt;
An example of using ab to send 1000 requests, 10 at a time to the URL http://example.com/ is shown below:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ab -c 10 –n 1000 http://example.com/&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output of the tool will look similar to this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Document Path:          /&lt;br /&gt;
Document Length:        25951 bytes&lt;br /&gt;
&lt;br /&gt;
Concurrency Level:      10&lt;br /&gt;
Time taken for tests:   11.072 seconds&lt;br /&gt;
Complete requests:      1000&lt;br /&gt;
Failed requests:        0&lt;br /&gt;
Write errors:           0&lt;br /&gt;
Total transferred:      26253056 bytes&lt;br /&gt;
HTML transferred:       25995542 bytes&lt;br /&gt;
Requests per second:    90.32 [#/sec] (mean)&lt;br /&gt;
Time per request:       110.722 [ms] (mean)&lt;br /&gt;
Time per request:       11.072 [ms] (mean, across all concurrent requests)&lt;br /&gt;
Transfer rate:          2315.51 [Kbytes/sec] received&lt;br /&gt;
&lt;br /&gt;
Connection Times (ms)&lt;br /&gt;
              min  mean[+/-sd] median   max&lt;br /&gt;
Connect:       32   33   0.4     33      39&lt;br /&gt;
Processing:    67   77  22.5     68     141&lt;br /&gt;
Waiting:       34   35   0.9     35      44&lt;br /&gt;
Total:        100  110  22.4    101     174&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From the tests, we see that Apache is able to serve 90 requests per second to the machine running the ab tool. We also have statistics on the min, mean, median, and max time to send a request.&lt;br /&gt;
&lt;br /&gt;
In order to effectively measure the performance of your server we should execute the ab tool from a separate machine. The ab tool can utilize a decent amount of CPU, which may interfere with the performance of your web server.  &lt;br /&gt;
&lt;br /&gt;
==== Using ab in AWS ====&lt;br /&gt;
When using ab in AWS with multiple instances, we should take advantage of Amazon's Virtual Private Cloud (VPC) [https://aws.amazon.com/vpc/getting-started/ VPC]. You actually have a VPC setup by default and to communicate with other instances, you simply use the private IP address assigned to it. Each instance by default will have a public IP address (which you ssh into) and the private IP address that is only accessible from inside the VPC.  When load testing your server on AWS you should always communicate with the private IP address of your server, to avoid contention from other networks.&lt;br /&gt;
&lt;br /&gt;
=== Additional Application Testing Tools ===&lt;br /&gt;
*Web Server Tests&lt;br /&gt;
**Siege [https://www.joedog.org/siege-home/]&lt;br /&gt;
**JMeter [http://jmeter.apache.org/]&lt;br /&gt;
**weighttp [http://redmine.lighttpd.net/projects/weighttp/wiki]&lt;br /&gt;
**httperf [http://sourceforge.net/projects/httperf/]&lt;br /&gt;
** Bees with Machine Guns [http://blog.bluemalkin.net/load-testing-on-ec2-using-bees-with-machine-guns/] &lt;br /&gt;
&lt;br /&gt;
*Database&lt;br /&gt;
**MySQL Benchmark Tools [https://dev.mysql.com/downloads/benchmarks.html]&lt;br /&gt;
**MongoDB Benchmark  [https://github.com/mongodb/mongo-perf] [https://docs.mongodb.org/manual/reference/program/mongoperf/]&lt;br /&gt;
&lt;br /&gt;
*JavaScript&lt;br /&gt;
**Visualizing Client-Side JavaScript Performance [https://developers.google.com/web/tools/chrome-devtools/profile/rendering-tools/js-execution] [https://developer.mozilla.org/en-US/docs/Tools/Performance/Call_Tree] [http://www.smashingmagazine.com/2012/06/javascript-profiling-chrome-developer-tools/]&lt;br /&gt;
** Node.js Profiling [https://nodejs.org/en/docs/guides/simple-profiling/]&lt;br /&gt;
&lt;br /&gt;
== Additional Performance Evaluation Resources ==&lt;br /&gt;
&lt;br /&gt;
[http://alvinalexander.com/linux/unix-linux-top-command-cpu-memory Linux Top Command]&lt;br /&gt;
&lt;br /&gt;
[https://www.thomas-krenn.com/en/wiki/Linux_Performance_Measurements_using_vmstat Linux Performance Measurements using vmstat]&lt;br /&gt;
&lt;br /&gt;
[https://web.archive.org/web/20180325093837/http://blog.scoutapp.com/articles/2013/07/25/understanding-cpu-steal-time-when-should-you-be-worried CPU Steal Time]&lt;br /&gt;
&lt;br /&gt;
[https://aws.amazon.com/blogs/aws/choosing-the-right-ec2-instance-type-for-your-application/ Choosing the Right EC2 Instance Type]&lt;br /&gt;
&lt;br /&gt;
[https://developers.google.com/speed/pagespeed/insights/ Google PageSpeed Insights]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Tools_for_Performance_Evaluation_in_Linux&amp;diff=3511</id>
		<title>Tools for Performance Evaluation in Linux</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Tools_for_Performance_Evaluation_in_Linux&amp;diff=3511"/>
		<updated>2026-04-22T17:19:51Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Additional Performance Evaluation Resources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Learning how to evaluate the performance of your system is critical to scaling up your web services to a large number of users. This article will discuss a few tools that will help you with evaluating the performance of your Linux server and web applications.&lt;br /&gt;
&lt;br /&gt;
== Linux Command Line Tools ==&lt;br /&gt;
The Linux command line offers an excellent set of tools to help you monitor the performance of your server.  When evaluating the performance of your web application you need to understand the impact your application has on the server you've deployed it on.  It is important to understand where a potential bottleneck exists for your application.  &lt;br /&gt;
&lt;br /&gt;
There are many different factors that influence an applications performance. A good place to start is to investigate the CPU utilization and IO (disk and network) of your server.  Below are a few examples of tools to start to dissect performance issues in Linux.  &lt;br /&gt;
 &lt;br /&gt;
=== top ===&lt;br /&gt;
The Linux command ''top'' provides real-time view of the running system. The command is helpful for displaying high utilizing resources.&lt;br /&gt;
To run the command simply type the name:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ top&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output of the screen will look something like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
top - 18:38:09 up  2:13,  3 users,  load average: 0.57, 0.13, 0.04&lt;br /&gt;
Tasks:  83 total,   2 running,  81 sleeping,   0 stopped,   0 zombie&lt;br /&gt;
Cpu(s): 49.7%us, 39.3%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi, 11.0%si,  0.0%st&lt;br /&gt;
Mem:   1019452k total,   186040k used,   833412k free,    12000k buffers&lt;br /&gt;
Swap:        0k total,        0k used,        0k free,    99032k cached&lt;br /&gt;
&lt;br /&gt;
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND           &lt;br /&gt;
 3854 ec2-user  20   0 88776 8012 4956 R 21.0  0.8   0:03.69 ab                 &lt;br /&gt;
 3871 apache    20   0  334m  10m 6252 S 14.3  1.1   0:00.55 httpd              &lt;br /&gt;
 3868 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3869 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3870 apache    20   0  334m  10m 6252 S 14.0  1.1   0:00.55 httpd              &lt;br /&gt;
 3873 apache    20   0  334m  10m 6252 S  5.7  1.1   0:00.17 httpd              &lt;br /&gt;
 3874 apache    20   0  334m  10m 6252 S  0.7  1.1   0:00.02 httpd              &lt;br /&gt;
 3875 apache    20   0  334m  10m 6252 S  0.7  1.1   0:00.02 httpd              &lt;br /&gt;
    1 root      20   0 19612 2416 2092 S  0.0  0.2   0:00.78 init               &lt;br /&gt;
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kthreadd           &lt;br /&gt;
    3 root      20   0     0    0    0 S  0.0  0.0   0:00.03 ksoftirqd/0        &lt;br /&gt;
    5 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 kworker/0:0H       &lt;br /&gt;
    6 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kworker/u30:0      &lt;br /&gt;
    7 root      20   0     0    0    0 S  0.0  0.0   0:00.07 rcu_sched          &lt;br /&gt;
    8 root      20   0     0    0    0 S  0.0  0.0   0:00.00 rcu_bh             &lt;br /&gt;
    9 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 migration/0        &lt;br /&gt;
   10 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 khelper            &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explanation of Columns:&lt;br /&gt;
* PID – process ID&lt;br /&gt;
* USER – username for the owner of each process&lt;br /&gt;
* PR – process priority (RT means a Real Time priority class – used for system processes)&lt;br /&gt;
* NI – priority set by nice utility&lt;br /&gt;
* VIRT – the amount of virtual memory used by a process: code, data and shared libraries plus pages that have been swapped out&lt;br /&gt;
* RES – the resident part of a process – how much of it resides in the physical memory (non-swapped memory)&lt;br /&gt;
* SHR – shows you the size of potentially shared memory segments for a process&lt;br /&gt;
* S – the current state of each process&lt;br /&gt;
* %CPU – percentage of the time shares CPU spends running a particular process&lt;br /&gt;
* %MEM – percentage of the physical memory of your system which is used by each process&lt;br /&gt;
* %TIME+ – total time CPUs spent running each process&lt;br /&gt;
* COMMAND – a command used to initiate each process.&lt;br /&gt;
&lt;br /&gt;
This ''top'' interactive mode allows you to control the output of top and even send signals to kill a process.&lt;br /&gt;
&lt;br /&gt;
From this example, we can see that the process 'ab' (which is the Apache benchmark tool) is utilizing 21% of the CPU and 0.8% of the memory. &lt;br /&gt;
=== vmstat ===&lt;br /&gt;
&lt;br /&gt;
''vmstat'' (virtual memory statistics) is a monitoring utility, which also provides information about block IO and CPU activity in addition to memory.&lt;br /&gt;
&lt;br /&gt;
vmstat provides a number of values and is typically called with one or two parameters. For example:&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ vmstat 1 5&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first argument specifies a sampling period of 1 second, the second argument instructs vmstat to stop after 5 measurements.&lt;br /&gt;
&lt;br /&gt;
The first line of the report below will contain the average values since the last reboot. The additional lines provide information based on the sampling period.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[ec2-user@ip-172-30-0-240 ~]$ vmstat 1 5&lt;br /&gt;
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----&lt;br /&gt;
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st&lt;br /&gt;
 1  0      0 1869728  11612 112776    0    0    16     2    8   19  0  0 99  0  0	&lt;br /&gt;
 0  0      0 1869756  11612 112776    0    0     0     0   13   22  1  0 99  0  0	&lt;br /&gt;
 0  0      0 1869752  11612 112776    0    0     0     0    9   11  0  0 100  0  0	&lt;br /&gt;
 0  0      0 1869748  11612 112776    0    0     0     0   12   18  0  0 100  0  0	&lt;br /&gt;
 1  0      0 1869748  11612 112776    0    0     0     0    9    8  0  0 100  0  0	&lt;br /&gt;
[ec2-user@ip-172-30-0-240 ~]$ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explanation of Individual Values:&lt;br /&gt;
(Source &amp;lt;code&amp;gt;man vmstat&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Procs&lt;br /&gt;
** r: The number of processes waiting for run time.&lt;br /&gt;
** b: The number of processes in uninterruptible sleep.&lt;br /&gt;
* Memory&lt;br /&gt;
** swpd: the amount of virtual memory used.&lt;br /&gt;
** free: the amount of idle memory.&lt;br /&gt;
** buff: the amount of memory used as buffers.&lt;br /&gt;
** cache: the amount of memory used as cache.&lt;br /&gt;
** inact: the amount of inactive memory. (-a option)&lt;br /&gt;
** active: the amount of active memory. (-a option)&lt;br /&gt;
* Swap&lt;br /&gt;
** si: Amount of memory swapped in from disk (/s).&lt;br /&gt;
** so: Amount of memory swapped to disk (/s).&lt;br /&gt;
* IO&lt;br /&gt;
** bi: Blocks received from a block device (blocks/s).&lt;br /&gt;
** bo: Blocks sent to a block device (blocks/s).&lt;br /&gt;
* System&lt;br /&gt;
** in: The number of interrupts per second, including the clock.&lt;br /&gt;
** cs: The number of context switches per second.&lt;br /&gt;
* CPU&lt;br /&gt;
** These are percentages of total CPU time.&lt;br /&gt;
** us: Time spent running non-kernel code. (user time, including nice time)&lt;br /&gt;
** sy: Time spent running kernel code. (system time)&lt;br /&gt;
** id: Time spent idle. Prior to Linux 2.5.41, this includes IO-wait time.&lt;br /&gt;
** wa: Time spent waiting for IO. Prior to Linux 2.5.41, included in idle.&lt;br /&gt;
** st: Time stolen from a virtual machine.&lt;br /&gt;
&lt;br /&gt;
A detailed discussion of using vmstat to diagnose Linux performance can be found here [https://www.thomas-krenn.com/en/wiki/Linux_Performance_Measurements_using_vmstat Linux]. &lt;br /&gt;
&lt;br /&gt;
=== Additional System Tools ===&lt;br /&gt;
A comprehensive list of tools can be found here [http://www.brendangregg.com/linuxperf.html].  Depending on what aspect of the Linux OS you want to explore, an appropriate tool exists.&lt;br /&gt;
&lt;br /&gt;
== Application Benchmarks ==&lt;br /&gt;
&lt;br /&gt;
Below is a list of common application benchmark tools.  &lt;br /&gt;
&lt;br /&gt;
=== Apache Benchmark Tool ===&lt;br /&gt;
&lt;br /&gt;
The Apache Benchmark ab [https://httpd.apache.org/docs/2.2/programs/ab.html] provides a convenient way to stress test your web server. The tool is included with the default installation of Apache.&lt;br /&gt;
&lt;br /&gt;
An example of using ab to send 1000 requests, 10 at a time to the URL http://example.com/ is shown below:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ ab -c 10 –n 1000 http://example.com/&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output of the tool will look similar to this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Document Path:          /&lt;br /&gt;
Document Length:        25951 bytes&lt;br /&gt;
&lt;br /&gt;
Concurrency Level:      10&lt;br /&gt;
Time taken for tests:   11.072 seconds&lt;br /&gt;
Complete requests:      1000&lt;br /&gt;
Failed requests:        0&lt;br /&gt;
Write errors:           0&lt;br /&gt;
Total transferred:      26253056 bytes&lt;br /&gt;
HTML transferred:       25995542 bytes&lt;br /&gt;
Requests per second:    90.32 [#/sec] (mean)&lt;br /&gt;
Time per request:       110.722 [ms] (mean)&lt;br /&gt;
Time per request:       11.072 [ms] (mean, across all concurrent requests)&lt;br /&gt;
Transfer rate:          2315.51 [Kbytes/sec] received&lt;br /&gt;
&lt;br /&gt;
Connection Times (ms)&lt;br /&gt;
              min  mean[+/-sd] median   max&lt;br /&gt;
Connect:       32   33   0.4     33      39&lt;br /&gt;
Processing:    67   77  22.5     68     141&lt;br /&gt;
Waiting:       34   35   0.9     35      44&lt;br /&gt;
Total:        100  110  22.4    101     174&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From the tests, we see that Apache is able to serve 90 requests per second to the machine running the ab tool. We also have statistics on the min, mean, median, and max time to send a request.&lt;br /&gt;
&lt;br /&gt;
In order to effectively measure the performance of your server we should execute the ab tool from a separate machine. The ab tool can utilize a decent amount of CPU, which may interfere with the performance of your web server.  &lt;br /&gt;
&lt;br /&gt;
==== Using ab in AWS ====&lt;br /&gt;
When using ab in AWS with multiple instances, we should take advantage of Amazon's Virtual Private Cloud (VPC) [https://aws.amazon.com/vpc/getting-started/ VPC]. You actually have a VPC setup by default and to communicate with other instances, you simply use the private IP address assigned to it. Each instance by default will have a public IP address (which you ssh into) and the private IP address that is only accessible from inside the VPC.  When load testing your server on AWS you should always communicate with the private IP address of your server, to avoid contention from other networks.&lt;br /&gt;
&lt;br /&gt;
=== Additional Application Testing Tools ===&lt;br /&gt;
*Web Server Tests&lt;br /&gt;
**Siege [https://www.joedog.org/siege-home/]&lt;br /&gt;
**JMeter [http://jmeter.apache.org/]&lt;br /&gt;
**weighttp [http://redmine.lighttpd.net/projects/weighttp/wiki]&lt;br /&gt;
**httperf [http://sourceforge.net/projects/httperf/]&lt;br /&gt;
** Bees with Machine Guns [http://blog.bluemalkin.net/load-testing-on-ec2-using-bees-with-machine-guns/] &lt;br /&gt;
&lt;br /&gt;
*Database&lt;br /&gt;
**MySQL Benchmark Tools [https://dev.mysql.com/downloads/benchmarks.html]&lt;br /&gt;
**MongoDB Benchmark  [https://github.com/mongodb/mongo-perf] [https://docs.mongodb.org/manual/reference/program/mongoperf/]&lt;br /&gt;
&lt;br /&gt;
*JavaScript&lt;br /&gt;
**Visualizing Client-Side JavaScript Performance [https://developers.google.com/web/tools/chrome-devtools/profile/rendering-tools/js-execution] [https://developer.mozilla.org/en-US/docs/Tools/Performance/Call_Tree] [http://www.smashingmagazine.com/2012/06/javascript-profiling-chrome-developer-tools/]&lt;br /&gt;
** Node.js Profiling [https://nodejs.org/en/docs/guides/simple-profiling/]&lt;br /&gt;
&lt;br /&gt;
== Additional Performance Evaluation Resources ==&lt;br /&gt;
&lt;br /&gt;
[http://alvinalexander.com/linux/unix-linux-top-command-cpu-memory Linux Top Command]&lt;br /&gt;
&lt;br /&gt;
[https://www.thomas-krenn.com/en/wiki/Linux_Performance_Measurements_using_vmstat Linux Performance Measurements using vmstat]&lt;br /&gt;
&lt;br /&gt;
[https://web.archive.org/web/20180325093837/http://blog.scoutapp.com/articles/2013/07/25/understanding-cpu-steal-time-when-should-you-be-worried CPU Steal Time]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[https://aws.amazon.com/blogs/aws/choosing-the-right-ec2-instance-type-for-your-application/ Choosing the Right EC2 Instance Type]&lt;br /&gt;
&lt;br /&gt;
[https://developers.google.com/speed/pagespeed/insights/ Google PageSpeed Insights]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_7&amp;diff=3510</id>
		<title>Module 7</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_7&amp;diff=3510"/>
		<updated>2026-04-06T15:22:16Z</updated>

		<summary type="html">&lt;p&gt;Todd: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The goal of the creative project is for you to learn a new framework or language that you may be interested in. This is a good opportunity to get a head start on learning the frameworks/languages your employer/potential employer uses.&lt;br /&gt;
&lt;br /&gt;
You’re free to choose which frameworks/languages you’ll use, and you’ll earn more points for learning more or harder frameworks, but '''you’re required to learn at least one new framework/technology.'''&lt;br /&gt;
&lt;br /&gt;
'''As an example''':&lt;br /&gt;
&lt;br /&gt;
You cannot use the combination of Plain HTML/JavaScript (frontend), PHP (backend), and MySQL (Database) because we've covered those extensively in class.&lt;br /&gt;
&lt;br /&gt;
You could, however, use React.JS (frontend), PHP, and MySQL, because your React front end would be new.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignment.&lt;br /&gt;
&lt;br /&gt;
* [[Choosing a Platform]]&lt;br /&gt;
* [[Web Frameworks]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Creating Your Rubric ==&lt;br /&gt;
&lt;br /&gt;
Before creating a rubric, you should come up with an idea for a site you want to build. From there, check [https://classes.engineering.wustl.edu/cse330/index.php?title=Web_Frameworks here] for guidance on which frameworks/languages to learn for your project and for links on how to get started with each.&lt;br /&gt;
&lt;br /&gt;
As a general structure, your project will consist of:&lt;br /&gt;
*Front-end&lt;br /&gt;
*Back-end&lt;br /&gt;
*Database&lt;br /&gt;
*Styling&lt;br /&gt;
*Best practices&lt;br /&gt;
(Though note that using all may not be necessary for your project! Reallocate as you need)&lt;br /&gt;
&lt;br /&gt;
==== General Rubric Guidelines ====&lt;br /&gt;
Your rubric will add up to '''100 points''' (including the 5 points earned for submitting your rubric on time). For each component of your stack, you can allocate the following numbers of points:&lt;br /&gt;
*5 points for creating a rubric and uploading it to Github on time (yes, we will be checking your commit timestamps!). Please make sure you check your rubric out with a TA, or else it would not be considered submitted.&lt;br /&gt;
*[10 - 40] points: Learning and implementing new framework(s).&lt;br /&gt;
*Functionality&lt;br /&gt;
**Think about what's happening in your front end, backend, and database that creates functionality. See example rubric.&lt;br /&gt;
*5 points: Best Practices&lt;br /&gt;
**Code well formatted&lt;br /&gt;
**HTML output passes the validator&lt;br /&gt;
*[0 - 20] points: Creative Portion&lt;br /&gt;
*[0 - 5] points: Styling. &amp;quot;Looks really cool&amp;quot; or &amp;quot;Used Bootstrap&amp;quot; would '''not''' earn any points. Learning and implementing SCSS/SASS, animations, or making your page responsive/mobile-friendly would.&lt;br /&gt;
&lt;br /&gt;
''Note that exceptions can be made to these guidelines, but make sure to get approval from a TA.''&lt;br /&gt;
&lt;br /&gt;
===== Points you can allocate for frameworks =====&lt;br /&gt;
Because some stacks have a higher learning curve, more points will be earned for using them. Use the following as a guide:&lt;br /&gt;
*React, Vue, Angular, and other frontend frameworks : 10&lt;br /&gt;
*Express (Node.js framework), Laravel, Bottle, Flask, other back-end micro frameworks: 10&lt;br /&gt;
*Full stack frameworks like Django (python): 20&lt;br /&gt;
*Learning a new database system:&lt;br /&gt;
**Mongo DB: 10&lt;br /&gt;
**Firebase: 5 (lower learning curve)&lt;br /&gt;
**Other SQL Databases (Postgres, MariaDB): 5&lt;br /&gt;
&lt;br /&gt;
== Example Rubric ==&lt;br /&gt;
Say your project idea is to re-create Bitly (the URL shortener), where shortened URLs expire after 1 week, but users can purchase the shortened URL (using stripe) for it to work forever. You decide you want to use Vue for your frontend, Laravel for your backend, and a MySQL database.&lt;br /&gt;
Your rubric could look like the following:&lt;br /&gt;
======Rubric turned in on time (5 points)====== &lt;br /&gt;
Remember to get it checked out by a TA!&lt;br /&gt;
&lt;br /&gt;
======Languages/Frameworks used (20 points)======&lt;br /&gt;
*10 - Learned/Used Vue.js frontend&lt;br /&gt;
*10 - Learned/Used PHP Laravel backend&lt;br /&gt;
*0 - MySQL/MariaDB Database&lt;br /&gt;
&lt;br /&gt;
======Functionality (60 points)======&lt;br /&gt;
*5 Users can input a url and receive a shortened url that redirects to it.&lt;br /&gt;
*10 Url redirect lasts 1 week before the shortened link doesn't work&lt;br /&gt;
*10 Users can register, login, and logout&lt;br /&gt;
*10 Logged in users can edit and delete their shortened URLs&lt;br /&gt;
*15 Logged in users can purchase a shortened URL via Stripe integration&lt;br /&gt;
*10 Database contains Users, Links, and Purchases with the necessary columns and column types to maintain the above functionality&lt;br /&gt;
======Best Practices (5 points)======&lt;br /&gt;
*3 Code is readable and well formatted&lt;br /&gt;
*2 All pages pass the html validator&lt;br /&gt;
======Creative Portion (10 points)======&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
'''Assignments 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.''' &lt;br /&gt;
&lt;br /&gt;
___________&lt;br /&gt;
&lt;br /&gt;
Your project will be graded '''in person''' during the last week of classes, using the rubric you create.&lt;br /&gt;
&lt;br /&gt;
___________&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 7]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3509</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3509"/>
		<updated>2026-03-30T16:34:58Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Reading */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 6]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import logging&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Configure logging to stderr&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    level=logging.INFO,&lt;br /&gt;
    format=&amp;quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;quot;,&lt;br /&gt;
    stream=sys.stderr,&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(&amp;quot;CardinalsMCP-server&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run the hello tool in the chat of Claude Desktop:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to say hello&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If everything is working correctly, you should see a &amp;quot;Hello CSE 3300 Student&amp;quot; response.&lt;br /&gt;
&lt;br /&gt;
After you have implemented the MCP tools, you can run commands like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3508</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3508"/>
		<updated>2026-03-21T15:49:03Z</updated>

		<summary type="html">&lt;p&gt;Todd: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import logging&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Configure logging to stderr&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    level=logging.INFO,&lt;br /&gt;
    format=&amp;quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;quot;,&lt;br /&gt;
    stream=sys.stderr,&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(&amp;quot;CardinalsMCP-server&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run the hello tool in the chat of Claude Desktop:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to say hello&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If everything is working correctly, you should see a &amp;quot;Hello CSE 3300 Student&amp;quot; response.&lt;br /&gt;
&lt;br /&gt;
After you have implemented the MCP tools, you can run commands like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3507</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3507"/>
		<updated>2026-03-19T14:44:14Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Restart Claude Desktop */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import logging&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Configure logging to stderr&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    level=logging.INFO,&lt;br /&gt;
    format=&amp;quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;quot;,&lt;br /&gt;
    stream=sys.stderr,&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(&amp;quot;CardinalsMCP-server&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run the hello tool in the chat of Claude Desktop:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to say hello&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If everything is working correctly, you should see a &amp;quot;Hello CSE 3300 Student&amp;quot; response.&lt;br /&gt;
&lt;br /&gt;
After you have implemented the MCP tools, you can run commands like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3506</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3506"/>
		<updated>2026-03-19T14:41:34Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Restart Claude Desktop */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import logging&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Configure logging to stderr&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    level=logging.INFO,&lt;br /&gt;
    format=&amp;quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;quot;,&lt;br /&gt;
    stream=sys.stderr,&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(&amp;quot;CardinalsMCP-server&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run the hello tool:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to say hello&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After you have implemented the MCP tools, you can run commands like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3505</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3505"/>
		<updated>2026-03-19T14:31:37Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Starter Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import logging&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Configure logging to stderr&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    level=logging.INFO,&lt;br /&gt;
    format=&amp;quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;quot;,&lt;br /&gt;
    stream=sys.stderr,&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(&amp;quot;CardinalsMCP-server&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3504</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3504"/>
		<updated>2026-03-19T14:19:22Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Connect Claude to Docker */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3503</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3503"/>
		<updated>2026-03-19T14:18:59Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Connect Claude to Docker */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''MCP Toolkit'''' → '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3502</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3502"/>
		<updated>2026-03-18T15:59:10Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Starter Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can find an explanation of the hello function in the [[MCP_Fundamentals#Example MCP Tool: hello|MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=MCP_Fundamentals&amp;diff=3501</id>
		<title>MCP Fundamentals</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=MCP_Fundamentals&amp;diff=3501"/>
		<updated>2026-03-18T15:55:29Z</updated>

		<summary type="html">&lt;p&gt;Todd: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
= Model Context Protocol (MCP) =&lt;br /&gt;
&lt;br /&gt;
Large Language Models (LLMs) like Claude, ChatGPT, and other AI assistants are powerful, but they normally operate in isolation.  &lt;br /&gt;
They cannot directly access your computer, read files, run programs, or query databases.&lt;br /&gt;
&lt;br /&gt;
The '''Model Context Protocol (MCP)''' is a standard that allows language models to safely interact with external tools and data sources.&lt;br /&gt;
&lt;br /&gt;
Instead of executing code directly, the language model communicates with an '''MCP server''', which exposes specific capabilities that the model can use.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol Documentation]&lt;br /&gt;
&lt;br /&gt;
== Mental Model: How MCP Actually Works ==&lt;br /&gt;
&lt;br /&gt;
A helpful way to think about MCP is that the language model is a '''decision maker''' and the MCP server is a '''toolbox'''.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
User asks question&lt;br /&gt;
      ↓&lt;br /&gt;
Claude decides which tool to use&lt;br /&gt;
      ↓&lt;br /&gt;
Claude calls the MCP server&lt;br /&gt;
      ↓&lt;br /&gt;
Your Python function runs&lt;br /&gt;
      ↓&lt;br /&gt;
The result is returned to Claude&lt;br /&gt;
      ↓&lt;br /&gt;
Claude explains the result to the user&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Important:''' The language model does not run your code directly.  &lt;br /&gt;
It decides '''which tool''' should be executed.  &lt;br /&gt;
The MCP server runs the code and returns the result.&lt;br /&gt;
&lt;br /&gt;
== MCP Architecture ==&lt;br /&gt;
&lt;br /&gt;
The interaction between the user, the language model, and your MCP server looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
User Prompt&lt;br /&gt;
     ↓&lt;br /&gt;
Language Model (Claude)&lt;br /&gt;
     ↓&lt;br /&gt;
MCP Client&lt;br /&gt;
     ↓&lt;br /&gt;
MCP Server&lt;br /&gt;
     ↓&lt;br /&gt;
Tools / Data Sources&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The language model analyzes the prompt and determines whether a tool should be used.&lt;br /&gt;
&lt;br /&gt;
If a tool is needed:&lt;br /&gt;
&lt;br /&gt;
1. The model selects a tool&lt;br /&gt;
2. The MCP client sends the request&lt;br /&gt;
3. The MCP server runs the function&lt;br /&gt;
4. The result is returned to the model&lt;br /&gt;
5. The model formats the final response&lt;br /&gt;
&lt;br /&gt;
== MCP Building Blocks ==&lt;br /&gt;
&lt;br /&gt;
MCP servers provide three types of capabilities.&lt;br /&gt;
&lt;br /&gt;
=== Tools ===&lt;br /&gt;
&lt;br /&gt;
'''Tools''' are executable functions that perform actions or retrieve data.&lt;br /&gt;
&lt;br /&gt;
Examples include:&lt;br /&gt;
&lt;br /&gt;
* querying a database&lt;br /&gt;
* reading files&lt;br /&gt;
* performing calculations&lt;br /&gt;
* calling APIs&lt;br /&gt;
* searching datasets&lt;br /&gt;
&lt;br /&gt;
Example tool:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
get_weather(city=&amp;quot;St. Louis&amp;quot;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The MCP server runs the function and returns the result.&lt;br /&gt;
&lt;br /&gt;
=== Resources ===&lt;br /&gt;
&lt;br /&gt;
'''Resources''' are the data sources your MCP tools use.&lt;br /&gt;
&lt;br /&gt;
Examples include:&lt;br /&gt;
&lt;br /&gt;
* files&lt;br /&gt;
* documents&lt;br /&gt;
* databases&lt;br /&gt;
* logs&lt;br /&gt;
* datasets&lt;br /&gt;
&lt;br /&gt;
For example, a tool might read a dataset stored in a file.&lt;br /&gt;
&lt;br /&gt;
=== Prompts ===&lt;br /&gt;
&lt;br /&gt;
'''Prompts''' are the instructions given by the user.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Show the top hitters on the 1940 Cardinals.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The model interprets the prompt and determines if a tool should be used.&lt;br /&gt;
&lt;br /&gt;
The user never runs the tool directly.&lt;br /&gt;
&lt;br /&gt;
== Example Workflow ==&lt;br /&gt;
&lt;br /&gt;
Suppose the user asks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Show the top hitters on the 1940 Cardinals.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The system behaves like this:&lt;br /&gt;
&lt;br /&gt;
1. Claude reads the prompt&lt;br /&gt;
2. Claude decides the &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt; tool is needed&lt;br /&gt;
3. Claude calls the MCP server&lt;br /&gt;
4. The server runs the Python function&lt;br /&gt;
5. The results are returned to Claude&lt;br /&gt;
&lt;br /&gt;
== Why MCP Matters ==&lt;br /&gt;
&lt;br /&gt;
Without MCP, language models can only generate text.&lt;br /&gt;
&lt;br /&gt;
With MCP, models can:&lt;br /&gt;
&lt;br /&gt;
* access real data&lt;br /&gt;
* run programs&lt;br /&gt;
* query databases&lt;br /&gt;
* analyze datasets&lt;br /&gt;
* automate workflows&lt;br /&gt;
&lt;br /&gt;
This transforms AI systems from chatbots into '''agents'''.&lt;br /&gt;
&lt;br /&gt;
== MCP in This Course ==&lt;br /&gt;
&lt;br /&gt;
In this module, you will build a simple MCP server using Python.&lt;br /&gt;
&lt;br /&gt;
Your server will expose tools that allow Claude Desktop to analyze historical St. Louis Cardinals batting statistics.&lt;br /&gt;
&lt;br /&gt;
Claude will automatically call your tools when needed.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Example MCP Tool: hello ==&lt;br /&gt;
&lt;br /&gt;
Below is a simple example of an MCP tool:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function defines a tool that Claude Desktop can call through the MCP server.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== What This Function Does ==&lt;br /&gt;
&lt;br /&gt;
This tool returns a simple greeting message. It is primarily used to verify that:&lt;br /&gt;
&lt;br /&gt;
* your MCP server is running&lt;br /&gt;
* Docker is working correctly&lt;br /&gt;
* Claude Desktop is connected to your server&lt;br /&gt;
&lt;br /&gt;
If this tool works, your MCP setup is likely correct.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Line-by-Line Explanation ==&lt;br /&gt;
&lt;br /&gt;
=== @mcp.tool() ===&lt;br /&gt;
&lt;br /&gt;
This is a '''decorator''' that registers the function as an MCP tool.&lt;br /&gt;
&lt;br /&gt;
Without this line, Claude will not be able to see or use the function.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== async def hello(...) ===&lt;br /&gt;
&lt;br /&gt;
This defines the function.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;hello&amp;lt;/code&amp;gt; is the '''tool name'''&lt;br /&gt;
* Claude will refer to this function by this name when calling it&lt;br /&gt;
&lt;br /&gt;
The keyword &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; indicates that the function runs within the MCP system. You do not need to fully understand asynchronous programming for this assignment.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== name: str = &amp;quot;world&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
This defines an input parameter:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; is the parameter name&lt;br /&gt;
* &amp;lt;code&amp;gt;str&amp;lt;/code&amp;gt; indicates it should be a string&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;quot;world&amp;quot;&amp;lt;/code&amp;gt; is the default value&lt;br /&gt;
&lt;br /&gt;
If no value is provided, the function will use &amp;quot;world&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== -&amp;gt; str ===&lt;br /&gt;
&lt;br /&gt;
This is a return type hint.&lt;br /&gt;
&lt;br /&gt;
It indicates that the function returns a string.&lt;br /&gt;
&lt;br /&gt;
Note: Python does not strictly enforce this, but it is important for clarity.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Docstring ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a '''docstring'''.&lt;br /&gt;
&lt;br /&gt;
It serves two purposes:&lt;br /&gt;
&lt;br /&gt;
* explains the function to humans&lt;br /&gt;
* helps Claude understand what the tool does and when to use it&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== return statement ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This returns the output of the tool.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
hello(&amp;quot;Todd&amp;quot;) → Hello CSE 3300 Student, Todd!&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== How Claude Uses This Tool ==&lt;br /&gt;
&lt;br /&gt;
When you type a prompt such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Use CardinalsMCP to say hello to Todd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will:&lt;br /&gt;
&lt;br /&gt;
# recognize that the &amp;lt;code&amp;gt;hello&amp;lt;/code&amp;gt; tool is relevant&lt;br /&gt;
# call the function with &amp;lt;code&amp;gt;name=&amp;quot;Todd&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
# receive the returned string&lt;br /&gt;
# display the result in the chat&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Key Takeaway ==&lt;br /&gt;
&lt;br /&gt;
An MCP tool is just a Python function that:&lt;br /&gt;
&lt;br /&gt;
* is decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;&lt;br /&gt;
* has clearly defined inputs (parameters)&lt;br /&gt;
* returns a result (usually a string)&lt;br /&gt;
&lt;br /&gt;
Claude reads the function name, parameters, and docstring to decide how to use it.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Try It ==&lt;br /&gt;
&lt;br /&gt;
After your MCP server is running, test this tool in Claude Desktop:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Use CardinalsMCP to say hello&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it works, your MCP server is correctly configured.&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3500</id>
		<title>Module 5</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3500"/>
		<updated>2026-03-16T16:57:19Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Option B — Local Execution + Demo Video */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
Module 5 builds upon the JavaScript skills you learned in Module 4 and introduces server-side JavaScript using Node.js, along with real-time communication using Socket.IO.&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the class wiki contain information that will help you complete the assignments:&lt;br /&gt;
&lt;br /&gt;
* [[Node.JS]]&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Install Node.js ===&lt;br /&gt;
&lt;br /&gt;
Install Node.js using the instructions in the [[Node.JS]] guide (use the current LTS version via nvm).&lt;br /&gt;
&lt;br /&gt;
=== Static File Server in Node.js ===&lt;br /&gt;
&lt;br /&gt;
The individual portion is intentionally short so you have sufficient time for the group project.&lt;br /&gt;
&lt;br /&gt;
Follow these steps carefully.&lt;br /&gt;
&lt;br /&gt;
# Copy the static file server example from the [[Node.JS]] page into a file named &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt;. Save it on your EC2 instance in a directory that is NOT served by Apache.&lt;br /&gt;
# Ensure that you understand what the static file server script is doing.&lt;br /&gt;
# Create a directory parallel to &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt; named &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Confirm that your server’s &amp;lt;code&amp;gt;STATIC_DIR&amp;lt;/code&amp;gt; variable matches this directory name.&lt;br /&gt;
# Create the following files inside the &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt; directory:&lt;br /&gt;
#* &amp;lt;code&amp;gt;hello.txt&amp;lt;/code&amp;gt; — a text file containing: &amp;lt;b&amp;gt;Hello World&amp;lt;/b&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt; — download from: http://classes.engineering.wustl.edu/cse330/content/brookings.jpg&lt;br /&gt;
#* &amp;lt;code&amp;gt;college.html&amp;lt;/code&amp;gt; — an HTML page based on the [[HTML and CSS#Quick and Easy Page Layout|Quick and Easy HTML Layout]] that includes an &amp;lt;code&amp;gt;&amp;amp;lt;img&amp;amp;gt;&amp;lt;/code&amp;gt; referencing &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; — a PHP file containing:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;php&amp;quot;&amp;gt;&amp;lt;?php phpinfo(); ?&amp;gt;&amp;lt;/source&amp;gt;&lt;br /&gt;
# Start your Node.js server and load all four files in your browser.&lt;br /&gt;
&lt;br /&gt;
Questions to consider:&lt;br /&gt;
&lt;br /&gt;
* Which files work and which do not?&lt;br /&gt;
* Why does &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; behave differently when served by Node instead of Apache?&lt;br /&gt;
&lt;br /&gt;
== Group Project: Multi-Room Chat Server ==&lt;br /&gt;
&lt;br /&gt;
In the group portion of Module 5, you will build a multi-room chat system using Node.js and Socket.IO.&lt;br /&gt;
&lt;br /&gt;
The system must include:&lt;br /&gt;
&lt;br /&gt;
* A main lobby where users select a nickname&lt;br /&gt;
* Ability to create chat rooms&lt;br /&gt;
* Ability to join chat rooms&lt;br /&gt;
* A list of available rooms&lt;br /&gt;
* A list of users currently in the room&lt;br /&gt;
* The entire application displayed on a single webpage&lt;br /&gt;
&lt;br /&gt;
The single-room example is provided here:&lt;br /&gt;
&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
&lt;br /&gt;
== AI Agent Extension ==&lt;br /&gt;
&lt;br /&gt;
You will enhance your chat server with an AI assistant.&lt;br /&gt;
&lt;br /&gt;
Users can message the AI by typing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;/agent What is the capital of France?&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The server detects messages that begin with &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt;, sends the prompt to an AI model, and broadcasts the response as &amp;lt;b&amp;gt;Agent&amp;lt;/b&amp;gt; in the same room.&lt;br /&gt;
&lt;br /&gt;
You may use:&lt;br /&gt;
&lt;br /&gt;
* Ollama (recommended — runs locally and free)&lt;br /&gt;
* OpenAI&lt;br /&gt;
* Gemini&lt;br /&gt;
* HuggingFace&lt;br /&gt;
* Any approved model&lt;br /&gt;
&lt;br /&gt;
If using an API, store your key in a file named &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; at the root of your project:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=your_key_here&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Load it securely in your server:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import 'dotenv/config';&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Refer to your chosen model’s documentation for setup details.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 100 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. '''&lt;br /&gt;
&lt;br /&gt;
'''Additionally, for this module, the TA should be able to simply download your repo and start up your file server, without having to copy files or create additional subdirectories.  This applies to both the individual and the creative portion'''&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''Individual Portion (30 Points):'''&lt;br /&gt;
#* Node.JS is installed on your EC2 instance (5 points)&lt;br /&gt;
#*: ''Take a screenshot of your browser visiting your EC2 instance serving up the college.html web page on port 3456, make sure the URL is visible in the screenshot.''&lt;br /&gt;
#* The ''hello.txt'', ''brookings.jpg'', and ''college.html'' files all load successfully (4 points each)&lt;br /&gt;
#* Visiting a file that does not exist inside the ''static'' directory results in a 404 (4 points)&lt;br /&gt;
#* Discuss in the README.md why ''phpinfo.php'' behaves the way it does when loaded through Node.JS (4 points)&lt;br /&gt;
#* Make sure all of your individual portion files are pushed to Github (include your node.js files, hello.txt, brookings.jpg, college.html, and phpinfo.php)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
# '''Multi-room Chat Server (70 Points):'''&lt;br /&gt;
#* '''''Administration of user created chat rooms (25 Points):'''''&lt;br /&gt;
#** Users can create chat rooms with an arbitrary room name(5 points)&lt;br /&gt;
#** Users can join an arbitrary chat room (5 points)&lt;br /&gt;
#** The chat room displays all users currently in the room (5 points)&lt;br /&gt;
#** A private room can be created that is password protected (5 points)&lt;br /&gt;
#** Creators of chat rooms can temporarily kick others out of the room (3 points)&lt;br /&gt;
#** Creators of chat rooms can permanently ban users from joining that particular room (2 points)&lt;br /&gt;
#* '''''Messaging (5 Points):'''''&lt;br /&gt;
#** A user's message shows their username and is sent to everyone in the room (1 point)&lt;br /&gt;
#** Users can send private messages to another user in the same room (4 points)&lt;br /&gt;
#* '''''Best Practices (5 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** Code passes HTML validation (2 points)&lt;br /&gt;
#** node_modules folder is ignored by version control (1 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Communicating with others and joining rooms is easy and intuitive (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
#* '''AI Agent and Reflection (20 Points)'''&lt;br /&gt;
#** Implement an AI assistant accessible via the command \agent ... that replies in the current room (10 points)&lt;br /&gt;
#** Handles basic errors gracefully and does not crash the server (5 point)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
#* '''Creative Portion (10 Points)'''&lt;br /&gt;
&amp;lt;!-- #** Make sure to create a README.md file pointing to your server --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Submission Instructions ==&lt;br /&gt;
&lt;br /&gt;
To ensure your project can be graded quickly and reliably, follow these submission requirements carefully.&lt;br /&gt;
&lt;br /&gt;
All students must submit a working GitHub repository. In addition, your project must be verifiable by the TA using one of the two methods below.&lt;br /&gt;
&lt;br /&gt;
=== Required for ALL Submissions ===&lt;br /&gt;
&lt;br /&gt;
Your GitHub repository must include:&lt;br /&gt;
&lt;br /&gt;
* All source code for both the individual and group portions&lt;br /&gt;
* A complete &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file&lt;br /&gt;
* A &amp;lt;code&amp;gt;README.md&amp;lt;/code&amp;gt; containing:&lt;br /&gt;
** How to run your server&lt;br /&gt;
** Which AI model you used (Ollama, OpenAI, Gemini, etc.)&lt;br /&gt;
** Any special setup instructions&lt;br /&gt;
* A &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file that excludes:&lt;br /&gt;
** &amp;lt;code&amp;gt;node_modules/&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Do NOT commit your API keys.'''&lt;br /&gt;
&lt;br /&gt;
If your project uses environment variables, include a file named:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
.env.example&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Your server must not crash if the API key is missing. It should fail gracefully.&lt;br /&gt;
&lt;br /&gt;
=== Option A - Running on EC2 ===&lt;br /&gt;
&lt;br /&gt;
Deploy your chat server on your EC2 instance and keep it running.&lt;br /&gt;
&lt;br /&gt;
Your README must include:&lt;br /&gt;
&lt;br /&gt;
* Public EC2 URL&lt;br /&gt;
* Port number (e.g., 3456)&lt;br /&gt;
* Any login instructions (if applicable)&lt;br /&gt;
&lt;br /&gt;
The TA will:&lt;br /&gt;
&lt;br /&gt;
* Visit your running server&lt;br /&gt;
* Test chat functionality&lt;br /&gt;
* Test multi-room behavior&lt;br /&gt;
* Test the &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; AI command&lt;br /&gt;
&lt;br /&gt;
This is the fastest and preferred grading method.&lt;br /&gt;
&lt;br /&gt;
=== Option B — Local Execution + Demo Video ===&lt;br /&gt;
&lt;br /&gt;
If your server is NOT running on EC2, you must provide a short demonstration video.&lt;br /&gt;
&lt;br /&gt;
The video must show:&lt;br /&gt;
&lt;br /&gt;
* Starting the server&lt;br /&gt;
* Two browser windows connected&lt;br /&gt;
* Chat working between users&lt;br /&gt;
* The &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; command working&lt;br /&gt;
&lt;br /&gt;
Video length: approximately 1–2 minutes.&lt;br /&gt;
&lt;br /&gt;
The TA may still run your code locally if needed.&lt;br /&gt;
&lt;br /&gt;
=== Server Requirements ===&lt;br /&gt;
&lt;br /&gt;
Your submission must:&lt;br /&gt;
&lt;br /&gt;
* Start successfully with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm install&lt;br /&gt;
node chat-server.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(or equivalent script)&lt;br /&gt;
&lt;br /&gt;
* Not require manual file copying or directory creation&lt;br /&gt;
* Not require editing source code to run&lt;br /&gt;
* Handle missing AI keys gracefully&lt;br /&gt;
* Not crash during normal use&lt;br /&gt;
&lt;br /&gt;
=== Common Causes of Lost Points ===&lt;br /&gt;
&lt;br /&gt;
* Missing README instructions&lt;br /&gt;
* API key committed to GitHub&lt;br /&gt;
* Server crashes when AI key missing&lt;br /&gt;
* Broken EC2 deployment (port closed)&lt;br /&gt;
* Cannot run after &amp;lt;code&amp;gt;npm install&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;node_modules&amp;lt;/code&amp;gt; committed to repo&lt;br /&gt;
* Hardcoded absolute file paths&lt;br /&gt;
* Missing AI demonstration&lt;br /&gt;
&lt;br /&gt;
Following these instructions ensures your project can be graded quickly and fairly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3499</id>
		<title>Module 5</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_5&amp;diff=3499"/>
		<updated>2026-03-16T16:57:01Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Option A - Running on EC2 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&lt;br /&gt;
Module 5 builds upon the JavaScript skills you learned in Module 4 and introduces server-side JavaScript using Node.js, along with real-time communication using Socket.IO.&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the class wiki contain information that will help you complete the assignments:&lt;br /&gt;
&lt;br /&gt;
* [[Node.JS]]&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
* [[FAQ - Mod 5]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Install Node.js ===&lt;br /&gt;
&lt;br /&gt;
Install Node.js using the instructions in the [[Node.JS]] guide (use the current LTS version via nvm).&lt;br /&gt;
&lt;br /&gt;
=== Static File Server in Node.js ===&lt;br /&gt;
&lt;br /&gt;
The individual portion is intentionally short so you have sufficient time for the group project.&lt;br /&gt;
&lt;br /&gt;
Follow these steps carefully.&lt;br /&gt;
&lt;br /&gt;
# Copy the static file server example from the [[Node.JS]] page into a file named &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt;. Save it on your EC2 instance in a directory that is NOT served by Apache.&lt;br /&gt;
# Ensure that you understand what the static file server script is doing.&lt;br /&gt;
# Create a directory parallel to &amp;lt;code&amp;gt;app.js&amp;lt;/code&amp;gt; named &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Confirm that your server’s &amp;lt;code&amp;gt;STATIC_DIR&amp;lt;/code&amp;gt; variable matches this directory name.&lt;br /&gt;
# Create the following files inside the &amp;lt;code&amp;gt;static&amp;lt;/code&amp;gt; directory:&lt;br /&gt;
#* &amp;lt;code&amp;gt;hello.txt&amp;lt;/code&amp;gt; — a text file containing: &amp;lt;b&amp;gt;Hello World&amp;lt;/b&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt; — download from: http://classes.engineering.wustl.edu/cse330/content/brookings.jpg&lt;br /&gt;
#* &amp;lt;code&amp;gt;college.html&amp;lt;/code&amp;gt; — an HTML page based on the [[HTML and CSS#Quick and Easy Page Layout|Quick and Easy HTML Layout]] that includes an &amp;lt;code&amp;gt;&amp;amp;lt;img&amp;amp;gt;&amp;lt;/code&amp;gt; referencing &amp;lt;code&amp;gt;brookings.jpg&amp;lt;/code&amp;gt;&lt;br /&gt;
#* &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; — a PHP file containing:&lt;br /&gt;
#: &amp;lt;source lang=&amp;quot;php&amp;quot;&amp;gt;&amp;lt;?php phpinfo(); ?&amp;gt;&amp;lt;/source&amp;gt;&lt;br /&gt;
# Start your Node.js server and load all four files in your browser.&lt;br /&gt;
&lt;br /&gt;
Questions to consider:&lt;br /&gt;
&lt;br /&gt;
* Which files work and which do not?&lt;br /&gt;
* Why does &amp;lt;code&amp;gt;phpinfo.php&amp;lt;/code&amp;gt; behave differently when served by Node instead of Apache?&lt;br /&gt;
&lt;br /&gt;
== Group Project: Multi-Room Chat Server ==&lt;br /&gt;
&lt;br /&gt;
In the group portion of Module 5, you will build a multi-room chat system using Node.js and Socket.IO.&lt;br /&gt;
&lt;br /&gt;
The system must include:&lt;br /&gt;
&lt;br /&gt;
* A main lobby where users select a nickname&lt;br /&gt;
* Ability to create chat rooms&lt;br /&gt;
* Ability to join chat rooms&lt;br /&gt;
* A list of available rooms&lt;br /&gt;
* A list of users currently in the room&lt;br /&gt;
* The entire application displayed on a single webpage&lt;br /&gt;
&lt;br /&gt;
The single-room example is provided here:&lt;br /&gt;
&lt;br /&gt;
* [[Socket.IO]]&lt;br /&gt;
&lt;br /&gt;
== AI Agent Extension ==&lt;br /&gt;
&lt;br /&gt;
You will enhance your chat server with an AI assistant.&lt;br /&gt;
&lt;br /&gt;
Users can message the AI by typing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;/agent What is the capital of France?&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The server detects messages that begin with &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt;, sends the prompt to an AI model, and broadcasts the response as &amp;lt;b&amp;gt;Agent&amp;lt;/b&amp;gt; in the same room.&lt;br /&gt;
&lt;br /&gt;
You may use:&lt;br /&gt;
&lt;br /&gt;
* Ollama (recommended — runs locally and free)&lt;br /&gt;
* OpenAI&lt;br /&gt;
* Gemini&lt;br /&gt;
* HuggingFace&lt;br /&gt;
* Any approved model&lt;br /&gt;
&lt;br /&gt;
If using an API, store your key in a file named &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; at the root of your project:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=your_key_here&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Load it securely in your server:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import 'dotenv/config';&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Refer to your chosen model’s documentation for setup details.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 100 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. '''&lt;br /&gt;
&lt;br /&gt;
'''Additionally, for this module, the TA should be able to simply download your repo and start up your file server, without having to copy files or create additional subdirectories.  This applies to both the individual and the creative portion'''&lt;br /&gt;
&lt;br /&gt;
_________&lt;br /&gt;
&lt;br /&gt;
# '''Individual Portion (30 Points):'''&lt;br /&gt;
#* Node.JS is installed on your EC2 instance (5 points)&lt;br /&gt;
#*: ''Take a screenshot of your browser visiting your EC2 instance serving up the college.html web page on port 3456, make sure the URL is visible in the screenshot.''&lt;br /&gt;
#* The ''hello.txt'', ''brookings.jpg'', and ''college.html'' files all load successfully (4 points each)&lt;br /&gt;
#* Visiting a file that does not exist inside the ''static'' directory results in a 404 (4 points)&lt;br /&gt;
#* Discuss in the README.md why ''phpinfo.php'' behaves the way it does when loaded through Node.JS (4 points)&lt;br /&gt;
#* Make sure all of your individual portion files are pushed to Github (include your node.js files, hello.txt, brookings.jpg, college.html, and phpinfo.php)&lt;br /&gt;
#* Completion of the AI Reflections for the individual portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
# '''Multi-room Chat Server (70 Points):'''&lt;br /&gt;
#* '''''Administration of user created chat rooms (25 Points):'''''&lt;br /&gt;
#** Users can create chat rooms with an arbitrary room name(5 points)&lt;br /&gt;
#** Users can join an arbitrary chat room (5 points)&lt;br /&gt;
#** The chat room displays all users currently in the room (5 points)&lt;br /&gt;
#** A private room can be created that is password protected (5 points)&lt;br /&gt;
#** Creators of chat rooms can temporarily kick others out of the room (3 points)&lt;br /&gt;
#** Creators of chat rooms can permanently ban users from joining that particular room (2 points)&lt;br /&gt;
#* '''''Messaging (5 Points):'''''&lt;br /&gt;
#** A user's message shows their username and is sent to everyone in the room (1 point)&lt;br /&gt;
#** Users can send private messages to another user in the same room (4 points)&lt;br /&gt;
#* '''''Best Practices (5 Points):'''''&lt;br /&gt;
#** Code is well formatted and easy to read, with proper commenting (2 points)&lt;br /&gt;
#** Code passes HTML validation (2 points)&lt;br /&gt;
#** node_modules folder is ignored by version control (1 points)&lt;br /&gt;
#* '''''Usability (5 Points):'''''&lt;br /&gt;
#** Communicating with others and joining rooms is easy and intuitive (4 points)&lt;br /&gt;
#** Site is visually appealing (1 point)&lt;br /&gt;
#* '''AI Agent and Reflection (20 Points)'''&lt;br /&gt;
#** Implement an AI assistant accessible via the command \agent ... that replies in the current room (10 points)&lt;br /&gt;
#** Handles basic errors gracefully and does not crash the server (5 point)&lt;br /&gt;
#** Completion of the AI Reflections for the group portion of the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
#* '''Creative Portion (10 Points)'''&lt;br /&gt;
&amp;lt;!-- #** Make sure to create a README.md file pointing to your server --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Submission Instructions ==&lt;br /&gt;
&lt;br /&gt;
To ensure your project can be graded quickly and reliably, follow these submission requirements carefully.&lt;br /&gt;
&lt;br /&gt;
All students must submit a working GitHub repository. In addition, your project must be verifiable by the TA using one of the two methods below.&lt;br /&gt;
&lt;br /&gt;
=== Required for ALL Submissions ===&lt;br /&gt;
&lt;br /&gt;
Your GitHub repository must include:&lt;br /&gt;
&lt;br /&gt;
* All source code for both the individual and group portions&lt;br /&gt;
* A complete &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; file&lt;br /&gt;
* A &amp;lt;code&amp;gt;README.md&amp;lt;/code&amp;gt; containing:&lt;br /&gt;
** How to run your server&lt;br /&gt;
** Which AI model you used (Ollama, OpenAI, Gemini, etc.)&lt;br /&gt;
** Any special setup instructions&lt;br /&gt;
* A &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file that excludes:&lt;br /&gt;
** &amp;lt;code&amp;gt;node_modules/&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Do NOT commit your API keys.'''&lt;br /&gt;
&lt;br /&gt;
If your project uses environment variables, include a file named:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
.env.example&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
OPENAI_API_KEY=&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Your server must not crash if the API key is missing. It should fail gracefully.&lt;br /&gt;
&lt;br /&gt;
=== Option A - Running on EC2 ===&lt;br /&gt;
&lt;br /&gt;
Deploy your chat server on your EC2 instance and keep it running.&lt;br /&gt;
&lt;br /&gt;
Your README must include:&lt;br /&gt;
&lt;br /&gt;
* Public EC2 URL&lt;br /&gt;
* Port number (e.g., 3456)&lt;br /&gt;
* Any login instructions (if applicable)&lt;br /&gt;
&lt;br /&gt;
The TA will:&lt;br /&gt;
&lt;br /&gt;
* Visit your running server&lt;br /&gt;
* Test chat functionality&lt;br /&gt;
* Test multi-room behavior&lt;br /&gt;
* Test the &amp;lt;code&amp;gt;/agent&amp;lt;/code&amp;gt; AI command&lt;br /&gt;
&lt;br /&gt;
This is the fastest and preferred grading method.&lt;br /&gt;
&lt;br /&gt;
=== Option B — Local Execution + Demo Video ===&lt;br /&gt;
&lt;br /&gt;
If your server is NOT running on EC2, you must provide a short demonstration video.&lt;br /&gt;
&lt;br /&gt;
The video must show:&lt;br /&gt;
&lt;br /&gt;
* Starting the server&lt;br /&gt;
* Two browser windows connected&lt;br /&gt;
* Chat working between users&lt;br /&gt;
* The &amp;lt;code&amp;gt;\agent&amp;lt;/code&amp;gt; command working&lt;br /&gt;
&lt;br /&gt;
Video length: approximately 1–2 minutes.&lt;br /&gt;
&lt;br /&gt;
The TA may still run your code locally if needed.&lt;br /&gt;
&lt;br /&gt;
=== Server Requirements ===&lt;br /&gt;
&lt;br /&gt;
Your submission must:&lt;br /&gt;
&lt;br /&gt;
* Start successfully with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm install&lt;br /&gt;
node chat-server.js&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(or equivalent script)&lt;br /&gt;
&lt;br /&gt;
* Not require manual file copying or directory creation&lt;br /&gt;
* Not require editing source code to run&lt;br /&gt;
* Handle missing AI keys gracefully&lt;br /&gt;
* Not crash during normal use&lt;br /&gt;
&lt;br /&gt;
=== Common Causes of Lost Points ===&lt;br /&gt;
&lt;br /&gt;
* Missing README instructions&lt;br /&gt;
* API key committed to GitHub&lt;br /&gt;
* Server crashes when AI key missing&lt;br /&gt;
* Broken EC2 deployment (port closed)&lt;br /&gt;
* Cannot run after &amp;lt;code&amp;gt;npm install&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;node_modules&amp;lt;/code&amp;gt; committed to repo&lt;br /&gt;
* Hardcoded absolute file paths&lt;br /&gt;
* Missing AI demonstration&lt;br /&gt;
&lt;br /&gt;
Following these instructions ensures your project can be graded quickly and fairly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 5]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module6-New2&amp;diff=3498</id>
		<title>Module6-New2</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module6-New2&amp;diff=3498"/>
		<updated>2026-03-15T23:57:29Z</updated>

		<summary type="html">&lt;p&gt;Todd: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
In Module 6, you will learn the fundamentals of Python, a popular scripting language, and Regular Expressions, which give you power in text processing. You will also be introduced to the '''Model Context Protocol (MCP)''', a standard for connecting AI assistants like Claude to your own tools and data.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 6]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Up to this point in CSE 3300, you have used only one programming language: PHP.  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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
You can learn about this in [[MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.&lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:&lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:&lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize's batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The prompt is user controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.&lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to calculate batting averages.&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to show the top 10 hitters from 1940.&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Get batting averages for Johnny Mize in both 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.&lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''&amp;quot;Docker MCP Toolkit&amp;quot;'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''Clients → Claude Desktop → Connect'''.&lt;br /&gt;
If it says '''&amp;quot;Disconnect&amp;quot;''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.&lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
import logging&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Set up logging&lt;br /&gt;
logging.basicConfig(level=logging.INFO, format=&amp;quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;quot;)&lt;br /&gt;
logger = logging.getLogger(&amp;quot;CardinalsMCP-server&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
&lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
&lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
&lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
&lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.&lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.&lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.&lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.&lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to list the top 10 hitters from 1940.&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize's batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Who were the top 5 hitters on the 1930 Cardinals?&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;quot;Search for players with 'Martin' in their name.&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 50 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. '''&lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (10 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (&amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt;) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (3 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (2 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP's response.''&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3497</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3497"/>
		<updated>2026-03-15T21:43:02Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* cardinals_server.py */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3496</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3496"/>
		<updated>2026-03-15T21:42:28Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Where Prompts, Resources, and Tools Fit in the MCP Server */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user-controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
** &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
** &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
** &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module6-New2&amp;diff=3495</id>
		<title>Module6-New2</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module6-New2&amp;diff=3495"/>
		<updated>2026-03-15T18:21:19Z</updated>

		<summary type="html">&lt;p&gt;Todd: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
In Module 6, you will learn the fundamentals of Python, a popular scripting language, and Regular Expressions, which give you power in text processing.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
Example&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP_Fundamentals|Model Context Protocol (MCP)]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Up to this point in CSE 3300, you have used two programming languages: PHP and JavaScript. In Module 6, you will learn a new language: Python.&lt;br /&gt;
&lt;br /&gt;
Before starting this assignment review:&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
&lt;br /&gt;
You should use Python version 3 or greater.&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot;&lt;br /&gt;
# Match strings containing three or more consecutive vowels&lt;br /&gt;
# Match a flight code of format AA####&lt;br /&gt;
&lt;br /&gt;
Save them as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt;  &lt;br /&gt;
&amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt;  &lt;br /&gt;
&amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
The St. Louis Cardinals are the most legendary baseball team in the national league.&lt;br /&gt;
&lt;br /&gt;
In this exercise you will create a Python script that reads box scores from a file and computes the Cardinals' players' batting averages.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here:&lt;br /&gt;
&lt;br /&gt;
http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
XXX: #.###&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Players should be sorted by batting average.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
This project builds a custom MCP server that lets Claude Desktop explore St. Louis Cardinals batting data.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a Python MCP server that:&lt;br /&gt;
&lt;br /&gt;
* reads Cardinals batting averages&lt;br /&gt;
* lists team rosters&lt;br /&gt;
* returns top hitters&lt;br /&gt;
* allows player search&lt;br /&gt;
* runs inside Docker&lt;br /&gt;
* connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
Install:&lt;br /&gt;
&lt;br /&gt;
* Docker Desktop&lt;br /&gt;
* Claude Desktop&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== cardinals_server.py =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    players=set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    player_data=[]&lt;br /&gt;
    for year,year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower()==player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year':year,&lt;br /&gt;
                    'name':player['name'],&lt;br /&gt;
                    'batting_average':player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    # TODO&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    # TODO&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    # TODO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    # TODO&lt;br /&gt;
&lt;br /&gt;
if __name__==&amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 50 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see https://en.wikipedia.org/wiki/Usage_message#Examples''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (10 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (3 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (2 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--- NEW -----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Verify Your MCP Server Works ====&lt;br /&gt;
&lt;br /&gt;
Before connecting Claude Desktop, verify that your MCP server runs correctly.&lt;br /&gt;
&lt;br /&gt;
Run your container manually:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker run --rm -it cardinals-mcp-server:latest&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If everything is working correctly, you should see a startup message similar to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the container exits with an error, fix the issue before continuing.&lt;br /&gt;
&lt;br /&gt;
You can stop the container with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
Ctrl+C&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Do not attempt to connect Claude until the container runs successfully.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Common Setup Mistakes ====&lt;br /&gt;
&lt;br /&gt;
If Claude cannot see your MCP server, check the following:&lt;br /&gt;
&lt;br /&gt;
'''Docker image was not built'''&lt;br /&gt;
&lt;br /&gt;
You must run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
before attempting to run the container.&lt;br /&gt;
&lt;br /&gt;
'''Container name mismatch'''&lt;br /&gt;
&lt;br /&gt;
The Docker image name in your Claude configuration must match the image name used during the build step.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
must match:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;args&amp;quot;: [&lt;br /&gt;
  &amp;quot;run&amp;quot;,&lt;br /&gt;
  &amp;quot;--rm&amp;quot;,&lt;br /&gt;
  &amp;quot;-i&amp;quot;,&lt;br /&gt;
  &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Claude Desktop was not restarted'''&lt;br /&gt;
&lt;br /&gt;
After editing the MCP configuration file, completely quit and reopen Claude Desktop.&lt;br /&gt;
&lt;br /&gt;
'''The container exits immediately'''&lt;br /&gt;
&lt;br /&gt;
This usually means your Python script crashed. Run the container manually to view the error:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker run --rm -it cardinals-mcp-server:latest&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== What Claude Is Actually Doing ====&lt;br /&gt;
&lt;br /&gt;
When you ask Claude a question such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Use CardinalsMCP to list the top 10 hitters from 1940.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the following process occurs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
User Prompt&lt;br /&gt;
      ↓&lt;br /&gt;
Claude reads the prompt&lt;br /&gt;
      ↓&lt;br /&gt;
Claude decides which tool to use&lt;br /&gt;
      ↓&lt;br /&gt;
Claude calls the MCP server&lt;br /&gt;
      ↓&lt;br /&gt;
Docker launches your container&lt;br /&gt;
      ↓&lt;br /&gt;
Your Python tool runs&lt;br /&gt;
      ↓&lt;br /&gt;
The results are returned to Claude&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude never directly runs your Python code.&lt;br /&gt;
&lt;br /&gt;
Your MCP server runs the code and sends the results back to Claude.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Debugging Your MCP Server ====&lt;br /&gt;
&lt;br /&gt;
If Claude does not return results, try the following:&lt;br /&gt;
&lt;br /&gt;
'''Step 1: Verify the container runs'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker run --rm -it cardinals-mcp-server:latest&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Step 2: Verify your Python script loads'''&lt;br /&gt;
&lt;br /&gt;
Your container should print a startup message when it launches.&lt;br /&gt;
&lt;br /&gt;
'''Step 3: Verify tools exist'''&lt;br /&gt;
&lt;br /&gt;
Claude should display available tools in the MCP indicator inside the chat interface.&lt;br /&gt;
&lt;br /&gt;
'''Step 4: Check Claude configuration'''&lt;br /&gt;
&lt;br /&gt;
Ensure the file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
contains your MCP server configuration.&lt;br /&gt;
&lt;br /&gt;
If necessary, restart Claude Desktop.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3494</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3494"/>
		<updated>2026-03-15T15:51:04Z</updated>

		<summary type="html">&lt;p&gt;Todd: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3493</id>
		<title>Module 6</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module_6&amp;diff=3493"/>
		<updated>2026-03-15T15:47:03Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Reading */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size:1.3em; line-height:1.2em; color:red;&amp;quot;&amp;gt;&lt;br /&gt;
'''This module is under development for Spring 2026. Do not begin this assignment until this notice is removed.''' &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In Module 6, you will learn the fundamentals of Python, a popular scripting language, and Regular Expressions, which give you power in text processing.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 6.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 70 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (30 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (14 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (6 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
#* Completion of the AI Reflections for the assignment following the [[AI Best Practices]] (5 points)&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
	<entry>
		<id>http://classes.engineering.wustl.edu/cse330/index.php?title=Module6-AI&amp;diff=3492</id>
		<title>Module6-AI</title>
		<link rel="alternate" type="text/html" href="http://classes.engineering.wustl.edu/cse330/index.php?title=Module6-AI&amp;diff=3492"/>
		<updated>2026-03-15T15:45:45Z</updated>

		<summary type="html">&lt;p&gt;Todd: /* Reading */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
In Module 4, you will learn the fundamentals of Python, a popular scripting language, and Regular Expressions, which give you power in text processing.&lt;br /&gt;
&lt;br /&gt;
This article contains your assignments for Module 4.&lt;br /&gt;
&lt;br /&gt;
=== Learning Goals ===&lt;br /&gt;
* Regular Expressions&lt;br /&gt;
* Python&lt;br /&gt;
* MCP Fundamentals&lt;br /&gt;
&lt;br /&gt;
== Using the Wiki ==&lt;br /&gt;
{{RequiredInstructions|content=&lt;br /&gt;
Text enclosed by &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;lt;&amp;lt;/syntaxhighlight&amp;gt; and &amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; inline&amp;gt;&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; should be replaced by content unique to you.&lt;br /&gt;
&lt;br /&gt;
'''Example'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;&amp;lt;My Full Name&amp;gt;&amp;quot; &amp;lt;usernameHere&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
becomes&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ sudo useradd -r -m -c &amp;quot;Zach Cohn&amp;quot; zcohn&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Reading ==&lt;br /&gt;
&lt;br /&gt;
The following articles on the online class wiki textbook contain information that will help you complete the assignments.&lt;br /&gt;
&lt;br /&gt;
* [[Python]]&lt;br /&gt;
* [[Regular Expressions]]&lt;br /&gt;
* [[MCP Fundamentals]]&lt;br /&gt;
* [[FAQ - Mod 4]]&lt;br /&gt;
&lt;br /&gt;
== Individual Assignments ==&lt;br /&gt;
&lt;br /&gt;
=== Learn Python and Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Up to this point in CSE 330, you have used only one programming language: PHP.  In Module 4, 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]].&lt;br /&gt;
&lt;br /&gt;
=== Write some Regular Expressions ===&lt;br /&gt;
&lt;br /&gt;
Please write regular expressions that do each of the following:&lt;br /&gt;
&lt;br /&gt;
# Match the substring &amp;quot;hello world&amp;quot; in a string.&lt;br /&gt;
# 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.&lt;br /&gt;
# 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.&lt;br /&gt;
Examples:&lt;br /&gt;
   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.&lt;br /&gt;
   Regex 1:  Programmers will often write ''[[hello world]]'' as their first project with a programming language.&lt;br /&gt;
   Regex 2:  The ''[[gooey]]'' peanut butter and jelly sandwich was a ''[[beauty]]''.&lt;br /&gt;
   Regex 3:  ''[[AA312]]''&lt;br /&gt;
             ''[[AA1298]]''&lt;br /&gt;
             ''[[NW1234]]''&lt;br /&gt;
             ''[[US443]]''&lt;br /&gt;
             US31344&lt;br /&gt;
             AA123 extratext&lt;br /&gt;
&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
Save your regexes to text files – one each.&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats Counter ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Tips and Instructions ====&lt;br /&gt;
&lt;br /&gt;
* You should write a Python script file to solve this problem.&lt;br /&gt;
* You should use a regular expression to parse players' names, at-bats, hits, and runs from the input file.&lt;br /&gt;
* You may want to create a class to hold and compute information about each player.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
==== Input ====&lt;br /&gt;
&lt;br /&gt;
Sample input files may be found here: http://classes.engineering.wustl.edu/cse330/content/cardinals/&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
 === St.Louis Cardinals vs. Chicago Cubs, 1940-04-19 ===&lt;br /&gt;
&lt;br /&gt;
What follows are the performance of each player in that game.  The format of each line is as follows:&lt;br /&gt;
&lt;br /&gt;
 XXX batted # times with # hits and # runs&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name (you may assume that the name is unique), and each '''#''' are integers representing the number of &amp;quot;at bats&amp;quot;, the number of &amp;quot;hits&amp;quot;, and the number of &amp;quot;runs&amp;quot;, respectively.&lt;br /&gt;
&lt;br /&gt;
==== Output ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each line in the output should have the format:&lt;br /&gt;
&lt;br /&gt;
 XXX: #.###&lt;br /&gt;
&lt;br /&gt;
where '''XXX''' is the player's name, and '''#.###''' is the player's batting average, '''rounded''' (not truncated) to three decimal places.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
For example, the correct output for the 1940 season is:&lt;br /&gt;
&amp;lt;!-- Jimmy Brown was 0.280 and Ira Hutchinson was 0.125 --&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Pepper Martin: 0.316&lt;br /&gt;
Walker Cooper: 0.316&lt;br /&gt;
Johnny Mize: 0.314&lt;br /&gt;
Ernie Koy: 0.310&lt;br /&gt;
Enos Slaughter: 0.306&lt;br /&gt;
Joe Medwick: 0.304&lt;br /&gt;
Terry Moore: 0.304&lt;br /&gt;
Joe Orengo: 0.286&lt;br /&gt;
Jimmy Brown: 0.280&lt;br /&gt;
Marty Marion: 0.279&lt;br /&gt;
Don Gutteridge: 0.276&lt;br /&gt;
Johnny Hopp: 0.275&lt;br /&gt;
Creepy Crespi: 0.273&lt;br /&gt;
Mickey Owen: 0.265&lt;br /&gt;
Bill DeLancey: 0.250&lt;br /&gt;
Don Padgett: 0.242&lt;br /&gt;
Stu Martin: 0.238&lt;br /&gt;
Eddie Lake: 0.222&lt;br /&gt;
Hal Epps: 0.214&lt;br /&gt;
Lon Warneke: 0.209&lt;br /&gt;
Harry Walker: 0.185&lt;br /&gt;
Max Lanier: 0.179&lt;br /&gt;
Bill McGee: 0.178&lt;br /&gt;
Carl Doyle: 0.174&lt;br /&gt;
Mort Cooper: 0.157&lt;br /&gt;
Clyde Shoun: 0.145&lt;br /&gt;
Carden Gillenwater: 0.130&lt;br /&gt;
Bob Bowman: 0.067&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baseball Stats MCP Extension ===&lt;br /&gt;
&lt;br /&gt;
====Cardinals MCP Server ====&lt;br /&gt;
&lt;br /&gt;
You can learn about this in [[MCP Fundamentals]]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The MCP server reads player batting averages from text files, sorts them, and returns the results through '''Claude Desktop'''.&lt;br /&gt;
&lt;br /&gt;
For all of the assignment, feel free to use your favorite AI tool to help.&lt;br /&gt;
&lt;br /&gt;
==== What Is an MCP Server? ====&lt;br /&gt;
&lt;br /&gt;
An '''MCP (Model Context Protocol)''' server is a local tool that '''Claude Desktop''' can talk to directly.  &lt;br /&gt;
It runs safely inside '''Docker''', allowing Claude to use your Python scripts, such as this Cardinals data processor, through its chat interface.&lt;br /&gt;
&lt;br /&gt;
Learn more:  &lt;br /&gt;
[https://modelcontextprotocol.io/docs/getting-started/intro Model Context Protocol: Getting Started]&lt;br /&gt;
&lt;br /&gt;
Servers provide the fundamental building blocks for adding context to language models via MCP. These primitives enable interactions between clients, servers, and language models:&lt;br /&gt;
&lt;br /&gt;
* '''Prompts''': Predefined templates or instructions that guide language model interactions&lt;br /&gt;
* '''Resources''': Structured data or content that provides additional context to the model&lt;br /&gt;
* '''Tools''': Executable functions that allow models to perform actions or retrieve information&lt;br /&gt;
&lt;br /&gt;
Each primitive can be summarized in the following control hierarchy:&lt;br /&gt;
&lt;br /&gt;
====== Where Prompts, Resources, and Tools Fit in the MCP Server ======&lt;br /&gt;
&lt;br /&gt;
* '''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:  &lt;br /&gt;
  &amp;lt;pre&amp;gt;&amp;quot;Show Johnny Mize’s batting averages for 1930 and 1940.&amp;quot;&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
The prompt is user controlled, and the system will interpret it to execute the appropriate action.&lt;br /&gt;
&lt;br /&gt;
* '''Resources''': These are the data files, such as &amp;lt;code&amp;gt;cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;cardinals-1940.txt&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
* '''Tools''': The server exposes various '''tools''' that perform specific actions, like:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Retrieve and show batting averages for a player.&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players from a given season.&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Sort and show the top hitters.&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Search for players by name.&lt;br /&gt;
&lt;br /&gt;
Each of these functions is a '''tool''' in the MCP system that the language model can call when responding to prompts from the user.&lt;br /&gt;
&lt;br /&gt;
==== What Is Docker? ====&lt;br /&gt;
&lt;br /&gt;
'''Docker''' lets you run software in isolated, self-contained environments called '''containers'''.  &lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Claude uses Docker to safely run MCP tools '''without touching your main system'''.  &lt;br /&gt;
When you say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to calculate batting averages.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude does not execute code directly. It asks Docker to launch your MCP container, which runs the script in isolation.&lt;br /&gt;
&lt;br /&gt;
==== Goal ====&lt;br /&gt;
&lt;br /&gt;
You will build a '''Python-based MCP server''' that:&lt;br /&gt;
&lt;br /&gt;
* Reads Cardinals player batting averages from '''1930''' and '''1940''' text files&lt;br /&gt;
* Lists team rosters and top hitters&lt;br /&gt;
* Lets you search players or compare across seasons&lt;br /&gt;
* Runs inside Docker and connects to Claude Desktop&lt;br /&gt;
&lt;br /&gt;
After setup, you will be able to say things like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to show the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Get batting averages for Johnny Mize in both 1930 and 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will automatically run your tool and show the results.&lt;br /&gt;
&lt;br /&gt;
==== Project Structure ====&lt;br /&gt;
&lt;br /&gt;
Your final folder will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
cardinals-mcp/&lt;br /&gt;
├── Dockerfile&lt;br /&gt;
├── cardinals_server.py&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
└── data/&lt;br /&gt;
    ├── cardinals-1930.txt&lt;br /&gt;
    └── cardinals-1940.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/data&amp;lt;/code&amp;gt; directory contains the &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files you created from the Baseball Stats Counter section, listing player names and batting averages.  &lt;br /&gt;
If you only printed the results, copy and paste them into &amp;lt;code&amp;gt;.txt&amp;lt;/code&amp;gt; files.&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
&lt;br /&gt;
* [https://www.docker.com/products/docker-desktop/ Docker Desktop]&lt;br /&gt;
* [https://claude.ai/download Claude Desktop] (''not the browser version'')&lt;br /&gt;
&lt;br /&gt;
==== Enable MCP in Docker ====&lt;br /&gt;
&lt;br /&gt;
# Open '''Docker Desktop'''.&lt;br /&gt;
# Go to '''Settings → Beta Features'''.&lt;br /&gt;
# Enable '''“Docker MCP Toolkit”'''.&lt;br /&gt;
# Click '''Apply &amp;amp; Restart'''.&lt;br /&gt;
&lt;br /&gt;
===== Connect Claude to Docker =====&lt;br /&gt;
&lt;br /&gt;
In Docker Desktop, go to '''Clients → Claude Desktop → Connect'''.  &lt;br /&gt;
If it says '''“Disconnect”''', you are good to go.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create the Files ====&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;cardinals_server.py&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
This is your main MCP script. It should:&lt;br /&gt;
&lt;br /&gt;
* Load player data from &amp;lt;code&amp;gt;/data/cardinals-1930.txt&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/data/cardinals-1940.txt&amp;lt;/code&amp;gt;&lt;br /&gt;
* Implement at least '''two tools''', such as:&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;: Show batting averages for one player&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;: List all players for a given season (1930 or 1940)&lt;br /&gt;
  * &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;: Show the top hitters for a given season (by batting average)&lt;br /&gt;
  * &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;: Find players by partial name and show their batting averages&lt;br /&gt;
&lt;br /&gt;
Each tool should be implemented as an '''asynchronous function''' decorated with &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt;. For example, the provided &amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt; function uses the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to indicate that it is a tool that can be invoked by Claude.&lt;br /&gt;
&lt;br /&gt;
In this script, you need to:&lt;br /&gt;
&lt;br /&gt;
'''Create MCP Tools:'''&lt;br /&gt;
* Implement at least two MCP tools (&amp;lt;code&amp;gt;get_player_stats&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_team_roster&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;get_top_players&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;search_players&amp;lt;/code&amp;gt;).  &lt;br /&gt;
* Use the &amp;lt;code&amp;gt;@mcp.tool()&amp;lt;/code&amp;gt; decorator to register each function as a tool that Claude can use.  &lt;br /&gt;
* Your functions should:&lt;br /&gt;
** Retrieve data from the loaded player stats.&lt;br /&gt;
** Perform calculations or sorting (for example, finding the top hitters or listing all players for a season).&lt;br /&gt;
** Return well-formatted strings with the results.&lt;br /&gt;
&lt;br /&gt;
===== Starter Code =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import re&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from mcp.server.fastmcp import FastMCP&lt;br /&gt;
&lt;br /&gt;
# Initialize MCP server&lt;br /&gt;
mcp = FastMCP(&amp;quot;CardinalsMCP&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Data file paths&lt;br /&gt;
DATA_DIR = os.path.join(os.path.dirname(__file__), &amp;quot;data&amp;quot;)&lt;br /&gt;
CARDINALS_1930_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1930.txt&amp;quot;)&lt;br /&gt;
CARDINALS_1940_FILE = os.path.join(DATA_DIR, &amp;quot;cardinals-1940.txt&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# ==== Helper Functions (You DO NOT need to use these) ====&lt;br /&gt;
&lt;br /&gt;
def load_cardinals_data():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Load Cardinals player data from both 1930 and 1940 files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    data = {&amp;quot;1930&amp;quot;: [], &amp;quot;1940&amp;quot;: []}&lt;br /&gt;
    &lt;br /&gt;
    for year, file_path in [(&amp;quot;1930&amp;quot;, CARDINALS_1930_FILE), (&amp;quot;1940&amp;quot;, CARDINALS_1940_FILE)]:&lt;br /&gt;
        try:&lt;br /&gt;
            with open(file_path, 'r', encoding='utf-8') as f:&lt;br /&gt;
                content = f.read()&lt;br /&gt;
                data[year] = parse_player_data(content)&lt;br /&gt;
        except FileNotFoundError:&lt;br /&gt;
            logger.warning(f&amp;quot;Data file not found: {file_path}&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Error loading {year} data: {e}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    return data&lt;br /&gt;
&lt;br /&gt;
def parse_player_data(content):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse player data from the simplified Cardinals text files.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = []&lt;br /&gt;
    &lt;br /&gt;
    for line in content.split('\n'):&lt;br /&gt;
        line = line.strip()&lt;br /&gt;
        if not line or line.startswith('#'):&lt;br /&gt;
            continue&lt;br /&gt;
            &lt;br /&gt;
        # Parse format: &amp;quot;Player Name: 0.XXX&amp;quot;&lt;br /&gt;
        if ':' in line:&lt;br /&gt;
            parts = line.split(':')&lt;br /&gt;
            if len(parts) == 2:&lt;br /&gt;
                name = parts[0].strip()&lt;br /&gt;
                try:&lt;br /&gt;
                    batting_avg = float(parts[1].strip())&lt;br /&gt;
                    players.append({&lt;br /&gt;
                        'name': name,&lt;br /&gt;
                        'batting_average': batting_avg&lt;br /&gt;
                    })&lt;br /&gt;
                except ValueError:&lt;br /&gt;
                    continue&lt;br /&gt;
    &lt;br /&gt;
    return players&lt;br /&gt;
&lt;br /&gt;
def get_all_players(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get all unique players from the data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    players = set()&lt;br /&gt;
    for year_data in data.values():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            players.add(player['name'])&lt;br /&gt;
    return sorted(list(players))&lt;br /&gt;
&lt;br /&gt;
def get_player_by_name(data, player_name):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get player data for a specific player.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    player_data = []&lt;br /&gt;
    for year, year_data in data.items():&lt;br /&gt;
        for player in year_data:&lt;br /&gt;
            if player['name'].lower() == player_name.lower():&lt;br /&gt;
                player_data.append({&lt;br /&gt;
                    'year': year,&lt;br /&gt;
                    'name': player['name'],&lt;br /&gt;
                    'batting_average': player['batting_average']&lt;br /&gt;
                })&lt;br /&gt;
    return player_data&lt;br /&gt;
&lt;br /&gt;
# ==== MCP Tools ====&lt;br /&gt;
&lt;br /&gt;
# This tool works - use to test your MCP connection.&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def hello(name: str = &amp;quot;world&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Return a simple greeting to verify the server is working.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return f&amp;quot;Hello CSE 3300 Student, {name}!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Only two tools are required here&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_player_stats(player: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get a player's batting averages for both 1930 and 1940 seasons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch player data using the 'load_cardinals_data'&lt;br /&gt;
    # and 'get_player_by_name' functions&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_team_roster(year: str = &amp;quot;1930&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the roster of the Cardinals for the given year, listing player names and batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement the function to fetch and list the team roster for the specified year (1930 or 1940)&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def get_top_players(year: str = &amp;quot;1930&amp;quot;, limit: int = 10) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Get the top Cardinals players for a specific year, sorted by batting average.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to return the top 'limit' players sorted by their batting averages&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@mcp.tool()&lt;br /&gt;
async def search_players(search_term: str = &amp;quot;&amp;quot;) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Search for Cardinals players by name and show their batting averages.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # TODO: Implement this function to search for players by name and return their batting averages&lt;br /&gt;
&lt;br /&gt;
# ==== Server Startup ====&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    logger.info(&amp;quot;Starting Cardinals Historical Data MCP server...&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        mcp.run(transport=&amp;quot;stdio&amp;quot;)&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logger.error(f&amp;quot;Server error: {e}&amp;quot;, exc_info=True)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;dockerfile&amp;quot;&amp;gt;&lt;br /&gt;
FROM python:3.11-slim&lt;br /&gt;
&lt;br /&gt;
LABEL maintainer=&amp;quot;cardinals_server&amp;quot;&lt;br /&gt;
ENV PYTHONUNBUFFERED=1&lt;br /&gt;
&lt;br /&gt;
WORKDIR /app&lt;br /&gt;
&lt;br /&gt;
# Copy dependencies&lt;br /&gt;
COPY requirements.txt /app/requirements.txt&lt;br /&gt;
RUN pip install --no-cache-dir -r requirements.txt&lt;br /&gt;
&lt;br /&gt;
# Copy application files&lt;br /&gt;
COPY cardinals_server.py /app/cardinals_server.py&lt;br /&gt;
COPY data/ /app/data/&lt;br /&gt;
COPY *.md /app/&lt;br /&gt;
&lt;br /&gt;
# Create non-root user&lt;br /&gt;
RUN useradd -m -u 1000 mcpuser &amp;amp;&amp;amp; chown -R mcpuser:mcpuser /app&lt;br /&gt;
USER mcpuser&lt;br /&gt;
&lt;br /&gt;
CMD [&amp;quot;python&amp;quot;, &amp;quot;/app/cardinals_server.py&amp;quot;]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== &amp;lt;code&amp;gt;requirements.txt&amp;lt;/code&amp;gt; =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mcp&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is all you need unless you use extra libraries.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Build and Test the Container ====&lt;br /&gt;
&lt;br /&gt;
Build your Docker image:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker build -t cardinals-mcp-server:latest .&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ docker run --rm -it cardinals-mcp-server&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it launches correctly, you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
2025-10-19 20:00:00,000 - CardinalsMCP-server - INFO - Starting Cardinals Historical Data MCP server...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Connect in Claude Desktop ====&lt;br /&gt;
&lt;br /&gt;
===== Open Claude Desktop Settings =====&lt;br /&gt;
&lt;br /&gt;
On macOS, click '''Claude → Settings…''' from the top menu bar.  &lt;br /&gt;
(''Do not use the settings inside the Claude chat window; you need the app-level settings.'')&lt;br /&gt;
&lt;br /&gt;
This opens the '''Claude Desktop Settings''' panel, separate from your Claude account settings.&lt;br /&gt;
&lt;br /&gt;
===== Go to Developer Settings =====&lt;br /&gt;
&lt;br /&gt;
In the Settings window, select the '''Developer''' tab in the left sidebar.  &lt;br /&gt;
This section manages MCP servers and other developer tools.&lt;br /&gt;
&lt;br /&gt;
Click '''Edit Config''' to open the configuration file.  &lt;br /&gt;
If you do not have one yet, Claude will create it automatically.&lt;br /&gt;
&lt;br /&gt;
'''Configuration file locations:'''&lt;br /&gt;
&lt;br /&gt;
* macOS: &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* Windows: &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Add the MCP Gateway Configuration =====&lt;br /&gt;
&lt;br /&gt;
Replace (or merge) the contents of the file with the JSON below.  &lt;br /&gt;
This tells Claude to launch the '''Docker MCP Gateway''', which connects to your custom Cardinals MCP server.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;cardinals-mcp-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;command&amp;quot;: &amp;quot;docker&amp;quot;,&lt;br /&gt;
      &amp;quot;args&amp;quot;: [&lt;br /&gt;
        &amp;quot;run&amp;quot;,&lt;br /&gt;
        &amp;quot;--rm&amp;quot;,&lt;br /&gt;
        &amp;quot;-i&amp;quot;,&lt;br /&gt;
        &amp;quot;cardinals-mcp-server:latest&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Restart Claude Desktop =====&lt;br /&gt;
&lt;br /&gt;
After saving the file:&lt;br /&gt;
* Completely quit Claude Desktop (not just close the window).&lt;br /&gt;
* Reopen it.&lt;br /&gt;
&lt;br /&gt;
Claude will now start the MCP gateway automatically.&lt;br /&gt;
&lt;br /&gt;
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'''.&lt;br /&gt;
&lt;br /&gt;
Once connected, you can run commands like:&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the top 10 hitters from 1940.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Step 5: Test It Out ====&lt;br /&gt;
&lt;br /&gt;
Open a new chat in Claude and try:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;“Use CardinalsMCP to list the 1940 Cardinals roster sorted by batting average.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Show Johnny Mize’s batting averages for 1930 and 1940.”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Who were the top 5 hitters on the 1930 Cardinals?”&amp;lt;/pre&amp;gt;  &lt;br /&gt;
&amp;lt;pre&amp;gt;“Search for players with ‘Martin’ in their name.”&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Claude will call your MCP tool, execute your Python script inside Docker, and display formatted results.&lt;br /&gt;
&lt;br /&gt;
== Grading ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We will be grading the following aspects of your work.  There are 50 points total.&lt;br /&gt;
&lt;br /&gt;
'''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. ''' &lt;br /&gt;
&lt;br /&gt;
Upload your baseball code and a file with your Regular expressions.&lt;br /&gt;
&lt;br /&gt;
# '''Regular Expressions (15 Points):'''&lt;br /&gt;
#* '''Your regex files should contain your regex (with no delimiters or flags) and nothing else.'''&lt;br /&gt;
#* Regular Expression 1 Correct and saved in a file named &amp;lt;code&amp;gt;regex1.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 2 Correct and saved in a file named &amp;lt;code&amp;gt;regex2.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#* Regular Expression 3 Correct and saved in a file named &amp;lt;code&amp;gt;regex3.txt&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats Counter (25 Points):'''&lt;br /&gt;
#* Solution is written entirely in Python and saved in a file named &amp;lt;code&amp;gt;baseball.py&amp;lt;/code&amp;gt; (5 points)&lt;br /&gt;
#*: ''Note: Failing to write your code in Python 3 will result in losing, at a minimum, points for this category.''&lt;br /&gt;
#* Correct usage of one or more regular expressions to parse and extract data from each line of the input file (5 points)&lt;br /&gt;
#*: ''Note: You should not be using &amp;lt;code&amp;gt;str.split&amp;lt;/code&amp;gt; to extract data.''&lt;br /&gt;
#* Script prints a usage message if a command line argument is not present (3 points)&lt;br /&gt;
#*: ''Note: For an example of a usage message, see [https://en.wikipedia.org/wiki/Usage_message#Examples this link.]''&lt;br /&gt;
#* Output is correct for all test cases (12 points)&lt;br /&gt;
#*: ''This includes sorting and rounding.''&lt;br /&gt;
&lt;br /&gt;
# '''Baseball Stats MCP Extension (10 Points):'''&lt;br /&gt;
#* Docker image builds successfully without errors (5 points)&lt;br /&gt;
#*: ''MCP server (`cardinals_server.py`) loads player data and returns correct results.''&lt;br /&gt;
#* Includes at least 2 MCP tools (3 points)&lt;br /&gt;
#*: ''Each tool must function correctly and return expected output through Claude Desktop.''&lt;br /&gt;
#* README includes 3 different example prompts used in Claude (2 points)&lt;br /&gt;
#*: ''Each example should show the prompt you asked and the MCP’s response.''&lt;br /&gt;
&lt;br /&gt;
[[Category:Module 6]]&lt;br /&gt;
[[Category:Modules]]&lt;/div&gt;</summary>
		<author><name>Todd</name></author>
		
	</entry>
</feed>