Skip to content

Improved github CI #23

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

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
8108558
Fixed some github CI yaml errors
hillarysanders Jun 9, 2025
f69491d
comments back in
hillarysanders Jun 9, 2025
b67c472
comment updates
hillarysanders Jun 9, 2025
de10b68
Testing auto-deploy-test-app
hillarysanders Jun 9, 2025
9af2106
python version
hillarysanders Jun 9, 2025
66f0c96
refs/heads/main fix?
hillarysanders Jun 9, 2025
70a21e5
shallow clone fix
hillarysanders Jun 9, 2025
71bc450
Trigger CI
hillarysanders Jun 9, 2025
9c9bf99
set API key
hillarysanders Jun 9, 2025
2df8b7c
Correct MCP_SERVER_URL
hillarysanders Jun 9, 2025
237b506
Trigger CI
hillarysanders Jun 10, 2025
a55ca1d
Trigger CI
hillarysanders Jun 10, 2025
0adf81c
safe call maybe fix
hillarysanders Jun 10, 2025
ed6fd95
undo
hillarysanders Jun 10, 2025
eb0d221
test
hillarysanders Jun 10, 2025
a332d06
Trigger CI
hillarysanders Jun 10, 2025
2558d5e
Trigger CI
hillarysanders Jun 10, 2025
da91c05
Trigger CI
hillarysanders Jun 10, 2025
6ff4f8a
Trigger CI
hillarysanders Jun 10, 2025
191f59d
more elegant sleep instead of echo
hillarysanders Jun 10, 2025
d3574a3
Back to debug env
hillarysanders Jun 10, 2025
a17ade0
Back to debug env
hillarysanders Jun 10, 2025
5df001f
Trigger CI
hillarysanders Jun 10, 2025
09d4fc1
Trigger CI
hillarysanders Jun 10, 2025
938c9b4
Trigger CI
hillarysanders Jun 10, 2025
f279a60
slight README update
hillarysanders Jun 10, 2025
5c4637c
Trigger CI
hillarysanders Jun 11, 2025
0077b3c
Longer sleep to see if this helps integration tests
hillarysanders Jun 11, 2025
eb5a091
30s wait test
hillarysanders Jun 11, 2025
f19620e
test
hillarysanders Jun 11, 2025
689081e
My fix
hillarysanders Jun 11, 2025
2900a5b
My fix
hillarysanders Jun 11, 2025
3e9672d
My fix
hillarysanders Jun 11, 2025
80d62a6
test
hillarysanders Jun 11, 2025
e35967f
test
hillarysanders Jun 11, 2025
51b67ed
Trigger CI
hillarysanders Jun 11, 2025
071d2e7
test
hillarysanders Jun 11, 2025
67118f0
test
hillarysanders Jun 11, 2025
306fbf5
test
hillarysanders Jun 12, 2025
a6cff21
test
hillarysanders Jun 12, 2025
c32d984
test
hillarysanders Jun 12, 2025
35fb837
test
hillarysanders Jun 12, 2025
1267d0d
test
hillarysanders Jun 12, 2025
de4978d
test
hillarysanders Jun 12, 2025
7cbfdc1
test
hillarysanders Jun 12, 2025
0c0adf5
test
hillarysanders Jun 12, 2025
3bf3e26
test
hillarysanders Jun 12, 2025
f89c148
test
hillarysanders Jun 12, 2025
ba6c0d6
test
hillarysanders Jun 12, 2025
1c540b1
test
hillarysanders Jun 12, 2025
b790f00
Fiiiinally
hillarysanders Jun 12, 2025
7598ccf
comments
hillarysanders Jun 12, 2025
082b497
comments
hillarysanders Jun 12, 2025
2f295a6
precautionary pre-cleanup step
hillarysanders Jun 12, 2025
61946c1
README edit
hillarysanders Jun 12, 2025
e5f1250
avoid name clashing in app name
hillarysanders Jun 12, 2025
3311a92
Moved python install location in yml
hillarysanders Jun 12, 2025
44809e0
yml comment
hillarysanders Jun 12, 2025
76c55df
Short app name precaution
hillarysanders Jun 12, 2025
aa02a1a
Short app name precaution
hillarysanders Jun 12, 2025
df406c6
automated python version
hillarysanders Jun 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 85 additions & 25 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
name: MCP Tests

