#
tokens: 9867/50000 24/24 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   ├── actions
│   │   └── setup-python-env
│   │       └── action.yml
│   └── workflows
│       ├── main.yml
│       ├── on-release-main.yml
│       └── validate-codecov-config.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── .vscode
│   └── settings.json
├── assets
│   ├── config-cursor.png
│   └── showcase.jpeg
├── codecov.yaml
├── CONTRIBUTING.md
├── Dockerfile
├── examples
│   └── index.html
├── LICENSE
├── Makefile
├── pyproject.toml
├── pytest.ini
├── README.md
├── tests
│   └── test_hello.py
├── tox.ini
├── uv.lock
└── yourware_mcp
    ├── __init__.py
    ├── app.py
    ├── cli.py
    ├── client.py
    ├── credentials.py
    ├── log.py
    └── utils.py
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
3.12

```

--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------

```yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: "v5.0.0"
    hooks:
      - id: check-case-conflict
      - id: check-merge-conflict
      - id: check-toml
      - id: check-yaml
      - id: check-json
        exclude: ^.devcontainer/devcontainer.json
      - id: pretty-format-json
        exclude: ^.devcontainer/devcontainer.json
        args: [--autofix]
      - id: end-of-file-fixer
      - id: trailing-whitespace
  - repo: https://github.com/executablebooks/mdformat
    rev: 0.7.22
    hooks:
      - id: mdformat
        additional_dependencies:
          [mdformat-gfm, mdformat-frontmatter, mdformat-footnote]
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: "v0.11.9"
    hooks:
      - id: ruff
        args: [--exit-non-zero-on-fix]
      - id: ruff-format

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
docs/source

