#
tokens: 13638/50000 24/24 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | 3.12
2 | 
```

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

```yaml
 1 | repos:
 2 |   - repo: https://github.com/pre-commit/pre-commit-hooks
 3 |     rev: "v5.0.0"
 4 |     hooks:
 5 |       - id: check-case-conflict
 6 |       - id: check-merge-conflict
 7 |       - id: check-toml
 8 |       - id: check-yaml
 9 |       - id: check-json
10 |         exclude: ^.devcontainer/devcontainer.json
11 |       - id: pretty-format-json
12 |         exclude: ^.devcontainer/devcontainer.json
13 |         args: [--autofix]
14 |       - id: end-of-file-fixer
15 |       - id: trailing-whitespace
16 |   - repo: https://github.com/executablebooks/mdformat
17 |     rev: 0.7.22
18 |     hooks:
19 |       - id: mdformat
20 |         additional_dependencies:
21 |           [mdformat-gfm, mdformat-frontmatter, mdformat-footnote]
22 |   - repo: https://github.com/astral-sh/ruff-pre-commit
23 |     rev: "v0.11.9"
24 |     hooks:
25 |       - id: ruff
26 |         args: [--exit-non-zero-on-fix]
27 |       - id: ruff-format
28 | 
```

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

```
  1 | docs/source
  2 | 
  3 | # From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
  4 | 
  5 | # Byte-compiled / optimized / DLL files
  6 | __pycache__/
  7 | *.py[cod]
  8 | *$py.class
  9 | 
 10 | # C extensions
 11 | *.so
 12 | 
 13 | # Distribution / packaging
 14 | .Python
 15 | build/
 16 | develop-eggs/
 17 | dist/
 18 | downloads/
 19 | eggs/
 20 | .eggs/
 21 | lib/
 22 | lib64/
 23 | parts/
 24 | sdist/
 25 | var/
 26 | wheels/
 27 | share/python-wheels/
 28 | *.egg-info/
 29 | .installed.cfg
 30 | *.egg
 31 | MANIFEST
 32 | 
 33 | # PyInstaller
 34 | #  Usually these files are written by a python script from a template
 35 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 36 | *.manifest
 37 | *.spec
 38 | 
 39 | # Installer logs
 40 | pip-log.txt
 41 | pip-delete-this-directory.txt
 42 | 
 43 | # Unit test / coverage reports
 44 | htmlcov/
 45 | .tox/
 46 | .nox/
 47 | .coverage
 48 | .coverage.*
 49 | .cache
 50 | nosetests.xml
 51 | coverage.xml
 52 | *.cover
 53 | *.py,cover
 54 | .hypothesis/
 55 | .pytest_cache/
 56 | cover/
 57 | 
 58 | # Translations
 59 | *.mo
 60 | *.pot
 61 | 
 62 | # Django stuff:
 63 | *.log
 64 | local_settings.py
 65 | db.sqlite3
 66 | db.sqlite3-journal
 67 | 
 68 | # Flask stuff:
 69 | instance/
 70 | .webassets-cache
 71 | 
 72 | # Scrapy stuff:
 73 | .scrapy
 74 | 
 75 | # Sphinx documentation
 76 | docs/_build/
 77 | 
 78 | # PyBuilder
 79 | .pybuilder/
 80 | target/
 81 | 
 82 | # Jupyter Notebook
 83 | .ipynb_checkpoints
 84 | 
 85 | # IPython
 86 | profile_default/
 87 | ipython_config.py
 88 | 
 89 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
 90 | __pypackages__/
 91 | 
 92 | # Celery stuff
 93 | celerybeat-schedule
 94 | celerybeat.pid
 95 | 
 96 | # SageMath parsed files
 97 | *.sage.py
 98 | 
 99 | # Environments
