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

```
├── .github
│   └── workflows
│       └── python-publish.yml
├── .gitignore
├── LICENSE
├── pyproject.toml
├── README.md
├── src
│   └── mcp_server_chatgpt
│       ├── __init__.py
│       └── server.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.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

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# 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/

# 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/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

```

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

```markdown
# mcp-server-chatgpt-app

## Prerequisite

- [ChatGPT macOS app](https://openai.com/chatgpt/download/)
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
- Install the ["Ask ChatGPT on Mac" shortcuts](https://www.icloud.com/shortcuts/6ae86a39a31e4ec5938abad953ecfd64)

## Usage

### cursor

update `.mcp.json` to add the following:

```
{
    "mcpServers": {
      "chatgpt": {
        "command": "uvx",
        "args": ["mcp-server-chatgpt-app"],
        "env": {},
        "disabled": false,
        "autoApprove": []
      }
    }
}
```

### chatwise

Go to Settings -> Tools -> Add and use the following config:

```
Type: stdio
ID: ChatGPT
Command: uvx mcp-server-chatgpt-app
```

> [!CAUTION]
> Chatwise did not close mcp server even when itself is closed, which may lead to multiple mcp servers running at the same time.
> DO remember to clean them all up: `pkill -f 'mcp-server-chatgpt-app'`

### [MCP Inspector](https://github.com/modelcontextprotocol/inspector)

```
Transport type: stdio
Command: uv
Arguments: --directory /Users/<your-username>/Developer/mcp-server-chatgpt-app/src/mcp_server_chatgpt run server.py
Configuration/Request Timeout: 100000
```


## local development

```
uv --directory $HOME/Developer/mcp-server-chatgpt-app/src/mcp_server_chatgpt run server.py
```


```

--------------------------------------------------------------------------------
/src/mcp_server_chatgpt/__init__.py:
--------------------------------------------------------------------------------

```python
from .server import main

__all__ = ['main'] 
```

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

```toml
[project]
name = "mcp-server-chatgpt-app"
version = "0.1.5"
description = "MCP Server for ChatGPT via AppleScript"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
authors = [
    {name = "cdpath", email = "[email protected]"}
]
keywords = ["ChatGPT", "mcp", "AppleScript"]
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
]
dependencies = [
    "mcp>=1.6.0",
]

[project.urls]
Homepage = "https://github.com/cdpath/mcp-server-chatgpt-app"
Repository = "https://github.com/cdpath/mcp-server-chatgpt-app.git"
Issues = "https://github.com/cdpath/mcp-server-chatgpt-app/issues"

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

[tool.hatch.build.targets.wheel]
packages = ["src/mcp_server_chatgpt"]

[project.scripts]
mcp-server-chatgpt-app = "mcp_server_chatgpt.server:main"

```

--------------------------------------------------------------------------------
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------

```yaml
# This workflow will upload a Python Package to PyPI when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  release-build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.x"

      - name: Build release distributions
        run: |
          # NOTE: put your own distribution build steps here.
          python -m pip install build
          python -m build

      - name: Upload distributions
        uses: actions/upload-artifact@v4
        with:
          name: release-dists
          path: dist/

  pypi-publish:
    runs-on: ubuntu-latest
    needs:
      - release-build
    permissions:
      # IMPORTANT: this permission is mandatory for trusted publishing
      id-token: write

    # Dedicated environments with protections for publishing are strongly recommended.
    # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
    environment:
      name: pypi
      # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
      # url: https://pypi.org/p/YOURPROJECT
      #
      # ALTERNATIVE: if your GitHub Release name is the PyPI project version string
      # ALTERNATIVE: exactly, uncomment the following line instead:
      # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }}

    steps:
      - name: Retrieve release distributions
        uses: actions/download-artifact@v4
        with:
          name: release-dists
          path: dist/

      - name: Publish release distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist/

```

--------------------------------------------------------------------------------
/src/mcp_server_chatgpt/server.py:
--------------------------------------------------------------------------------

```python
import subprocess
import os
from typing import Optional, Dict, Any
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("ChatGPT")

def run_applescript(script: str, wait_for_output: bool = True) -> Dict[str, Any]:
    """
    Run an AppleScript and return its output and status.
    
    Args:
        script: The AppleScript to run
        wait_for_output: Whether to wait for and capture output
    """
    try:
        if wait_for_output:
            # Run synchronously and capture output
            result = subprocess.run(
                ['osascript', '-e', script],
                capture_output=True,
                text=True,
                check=True
            )
            return {
                "success": True,
                "output": result.stdout.strip()
            }
        else:
            # Use Popen for non-blocking execution
            subprocess.Popen(
                ['osascript', '-e', script], 
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                start_new_session=True
            )
            return {
                "success": True,
                "output": ""
            }
    except Exception as e:
        error_msg = str(e)
        return {
            "success": False,
            "error": error_msg,
        }

@mcp.tool()
def ask_chatgpt(prompt: str, wait_for_output: bool = False) -> Dict[str, Any]:
    """
    Send a prompt to ChatGPT macOS app using Shortcuts.
    
    Args:
        prompt: The text to send to ChatGPT
        wait_for_output: Whether to wait for ChatGPT to respond
    Returns:
        Dict containing operation status and response
    """
    try:
        if wait_for_output:
            # Use shortcuts command directly to capture output
            result = subprocess.run(
                ['shortcuts', 'run', 'Ask ChatGPT on Mac', '-i', prompt],
                capture_output=True,
                text=True,
                check=True
            )
            return {
                "operation": "ask_chatgpt",
                "status": "success",
                "message": result.stdout.strip()
            }
        else:
            # Run without waiting for output
            subprocess.Popen(
                ['shortcuts', 'run', 'Ask ChatGPT on Mac', '-i', prompt],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                start_new_session=True
            )
            return {
                "operation": "ask_chatgpt",
                "status": "success",
                "message": "Sent to ChatGPT (not waiting for response)"
            }
    except subprocess.CalledProcessError as e:
        return {
            "operation": "ask_chatgpt",
            "status": "error",
            "message": f"Shortcuts command failed: {e.stderr.strip() if e.stderr else str(e)}"
        }
    except Exception as e:
        return {
            "operation": "ask_chatgpt",
            "status": "error",
            "message": f"Unexpected error: {str(e)}"
        }

def main():
    """Entry point for the MCP server."""
    mcp.run()

if __name__ == "__main__":
    main() 
```