# From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# Vscode config files
# .vscode/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
[![Release](https://img.shields.io/github/v/release/ai-zerolab/yourware-mcp)](https://img.shields.io/github/v/release/ai-zerolab/yourware-mcp)
[![Build status](https://img.shields.io/github/actions/workflow/status/ai-zerolab/yourware-mcp/main.yml?branch=main)](https://github.com/ai-zerolab/yourware-mcp/actions/workflows/main.yml?query=branch%3Amain)
[![Commit activity](https://img.shields.io/github/commit-activity/m/ai-zerolab/yourware-mcp)](https://img.shields.io/github/commit-activity/m/ai-zerolab/yourware-mcp)
[![License](https://img.shields.io/github/license/ai-zerolab/yourware-mcp)](https://img.shields.io/github/license/ai-zerolab/yourware-mcp)

<!-- [![codecov](https://codecov.io/gh/ai-zerolab/yourware-mcp/branch/main/graph/badge.svg)](https://codecov.io/gh/ai-zerolab/yourware-mcp) -->

# Yourware MCP

MCP server to upload your project to [yourware](https://www.yourware.so). Support single file or directory.

## Showcase

Visit on [yourware](https://v9gfmmif5s.app.yourware.so/): https://v9gfmmif5s.app.yourware.so/

![Showcase](./assets/showcase.jpeg)

## Pre-requisites

1. You need to login to [yourware](https://www.yourware.so)
1. Then you can create a new API key, and set the `YOURWARE_API_KEY` environment variable. Don't worry, you chat with LLM to create and store the API key.

## Configuration

### General configuration

You can use the following configuration for cline/cursor/windsurf...

```json
{
  "mcpServers": {
    "yourware-mcp": {
      "command": "uvx",
      "args": ["yourware-mcp@latest", "stdio"],
      "env": {}
    }
  }
}
```

### Cursor config guide

In cursor settings -> features -> MCP Servers, Add a new MCP Server, name it `yourware-mcp` and set the command to `uvx yourware-mcp@latest stdio`

![Config cursor screenshot](./assets/config-cursor.png)

### Config claude code

```bash
claude mcp add yourware-mcp -s user -- uvx yourware-mcp@latest stdio
```

## Available environments variables

`YOURWARE_API_KEY` for the API key, you can also let llm config it for you.

```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
# Contributing to `yourware-mcp`

Contributions are welcome, and they are greatly appreciated!
Every little bit helps, and credit will always be given.

You can contribute in many ways:

# Types of Contributions

## Report Bugs

Report bugs at https://github.com/ai-zerolab/yourware-mcp/issues

If you are reporting a bug, please include:

- Your operating system name and version.
- Any details about your local setup that might be helpful in troubleshooting.
- Detailed steps to reproduce the bug.

## Fix Bugs

Look through the GitHub issues for bugs.
Anything tagged with "bug" and "help wanted" is open to whoever wants to implement a fix for it.

## Implement Features

Look through the GitHub issues for features.
Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it.

## Write Documentation

yourware-mcp could always use more documentation, whether as part of the official docs, in docstrings, or even on the web in blog posts, articles, and such.

## Submit Feedback

The best way to send feedback is to file an issue at https://github.com/ai-zerolab/yourware-mcp/issues.

If you are proposing a new feature:

- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Remember that this is a volunteer-driven project, and that contributions
  are welcome :)

# Get Started!

Ready to contribute? Here's how to set up `yourware-mcp` for local development.
Please note this documentation assumes you already have `uv` and `Git` installed and ready to go.

1. Fork the `yourware-mcp` repo on GitHub.

1. Clone your fork locally:

```bash
cd <directory_in_which_repo_should_be_created>
git clone [email protected]:YOUR_NAME/yourware-mcp.git
```

3. Now we need to install the environment. Navigate into the directory

```bash
cd yourware-mcp
```

Then, install and activate the environment with:

```bash
uv sync
```

4. Install pre-commit to run linters/formatters at commit time:

```bash
uv run pre-commit install
```

5. Create a branch for local development:

```bash
git checkout -b name-of-your-bugfix-or-feature
```

Now you can make your changes locally.

6. Don't forget to add test cases for your added functionality to the `tests` directory.

1. When you're done making changes, check that your changes pass the formatting tests.

```bash
make check
```

Now, validate that all unit tests are passing:

```bash
make test
```

9. Before raising a pull request you should also run tox.
   This will run the tests across different versions of Python:

```bash
tox
```

This requires you to have multiple versions of python installed.
This step is also triggered in the CI/CD pipeline, so you could also choose to skip this step locally.

10. Commit your changes and push your branch to GitHub:

```bash
git add .
git commit -m "Your detailed description of your changes."
git push origin name-of-your-bugfix-or-feature
```

11. Submit a pull request through the GitHub website.

# Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

1. The pull request should include tests.

1. If the pull request adds functionality, the docs should be updated.
   Put your new functionality into a function with a docstring, and add the feature to the list in `README.md`.

```

--------------------------------------------------------------------------------
/yourware_mcp/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/tests/test_hello.py:
--------------------------------------------------------------------------------

```python
def test_hello():
    assert 1 == 1

```

--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------

```
# pytest.ini
[pytest]
asyncio_mode = auto

```

--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------

```json
{
  "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python"
}

```

--------------------------------------------------------------------------------
/yourware_mcp/utils.py:
--------------------------------------------------------------------------------

```python
def urljoin(base: str, url: str) -> str:
    return "/".join([base.rstrip("/"), url.lstrip("/")])

```

--------------------------------------------------------------------------------
/yourware_mcp/log.py:
--------------------------------------------------------------------------------

```python
import os

USER_DEFINED_LOG_LEVEL = os.getenv("YOURWARE_MCP_LOG_LEVEL", "INFO")

os.environ["LOGURU_LEVEL"] = USER_DEFINED_LOG_LEVEL

from loguru import logger  # noqa: E402

__all__ = ["logger"]

```

--------------------------------------------------------------------------------
/codecov.yaml:
--------------------------------------------------------------------------------

```yaml
coverage:
  range: 70..100
  round: down
  precision: 1
  status:
    project:
      default:
        target: 90%
        threshold: 0.5%
    patch:
      default:
        target: auto
        threshold: 0%
        informational: true
codecov:
 token: f927bff4-d404-4986-8c11-624eadda8431

```

--------------------------------------------------------------------------------
/yourware_mcp/cli.py:
--------------------------------------------------------------------------------

```python
import typer

from yourware_mcp.app import mcp

app = typer.Typer()


@app.command()
def stdio():
    mcp.run(transport="stdio")


@app.command()
def sse(
    host: str = "localhost",
    port: int = 9123,
):
    mcp.settings.host = host
    mcp.settings.port = port
    mcp.run(transport="sse")


if __name__ == "__main__":
    app(["stdio"])

```

--------------------------------------------------------------------------------
/.github/workflows/validate-codecov-config.yml:
--------------------------------------------------------------------------------

```yaml
name: validate-codecov-config

on:
  pull_request:
    paths: [codecov.yaml]
  push:
    branches: [main]

jobs:
  validate-codecov-config:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - name: Validate codecov configuration
        run: curl -sSL --fail-with-body --data-binary @codecov.yaml https://codecov.io/validate

```

--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------

```
[tox]
skipsdist = true
envlist = py39, py310, py311, py312, py313

[gh-actions]
python =
    3.10: py310
    3.11: py311
    3.12: py312
    3.13: py313

[testenv]
passenv = PYTHON_VERSION
allowlist_externals = uv
commands =
    uv sync --python {envpython}
    uv run python -m pytest --doctest-modules tests --cov --cov-config=pyproject.toml --cov-report=xml

```

--------------------------------------------------------------------------------
/yourware_mcp/client.py:
--------------------------------------------------------------------------------

```python
from __future__ import annotations

from functools import cache
from typing import TYPE_CHECKING

import httpx

if TYPE_CHECKING:
    from yourware_mcp.credentials import Credentials


@cache
def get_client(credentials: Credentials) -> httpx.AsyncClient:
    return httpx.AsyncClient(base_url=credentials.base_url, headers={"Authorization": f"Bearer {credentials.api_key}"})

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Install uv
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv

# Change the working directory to the `app` directory
WORKDIR /app

# Copy the lockfile and `pyproject.toml` into the image
COPY uv.lock /app/uv.lock
COPY pyproject.toml /app/pyproject.toml

# Install dependencies
RUN uv sync --frozen --no-install-project

# Copy the project into the image
COPY . /app

# Sync the project
RUN uv sync --frozen

CMD [ "python", "yourware_mcp/foo.py" ]

```

--------------------------------------------------------------------------------
/.github/actions/setup-python-env/action.yml:
--------------------------------------------------------------------------------

```yaml
name: "Setup Python Environment"
description: "Set up Python environment for the given Python version"

inputs:
  python-version:
    description: "Python version to use"
    required: true
    default: "3.12"
  uv-version:
    description: "uv version to use"
    required: true
    default: "0.6.2"

runs:
  using: "composite"
  steps:
    - uses: actions/setup-python@v5
      with:
        python-version: ${{ inputs.python-version }}

    - name: Install uv
      uses: astral-sh/setup-uv@v5
      with:
        version: ${{ inputs.uv-version }}
        enable-cache: 'true'
        cache-suffix: ${{ matrix.python-version }}

    - name: Install Python dependencies
      run: uv sync --frozen
      shell: bash

```

--------------------------------------------------------------------------------
/yourware_mcp/credentials.py:
--------------------------------------------------------------------------------

```python
from __future__ import annotations

import os
from pathlib import Path

from pydantic import BaseModel, ConfigDict

from yourware_mcp.client import get_client

CREDENTIALS_PATH = Path("~/.yourware/credentials.json").expanduser().resolve()
API_BASE_URL = os.getenv("YOURWARE_ENDPOINT", "https://www.yourware.so")


class Credentials(BaseModel):
    api_key: str
    base_url: str = API_BASE_URL

    model_config = ConfigDict(frozen=True)

    def store_credentials(self) -> None:
        CREDENTIALS_PATH.parent.mkdir(parents=True, exist_ok=True)
        CREDENTIALS_PATH.write_text(self.model_dump_json(include=["api_key"]))

    @classmethod
    def load(cls) -> Credentials:
        api_key_from_env = os.getenv("YOURWARE_API_KEY")
        if api_key_from_env:
            return cls(api_key=api_key_from_env)

        if not CREDENTIALS_PATH.exists():
            raise FileNotFoundError(f"Credentials not found at {CREDENTIALS_PATH}")  # noqa: TRY003

        return cls.model_validate_json(CREDENTIALS_PATH.read_text())

    async def check_credentials(self) -> bool:
        api_key_list_path = "/api/v1/api-keys/list"
        client = get_client(self)
        response = await client.get(api_key_list_path)
        return response.status_code == 200

```

--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------

```yaml
name: Main

on:
  push:
    branches:
      - main
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - name: Check out
        uses: actions/checkout@v4

      - uses: actions/cache@v4
        with:
          path: ~/.cache/pre-commit
          key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}

      - name: Set up the environment
        uses: ./.github/actions/setup-python-env

      - name: Run checks
        run: make check

  tests-and-type-check:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12", "3.13"]
      fail-fast: false
    env:
      UV_PYTHON: ${{ inputs.python-version }}
    defaults:
      run:
        shell: bash
    steps:
      - name: Check out
        uses: actions/checkout@v4

      - name: Set up the environment
        uses: ./.github/actions/setup-python-env
        with:
          python-version: ${{ matrix.python-version }}

      - name: Run tests
        run: uv run python -m pytest tests --cov --cov-config=pyproject.toml --cov-report=xml

      - name: Upload coverage reports to Codecov with GitHub Action on Python 3.11
        uses: codecov/codecov-action@v4
        if: ${{ matrix.python-version == '3.11' }}

```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "yourware-mcp"
version = "0.0.1"
description = "yourware mcp server"
authors = [{ name = "Wh1isper", email = "[email protected]" }]
readme = "README.md"
keywords = ['python']
requires-python = ">=3.10,<4.0"
classifiers = [
    "Intended Audience :: Developers",
    "Programming Language :: Python",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
    "httpx>=0.28.1",
    "loguru>=0.7.3",
    "mcp[cli]>=1.6.0",
    "pydantic>=2.11.1",
    "typer>=0.15.2",
]

[project.urls]
Homepage = "https://ai-zerolab.github.io/yourware-mcp/"
Repository = "https://github.com/ai-zerolab/yourware-mcp"
Documentation = "https://ai-zerolab.github.io/yourware-mcp/"

[dependency-groups]
dev = [
    "pytest>=7.2.0",
    "pre-commit>=2.20.0",
    "pytest-asyncio>=0.25.3",
    "tox-uv>=1.11.3",
    "deptry>=0.22.0",
    "pytest-cov>=4.0.0",
    "ruff>=0.9.2",

]

[project.scripts]
yourware-mcp = "yourware_mcp.cli:app"


[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.setuptools]
py-modules = ["yourware_mcp"]

[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.ruff]
target-version = "py39"
line-length = 120
fix = true

[tool.ruff.lint]
select = [
    # flake8-2020
    "YTT",
    # flake8-bandit
    "S",
    # flake8-bugbear
    "B",
    # flake8-builtins
    "A",
    # flake8-comprehensions
    "C4",
    # flake8-debugger
    "T10",
    # flake8-simplify
    "SIM",
    # isort
    "I",
    # mccabe
    "C90",
    # pycodestyle
    "E",
    "W",
    # pyflakes
    "F",
    # pygrep-hooks
    "PGH",
    # pyupgrade
    "UP",
    # ruff
    "RUF",
    # tryceratops
    "TRY",
]
ignore = [
    # LineTooLong
    "E501",
    # DoNotAssignLambda
    "E731",
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]

[tool.ruff.format]
preview = true

[tool.coverage.report]
skip_empty = true

[tool.coverage.run]
branch = true
source = ["yourware_mcp"]

```

--------------------------------------------------------------------------------
/.github/workflows/on-release-main.yml:
--------------------------------------------------------------------------------

```yaml
name: release-main

permissions:
  contents: write
  packages: write

on:
  release:
    types: [published]

jobs:
  set-version:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4

      - name: Export tag
        id: vars
        run: echo tag=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT
        if: ${{ github.event_name == 'release' }}

      - name: Update project version
        run: |
          sed -i "s/^version = \".*\"/version = \"$RELEASE_VERSION\"/" pyproject.toml
        env:
          RELEASE_VERSION: ${{ steps.vars.outputs.tag }}
        if: ${{ github.event_name == 'release' }}

      - name: Upload updated pyproject.toml
        uses: actions/upload-artifact@v4
        with:
          name: pyproject-toml
          path: pyproject.toml

  publish:
    runs-on: ubuntu-latest
    needs: [set-version]
    steps:
      - name: Check out
        uses: actions/checkout@v4

      - name: Set up the environment
        uses: ./.github/actions/setup-python-env

      - name: Download updated pyproject.toml
        uses: actions/download-artifact@v4
        with:
          name: pyproject-toml

      - name: Build package
        run: uv build

      - name: Publish package
        run: uv publish
        env:
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}

      - name: Upload dists to release
        uses: svenstaro/upload-release-action@v2
        with:
            repo_token: ${{ secrets.GITHUB_TOKEN }}
            file: dist/*
            file_glob: true
            tag: ${{ github.ref }}
            overwrite: true


  push-image:
    runs-on: ubuntu-latest
    needs: [set-version]
    steps:
      - uses: actions/checkout@v4
      - name: Export tag
        id: vars
        run: echo tag=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT
        if: ${{ github.event_name == 'release' }}
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Login to Github Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ai-zerolab
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build and push image
        id: docker_build_publish
        uses: docker/build-push-action@v5
        with:
            context: .
            platforms: linux/amd64,linux/arm64/v8
            cache-from: type=gha
            cache-to: type=gha,mode=max
            file: ./Dockerfile
            push: true
            tags: |
              ghcr.io/ai-zerolab/yourware-mcp:${{ steps.vars.outputs.tag }}
              ghcr.io/ai-zerolab/yourware-mcp:latest

```

--------------------------------------------------------------------------------
/yourware_mcp/app.py:
--------------------------------------------------------------------------------

```python
import io
import os
import zipfile
from pathlib import Path
from typing import Annotated

import httpx
from mcp.server.fastmcp import FastMCP

from yourware_mcp.client import get_client
from yourware_mcp.credentials import API_BASE_URL, CREDENTIALS_PATH, Credentials
from yourware_mcp.utils import urljoin

mcp = FastMCP("yourware-mcp")


@mcp.tool(description="Check your yourware credentials exists and are valid.")
async def check_credentials():
    try:
        credentials = Credentials.load()
    except FileNotFoundError:
        return {
            "success": False,
            "message": "Credentials not found",
            "help": "Run `create_api_key` to create one",
        }

    if not await credentials.check_credentials():
        return {
            "success": False,
            "message": "Credentials are invalid",
            "help": "Call `create_api_key` to create one",
        }

    return {
        "success": True,
        "message": "Credentials are valid",
    }


@mcp.tool(
    description=f"Create a new yourware API key. This will automatically be stored in {CREDENTIALS_PATH.as_posix()}. Use this tool if current credentials are invalid"
)
async def create_api_key(api_key: Annotated[str | None, "The API key to store"] = None):
    if not api_key:
        quick_create_address = urljoin(API_BASE_URL, "/api/v1/api-keys/quick-create")
        login_address = urljoin(API_BASE_URL, "/login")
        return {
            "success": False,
            "message": "API key is required, please guide the user to create one. Let the user tell you what the page shows and guide them through the login process if needed.",
            "help": f"Click this link to create one: {quick_create_address}\n\nClick this link to login if needed: {login_address}",
        }

    Credentials(api_key=api_key).store_credentials()
    return {
        "success": True,
        "message": "API key created",
    }


@mcp.tool(
    description="Upload a file or directory to yourware, might be a dist/out directory or a single html file. Use absolute path if possible. "
    "For multiple files, you should move them to a directory first, then use this tool to upload the directory"
)
async def upload_project(  # noqa: C901
    file_path: Annotated[
        str,
        "The path to the dist/out directory or single file. If ends with /, it will be treated as a directory",
    ],
    cwd: Annotated[
        str | None,
        "The current working directory to resolve relative paths from, should be a absolute path",
    ] = None,
):
    if cwd:
        cwd_path = Path(cwd).expanduser().resolve()
        file_path = cwd_path / file_path
    else:
        file_path = Path(file_path)

    file_path = file_path.expanduser().resolve()

    try:
        credentials = Credentials.load()
    except FileNotFoundError:
        return {
            "success": False,
            "message": "Credentials not found",
            "help": "Run `create_api_key` to create one",
        }

    if not await credentials.check_credentials():
        return {
            "success": False,
            "message": "Credentials are invalid",
            "help": "Call `create_api_key` to create one",
        }
    client = get_client(credentials)

    # 1. Create a zip in memory
    zip_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
        if file_path.is_dir():
            # 2. Zip the directory into it
            for root, dirs, files in os.walk(file_path):
                # Skip .git directories
                if ".git" in dirs:
                    dirs.remove(".git")  # This modifies dirs in-place to prevent os.walk from traversing .git

                for file in files:
                    file_full_path = Path(root) / file
                    arc_name = file_full_path.relative_to(file_path)
                    zip_file.write(file_full_path, arcname=arc_name)
        else:
            # Zip the single file
            zip_file.write(file_path, arcname=file_path.name)

    # Get the zip content
    zip_buffer.seek(0)
    zip_content = zip_buffer.getvalue()
    zip_size = len(zip_content)

    # 3. Call /api/v1/files/upload for upload infos
    upload_response = await client.post(
        "/api/v1/files/upload",
        json={
            "files": [
                {
                    "file_name": "source_code.zip",
                    "file_size": zip_size,
                    "mime_type": "application/zip",
                }
            ],
            "event_type": "source_code",
            "is_public": False,
        },
    )

    if upload_response.status_code != 200:
        return {
            "success": False,
            "message": f"Failed to get upload info: {upload_response.text}",
        }

    upload_data = upload_response.json()
    upload_info = upload_data["data"]["upload_infos"][0]
    file_id = upload_info["file_id"]
    upload_url = upload_info["upload_url"]
    fields = upload_info["fields"]

    # 4. Upload the zip to the upload url
    files = {"file": ("source_code.zip", zip_content, "application/zip")}
    form_data = {**fields}

    async with httpx.AsyncClient() as upload_client:
        upload_result = await upload_client.post(upload_url, data=form_data, files=files)

    if upload_result.status_code not in (200, 201, 204):
        return {
            "success": False,
            "message": f"Failed to upload file: {upload_result.text}",
        }

    # 5. Call /api/v1/projects/deploy with the file_id
    deploy_response = await client.post("/api/v1/projects/deploy", json={"file_id": file_id})

    if deploy_response.status_code != 200:
        return {
            "success": False,
            "message": f"Failed to deploy project: {deploy_response.text}",
        }

    deploy_data = deploy_response.json()
    project_data = deploy_data["data"]

    return {
        "success": True,
        "message": "Project uploaded successfully",
        "project_url": project_data["project_url"],
        "iframe_url": project_data["iframe_url"],
    }

```

--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Yourware MCP - Upload Your Projects to Yourware</title>
    <style>
        :root {
            --primary-color: #4f46e5;
            --secondary-color: #818cf8;
            --text-color: #1f2937;
            --bg-color: #ffffff;
            --card-bg: #f9fafb;
            --border-color: #e5e7eb;
            --code-bg: #f3f4f6;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: var(--text-color);
            background-color: var(--bg-color);
            margin: 0;
            padding: 0;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 0 20px;
        }

        header {
            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
            color: white;
            padding: 60px 0;
            text-align: center;
        }

        header h1 {
            margin: 0;
            font-size: 3rem;
            letter-spacing: -0.5px;
        }

        header p {
            font-size: 1.2rem;
            margin: 15px 0 0;
            opacity: 0.9;
        }

        section {
            padding: 60px 0;
            border-bottom: 1px solid var(--border-color);
        }

        h2 {
            font-size: 2rem;
            margin-top: 0;
            color: var(--primary-color);
        }

        h3 {
            font-size: 1.5rem;
            margin-top: 30px;
            color: var(--primary-color);
        }

        .features {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 30px;
            margin-top: 40px;
        }

        .feature-card {
            background-color: var(--card-bg);
            border-radius: 8px;
            padding: 25px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
            transition: transform 0.3s ease;
        }

        .feature-card:hover {
            transform: translateY(-5px);
        }

        .feature-card h3 {
            margin-top: 0;
            font-size: 1.3rem;
        }

        code {
            background-color: var(--code-bg);
            padding: 2px 5px;
            border-radius: 4px;
            font-family: 'Courier New', Courier, monospace;
            font-size: 0.9em;
        }

        pre {
            background-color: var(--code-bg);
            padding: 15px;
            border-radius: 8px;
            overflow-x: auto;
            font-family: 'Courier New', Courier, monospace;
            font-size: 0.9em;
            line-height: 1.4;
        }

        .btn {
            display: inline-block;
            background-color: var(--primary-color);
            color: white;
            padding: 12px 24px;
            border-radius: 6px;
            text-decoration: none;
            font-weight: 600;
            transition: background-color 0.3s ease;
        }

        .btn:hover {
            background-color: var(--secondary-color);
        }

        .installation-steps {
            counter-reset: step;
            list-style-type: none;
            padding-left: 0;
        }

        .installation-steps li {
            position: relative;
            margin-bottom: 20px;
            padding-left: 50px;
        }

        .installation-steps li::before {
            counter-increment: step;
            content: counter(step);
            position: absolute;
            left: 0;
            top: 0;
            background-color: var(--primary-color);
            color: white;
            width: 30px;
            height: 30px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
        }

        .resources {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
        }

        .resource-card {
            background-color: var(--card-bg);
            border-radius: 8px;
            padding: 20px;
            text-align: center;
        }

        footer {
            text-align: center;
            padding: 30px 0;
            color: #6b7280;
            font-size: 0.9rem;
        }

        @media (max-width: 768px) {
            header h1 {
                font-size: 2.2rem;
            }

            .features {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <header>
        <div class="container">
            <h1>Yourware MCP</h1>
            <p>An MCP server to upload your projects to <a href="https://www.yourware.so" style="color: white; text-decoration: underline;">yourware.so</a></p>
        </div>
    </header>

    <section id="overview">
        <div class="container">
            <h2>Project Overview</h2>
            <p>
                Yourware MCP is a Model Context Protocol (MCP) server that enables AI assistants like Claude to upload your projects directly to
                <a href="https://www.yourware.so">yourware.so</a>. This integration allows for seamless deployment of web projects,
                making it easy to showcase your work online without leaving your development environment.
            </p>
            <p>
                Whether you're building a simple HTML page, a complex web application, or any other web-based project,
                Yourware MCP streamlines the process of getting your work online and shareable.
            </p>
        </div>
    </section>

    <section id="features">
        <div class="container">
            <h2>Features & Capabilities</h2>
            <div class="features">
                <div class="feature-card">
                    <h3>Easy Authentication</h3>
                    <p>Simple API key-based authentication system that securely stores your credentials.</p>
                </div>
                <div class="feature-card">
                    <h3>Flexible Uploads</h3>
                    <p>Upload single files or entire directories with automatic compression and organization.</p>
                </div>
                <div class="feature-card">
                    <h3>Seamless Deployment</h3>
                    <p>One-step deployment process that makes your projects instantly available online.</p>
                </div>
                <div class="feature-card">
                    <h3>AI Assistant Integration</h3>
                    <p>Works with AI assistants like Claude through Cline, Cursor, and Windsurf for a smooth workflow.</p>
                </div>
            </div>
        </div>
    </section>

    <section id="installation">
        <div class="container">
            <h2>Installation & Setup</h2>
            <p>Getting started with Yourware MCP is straightforward:</p>

            <ol class="installation-steps">
                <li>
                    <strong>Install the package</strong>
                    <pre>pip install yourware-mcp</pre>
                    <p>Or use <code>uvx</code> directly as shown in the configuration examples below.</p>
                </li>
                <li>
                    <strong>Configure your AI assistant</strong>
                    <p>Add the Yourware MCP server to your AI assistant's configuration:</p>
                    <h3>General Configuration</h3>
                    <pre>{
  "mcpServers": {
    "yourware-mcp": {
      "command": "uvx",
      "args": ["yourware-mcp@latest", "stdio"],
      "env": {}
    }
  }
}</pre>
                    <h3>Cursor Configuration</h3>
                    <p>In Cursor settings → features → MCP Servers, add a new MCP Server named <code>yourware-mcp</code> and set the command to <code>uvx yourware-mcp@latest stdio</code></p>
                    <img src="../assets/config-cursor.png" alt="Config cursor screenshot" style="max-width: 100%; border-radius: 8px; margin-top: 10px;">
                </li>
                <li>
                    <strong>Create an API key</strong>
                    <p>You can either:</p>
                    <ul>
                        <li>Let your AI assistant guide you through creating an API key</li>
                        <li>Visit <a href="https://www.yourware.so/api/v1/api-keys/quick-create">https://www.yourware.so/api/v1/api-keys/quick-create</a> to create one manually</li>
                        <li>Set the <code>YOURWARE_API_KEY</code> environment variable</li>
                    </ul>
                </li>
            </ol>
        </div>
    </section>

    <section id="usage">
        <div class="container">
            <h2>Usage Examples</h2>
            <p>Once configured, you can use Yourware MCP through your AI assistant with the following tools:</p>

            <h3>Check Credentials</h3>
            <pre>
// Check if your credentials exist and are valid
await use_mcp_tool({
  server_name: "yourware-mcp",
  tool_name: "check_credentials",
  arguments: {}
});</pre>

            <h3>Create API Key</h3>
            <pre>
// Create a new API key
await use_mcp_tool({
  server_name: "yourware-mcp",
  tool_name: "create_api_key",
  arguments: {
    api_key: "your-api-key-here" // Optional, if not provided, will guide you to create one
  }
});</pre>

            <h3>Upload Project</h3>
            <pre>
// Upload a file or directory to yourware
await use_mcp_tool({
  server_name: "yourware-mcp",
  tool_name: "upload_project",
  arguments: {
    file_path: "path/to/your/project" // Can be a directory or single file
  }
});</pre>

            <h3>Example Workflow</h3>
            <p>A typical workflow might look like this:</p>
            <ol>
                <li>Build your web project</li>
                <li>Check if your credentials are valid</li>
                <li>If not, create a new API key</li>
                <li>Upload your project to Yourware</li>
                <li>Get the project URL and share it with others</li>
            </ol>
        </div>
    </section>

    <section id="resources">
        <div class="container">
            <h2>Resources & Links</h2>
            <div class="resources">
                <div class="resource-card">
                    <h3>GitHub Repository</h3>
                    <p>View the source code and contribute to the project.</p>
                    <a href="https://github.com/ai-zerolab/yourware-mcp" class="btn">Visit GitHub</a>
                </div>
                <div class="resource-card">
                    <h3>Yourware Platform</h3>
                    <p>Explore the Yourware platform and see examples.</p>
                    <a href="https://www.yourware.so" class="btn">Visit Yourware</a>
                </div>
                <div class="resource-card">
                    <h3>Documentation</h3>
                    <p>Read the full documentation for detailed information.</p>
                    <a href="https://www.yourware.so/docs" class="btn">Read Docs</a>
                </div>
            </div>
        </div>
    </section>

    <footer>
        <div class="container">
            <p>&copy; 2025 Yourware MCP. All rights reserved.</p>
        </div>
    </footer>
</body>
</html>

```