on:
push:
branches:
pull_request:

jobs:
###########################################################################
# 1 - Local integration tests (always run)
###########################################################################
local:
runs-on: ubuntu-latest

# Dummy key lets the clients authenticate against the local servers.
env:
API_KEY: ci-test-key

steps:
- uses: actions/checkout@v4

- name: Read Python version from .python-version
id: python-version
run: |
PY_VERSION=$(cat .python-version)
echo "version=$PY_VERSION" >> $GITHUB_OUTPUT

- uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: ${{ steps.python-version.outputs.version }}

# Optional: cache wheels to speed up numpy / scipy installs
- uses: actions/cache@v4
with:
path: ~/.cache/pip
Expand All @@ -33,47 +34,106 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt

- name: Run pytest (local transports)
run: pytest -q


###########################################################################
# 2 - Remote smoke-test (only when secrets are set)
#
# • Put MCP_SERVER_URL → “https://<app>.herokuapp.com”
# • Put API_KEY → same key you set as Heroku config var
#
# The fixture auto-skips the remote case if these are missing, so
# the job is conditionally *created* only when both secrets exist.
# 2 - Deploy this PR to a temp Heroku app and run tests against deployed app (in addition to 'local')
###########################################################################
remote:
if: ${{ secrets.MCP_SERVER_URL != '' && secrets.API_KEY != '' }}
runs-on: ubuntu-latest

env:
MCP_SERVER_URL: ${{ secrets.MCP_SERVER_URL }}
API_KEY: ${{ secrets.API_KEY }}
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
API_KEY: ci-test-key
# also note that github CI doesn't have access to your app's config vars, so here we're setting the remote
# server type to streamable HTTP. Folks using SSE would need to change this line for their e2e remote integration
# tests to test SSE instead of streamable HTTP.
REMOTE_SERVER_TRANSPORT_MODULE: streamable_http_server
# $APP_NAME is set below because we need to shorten the repo owner's name, as a precaution

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # <-- disables shallow clone, which heroku is upset by when running git push heroku later on

# Setting a short $APP_NAME that will be unique even if folks choose to fork this repo --> avoids clashes.
# Needs to be shortened if the github repo owner has a long name (max 30 char app name heroku limit).
- name: Generate short APP_NAME
id: appname
run: |
OWNER_SHORT=${GITHUB_REPOSITORY_OWNER:0:5}
REPO_NAME=$(basename "$GITHUB_REPOSITORY")
PR_NUMBER=$(jq .number "$GITHUB_EVENT_PATH")
APP_NAME="${OWNER_SHORT}-${REPO_NAME}-${PR_NUMBER}"
echo "APP_NAME=$APP_NAME"
echo "APP_NAME=$APP_NAME" >> $GITHUB_ENV

- name: Read Python version from .python-version
id: python-version
run: |
PY_VERSION=$(cat .python-version)
echo "version=$PY_VERSION" >> $GITHUB_OUTPUT

- uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: ${{ steps.python-version.outputs.version }}

- name: Install Heroku CLI
run: |
curl https://cli-assets.heroku.com/install.sh | sh

- name: Log in to Heroku
run: |
echo "$HEROKU_API_KEY" | heroku auth:token

- name: Pre-cleanup (destroy app if it exists)
continue-on-error: true
run: |
heroku apps:destroy --app $APP_NAME --confirm $APP_NAME

# github CI can't use our app.json, so the config etc bits must be set manually.
# note WEB_CONCURRENCY is important! You get non-deterministic errors w/out it.
- name: Create temp Heroku app for this PR
run: |
heroku create $APP_NAME
heroku buildpacks:set heroku/python -a $APP_NAME
heroku config:set API_KEY=$API_KEY --app $APP_NAME
heroku config:set STDIO_MODE_ONLY=false
heroku config:set REMOTE_SERVER_TRANSPORT_MODULE=$REMOTE_SERVER_TRANSPORT_MODULE --app $APP_NAME
heroku config:set WEB_CONCURRENCY=1 --app $APP_NAME

