From 8361ec51167d099bd607dfb3f1ef7af26b35da59 Mon Sep 17 00:00:00 2001 From: Nick Ferguson <98440329+itprodirect@users.noreply.github.com> Date: Sat, 11 Oct 2025 08:41:40 -0400 Subject: [PATCH] refactor: package business tools for reusable imports --- .github/workflows/ci.yml | 6 + README.md | 10 +- notebooks/Model-Context-Protocol-101.ipynb | 632 ++++++++++----------- pyproject.toml | 26 + requirements.txt | 11 +- src/business_tools.py | 84 --- src/mcp101/__init__.py | 23 + src/mcp101/business_tools.py | 62 ++ src/{ => mcp101}/cli.py | 17 +- tests/test_business_tools.py | 2 +- tests/test_cli.py | 13 +- 11 files changed, 467 insertions(+), 419 deletions(-) create mode 100644 pyproject.toml delete mode 100644 src/business_tools.py create mode 100644 src/mcp101/__init__.py create mode 100644 src/mcp101/business_tools.py rename src/{ => mcp101}/cli.py (78%) 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 -[![Python](https://img.shields.io/badge/Python-3.8%2B-blue.svg)](https://www.python.org/) +[![Python](https://img.shields.io/badge/Python-3.10%2B-blue.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) [![Build Status](https://img.shields.io/github/actions/workflow/status/itprodirect/Model-Context-Protocol-101/ci.yml)](https://github.com/itprodirect/Model-Context-Protocol-101/actions) [![Dependencies](https://img.shields.io/badge/Dependencies-Updated-brightgreen.svg)](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()