diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4eaa8930..dc631ab8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,6 +18,12 @@ jobs:
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+ - name: Lint with ruff
+ run: |
+ ruff .
+ - name: Type check with mypy
+ run: |
+ mypy --strict .
- name: Run tests
run: |
if [ -d tests ]; then pytest -q; else echo "No tests"; fi
diff --git a/README.md b/README.md
index c21752b2..b280412f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# 🚀 Model-Context-Protocol-101
-[](https://www.python.org/)
+[](https://www.python.org/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/itprodirect/Model-Context-Protocol-101/actions)
[](https://github.com/itprodirect/Model-Context-Protocol-101/blob/main/requirements.txt)
@@ -47,6 +47,8 @@ venv\Scripts\activate
```bash
pip install -r requirements.txt
```
+This installs both third-party libraries and the local `mcp101` package, so the
+CLI and utilities can be imported from anywhere in the project.
Time Saver: A single command installs everything needed so independent agents can start experimenting right away.
@@ -112,10 +114,10 @@ Use the command-line interface to run common tasks directly from the terminal.
```bash
# Calculate profit from revenue and cost
-python src/cli.py profit 1000 600
+mcp101-cli profit 1000 600
# Total commission from the sample dataset
-python src/cli.py commission data/insurance_sales.csv
+mcp101-cli commission data/insurance_sales.csv
```
---
@@ -159,7 +161,7 @@ For questions or collaborations, connect with me on **LinkedIn** or open an **Is
**Virtual environment won't activate**
Make sure you run `python -m venv venv` and then activate it with
`source venv/bin/activate` on Mac/Linux or `venv\Scripts\activate` on Windows.
-Verify Python 3.8+ is installed.
+Verify Python 3.10+ is installed.
**Missing packages**
Run `pip install -r requirements.txt` from the project root while your virtual
diff --git a/notebooks/Model-Context-Protocol-101.ipynb b/notebooks/Model-Context-Protocol-101.ipynb
index 49db7cf6..b15ff42e 100644
--- a/notebooks/Model-Context-Protocol-101.ipynb
+++ b/notebooks/Model-Context-Protocol-101.ipynb
@@ -1,324 +1,324 @@
{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 1,
- "id": "e6b78340-5c44-428c-82db-0aecd6ca7e96",
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd\n",
- "from business_tools import (\n",
- " calculate_commission,\n",
- " calculate_total_premium,\n",
- " filter_policies_by_state,\n",
- " load_insurance_sales,\n",
- ")\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "id": "75352684-14b1-4fba-9751-fa449f7346e9",
- "metadata": {
- "scrolled": true
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Python 3.10.0\n"
- ]
- }
- ],
- "source": [
- "!python --version"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "e9d3671f-6255-4b7f-a353-4f10c4aa6686",
- "metadata": {},
- "source": [
- "## Step 1: Create the Model Context Protocol Server"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "id": "34a3ed24-8df7-4afd-8bb7-8d1e00fc83ce",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "MCP Server ready! Tools: calculate_profit.\n"
- ]
- }
- ],
- "source": [
- "from fastmcp import FastMCP\n",
- "\n",
- "# Initialize MCP Server\n",
- "mcp = FastMCP(\"BusinessTools\")\n",
- "\n",
- "@mcp.tool()\n",
- "def calculate_profit(revenue: float, expenses: float) -> float:\n",
- " \"\"\"Calculate profit.\"\"\"\n",
- " return revenue - expenses\n",
- "\n",
- "print(\"MCP Server ready! Tools: calculate_profit.\")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "c061d2bb-f4fa-4284-9085-5ae2e9ddee55",
- "metadata": {},
- "source": [
- "## Step 2: Test the MCP Tool Locally"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "id": "31892845-8c8d-417a-a441-9893f86183d0",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Profit: 400\n"
- ]
- }
- ],
- "source": [
- "profit = calculate_profit(1000, 600)\n",
- "print(\"Profit:\", profit)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "10f55680-8f86-4b0f-a575-abf2be40aae4",
- "metadata": {},
- "source": [
- "## Step 3: Expand MCP by Adding More Functions"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "6bfbbc4d-ecee-4492-a472-b85fca86c44c",
- "metadata": {},
- "outputs": [],
- "source": [
- "\n",
- "@mcp.tool()\n",
- "def get_sales_from_csv(file_path: str) -> float:\n",
- " \"\"\"Read total sales from a CSV file.\"\"\"\n",
- " df = pd.read_csv(file_path)\n",
- " return df[\"sales\"].sum()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a1b749f7-f8b3-4ae0-b07f-599040b5ea43",
- "metadata": {},
- "source": [
- "## Step 4: Create the CSV File"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "id": "da41522c-f512-4772-864e-d084010cbbda",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "CSV file created successfully!\n"
- ]
- }
- ],
- "source": [
- "csv_data = \"\"\"date,sales\n",
- "2024-02-26,200\n",
- "2024-02-27,450\n",
- "2024-02-28,300\n",
- "\"\"\"\n",
- "\n",
- "# Save to a file in the data folder\n",
- "with open(\"data/sales_data.csv\", \"w\") as file:\n",
- " file.write(csv_data)\n",
- "\n",
- "print(\"CSV file created successfully!\")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "cff6480d-7f34-42aa-8cbe-b68ba3cedc77",
- "metadata": {},
- "source": [
- "## Step 5: Run get_sales_from_csv"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "id": "ef4b7533-cd81-4d68-9a38-73d778f81c24",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Total Sales: 950\n"
- ]
- }
- ],
- "source": [
- "sales_total = get_sales_from_csv(\"data/sales_data.csv\")\n",
- "print(\"Total Sales:\", sales_total)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Step 6: Calculate Commission"
- ]
- },
- {
- "cell_type": "code",
- "metadata": {},
- "execution_count": null,
- "outputs": [],
- "source": [
- "commission = calculate_commission([300, 700, 200], rate=0.1)\n",
- "print('Commission:', commission)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "id": "step-7-load",
- "source": [
- "## Step 7: Load `insurance_sales.csv`"
- ]
- },
- {
- "cell_type": "code",
- "metadata": {},
- "execution_count": null,
- "outputs": [],
- "id": "step-7-code",
- "source": [
- "records = load_insurance_sales('data/insurance_sales.csv')\n",
- "print(f'Total records: {len(records)}')"
- ]
- },
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "e6b78340-5c44-428c-82db-0aecd6ca7e96",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "from mcp101.business_tools import (\n",
+ " calculate_commission,\n",
+ " calculate_total_premium,\n",
+ " filter_policies_by_state,\n",
+ " load_insurance_sales,\n",
+ ")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "75352684-14b1-4fba-9751-fa449f7346e9",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
{
- "cell_type": "code",
- "metadata": {},
- "execution_count": null,
- "outputs": [],
- "id": "step-8-code",
- "source": [
- "total_premium = calculate_total_premium(records)\n",
- "print('Total Premium:', total_premium)"
- ]
- },
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Python 3.10.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "!python --version"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e9d3671f-6255-4b7f-a353-4f10c4aa6686",
+ "metadata": {},
+ "source": [
+ "## Step 1: Create the Model Context Protocol Server"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "34a3ed24-8df7-4afd-8bb7-8d1e00fc83ce",
+ "metadata": {},
+ "outputs": [
{
- "cell_type": "code",
- "metadata": {},
- "execution_count": null,
- "outputs": [],
- "id": "step-9-code",
- "source": [
- "ca_policies = filter_policies_by_state(records, 'CA')\n",
- "print('CA Policies:', len(ca_policies))"
- ]
- },
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "MCP Server ready! Tools: calculate_profit.\n"
+ ]
+ }
+ ],
+ "source": [
+ "from fastmcp import FastMCP\n",
+ "\n",
+ "# Initialize MCP Server\n",
+ "mcp = FastMCP(\"BusinessTools\")\n",
+ "\n",
+ "@mcp.tool()\n",
+ "def calculate_profit(revenue: float, expenses: float) -> float:\n",
+ " \"\"\"Calculate profit.\"\"\"\n",
+ " return revenue - expenses\n",
+ "\n",
+ "print(\"MCP Server ready! Tools: calculate_profit.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c061d2bb-f4fa-4284-9085-5ae2e9ddee55",
+ "metadata": {},
+ "source": [
+ "## Step 2: Test the MCP Tool Locally"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "31892845-8c8d-417a-a441-9893f86183d0",
+ "metadata": {},
+ "outputs": [
{
- "cell_type": "markdown",
- "metadata": {},
- "id": "step-9-explain",
- "source": [
- "These steps show how quickly you can tally premiums and isolate policies by state\u2014handy for reviewing territory performance and carrier payouts."
- ]
- },
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Profit: 400\n"
+ ]
+ }
+ ],
+ "source": [
+ "profit = calculate_profit(1000, 600)\n",
+ "print(\"Profit:\", profit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10f55680-8f86-4b0f-a575-abf2be40aae4",
+ "metadata": {},
+ "source": [
+ "## Step 3: Expand MCP by Adding More Functions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "6bfbbc4d-ecee-4492-a472-b85fca86c44c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "@mcp.tool()\n",
+ "def get_sales_from_csv(file_path: str) -> float:\n",
+ " \"\"\"Read total sales from a CSV file.\"\"\"\n",
+ " df = pd.read_csv(file_path)\n",
+ " return df[\"sales\"].sum()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a1b749f7-f8b3-4ae0-b07f-599040b5ea43",
+ "metadata": {},
+ "source": [
+ "## Step 4: Create the CSV File"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "da41522c-f512-4772-864e-d084010cbbda",
+ "metadata": {},
+ "outputs": [
{
- "cell_type": "markdown",
- "id": "3aee3fb6-e2ea-4761-b4e1-41d13ef8c507",
- "metadata": {},
- "source": [
- "# \ud83d\ude80 Super Simple Summary: MCP Project Overview\n",
- "\n",
- "## **What We Did** \ud83d\udee0\ufe0f\n",
- "1. **Set up our Python environment** \n",
- " - Installed Python 3.10 \n",
- " - Created a virtual environment (venv) \n",
- " - Installed necessary packages (like pandas) \n",
- " - Ensured Jupyter Notebook was using the correct environment \n",
- "\n",
- "2. **Created a simple MCP (Model Context Protocol) server** \n",
- " - Used `FastMCP` to set up a tool that can process functions \n",
- " - Created a `calculate_profit` function to subtract expenses from revenue \n",
- " - Registered that function with MCP \n",
- "\n",
- "3. **Expanded the MCP tool with more functionality** \n",
- " - Added a new function `get_sales_from_csv` to read a CSV file and sum the sales column \n",
- " - Used `pandas` to load and process the CSV data \n",
- "\n",
- "4. **Tested everything** \u2705 \n",
- " - Created a CSV file inside the notebook \n",
- " - Ran our function to sum the sales \n",
- " - Verified that the result was correct (`950` total sales) \n",
- "\n",
- "---\n",
- "\n",
- "## **Why Is This Important?** \ud83d\udd25\n",
- "- **MCP helps structure tools for AI models** \ud83e\udde0 \n",
- " \u2192 Instead of just writing random functions, MCP lets us build structured \"tools\" that can be reused. \n",
- "\n",
- "- **We practiced file handling, data processing, and function registration** \ud83d\udcc2 \n",
- " \u2192 This is a **real-world skill** that applies to **machine learning, AI automation, and software development**. \n",
- "\n",
- "---\n",
- "\n",
- "## **Next Steps:**\n",
- "Would you like to: \n",
- "1\ufe0f\u20e3 **Expand this project** (e.g., more data analysis, visualization)? \n",
- "2\ufe0f\u20e3 **Start a new MCP-based project**? \n",
- "3\ufe0f\u20e3 **Dive deeper into MCP internals** (how it works under the hood)? \n",
- "\n",
- "\ud83d\ude80\ud83d\udd25 Let\u2019s build something amazing!"
- ]
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CSV file created successfully!\n"
+ ]
}
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.10.0"
+ ],
+ "source": [
+ "csv_data = \"\"\"date,sales\n",
+ "2024-02-26,200\n",
+ "2024-02-27,450\n",
+ "2024-02-28,300\n",
+ "\"\"\"\n",
+ "\n",
+ "# Save to a file in the data folder\n",
+ "with open(\"data/sales_data.csv\", \"w\") as file:\n",
+ " file.write(csv_data)\n",
+ "\n",
+ "print(\"CSV file created successfully!\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cff6480d-7f34-42aa-8cbe-b68ba3cedc77",
+ "metadata": {},
+ "source": [
+ "## Step 5: Run get_sales_from_csv"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "ef4b7533-cd81-4d68-9a38-73d778f81c24",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Total Sales: 950\n"
+ ]
}
+ ],
+ "source": [
+ "sales_total = get_sales_from_csv(\"data/sales_data.csv\")\n",
+ "print(\"Total Sales:\", sales_total)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Step 6: Calculate Commission"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {},
+ "execution_count": null,
+ "outputs": [],
+ "source": [
+ "commission = calculate_commission([300, 700, 200], rate=0.1)\n",
+ "print('Commission:', commission)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "id": "step-7-load",
+ "source": [
+ "## Step 7: Load `insurance_sales.csv`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {},
+ "execution_count": null,
+ "outputs": [],
+ "id": "step-7-code",
+ "source": [
+ "records = load_insurance_sales('data/insurance_sales.csv')\n",
+ "print(f'Total records: {len(records)}')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {},
+ "execution_count": null,
+ "outputs": [],
+ "id": "step-8-code",
+ "source": [
+ "total_premium = calculate_total_premium(records)\n",
+ "print('Total Premium:', total_premium)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {},
+ "execution_count": null,
+ "outputs": [],
+ "id": "step-9-code",
+ "source": [
+ "ca_policies = filter_policies_by_state(records, 'CA')\n",
+ "print('CA Policies:', len(ca_policies))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "id": "step-9-explain",
+ "source": [
+ "These steps show how quickly you can tally premiums and isolate policies by state\u2014handy for reviewing territory performance and carrier payouts."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3aee3fb6-e2ea-4761-b4e1-41d13ef8c507",
+ "metadata": {},
+ "source": [
+ "# \ud83d\ude80 Super Simple Summary: MCP Project Overview\n",
+ "\n",
+ "## **What We Did** \ud83d\udee0\ufe0f\n",
+ "1. **Set up our Python environment** \n",
+ " - Installed Python 3.10 \n",
+ " - Created a virtual environment (venv) \n",
+ " - Installed necessary packages (like pandas) \n",
+ " - Ensured Jupyter Notebook was using the correct environment \n",
+ "\n",
+ "2. **Created a simple MCP (Model Context Protocol) server** \n",
+ " - Used `FastMCP` to set up a tool that can process functions \n",
+ " - Created a `calculate_profit` function to subtract expenses from revenue \n",
+ " - Registered that function with MCP \n",
+ "\n",
+ "3. **Expanded the MCP tool with more functionality** \n",
+ " - Added a new function `get_sales_from_csv` to read a CSV file and sum the sales column \n",
+ " - Used `pandas` to load and process the CSV data \n",
+ "\n",
+ "4. **Tested everything** \u2705 \n",
+ " - Created a CSV file inside the notebook \n",
+ " - Ran our function to sum the sales \n",
+ " - Verified that the result was correct (`950` total sales) \n",
+ "\n",
+ "---\n",
+ "\n",
+ "## **Why Is This Important?** \ud83d\udd25\n",
+ "- **MCP helps structure tools for AI models** \ud83e\udde0 \n",
+ " \u2192 Instead of just writing random functions, MCP lets us build structured \"tools\" that can be reused. \n",
+ "\n",
+ "- **We practiced file handling, data processing, and function registration** \ud83d\udcc2 \n",
+ " \u2192 This is a **real-world skill** that applies to **machine learning, AI automation, and software development**. \n",
+ "\n",
+ "---\n",
+ "\n",
+ "## **Next Steps:**\n",
+ "Would you like to: \n",
+ "1\ufe0f\u20e3 **Expand this project** (e.g., more data analysis, visualization)? \n",
+ "2\ufe0f\u20e3 **Start a new MCP-based project**? \n",
+ "3\ufe0f\u20e3 **Dive deeper into MCP internals** (how it works under the hood)? \n",
+ "\n",
+ "\ud83d\ude80\ud83d\udd25 Let\u2019s build something amazing!"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
},
- "nbformat": 4,
- "nbformat_minor": 5
-}
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..716be317
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,26 @@
+[build-system]
+requires = ["setuptools>=68", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "mcp101"
+version = "0.1.0"
+description = "Utilities and CLI for the Model Context Protocol 101 tutorial"
+readme = "README.md"
+requires-python = ">=3.10"
+license = {text = "MIT"}
+authors = [{name = "Model Context Protocol Team"}]
+keywords = ["model-context-protocol", "insurance", "tutorial"]
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.10",
+ "License :: OSI Approved :: MIT License",
+]
+
+[tool.setuptools]
+packages = ["mcp101"]
+package-dir = {"" = "src"}
+
+[project.scripts]
+mcp101-cli = "mcp101.cli:main"
diff --git a/requirements.txt b/requirements.txt
index 7f7cc3d1..7f829555 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,7 @@
-fastmcp
-pandas
-jupyter
-pytest
+fastmcp==2.12.2
+pandas==2.3.2
+jupyter==1.1.1
+pytest==8.4.1
+ruff==0.12.11
+mypy==1.17.1
+-e .
diff --git a/src/business_tools.py b/src/business_tools.py
deleted file mode 100644
index b2f6b605..00000000
--- a/src/business_tools.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""Utility functions for business operations."""
-
-import csv
-
-
-def calculate_profit(revenue: float, cost: float) -> float:
- """Return the profit calculated as revenue minus cost."""
- return revenue - cost
-
-
-def get_sales_from_csv(filename: str) -> float:
- """Read a CSV of sales data and return total sales as float."""
- total = 0.0
- with open(filename, newline="") as csvfile:
- reader = csv.DictReader(csvfile)
- for row in reader:
- total += float(row["sales"])
- return total
-
-
-def calculate_commission(premiums: list[float], rate: float = 0.1) -> float:
- """Return total commission in USD rounded to two decimals."""
- return round(sum(premiums) * rate, 2)
-
-
-def load_insurance_sales(filename: str) -> list[dict[str, str]]:
- """Return all rows from an insurance sales CSV as dictionaries.
-
- Args:
- filename: Path to ``insurance_sales.csv``.
-
- Returns:
- A list of dictionaries, one per CSV row.
- """
- with open(filename, newline="") as csvfile:
- reader = csv.DictReader(csvfile)
- return list(reader)
-
-
-def total_commission(records: list[dict[str, str]]) -> float:
- """Return the sum of the ``Commission`` column from insurance records.
-
- Args:
- records: Rows loaded via :func:`load_insurance_sales`.
-
- Returns:
- Total commission as a float.
- """
- total = 0.0
- for row in records:
- total += float(row["Commission"])
- return total
-
-
-def filter_by_state(records: list[dict[str, str]], state: str) -> list[dict[str, str]]:
- """Return only the rows matching a given state code.
-
- Args:
- records: Insurance sale rows.
- state: Two-letter state abbreviation.
-
- Returns:
- Filtered list containing rows where ``State`` equals ``state``.
- """
- return [row for row in records if row["State"] == state]
-
-def calculate_total_premium(records: list[dict[str, str]]) -> float:
- """Return the sum of the ``Premium`` column from insurance records.
-
- Args:
- records: Rows loaded via :func:`load_insurance_sales`.
-
- Returns:
- Total premium as a float.
- """
- total = 0.0
- for row in records:
- total += float(row["Premium"])
- return total
-
-
-def filter_policies_by_state(records: list[dict[str, str]], state: str) -> list[dict[str, str]]:
- """Wrapper around :func:`filter_by_state` with a clearer name."""
- return filter_by_state(records, state)
diff --git a/src/mcp101/__init__.py b/src/mcp101/__init__.py
new file mode 100644
index 00000000..a4e42a38
--- /dev/null
+++ b/src/mcp101/__init__.py
@@ -0,0 +1,23 @@
+"""Model Context Protocol 101 utilities."""
+
+from .business_tools import (
+ calculate_commission,
+ calculate_profit,
+ calculate_total_premium,
+ filter_by_state,
+ filter_policies_by_state,
+ get_sales_from_csv,
+ load_insurance_sales,
+ total_commission,
+)
+
+__all__ = [
+ "calculate_commission",
+ "calculate_profit",
+ "calculate_total_premium",
+ "filter_by_state",
+ "filter_policies_by_state",
+ "get_sales_from_csv",
+ "load_insurance_sales",
+ "total_commission",
+]
diff --git a/src/mcp101/business_tools.py b/src/mcp101/business_tools.py
new file mode 100644
index 00000000..74f285c2
--- /dev/null
+++ b/src/mcp101/business_tools.py
@@ -0,0 +1,62 @@
+"""Utility functions for business operations."""
+
+import csv
+from pathlib import Path
+from typing import Iterable
+
+PathLike = str | Path
+Record = dict[str, str]
+
+
+def calculate_profit(revenue: float, cost: float) -> float:
+ """Return the profit calculated as revenue minus cost."""
+ return revenue - cost
+
+
+def get_sales_from_csv(filename: PathLike) -> float:
+ """Read a CSV of sales data and return total sales as float."""
+ total = 0.0
+ with Path(filename).expanduser().open(newline="", encoding="utf-8") as csvfile:
+ reader = csv.DictReader(csvfile)
+ for row in reader:
+ total += float(row["sales"])
+ return total
+
+
+def calculate_commission(premiums: Iterable[float], rate: float = 0.1) -> float:
+ """Return total commission in USD rounded to two decimals."""
+ return round(sum(premiums) * rate, 2)
+
+
+def load_insurance_sales(filename: PathLike) -> list[Record]:
+ """Return all rows from an insurance sales CSV as dictionaries."""
+ with Path(filename).expanduser().open(newline="", encoding="utf-8") as csvfile:
+ reader = csv.DictReader(csvfile)
+ return list(reader)
+
+
+def total_commission(records: Iterable[Record]) -> float:
+ """Return the sum of the ``Commission`` column from insurance records."""
+ total = 0.0
+ for row in records:
+ total += float(row["Commission"])
+ return total
+
+
+def filter_by_state(records: Iterable[Record], state: str) -> list[Record]:
+ """Return only the rows matching a given state code."""
+ state_upper = state.upper()
+ return [row for row in records if row["State"].upper() == state_upper]
+
+
+def calculate_total_premium(records: Iterable[Record]) -> float:
+ """Return the sum of the ``Premium`` column from insurance records."""
+ total = 0.0
+ for row in records:
+ total += float(row["Premium"])
+ return total
+
+
+def filter_policies_by_state(records: Iterable[Record], state: str) -> list[Record]:
+ """Wrapper around :func:`filter_by_state` with a clearer name."""
+ return filter_by_state(records, state)
diff --git a/src/cli.py b/src/mcp101/cli.py
similarity index 78%
rename from src/cli.py
rename to src/mcp101/cli.py
index 642c6b59..6d1d3a9a 100644
--- a/src/cli.py
+++ b/src/mcp101/cli.py
@@ -8,13 +8,18 @@
import argparse
from pathlib import Path
+if __package__:
+ from . import business_tools as _business_tools
+else: # pragma: no cover - fallback when executed as a script
+ import sys
-from business_tools import (
- calculate_profit,
- calculate_total_premium,
- load_insurance_sales,
- total_commission,
-)
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+ from mcp101 import business_tools as _business_tools
+
+calculate_profit = _business_tools.calculate_profit
+calculate_total_premium = _business_tools.calculate_total_premium
+load_insurance_sales = _business_tools.load_insurance_sales
+total_commission = _business_tools.total_commission
def main(argv: list[str] | None = None) -> None:
diff --git a/tests/test_business_tools.py b/tests/test_business_tools.py
index 1ef4ad6b..ecaa52fb 100644
--- a/tests/test_business_tools.py
+++ b/tests/test_business_tools.py
@@ -6,7 +6,7 @@
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "src"))
-from business_tools import ( # noqa: E402
+from mcp101.business_tools import ( # noqa: E402
calculate_profit,
get_sales_from_csv,
calculate_commission,
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 8075c1d5..a2dcf6e4 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,15 +1,20 @@
+import os
import subprocess
import sys
from pathlib import Path
-REPO_ROOT = Path(__file__).resolve().parents[1]
-CLI = REPO_ROOT / "src" / "cli.py"
+MODULE = "mcp101.cli"
def run_cli(args: list[str]) -> str:
"""Run the CLI with the given arguments and return stdout."""
- cmd = [sys.executable, str(CLI), *args]
- result = subprocess.check_output(cmd, text=True)
+ cmd = [sys.executable, "-m", MODULE, *args]
+ env = os.environ.copy()
+ src_dir = Path(__file__).resolve().parents[1] / "src"
+ env["PYTHONPATH"] = os.pathsep.join(
+ [str(src_dir), env.get("PYTHONPATH", "")]
+ ).rstrip(os.pathsep)
+ result = subprocess.check_output(cmd, text=True, env=env)
return result.strip()