- name: Deploy this branch to Heroku
run: |
git push https://heroku:[email protected]/$APP_NAME.git HEAD:refs/heads/main --force

- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}

- name: Install dependencies
- name: Install test dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

# We reuse the *same* test-suite; the fixture detects MCP_SERVER_URL
# and adds the “remote” parameter automatically.
- name: Run pytest (remote smoke)
run: pytest -q
- name: Get Heroku env vars
id: heroku_env
run: |
url=$(heroku info -s -a $APP_NAME | grep web_url | cut -d= -f2 | tr -d '\n')
echo "url=$url" >> "$GITHUB_OUTPUT"

- name: Run pytest against deployed app
env:
MCP_SERVER_URL: ${{ steps.heroku_env.outputs.url }}
run: |
echo "APP_NAME = $APP_NAME"
echo "MCP_SERVER_URL = $MCP_SERVER_URL"
echo "REMOTE_SERVER_TRANSPORT_MODULE = $REMOTE_SERVER_TRANSPORT_MODULE"
echo "API_KEY is ${API_KEY:+set}" # won't print the key, just confirms it's non-empty
pytest -q

- name: Destroy Heroku app after test
if: always()
run: |
heroku apps:destroy --app $APP_NAME --confirm $APP_NAME
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ uvicorn src.streamable_http_server:app --reload
*Running with `--reload` is optional, but great for local development*

Next, in a new pane, you can try running some queries against your server:

#### Local Streamable HTTP, SSE - Example Requests
First run:
```bash
Expand Down
12 changes: 10 additions & 2 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,20 @@ pip install -r requirements.txt

## 2 · Run local transports only
```bash
git push heroku <your-branch>:main
```

## 2 · Run local & one-off-dyno (STDIO) deployed transports
```bash
pytest tests -q
```

## 3 - Run local & remote transports
## 3 - Run local & all deployed transports
```bash
REMOTE_SERVER_TYPE=$(heroku config:get REMOTE_SERVER_TYPE) \
MCP_SERVER_URL=$(heroku info -s -a "$APP_NAME" | grep web_url | cut -d= -f2 | tr -d '\n') \
API_KEY=$(heroku config:get API_KEY -a "$APP_NAME") \
pytest tests -q
```
```

*NOTE: if your `REMOTE_SERVER_TYPE` is set to `sse_server` and not the default `streamable_http_server`, you'll need to change the `REMOTE_SERVER_TRANSPORT_MODULE` declaration line in `.github/workflows/test.yml` to make sure that the end to end integration tests against the temporary deployed remote server are using the appropriate client code.*
9 changes: 6 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,14 @@ async def _ctx_stdio_local() -> AsyncGenerator[Dict, None]:

# ---------------------------------------------------------------- remote HTTP / SSE
async def _ctx_remote() -> AsyncGenerator[Dict, None]:
url = os.getenv("MCP_SERVER_URL"); key = os.getenv("API_KEY")
url = os.getenv("MCP_SERVER_URL")
key = os.getenv("API_KEY")
server_type = os.getenv("REMOTE_SERVER_TRANSPORT_MODULE")

if not url or not key:
pytest.skip("remote env-vars missing")
yield {"client": "streamable_http_client",
"extra_env": {"API_KEY": key, "MCP_SERVER_URL": url.rstrip("/")}}
yield {"client": server_type.replace("server", "client"),
"extra_env": {"API_KEY": key, "MCP_SERVER_URL": url, "REMOTE_SERVER_TRANSPORT_MODULE": server_type}}

# ---------------------------------------------------------------- remote STDIO ctx
async def _ctx_remote_stdio() -> AsyncGenerator[Dict, None]:
Expand Down
3 changes: 1 addition & 2 deletions tests/test_mcp_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ async def test_list_tools(ctx):


async def test_code_exec(ctx):
payload = json.dumps({"name": "code_exec_python",
"arguments": {"code": "print(2+2)"}})
payload = json.dumps({"name": "code_exec_python", "arguments": {"code": "print(2+2)"}})
data = await _safe_call(ctx, "call_tool", "--args", payload)
assert _extract_stdout(data) == "4"
Loading