100 | .env
101 | .venv
102 | env/
103 | venv/
104 | ENV/
105 | env.bak/
106 | venv.bak/
107 | 
108 | # Spyder project settings
109 | .spyderproject
110 | .spyproject
111 | 
112 | # Rope project settings
113 | .ropeproject
114 | 
115 | # mkdocs documentation
116 | /site
117 | 
118 | # mypy
119 | .mypy_cache/
120 | .dmypy.json
121 | dmypy.json
122 | 
123 | # Pyre type checker
124 | .pyre/
125 | 
126 | # pytype static type analyzer
127 | .pytype/
128 | 
129 | # Cython debug symbols
130 | cython_debug/
131 | 
132 | # Vscode config files
133 | # .vscode/
134 | 
135 | # PyCharm
136 | #  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
137 | #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
138 | #  and can be added to the global gitignore or merged into this file.  For a more nuclear
139 | #  option (not recommended) you can uncomment the following to ignore the entire idea folder.
140 | #.idea/
141 | 
```

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

```markdown
 1 | [![Release](https://img.shields.io/github/v/release/ai-zerolab/yourware-mcp)](https://img.shields.io/github/v/release/ai-zerolab/yourware-mcp)
 2 | [![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)
 3 | [![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)
 4 | [![License](https://img.shields.io/github/license/ai-zerolab/yourware-mcp)](https://img.shields.io/github/license/ai-zerolab/yourware-mcp)
 5 | 
 6 | <!-- [![codecov](https://codecov.io/gh/ai-zerolab/yourware-mcp/branch/main/graph/badge.svg)](https://codecov.io/gh/ai-zerolab/yourware-mcp) -->
 7 | 
 8 | # Yourware MCP
 9 | 
10 | MCP server to upload your project to [yourware](https://www.yourware.so). Support single file or directory.
11 | 
12 | ## Showcase
13 | 
14 | Visit on [yourware](https://v9gfmmif5s.app.yourware.so/): https://v9gfmmif5s.app.yourware.so/
15 | 
16 | ![Showcase](./assets/showcase.jpeg)
17 | 
18 | ## Pre-requisites
19 | 
20 | 1. You need to login to [yourware](https://www.yourware.so)
21 | 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.
22 | 
23 | ## Configuration
24 | 
25 | ### General configuration
26 | 
27 | You can use the following configuration for cline/cursor/windsurf...
28 | 
29 | ```json
30 | {
31 |   "mcpServers": {
32 |     "yourware-mcp": {
33 |       "command": "uvx",
34 |       "args": ["yourware-mcp@latest", "stdio"],
35 |       "env": {}
36 |     }
37 |   }
38 | }
39 | ```
40 | 
41 | ### Cursor config guide
42 | 
43 | 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`
44 | 
45 | ![Config cursor screenshot](./assets/config-cursor.png)
46 | 
47 | ### Config claude code
48 | 
49 | ```bash
50 | claude mcp add yourware-mcp -s user -- uvx yourware-mcp@latest stdio
51 | ```
52 | 
53 | ## Available environments variables
54 | 
55 | `YOURWARE_API_KEY` for the API key, you can also let llm config it for you.
56 | 
```

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

```markdown
  1 | # Contributing to `yourware-mcp`
  2 | 
  3 | Contributions are welcome, and they are greatly appreciated!
  4 | Every little bit helps, and credit will always be given.
  5 | 
  6 | You can contribute in many ways:
  7 | 
  8 | # Types of Contributions
  9 | 
 10 | ## Report Bugs
 11 | 
 12 | Report bugs at https://github.com/ai-zerolab/yourware-mcp/issues
 13 | 
 14 | If you are reporting a bug, please include:
 15 | 
 16 | - Your operating system name and version.
 17 | - Any details about your local setup that might be helpful in troubleshooting.
 18 | - Detailed steps to reproduce the bug.
 19 | 
 20 | ## Fix Bugs
 21 | 
 22 | Look through the GitHub issues for bugs.
 23 | Anything tagged with "bug" and "help wanted" is open to whoever wants to implement a fix for it.
 24 | 
 25 | ## Implement Features
 26 | 
 27 | Look through the GitHub issues for features.
 28 | Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it.
 29 | 
 30 | ## Write Documentation
 31 | 
 32 | 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.
 33 | 
 34 | ## Submit Feedback
 35 | 
 36 | The best way to send feedback is to file an issue at https://github.com/ai-zerolab/yourware-mcp/issues.
 37 | 
 38 | If you are proposing a new feature:
 39 | 
 40 | - Explain in detail how it would work.
 41 | - Keep the scope as narrow as possible, to make it easier to implement.
 42 | - Remember that this is a volunteer-driven project, and that contributions
 43 |   are welcome :)
 44 | 
 45 | # Get Started!
 46 | 
 47 | Ready to contribute? Here's how to set up `yourware-mcp` for local development.
 48 | Please note this documentation assumes you already have `uv` and `Git` installed and ready to go.
 49 | 
 50 | 1. Fork the `yourware-mcp` repo on GitHub.
 51 | 
 52 | 1. Clone your fork locally:
 53 | 
 54 | ```bash
 55 | cd <directory_in_which_repo_should_be_created>
 56 | git clone [email protected]:YOUR_NAME/yourware-mcp.git
 57 | ```
 58 | 
 59 | 3. Now we need to install the environment. Navigate into the directory
 60 | 
 61 | ```bash
 62 | cd yourware-mcp
 63 | ```
 64 | 
 65 | Then, install and activate the environment with:
 66 | 
 67 | ```bash
 68 | uv sync
 69 | ```
 70 | 
 71 | 4. Install pre-commit to run linters/formatters at commit time:
 72 | 
 73 | ```bash
 74 | uv run pre-commit install
 75 | ```
 76 | 
 77 | 5. Create a branch for local development:
 78 | 
 79 | ```bash
 80 | git checkout -b name-of-your-bugfix-or-feature
 81 | ```
 82 | 
 83 | Now you can make your changes locally.
 84 | 
 85 | 6. Don't forget to add test cases for your added functionality to the `tests` directory.
 86 | 
 87 | 1. When you're done making changes, check that your changes pass the formatting tests.
 88 | 
 89 | ```bash
 90 | make check
 91 | ```
 92 | 
 93 | Now, validate that all unit tests are passing:
 94 | 
 95 | ```bash
 96 | make test
 97 | ```
 98 | 
 99 | 9. Before raising a pull request you should also run tox.
100 |    This will run the tests across different versions of Python:
101 | 
102 | ```bash
103 | tox
104 | ```
105 | 
106 | This requires you to have multiple versions of python installed.
107 | This step is also triggered in the CI/CD pipeline, so you could also choose to skip this step locally.
108 | 
109 | 10. Commit your changes and push your branch to GitHub:
110 | 
111 | ```bash
112 | git add .
113 | git commit -m "Your detailed description of your changes."
114 | git push origin name-of-your-bugfix-or-feature
115 | ```
116 | 
117 | 11. Submit a pull request through the GitHub website.
118 | 
119 | # Pull Request Guidelines
120 | 
121 | Before you submit a pull request, check that it meets these guidelines:
122 | 
123 | 1. The pull request should include tests.
124 | 
125 | 1. If the pull request adds functionality, the docs should be updated.
126 |    Put your new functionality into a function with a docstring, and add the feature to the list in `README.md`.
127 | 
```

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

```python
1 | 
```

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

```python
1 | def test_hello():
2 |     assert 1 == 1
3 | 
```

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

```
1 | # pytest.ini
2 | [pytest]
3 | asyncio_mode = auto
4 | 
```

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

```json
1 | {
2 |   "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python"
3 | }
4 | 
```

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

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

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

```python
 1 | import os
 2 | 
 3 | USER_DEFINED_LOG_LEVEL = os.getenv("YOURWARE_MCP_LOG_LEVEL", "INFO")
 4 | 
 5 | os.environ["LOGURU_LEVEL"] = USER_DEFINED_LOG_LEVEL
 6 | 
 7 | from loguru import logger  # noqa: E402
 8 | 
 9 | __all__ = ["logger"]
10 | 
```

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

```yaml
 1 | coverage:
 2 |   range: 70..100
 3 |   round: down
 4 |   precision: 1
 5 |   status:
 6 |     project:
 7 |       default:
 8 |         target: 90%
 9 |         threshold: 0.5%
10 |     patch:
11 |       default:
12 |         target: auto
13 |         threshold: 0%
14 |         informational: true
15 | codecov:
16 |  token: f927bff4-d404-4986-8c11-624eadda8431
17 | 
```

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

```python
 1 | import typer
 2 | 
 3 | from yourware_mcp.app import mcp
 4 | 
 5 | app = typer.Typer()
 6 | 
 7 | 
 8 | @app.command()
 9 | def stdio():
10 |     mcp.run(transport="stdio")
11 | 
12 | 
13 | @app.command()
14 | def sse(
15 |     host: str = "localhost",
16 |     port: int = 9123,
17 | ):
18 |     mcp.settings.host = host
19 |     mcp.settings.port = port
20 |     mcp.run(transport="sse")
21 | 
22 | 
23 | if __name__ == "__main__":
24 |     app(["stdio"])
25 | 
```

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

```yaml
 1 | name: validate-codecov-config
 2 | 
 3 | on:
 4 |   pull_request:
 5 |     paths: [codecov.yaml]
 6 |   push:
 7 |     branches: [main]
 8 | 
 9 | jobs:
10 |   validate-codecov-config:
11 |     runs-on: ubuntu-22.04
12 |     steps:
13 |       - uses: actions/checkout@v4
14 |       - name: Validate codecov configuration
15 |         run: curl -sSL --fail-with-body --data-binary @codecov.yaml https://codecov.io/validate
16 | 
```

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

```
 1 | [tox]
 2 | skipsdist = true
 3 | envlist = py39, py310, py311, py312, py313
 4 | 
 5 | [gh-actions]
 6 | python =
 7 |     3.10: py310
 8 |     3.11: py311
 9 |     3.12: py312
10 |     3.13: py313
11 | 
12 | [testenv]
13 | passenv = PYTHON_VERSION
14 | allowlist_externals = uv
15 | commands =
16 |     uv sync --python {envpython}
17 |     uv run python -m pytest --doctest-modules tests --cov --cov-config=pyproject.toml --cov-report=xml
18 | 
```

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

```python
 1 | from __future__ import annotations
 2 | 
 3 | from functools import cache
 4 | from typing import TYPE_CHECKING
 5 | 
 6 | import httpx
 7 | 
 8 | if TYPE_CHECKING:
 9 |     from yourware_mcp.credentials import Credentials
10 | 
11 | 
12 | @cache
13 | def get_client(credentials: Credentials) -> httpx.AsyncClient:
14 |     return httpx.AsyncClient(base_url=credentials.base_url, headers={"Authorization": f"Bearer {credentials.api_key}"})
15 | 
```

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

```dockerfile
 1 | # Install uv
 2 | FROM python:3.12-slim
 3 | COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
 4 | 
 5 | # Change the working directory to the `app` directory
 6 | WORKDIR /app
 7 | 
 8 | # Copy the lockfile and `pyproject.toml` into the image
 9 | COPY uv.lock /app/uv.lock
10 | COPY pyproject.toml /app/pyproject.toml
11 | 
12 | # Install dependencies
13 | RUN uv sync --frozen --no-install-project
14 | 
15 | # Copy the project into the image
16 | COPY . /app
17 | 
18 | # Sync the project
19 | RUN uv sync --frozen
20 | 
21 | CMD [ "python", "yourware_mcp/foo.py" ]
22 | 
```

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

```yaml
 1 | name: "Setup Python Environment"
 2 | description: "Set up Python environment for the given Python version"
 3 | 
 4 | inputs:
 5 |   python-version:
 6 |     description: "Python version to use"
 7 |     required: true
 8 |     default: "3.12"
 9 |   uv-version:
10 |     description: "uv version to use"
11 |     required: true
12 |     default: "0.6.2"
13 | 
14 | runs:
15 |   using: "composite"
16 |   steps:
17 |     - uses: actions/setup-python@v5
18 |       with:
19 |         python-version: ${{ inputs.python-version }}
20 | 
21 |     - name: Install uv
22 |       uses: astral-sh/setup-uv@v5
23 |       with:
24 |         version: ${{ inputs.uv-version }}
25 |         enable-cache: 'true'
26 |         cache-suffix: ${{ matrix.python-version }}
27 | 
28 |     - name: Install Python dependencies
29 |       run: uv sync --frozen
30 |       shell: bash
31 | 
```

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

```python
 1 | from __future__ import annotations
 2 | 
 3 | import os
 4 | from pathlib import Path
 5 | 
 6 | from pydantic import BaseModel, ConfigDict
 7 | 
 8 | from yourware_mcp.client import get_client
 9 | 
10 | CREDENTIALS_PATH = Path("~/.yourware/credentials.json").expanduser().resolve()
11 | API_BASE_URL = os.getenv("YOURWARE_ENDPOINT", "https://www.yourware.so")
12 | 
13 | 
14 | class Credentials(BaseModel):
15 |     api_key: str
16 |     base_url: str = API_BASE_URL
17 | 
18 |     model_config = ConfigDict(frozen=True)
19 | 
20 |     def store_credentials(self) -> None:
21 |         CREDENTIALS_PATH.parent.mkdir(parents=True, exist_ok=True)
22 |         CREDENTIALS_PATH.write_text(self.model_dump_json(include=["api_key"]))
23 | 
24 |     @classmethod
25 |     def load(cls) -> Credentials:
26 |         api_key_from_env = os.getenv("YOURWARE_API_KEY")
27 |         if api_key_from_env:
28 |             return cls(api_key=api_key_from_env)
29 | 
30 |         if not CREDENTIALS_PATH.exists():
31 |             raise FileNotFoundError(f"Credentials not found at {CREDENTIALS_PATH}")  # noqa: TRY003
32 | 
33 |         return cls.model_validate_json(CREDENTIALS_PATH.read_text())
34 | 
35 |     async def check_credentials(self) -> bool:
36 |         api_key_list_path = "/api/v1/api-keys/list"
37 |         client = get_client(self)
38 |         response = await client.get(api_key_list_path)
39 |         return response.status_code == 200
40 | 
```

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

```yaml
 1 | name: Main
 2 | 
 3 | on:
 4 |   push:
 5 |     branches:
 6 |       - main
 7 |   pull_request:
 8 |     types: [opened, synchronize, reopened, ready_for_review]
 9 | 
10 | jobs:
11 |   quality:
12 |     runs-on: ubuntu-latest
13 |     steps:
14 |       - name: Check out
15 |         uses: actions/checkout@v4
16 | 
17 |       - uses: actions/cache@v4
18 |         with:
19 |           path: ~/.cache/pre-commit
20 |           key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
21 | 
22 |       - name: Set up the environment
23 |         uses: ./.github/actions/setup-python-env
24 | 
25 |       - name: Run checks
26 |         run: make check
27 | 
28 |   tests-and-type-check:
29 |     runs-on: ubuntu-latest
30 |     strategy:
31 |       matrix:
32 |         python-version: ["3.10", "3.11", "3.12", "3.13"]
33 |       fail-fast: false
34 |     env:
35 |       UV_PYTHON: ${{ inputs.python-version }}
36 |     defaults:
37 |       run:
38 |         shell: bash
39 |     steps:
40 |       - name: Check out
41 |         uses: actions/checkout@v4
42 | 
43 |       - name: Set up the environment
44 |         uses: ./.github/actions/setup-python-env
45 |         with:
46 |           python-version: ${{ matrix.python-version }}
47 | 
48 |       - name: Run tests
49 |         run: uv run python -m pytest tests --cov --cov-config=pyproject.toml --cov-report=xml
50 | 
51 |       - name: Upload coverage reports to Codecov with GitHub Action on Python 3.11
52 |         uses: codecov/codecov-action@v4
53 |         if: ${{ matrix.python-version == '3.11' }}
54 | 
```

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

```toml
  1 | [project]
  2 | name = "yourware-mcp"
  3 | version = "0.0.1"
  4 | description = "yourware mcp server"
  5 | authors = [{ name = "Wh1isper", email = "[email protected]" }]
  6 | readme = "README.md"
  7 | keywords = ['python']
  8 | requires-python = ">=3.10,<4.0"
  9 | classifiers = [
 10 |     "Intended Audience :: Developers",
 11 |     "Programming Language :: Python",
 12 |     "Programming Language :: Python :: 3",
 13 |     "Programming Language :: Python :: 3.10",
 14 |     "Programming Language :: Python :: 3.11",
 15 |     "Programming Language :: Python :: 3.12",
 16 |     "Programming Language :: Python :: 3.13",
 17 |     "Topic :: Software Development :: Libraries :: Python Modules",
 18 | ]
 19 | dependencies = [
 20 |     "httpx>=0.28.1",
 21 |     "loguru>=0.7.3",
 22 |     "mcp[cli]>=1.6.0",
 23 |     "pydantic>=2.11.1",
 24 |     "typer>=0.15.2",
 25 | ]
 26 | 
 27 | [project.urls]
 28 | Homepage = "https://ai-zerolab.github.io/yourware-mcp/"
 29 | Repository = "https://github.com/ai-zerolab/yourware-mcp"
 30 | Documentation = "https://ai-zerolab.github.io/yourware-mcp/"
 31 | 
 32 | [dependency-groups]
 33 | dev = [
 34 |     "pytest>=7.2.0",
 35 |     "pre-commit>=2.20.0",
 36 |     "pytest-asyncio>=0.25.3",
 37 |     "tox-uv>=1.11.3",
 38 |     "deptry>=0.22.0",
 39 |     "pytest-cov>=4.0.0",
 40 |     "ruff>=0.9.2",
 41 | 
 42 | ]
 43 | 
 44 | [project.scripts]
 45 | yourware-mcp = "yourware_mcp.cli:app"
 46 | 
 47 | 
 48 | [build-system]
 49 | requires = ["hatchling"]
 50 | build-backend = "hatchling.build"
 51 | 
 52 | [tool.setuptools]
 53 | py-modules = ["yourware_mcp"]
 54 | 
 55 | [tool.pytest.ini_options]
 56 | testpaths = ["tests"]
 57 | 
 58 | [tool.ruff]
 59 | target-version = "py39"
 60 | line-length = 120
 61 | fix = true
 62 | 
 63 | [tool.ruff.lint]
 64 | select = [
 65 |     # flake8-2020
 66 |     "YTT",
 67 |     # flake8-bandit
 68 |     "S",
 69 |     # flake8-bugbear
 70 |     "B",
 71 |     # flake8-builtins
 72 |     "A",
 73 |     # flake8-comprehensions
 74 |     "C4",
 75 |     # flake8-debugger
 76 |     "T10",
 77 |     # flake8-simplify
 78 |     "SIM",
 79 |     # isort
 80 |     "I",
 81 |     # mccabe
 82 |     "C90",
 83 |     # pycodestyle
 84 |     "E",
 85 |     "W",
 86 |     # pyflakes
 87 |     "F",
 88 |     # pygrep-hooks
 89 |     "PGH",
 90 |     # pyupgrade
 91 |     "UP",
 92 |     # ruff
 93 |     "RUF",
 94 |     # tryceratops
 95 |     "TRY",
 96 | ]
 97 | ignore = [
 98 |     # LineTooLong
 99 |     "E501",
100 |     # DoNotAssignLambda
101 |     "E731",
102 | ]
103 | 
104 | [tool.ruff.lint.per-file-ignores]
105 | "tests/*" = ["S101"]
106 | 
107 | [tool.ruff.format]
108 | preview = true
109 | 
110 | [tool.coverage.report]
111 | skip_empty = true
112 | 
113 | [tool.coverage.run]
114 | branch = true
115 | source = ["yourware_mcp"]
116 | 
```

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

```yaml
  1 | name: release-main
  2 | 
  3 | permissions:
  4 |   contents: write
  5 |   packages: write
  6 | 
  7 | on:
  8 |   release:
  9 |     types: [published]
 10 | 
 11 | jobs:
 12 |   set-version:
 13 |     runs-on: ubuntu-24.04
 14 |     steps:
 15 |       - uses: actions/checkout@v4
 16 | 
 17 |       - name: Export tag
 18 |         id: vars
 19 |         run: echo tag=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT
 20 |         if: ${{ github.event_name == 'release' }}
 21 | 
 22 |       - name: Update project version
 23 |         run: |
 24 |           sed -i "s/^version = \".*\"/version = \"$RELEASE_VERSION\"/" pyproject.toml
 25 |         env:
 26 |           RELEASE_VERSION: ${{ steps.vars.outputs.tag }}
 27 |         if: ${{ github.event_name == 'release' }}
 28 | 
 29 |       - name: Upload updated pyproject.toml
 30 |         uses: actions/upload-artifact@v4
 31 |         with:
 32 |           name: pyproject-toml
 33 |           path: pyproject.toml
 34 | 
 35 |   publish:
 36 |     runs-on: ubuntu-latest
 37 |     needs: [set-version]
 38 |     steps:
 39 |       - name: Check out
 40 |         uses: actions/checkout@v4
 41 | 
 42 |       - name: Set up the environment
 43 |         uses: ./.github/actions/setup-python-env
 44 | 
 45 |       - name: Download updated pyproject.toml
 46 |         uses: actions/download-artifact@v4
 47 |         with:
 48 |           name: pyproject-toml
 49 | 
 50 |       - name: Build package
 51 |         run: uv build
 52 | 
 53 |       - name: Publish package
 54 |         run: uv publish
 55 |         env:
 56 |           UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
 57 | 
 58 |       - name: Upload dists to release
 59 |         uses: svenstaro/upload-release-action@v2
 60 |         with:
 61 |             repo_token: ${{ secrets.GITHUB_TOKEN }}
 62 |             file: dist/*
 63 |             file_glob: true
 64 |             tag: ${{ github.ref }}
 65 |             overwrite: true
 66 | 
 67 | 
 68 |   push-image:
 69 |     runs-on: ubuntu-latest
 70 |     needs: [set-version]
 71 |     steps:
 72 |       - uses: actions/checkout@v4
 73 |       - name: Export tag
 74 |         id: vars
 75 |         run: echo tag=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT
 76 |         if: ${{ github.event_name == 'release' }}
 77 |       - name: Set up QEMU
 78 |         uses: docker/setup-qemu-action@v3
 79 |       - name: Set up Docker Buildx
 80 |         uses: docker/setup-buildx-action@v3
 81 |       - name: Login to Github Container Registry
 82 |         uses: docker/login-action@v3
 83 |         with:
 84 |           registry: ghcr.io
 85 |           username: ai-zerolab
 86 |           password: ${{ secrets.GITHUB_TOKEN }}
 87 |       - name: Build and push image
 88 |         id: docker_build_publish
 89 |         uses: docker/build-push-action@v5
 90 |         with:
 91 |             context: .
 92 |             platforms: linux/amd64,linux/arm64/v8
 93 |             cache-from: type=gha
 94 |             cache-to: type=gha,mode=max
 95 |             file: ./Dockerfile
 96 |             push: true
 97 |             tags: |
 98 |               ghcr.io/ai-zerolab/yourware-mcp:${{ steps.vars.outputs.tag }}
 99 |               ghcr.io/ai-zerolab/yourware-mcp:latest
100 | 
```

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

```python
  1 | import io
  2 | import os
  3 | import zipfile
  4 | from pathlib import Path
  5 | from typing import Annotated
  6 | 
  7 | import httpx
  8 | from mcp.server.fastmcp import FastMCP
  9 | 
 10 | from yourware_mcp.client import get_client
 11 | from yourware_mcp.credentials import API_BASE_URL, CREDENTIALS_PATH, Credentials
 12 | from yourware_mcp.utils import urljoin
 13 | 
 14 | mcp = FastMCP("yourware-mcp")
 15 | 
 16 | 
 17 | @mcp.tool(description="Check your yourware credentials exists and are valid.")
 18 | async def check_credentials():
 19 |     try:
 20 |         credentials = Credentials.load()
 21 |     except FileNotFoundError:
 22 |         return {
 23 |             "success": False,
 24 |             "message": "Credentials not found",
 25 |             "help": "Run `create_api_key` to create one",
 26 |         }
 27 | 
 28 |     if not await credentials.check_credentials():
 29 |         return {
 30 |             "success": False,
 31 |             "message": "Credentials are invalid",
 32 |             "help": "Call `create_api_key` to create one",
 33 |         }
 34 | 
 35 |     return {
 36 |         "success": True,
 37 |         "message": "Credentials are valid",
 38 |     }
 39 | 
 40 | 
 41 | @mcp.tool(
 42 |     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"
 43 | )
 44 | async def create_api_key(api_key: Annotated[str | None, "The API key to store"] = None):
 45 |     if not api_key:
 46 |         quick_create_address = urljoin(API_BASE_URL, "/api/v1/api-keys/quick-create")
 47 |         login_address = urljoin(API_BASE_URL, "/login")
 48 |         return {
 49 |             "success": False,
 50 |             "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.",
 51 |             "help": f"Click this link to create one: {quick_create_address}\n\nClick this link to login if needed: {login_address}",
 52 |         }
 53 | 
 54 |     Credentials(api_key=api_key).store_credentials()
 55 |     return {
 56 |         "success": True,
 57 |         "message": "API key created",
 58 |     }
 59 | 
 60 | 
 61 | @mcp.tool(
 62 |     description="Upload a file or directory to yourware, might be a dist/out directory or a single html file. Use absolute path if possible. "
 63 |     "For multiple files, you should move them to a directory first, then use this tool to upload the directory"
 64 | )
 65 | async def upload_project(  # noqa: C901
 66 |     file_path: Annotated[
 67 |         str,
 68 |         "The path to the dist/out directory or single file. If ends with /, it will be treated as a directory",
 69 |     ],
 70 |     cwd: Annotated[
 71 |         str | None,
 72 |         "The current working directory to resolve relative paths from, should be a absolute path",
 73 |     ] = None,
 74 | ):
 75 |     if cwd:
 76 |         cwd_path = Path(cwd).expanduser().resolve()
 77 |         file_path = cwd_path / file_path
 78 |     else:
 79 |         file_path = Path(file_path)
 80 | 
 81 |     file_path = file_path.expanduser().resolve()
 82 | 
 83 |     try:
 84 |         credentials = Credentials.load()
 85 |     except FileNotFoundError:
 86 |         return {
 87 |             "success": False,
 88 |             "message": "Credentials not found",
 89 |             "help": "Run `create_api_key` to create one",
 90 |         }
 91 | 
 92 |     if not await credentials.check_credentials():
 93 |         return {
 94 |             "success": False,
 95 |             "message": "Credentials are invalid",
 96 |             "help": "Call `create_api_key` to create one",
 97 |         }
 98 |     client = get_client(credentials)
 99 | 
100 |     # 1. Create a zip in memory
101 |     zip_buffer = io.BytesIO()
102 |     with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
103 |         if file_path.is_dir():
104 |             # 2. Zip the directory into it
105 |             for root, dirs, files in os.walk(file_path):
106 |                 # Skip .git directories
107 |                 if ".git" in dirs:
108 |                     dirs.remove(".git")  # This modifies dirs in-place to prevent os.walk from traversing .git
109 | 
110 |                 for file in files:
111 |                     file_full_path = Path(root) / file
112 |                     arc_name = file_full_path.relative_to(file_path)
113 |                     zip_file.write(file_full_path, arcname=arc_name)
114 |         else:
115 |             # Zip the single file
116 |             zip_file.write(file_path, arcname=file_path.name)
117 | 
118 |     # Get the zip content
119 |     zip_buffer.seek(0)
120 |     zip_content = zip_buffer.getvalue()
121 |     zip_size = len(zip_content)
122 | 
123 |     # 3. Call /api/v1/files/upload for upload infos
124 |     upload_response = await client.post(
125 |         "/api/v1/files/upload",
126 |         json={
127 |             "files": [
128 |                 {
129 |                     "file_name": "source_code.zip",
130 |                     "file_size": zip_size,
131 |                     "mime_type": "application/zip",
132 |                 }
133 |             ],
134 |             "event_type": "source_code",
135 |             "is_public": False,
136 |         },
137 |     )
138 | 
139 |     if upload_response.status_code != 200:
140 |         return {
141 |             "success": False,
142 |             "message": f"Failed to get upload info: {upload_response.text}",
143 |         }
144 | 
145 |     upload_data = upload_response.json()
146 |     upload_info = upload_data["data"]["upload_infos"][0]
147 |     file_id = upload_info["file_id"]
148 |     upload_url = upload_info["upload_url"]
149 |     fields = upload_info["fields"]
150 | 
151 |     # 4. Upload the zip to the upload url
152 |     files = {"file": ("source_code.zip", zip_content, "application/zip")}
153 |     form_data = {**fields}
154 | 
155 |     async with httpx.AsyncClient() as upload_client:
156 |         upload_result = await upload_client.post(upload_url, data=form_data, files=files)
157 | 
158 |     if upload_result.status_code not in (200, 201, 204):
159 |         return {
160 |             "success": False,
161 |             "message": f"Failed to upload file: {upload_result.text}",
162 |         }
163 | 
164 |     # 5. Call /api/v1/projects/deploy with the file_id
165 |     deploy_response = await client.post("/api/v1/projects/deploy", json={"file_id": file_id})
166 | 
167 |     if deploy_response.status_code != 200:
168 |         return {
169 |             "success": False,
170 |             "message": f"Failed to deploy project: {deploy_response.text}",
171 |         }
172 | 
173 |     deploy_data = deploy_response.json()
174 |     project_data = deploy_data["data"]
175 | 
176 |     return {
177 |         "success": True,
178 |         "message": "Project uploaded successfully",
179 |         "project_url": project_data["project_url"],
180 |         "iframe_url": project_data["iframe_url"],
181 |     }
182 | 
```

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

```html
  1 | <!DOCTYPE html>
  2 | <html lang="en">
  3 | <head>
  4 |     <meta charset="UTF-8">
  5 |     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 |     <title>Yourware MCP - Upload Your Projects to Yourware</title>
  7 |     <style>
  8 |         :root {
  9 |             --primary-color: #4f46e5;
 10 |             --secondary-color: #818cf8;
 11 |             --text-color: #1f2937;
 12 |             --bg-color: #ffffff;
 13 |             --card-bg: #f9fafb;
 14 |             --border-color: #e5e7eb;
 15 |             --code-bg: #f3f4f6;
 16 |         }
 17 | 
 18 |         body {
 19 |             font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 20 |             line-height: 1.6;
 21 |             color: var(--text-color);
 22 |             background-color: var(--bg-color);
 23 |             margin: 0;
 24 |             padding: 0;
 25 |         }
 26 | 
 27 |         .container {
 28 |             max-width: 1200px;
 29 |             margin: 0 auto;
 30 |             padding: 0 20px;
 31 |         }
 32 | 
 33 |         header {
 34 |             background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
 35 |             color: white;
 36 |             padding: 60px 0;
 37 |             text-align: center;
 38 |         }
 39 | 
 40 |         header h1 {
 41 |             margin: 0;
 42 |             font-size: 3rem;
 43 |             letter-spacing: -0.5px;
 44 |         }
 45 | 
 46 |         header p {
 47 |             font-size: 1.2rem;
 48 |             margin: 15px 0 0;
 49 |             opacity: 0.9;
 50 |         }
 51 | 
 52 |         section {
 53 |             padding: 60px 0;
 54 |             border-bottom: 1px solid var(--border-color);
 55 |         }
 56 | 
 57 |         h2 {
 58 |             font-size: 2rem;
 59 |             margin-top: 0;
 60 |             color: var(--primary-color);
 61 |         }
 62 | 
 63 |         h3 {
 64 |             font-size: 1.5rem;
 65 |             margin-top: 30px;
 66 |             color: var(--primary-color);
 67 |         }
 68 | 
 69 |         .features {
 70 |             display: grid;
 71 |             grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
 72 |             gap: 30px;
 73 |             margin-top: 40px;
 74 |         }
 75 | 
 76 |         .feature-card {
 77 |             background-color: var(--card-bg);
 78 |             border-radius: 8px;
 79 |             padding: 25px;
 80 |             box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
 81 |             transition: transform 0.3s ease;
 82 |         }
 83 | 
 84 |         .feature-card:hover {
 85 |             transform: translateY(-5px);
 86 |         }
 87 | 
 88 |         .feature-card h3 {
 89 |             margin-top: 0;
 90 |             font-size: 1.3rem;
 91 |         }
 92 | 
 93 |         code {
 94 |             background-color: var(--code-bg);
 95 |             padding: 2px 5px;
 96 |             border-radius: 4px;
 97 |             font-family: 'Courier New', Courier, monospace;
 98 |             font-size: 0.9em;
 99 |         }
100 | 
101 |         pre {
102 |             background-color: var(--code-bg);
103 |             padding: 15px;
104 |             border-radius: 8px;
105 |             overflow-x: auto;
106 |             font-family: 'Courier New', Courier, monospace;
107 |             font-size: 0.9em;
108 |             line-height: 1.4;
109 |         }
110 | 
111 |         .btn {
112 |             display: inline-block;
113 |             background-color: var(--primary-color);
114 |             color: white;
115 |             padding: 12px 24px;
116 |             border-radius: 6px;
117 |             text-decoration: none;
118 |             font-weight: 600;
119 |             transition: background-color 0.3s ease;
120 |         }
121 | 
122 |         .btn:hover {
123 |             background-color: var(--secondary-color);
124 |         }
125 | 
126 |         .installation-steps {
127 |             counter-reset: step;
128 |             list-style-type: none;
129 |             padding-left: 0;
130 |         }
131 | 
132 |         .installation-steps li {
133 |             position: relative;
134 |             margin-bottom: 20px;
135 |             padding-left: 50px;
136 |         }
137 | 
138 |         .installation-steps li::before {
139 |             counter-increment: step;
140 |             content: counter(step);
141 |             position: absolute;
142 |             left: 0;
143 |             top: 0;
144 |             background-color: var(--primary-color);
145 |             color: white;
146 |             width: 30px;
147 |             height: 30px;
148 |             border-radius: 50%;
149 |             display: flex;
150 |             align-items: center;
151 |             justify-content: center;
152 |             font-weight: bold;
153 |         }
154 | 
155 |         .resources {
156 |             display: grid;
157 |             grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
158 |             gap: 20px;
159 |         }
160 | 
161 |         .resource-card {
162 |             background-color: var(--card-bg);
163 |             border-radius: 8px;
164 |             padding: 20px;
165 |             text-align: center;
166 |         }
167 | 
168 |         footer {
169 |             text-align: center;
170 |             padding: 30px 0;
171 |             color: #6b7280;
172 |             font-size: 0.9rem;
173 |         }
174 | 
175 |         @media (max-width: 768px) {
176 |             header h1 {
177 |                 font-size: 2.2rem;
178 |             }
179 | 
180 |             .features {
181 |                 grid-template-columns: 1fr;
182 |             }
183 |         }
184 |     </style>
185 | </head>
186 | <body>
187 |     <header>
188 |         <div class="container">
189 |             <h1>Yourware MCP</h1>
190 |             <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>
191 |         </div>
192 |     </header>
193 | 
194 |     <section id="overview">
195 |         <div class="container">
196 |             <h2>Project Overview</h2>
197 |             <p>
198 |                 Yourware MCP is a Model Context Protocol (MCP) server that enables AI assistants like Claude to upload your projects directly to
199 |                 <a href="https://www.yourware.so">yourware.so</a>. This integration allows for seamless deployment of web projects,
200 |                 making it easy to showcase your work online without leaving your development environment.
201 |             </p>
202 |             <p>
203 |                 Whether you're building a simple HTML page, a complex web application, or any other web-based project,
204 |                 Yourware MCP streamlines the process of getting your work online and shareable.
205 |             </p>
206 |         </div>
207 |     </section>
208 | 
209 |     <section id="features">
210 |         <div class="container">
211 |             <h2>Features & Capabilities</h2>
212 |             <div class="features">
213 |                 <div class="feature-card">
214 |                     <h3>Easy Authentication</h3>
215 |                     <p>Simple API key-based authentication system that securely stores your credentials.</p>
216 |                 </div>
217 |                 <div class="feature-card">
218 |                     <h3>Flexible Uploads</h3>
219 |                     <p>Upload single files or entire directories with automatic compression and organization.</p>
220 |                 </div>
221 |                 <div class="feature-card">
222 |                     <h3>Seamless Deployment</h3>
223 |                     <p>One-step deployment process that makes your projects instantly available online.</p>
224 |                 </div>
225 |                 <div class="feature-card">
226 |                     <h3>AI Assistant Integration</h3>
227 |                     <p>Works with AI assistants like Claude through Cline, Cursor, and Windsurf for a smooth workflow.</p>
228 |                 </div>
229 |             </div>
230 |         </div>
231 |     </section>
232 | 
233 |     <section id="installation">
234 |         <div class="container">
235 |             <h2>Installation & Setup</h2>
236 |             <p>Getting started with Yourware MCP is straightforward:</p>
237 | 
238 |             <ol class="installation-steps">
239 |                 <li>
240 |                     <strong>Install the package</strong>
241 |                     <pre>pip install yourware-mcp</pre>
242 |                     <p>Or use <code>uvx</code> directly as shown in the configuration examples below.</p>
243 |                 </li>
244 |                 <li>
245 |                     <strong>Configure your AI assistant</strong>
246 |                     <p>Add the Yourware MCP server to your AI assistant's configuration:</p>
247 |                     <h3>General Configuration</h3>
248 |                     <pre>{
249 |   "mcpServers": {
250 |     "yourware-mcp": {
251 |       "command": "uvx",
252 |       "args": ["yourware-mcp@latest", "stdio"],
253 |       "env": {}
254 |     }
255 |   }
256 | }</pre>
257 |                     <h3>Cursor Configuration</h3>
258 |                     <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>
259 |                     <img src="../assets/config-cursor.png" alt="Config cursor screenshot" style="max-width: 100%; border-radius: 8px; margin-top: 10px;">
260 |                 </li>
261 |                 <li>
262 |                     <strong>Create an API key</strong>
263 |                     <p>You can either:</p>
264 |                     <ul>
265 |                         <li>Let your AI assistant guide you through creating an API key</li>
266 |                         <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>
267 |                         <li>Set the <code>YOURWARE_API_KEY</code> environment variable</li>
268 |                     </ul>
269 |                 </li>
270 |             </ol>
271 |         </div>
272 |     </section>
273 | 
274 |     <section id="usage">
275 |         <div class="container">
276 |             <h2>Usage Examples</h2>
277 |             <p>Once configured, you can use Yourware MCP through your AI assistant with the following tools:</p>
278 | 
279 |             <h3>Check Credentials</h3>
280 |             <pre>
281 | // Check if your credentials exist and are valid
282 | await use_mcp_tool({
283 |   server_name: "yourware-mcp",
284 |   tool_name: "check_credentials",
285 |   arguments: {}
286 | });</pre>
287 | 
288 |             <h3>Create API Key</h3>
289 |             <pre>
290 | // Create a new API key
291 | await use_mcp_tool({
292 |   server_name: "yourware-mcp",
293 |   tool_name: "create_api_key",
294 |   arguments: {
295 |     api_key: "your-api-key-here" // Optional, if not provided, will guide you to create one
296 |   }
297 | });</pre>
298 | 
299 |             <h3>Upload Project</h3>
300 |             <pre>
301 | // Upload a file or directory to yourware
302 | await use_mcp_tool({
303 |   server_name: "yourware-mcp",
304 |   tool_name: "upload_project",
305 |   arguments: {
306 |     file_path: "path/to/your/project" // Can be a directory or single file
307 |   }
308 | });</pre>
309 | 
310 |             <h3>Example Workflow</h3>
311 |             <p>A typical workflow might look like this:</p>
312 |             <ol>
313 |                 <li>Build your web project</li>
314 |                 <li>Check if your credentials are valid</li>
315 |                 <li>If not, create a new API key</li>
316 |                 <li>Upload your project to Yourware</li>
317 |                 <li>Get the project URL and share it with others</li>
318 |             </ol>
319 |         </div>
320 |     </section>
321 | 
322 |     <section id="resources">
323 |         <div class="container">
324 |             <h2>Resources & Links</h2>
325 |             <div class="resources">
326 |                 <div class="resource-card">
327 |                     <h3>GitHub Repository</h3>
328 |                     <p>View the source code and contribute to the project.</p>
329 |                     <a href="https://github.com/ai-zerolab/yourware-mcp" class="btn">Visit GitHub</a>
330 |                 </div>
331 |                 <div class="resource-card">
332 |                     <h3>Yourware Platform</h3>
333 |                     <p>Explore the Yourware platform and see examples.</p>
334 |                     <a href="https://www.yourware.so" class="btn">Visit Yourware</a>
335 |                 </div>
336 |                 <div class="resource-card">
337 |                     <h3>Documentation</h3>
338 |                     <p>Read the full documentation for detailed information.</p>
339 |                     <a href="https://www.yourware.so/docs" class="btn">Read Docs</a>
340 |                 </div>
341 |             </div>
342 |         </div>
343 |     </section>
344 | 
345 |     <footer>
346 |         <div class="container">
347 |             <p>&copy; 2025 Yourware MCP. All rights reserved.</p>
348 |         </div>
349 |     </footer>
350 | </body>
351 | </html>
352 | 
```