-
Notifications
You must be signed in to change notification settings - Fork 14
Add MCP server with image search and carousel display #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from 5 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
090891a
Add MCP server support
pamelafox e1f464e
Rename blob viewer to image viewer
pamelafox 99f848b
Update requirements with main
pamelafox 521ed9f
Update requirements with main
pamelafox 156351a
Drop Python 3.9, run ruff
pamelafox 482e8bf
Address feedback on MCP server
pamelafox c0d0bcb
Address rest of Copilot feedback
pamelafox d5bb693
Restore appinsights location
pamelafox 6c464e6
Simplify by removing unneeded code
pamelafox 089898e
Remove unneeded container name references and rename var
pamelafox ef0c9d9
Address latest comments
pamelafox 319ee4a
Move pictures into top level pictures folder
pamelafox 2e7305d
Run ruff format
pamelafox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| --- | ||
| name: convert-heic-to-jpeg | ||
| description: Convert HEIC or HEIF images to JPEG with `uv run` using inline script dependencies. Use this when new phone photos are added under `pictures/` and need to be converted before indexing or upload. | ||
| argument-hint: "[path] [--quality 90] [--overwrite]" | ||
| --- | ||
|
|
||
| # Convert HEIC To JPEG | ||
|
|
||
| Use this skill when you need to convert `.heic` or `.heif` images into `.jpg` files for local development workflows. | ||
|
|
||
| The script for this skill lives at [./convert_heic_to_jpeg.py](./convert_heic_to_jpeg.py) and is designed to be run with `uv run`. | ||
|
|
||
| ## What this skill does | ||
|
|
||
| - Converts a single HEIC or HEIF file to JPEG. | ||
| - Recursively scans a directory for HEIC or HEIF files. | ||
| - Writes JPEG files next to the source images by default. | ||
| - Can write output into a separate directory. | ||
| - Skips existing JPEG files unless `--overwrite` is passed. | ||
|
|
||
| ## How to run it | ||
|
|
||
| Run the script with `uv run` so the inline PEP 723 dependencies are installed automatically: | ||
|
|
||
| ```bash | ||
| uv run .agents/skills/convert-heic-to-jpeg/convert_heic_to_jpeg.py pictures | ||
| ``` | ||
|
|
||
| Convert one file: | ||
|
|
||
| ```bash | ||
| uv run .agents/skills/convert-heic-to-jpeg/convert_heic_to_jpeg.py pictures/clothes/IMG_3233.HEIC | ||
| ``` | ||
|
|
||
| Convert into a separate output directory: | ||
|
|
||
| ```bash | ||
| uv run .agents/skills/convert-heic-to-jpeg/convert_heic_to_jpeg.py pictures --output-dir converted-pictures | ||
| ``` | ||
|
|
||
| Overwrite existing JPEG files: | ||
|
|
||
| ```bash | ||
| uv run .agents/skills/convert-heic-to-jpeg/convert_heic_to_jpeg.py pictures --overwrite | ||
| ``` | ||
|
|
||
| ## Recommended Workflow | ||
|
|
||
| 1. Point the script at `pictures/` or a subdirectory such as `pictures/clothes/`. | ||
| 2. Review the summary output to confirm how many files were converted or skipped. | ||
| 3. Re-run with `--overwrite` only if you intentionally want to replace existing JPEG outputs. | ||
|
|
||
| ## Notes | ||
|
|
||
| - This skill is stored in `.agents/skills`, which VS Code documents as a default project skill location. | ||
| - The script depends on Pillow and `pillow-heif`, declared inline in the script so they can be resolved by `uv run`. |
162 changes: 162 additions & 0 deletions
162
.agents/skills/convert-heic-to-jpeg/convert_heic_to_jpeg.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| # /// script | ||
| # requires-python = ">=3.11" | ||
| # dependencies = [ | ||
| # "pillow>=11.0.0", | ||
| # "pillow-heif>=0.22.0", | ||
| # ] | ||
| # /// | ||
|
|
||
| """Convert HEIC and HEIF images to JPEG format.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| from dataclasses import dataclass | ||
| from pathlib import Path | ||
|
|
||
| from PIL import Image | ||
| import pillow_heif | ||
|
|
||
| # Register HEIF support with Pillow before opening files. | ||
| pillow_heif.register_heif_opener() | ||
|
|
||
| SUPPORTED_EXTENSIONS = {".heic", ".heif"} | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class ConversionResult: | ||
| """Capture the outcome for a single source image.""" | ||
|
|
||
| source_path: Path | ||
| output_path: Path | ||
| status: str | ||
|
|
||
|
|
||
| def is_heif_file(path: Path) -> bool: | ||
| """Return whether the path has a supported HEIF extension.""" | ||
| return path.suffix.lower() in SUPPORTED_EXTENSIONS | ||
|
|
||
|
|
||
| def build_output_path( | ||
| source_path: Path, input_root: Path, output_dir: Path | None | ||
| ) -> Path: | ||
| """Build the destination JPEG path for a source HEIF image.""" | ||
| if output_dir is None: | ||
| return source_path.with_suffix(".jpg") | ||
|
|
||
| if input_root.is_file(): | ||
| return output_dir / f"{source_path.stem}.jpg" | ||
|
|
||
| relative_path = source_path.relative_to(input_root).with_suffix(".jpg") | ||
| return output_dir / relative_path | ||
|
|
||
|
|
||
| def save_as_jpeg(source_path: Path, output_path: Path, quality: int) -> None: | ||
| """Open a HEIF image and save it as JPEG.""" | ||
| output_path.parent.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| with Image.open(source_path) as image: | ||
| if image.mode not in ("RGB", "L"): | ||
| image = image.convert("RGB") | ||
| image.save(output_path, "JPEG", quality=quality) | ||
|
|
||
|
|
||
| def convert_heic_to_jpeg( | ||
| source_path: Path, | ||
| input_root: Path, | ||
| output_dir: Path | None = None, | ||
| quality: int = 90, | ||
| overwrite: bool = False, | ||
| ) -> ConversionResult: | ||
| """Convert one HEIF image to JPEG.""" | ||
| output_path = build_output_path(source_path, input_root, output_dir) | ||
|
|
||
| if output_path.exists() and not overwrite: | ||
| return ConversionResult( | ||
| source_path=source_path, output_path=output_path, status="skipped" | ||
| ) | ||
|
|
||
| save_as_jpeg(source_path, output_path, quality) | ||
| return ConversionResult( | ||
| source_path=source_path, output_path=output_path, status="converted" | ||
| ) | ||
|
|
||
|
|
||
| def gather_source_files(input_path: Path) -> list[Path]: | ||
| """Return all HEIF images under the given file or directory.""" | ||
| if input_path.is_file(): | ||
| if not is_heif_file(input_path): | ||
| raise ValueError(f"Unsupported input file: {input_path}") | ||
| return [input_path] | ||
|
|
||
| return sorted( | ||
| path for path in input_path.rglob("*") if path.is_file() and is_heif_file(path) | ||
| ) | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| """Parse command-line arguments.""" | ||
| parser = argparse.ArgumentParser(description="Convert HEIC or HEIF images to JPEG") | ||
| parser.add_argument("input", type=Path, help="Input HEIC/HEIF file or directory") | ||
| parser.add_argument( | ||
| "-o", | ||
| "--output-dir", | ||
| type=Path, | ||
| default=None, | ||
| help="Optional output directory for JPEG files", | ||
| ) | ||
| parser.add_argument( | ||
| "-q", | ||
| "--quality", | ||
| type=int, | ||
| default=90, | ||
| help="JPEG quality from 1 to 100 (default: 90)", | ||
| ) | ||
| parser.add_argument( | ||
| "--overwrite", | ||
| action="store_true", | ||
| help="Replace existing JPEG files instead of skipping them", | ||
| ) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def main() -> int: | ||
| """Run the CLI conversion workflow.""" | ||
| args = parse_args() | ||
|
|
||
| if not args.input.exists(): | ||
| raise FileNotFoundError(f"Input path does not exist: {args.input}") | ||
|
|
||
| if not 1 <= args.quality <= 100: | ||
| raise ValueError("Quality must be between 1 and 100") | ||
|
|
||
| source_files = gather_source_files(args.input) | ||
| if not source_files: | ||
| print("No HEIC or HEIF files found.") | ||
| return 0 | ||
|
|
||
| converted_count = 0 | ||
| skipped_count = 0 | ||
|
|
||
| for source_path in source_files: | ||
| result = convert_heic_to_jpeg( | ||
| source_path=source_path, | ||
| input_root=args.input, | ||
| output_dir=args.output_dir, | ||
| quality=args.quality, | ||
| overwrite=args.overwrite, | ||
| ) | ||
| print(f"{result.status:9} {result.source_path} -> {result.output_path}") | ||
| if result.status == "converted": | ||
| converted_count += 1 | ||
| else: | ||
| skipped_count += 1 | ||
|
|
||
| print( | ||
| f"Summary: converted={converted_count} skipped={skipped_count} total={len(source_files)}" | ||
| ) | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "servers": { | ||
| "image-search-local": { | ||
| "type": "http", | ||
| "url": "http://localhost:8001/mcp" | ||
| }, | ||
| "image-search-azure": { | ||
| "type": "http", | ||
| "url": "https://ca-mcp-bghd6fzdtn5ce.bravemushroom-d89fed68.westcentralus.azurecontainerapps.io/mcp" | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # ------------------- Stage 0: Base Stage ------------------------------ | ||
| FROM python:3.11-alpine AS base | ||
|
|
||
| WORKDIR /code | ||
|
|
||
| # Install tini, a tiny init for containers | ||
| RUN apk add --update --no-cache tini | ||
|
|
||
| # Install required packages for cryptography package | ||
| # https://cryptography.io/en/latest/installation/#building-cryptography-on-linux | ||
| RUN apk add gcc musl-dev python3-dev libffi-dev openssl-dev cargo pkgconfig | ||
|
|
||
|
pamelafox marked this conversation as resolved.
|
||
| # ------------------- Stage 1: Build Stage ------------------------------ | ||
| FROM base AS build | ||
|
|
||
| COPY requirements.txt . | ||
|
|
||
| RUN pip3 install -r requirements.txt | ||
|
|
||
| COPY . . | ||
| # ------------------- Stage 2: Final Stage ------------------------------ | ||
| FROM base AS final | ||
|
|
||
| RUN addgroup -S app && adduser -S app -G app | ||
|
|
||
| COPY --from=build --chown=app:app /usr/local/lib/python3.11 /usr/local/lib/python3.11 | ||
| COPY --from=build --chown=app:app /usr/local/bin /usr/local/bin | ||
| COPY --from=build --chown=app:app /code /code | ||
|
|
||
| USER app | ||
|
|
||
| EXPOSE 8001 | ||
|
|
||
| ENTRYPOINT ["tini", "python", "mcp_server.py"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.