Skip to content
Draft
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
65baa24
PSGO-268: chore: scaffold TypeScript rewrite (Phase 0)
Matovidlo Jun 26, 2026
8323ee1
PSGO-268: feat: add streamable-HTTP transport (Phase 1)
Matovidlo Jun 26, 2026
1b0c2e5
PSGO-268: feat: add Keboola client factory + service-URL derivation (…
Matovidlo Jun 26, 2026
2a120c2
PSGO-268: feat: add TOON output serializer + tool-registration helper
Matovidlo Jun 26, 2026
e7658cb
PSGO-268: feat: port ProjectLinksManager + component-id constants
Matovidlo Jun 26, 2026
3b857c2
PSGO-268: feat: port first real tool — update_project_description
Matovidlo Jun 26, 2026
82ca9df
PSGO-268: feat: port get_jobs tool (list + detail + logs)
Matovidlo Jun 26, 2026
c0236b4
PSGO-268: feat: add raw SAPI client + port update_descriptions
Matovidlo Jun 26, 2026
8d8e2d4
PSGO-268: feat: port run_job + fix get_jobs queue branch id
Matovidlo Jun 26, 2026
1d7893b
PSGO-268: feat: port create_oauth_url + pass string tool results thro…
Matovidlo Jun 26, 2026
0438953
PSGO-268: feat: port get_config_examples + raw AI client
Matovidlo Jun 26, 2026
00910f6
PSGO-268: feat: port docs_query tool
Matovidlo Jun 26, 2026
5b6f495
PSGO-268: feat: port find_component_id tool
Matovidlo Jun 26, 2026
46a4662
PSGO-268: feat: port get_components + component fetch/mapping + RawHt…
Matovidlo Jun 26, 2026
c34494a
PSGO-268: feat: port run_sync_action + raw sync-actions client
Matovidlo Jun 26, 2026
dbbb5ca
PSGO-268: feat: port get_configs + configuration model layer + secret…
Matovidlo Jun 26, 2026
036bdaf
chore(PSGO-268): add ajv/jose/dd-trace deps + tools-docs scripts
Matovidlo Jun 29, 2026
7952a51
feat(PSGO-268): client plumbing for the ported tools
Matovidlo Jun 29, 2026
f333d40
feat(PSGO-268): port storage reads, query_data, and the workspace layer
Matovidlo Jun 29, 2026
2102cb3
feat(PSGO-268): port component write tools + config validation
Matovidlo Jun 29, 2026
df374b9
feat(PSGO-268): port the flow module
Matovidlo Jun 29, 2026
c65ec12
feat(PSGO-268): port project, search, semantic, and data-app tools
Matovidlo Jun 29, 2026
7621b1f
feat(PSGO-268): port cross-cutting server infra
Matovidlo Jun 29, 2026
ea56072
docs(PSGO-268): TOOLS.md generator + regenerate for the 39 TS tools
Matovidlo Jun 29, 2026
cdf0bff
build(PSGO-268): Node Dockerfile + deployment env (build/run split)
Matovidlo Jun 29, 2026
cd0e1a0
ci(PSGO-268): convert CI to Node + npm publish
Matovidlo Jun 29, 2026
0531ff8
docs(PSGO-268): RFC for redis-leased per-case integtest project pool
Matovidlo Jun 29, 2026
102ea84
feat(PSGO-268): integtest project-pool harness (redis lease + fs fall…
Matovidlo Jun 29, 2026
c29496e
ci(PSGO-268): integtest pool config, export action, vitest config + docs
Matovidlo Jun 29, 2026
9269200
feat(PSGO-268): retry transient 5xx/429 on the api-client clients
Matovidlo Jun 29, 2026
a014025
feat(PSGO-268): rename lock env to TEST_MCP_PROJECTS_LOCK_* + redis t…
Matovidlo Jun 29, 2026
d468269
test(PSGO-268): port seed-free integtests (doc, project, sql) onto th…
Matovidlo Jun 29, 2026
2c6bf45
chore(PSGO-268): gitignore local integtest projects.json (real tokens)
Matovidlo Jun 29, 2026
b9fca9b
fix(PSGO-268): get_project_info provisions the workspace via Workspac…
Matovidlo Jun 29, 2026
066df3f
test(PSGO-268): add clean opt-out for non-mutating integtests
Matovidlo Jun 29, 2026
aeb653e
refactor(PSGO-268): expose typed ai/syncActions/dataScience clients
Matovidlo Jun 29, 2026
ae56f96
refactor(PSGO-268): decompose components module + migrate raw→typed c…
Matovidlo Jun 29, 2026
79aa14c
refactor(PSGO-268): decompose flow module + migrate config CRUD to ty…
Matovidlo Jun 29, 2026
a647933
refactor(PSGO-268): decompose data_apps module + migrate to typed dat…
Matovidlo Jun 29, 2026
7796ec5
refactor(PSGO-268): decompose semantic module
Matovidlo Jun 29, 2026
5065c2b
refactor(PSGO-268): decompose search module + migrate find_component_…
Matovidlo Jun 29, 2026
22419b0
refactor(PSGO-268): remove old monolith model/module files post-decom…
Matovidlo Jun 29, 2026
33085fa
test(PSGO-268): live integtest for find_component_id (typed ai.sugges…
Matovidlo Jun 29, 2026
c56b334
ci(PSGO-268): add separate integration_tests job + project pool template
Matovidlo Jun 29, 2026
35ded93
ci(PSGO-268): take integtest projects file path from vars.TEST_KBC_PR…
Matovidlo Jun 30, 2026
3f3ac85
ci(PSGO-268): point CI project pool at the integtest projects (2728/2…
Matovidlo Jun 30, 2026
cb9c754
ci(PSGO-268): update integtest project pool IDs (3053-3057)
Matovidlo Jun 30, 2026
3a2ffc2
test(PSGO-268): seed helper + storage integtests (verified live)
Matovidlo Jun 30, 2026
438886b
fix(PSGO-268): async bucket drop in integtest cleanProject
Matovidlo Jun 30, 2026
8e2284c
test(PSGO-268): port the integration-test suite to vitest (1:1 with P…
Matovidlo Jun 30, 2026
eefdd39
test(PSGO-268): fix integ failures surfaced by the live run
Matovidlo Jun 30, 2026
e0bff70
refactor(PSGO-268): decompose validation module
Matovidlo Jun 30, 2026
890bc1e
refactor(PSGO-268): decompose storage module + dialect-aware FQN (Big…
Matovidlo Jun 30, 2026
0ffe6af
chore(PSGO-268): remove the Python implementation
Matovidlo Jun 30, 2026
447c868
chore(PSGO-268): drop dead Python references after removal
Matovidlo Jun 30, 2026
aa85a11
test(PSGO-268): provision a workspace for the python-js data app inte…
Matovidlo Jul 1, 2026
ec0348f
docs(PSGO-268): RFC + architecture for pgvector docs-search (replace …
Matovidlo Jul 1, 2026
0136fbf
PSGO-268: feat: serve docs_query + find_component_id from pgvector do…
Matovidlo Jul 1, 2026
f595587
PSGO-268: docs: note sourceKey landed on keboola/ui#6672
Matovidlo Jul 1, 2026
2977124
PSGO-268: fix: skip docs_query integ without a configured index; docu…
Matovidlo Jul 1, 2026
f045645
PSGO-268: feat: local + CI docs index — docs:build, stub embedder, te…
Matovidlo Jul 1, 2026
67b53c5
PSGO-268: fix: run docs integ against docker-compose postgres; degate…
Matovidlo Jul 2, 2026
28d22f7
PSGO-268: fix: prod image boot (bundle api-client/dayjs) + full docke…
Matovidlo Jul 2, 2026
00b371b
PSGO-268: fix: surface api-client error detail in tool errors; quiet …
Matovidlo Jul 2, 2026
f373a9a
PSGO-268: feat: in-process local (HuggingFace) embedder + configurabl…
Matovidlo Jul 2, 2026
399cd4f
PSGO-268: feat: log a redacted config/env dump at startup
Matovidlo Jul 13, 2026
3e5db03
PSGO-268: feat: docs:crawl — build a real local docs index from the p…
Matovidlo Jul 13, 2026
79aa105
PSGO-268: fix: bound docs_query payload (extractive fallback dumped f…
Matovidlo Jul 13, 2026
fe4f68e
PSGO-268: feat: emit docs-crawl as a production build entrypoint
Matovidlo Jul 21, 2026
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
25 changes: 25 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
node_modules
dist
.git
.github
__tests__
tests
src/keboola_mcp_server
3.10.venv
3.12.venv
.venv
.tox
.mypy_cache
.pytest_cache
*.venv
coverage.xml
.coverage
test-results.xml
feature_spec
docs
*.md
!README.md
.env
.mcp.json
uv.lock
pyproject.toml
30 changes: 30 additions & 0 deletions .github/actions/export-kbc-projects/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: 'Export kbc projects with secrets'
description: 'Generates projects.json from .github/ci/projects.json, substituting TEST_KBC_PROJECT_* secrets'
inputs:
secrets:
description: workflow secrets (pass `toJSON(secrets)`)
required: true
outputs:
projects-file:
description: Absolute path to the generated projects.json
value: ${{ steps.gen.outputs.projects-file }}
runs:
using: 'composite'
steps:
- name: Generate projects.json file, replace secrets
id: gen
shell: bash
run: |
# Export every TEST_KBC_PROJECT_* secret as an env var, then envsubst the template.
jqQuery='to_entries[] | select(.key | startswith("TEST_KBC_PROJECT_")) | "\(.key)=\(.value|tostring) "'
export $(
(jq -r -j "$jqQuery") << EndOfSecrets
${{ inputs.secrets }}
EndOfSecrets
)

envsubst < .github/ci/projects.json > projects.json

# Stable mtime so test caching is not invalidated by regeneration.
touch -d '1970-01-01T00:00:01' projects.json
echo "projects-file=$(pwd)/projects.json" >> "$GITHUB_OUTPUT"
72 changes: 72 additions & 0 deletions .github/ci/projects.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
[
{
"host": "connection.europe-west3.gcp.keboola.com",
"project": 154,
"stagingStorage": "gcs",
"backend": "snowflake",
"token": "$TEST_KBC_PROJECT_154_TOKEN"
},
{
"host": "connection.europe-west3.gcp.keboola.com",
"project": 1931,
"stagingStorage": "gcs",
"backend": "snowflake",
"token": "$TEST_KBC_PROJECT_1931_TOKEN"
},
{
"host": "connection.keboola.com",
"project": 10013,
"stagingStorage": "s3",
"backend": "snowflake",
"token": "$TEST_KBC_PROJECT_10013_TOKEN"
},
{
"host": "connection.keboola.com",
"project": 10014,
"stagingStorage": "s3",
"backend": "snowflake",
"token": "$TEST_KBC_PROJECT_10014_TOKEN"
},
{
"host": "connection.europe-west3.gcp.keboola.com",
"project": 1297,
"stagingStorage": "gcs",
"backend": "bigquery",
"token": "$TEST_KBC_PROJECT_1297_TOKEN"
},
{
"host": "connection.europe-west3.gcp.keboola.com",
"project": 2832,
"stagingStorage": "gcs",
"backend": "bigquery",
"token": "$TEST_KBC_PROJECT_2832_TOKEN"
},
{
"host": "connection.europe-west3.gcp.keboola.com",
"project": 2833,
"stagingStorage": "gcs",
"backend": "bigquery",
"token": "$TEST_KBC_PROJECT_2833_TOKEN"
},
{
"host": "connection.europe-west3.gcp.keboola.com",
"project": 2834,
"stagingStorage": "gcs",
"backend": "bigquery",
"token": "$TEST_KBC_PROJECT_2834_TOKEN"
},
{
"host": "connection.keboola.com",
"project": 10166,
"stagingStorage": "s3",
"backend": "snowflake",
"token": "$TEST_KBC_PROJECT_10166_TOKEN"
},
{
"host": "connection.keboola.com",
"project": 10169,
"stagingStorage": "s3",
"backend": "snowflake",
"token": "$TEST_KBC_PROJECT_10169_TOKEN"
}
]
203 changes: 84 additions & 119 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,183 +2,148 @@

# How the CI Pipeline Works
# - The CI workflow runs on all push events and pull requests.
# - For pull requests from forks (coming from a different repository), we skip jobs/actions that need secrets (e.g.,
# publishing results, integration tests) to avoid exposing them. Only safe checks like local tests and flake8 are run.
# - For pull requests from branches in this repository, the workflow is skipped to avoid running it twice (once for the
# push and once for the PR).
# - The full workflow (including jobs requiring secrets) runs only on pushes events to branches in this repository
on: [ push, pull_request ]
# - For pull requests from forks (coming from a different repository), we still run the full
# build/test/lint matrix — none of those steps need secrets.
# - For pull requests from branches in this repository, the workflow is skipped to avoid running
# it twice (once for the push and once for the PR).
# - npm publish + Anthropic registration run only on semantic version tags (vX.Y.Z) pushed to
# this repository.
on: [push, pull_request]
concurrency: ci-${{ github.ref }}

permissions:
contents: read
checks: write

jobs:
build:
name: Build, test and package
# run this job only for push (avoiding pull requests from the same repo) or for pull requests from different repo
# run this job only for push (avoiding pull requests from the same repo) or for pull requests from a different repo
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
node-version: ['22', '24']
outputs:
is_semantic_tag: ${{ steps.get_package_version.outputs.is_semantic_tag }}
tag: ${{ steps.get_package_version.outputs.tag }}
version: ${{ steps.get_package_version.outputs.version }}
wheel_artifact_id: ${{ steps.wheel_artifact_upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
- name: Set up Node ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
python-version: ${{ matrix.python-version }}
node-version: ${{ matrix.node-version }}
cache: npm

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install uv
uv sync --frozen --no-editable --extra dev

# Runs tests with coverage and generates coverage.xml file and test-results.xml file
# and checks flake8 code-style formatting that is compatible with isort & black
# and verifies TOOLS.md is up-to-date with tool definitions
# see setup in pyproject.toml
- name: Unit tests, code style and documentation check
run: |
uv run tox

- name: Publish test results
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
# don't fail the job if the Checks API call flakes — the tests themselves already ran
continue-on-error: true
# run this step even if a previous step failed, but only for push events or pull requests from the same repo
if: (success() || failure()) && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
with:
name: Test results (${{ matrix.python-version }})
path: ./test-results.xml
reporter: 'java-junit'

- name: Upload coverage to Codecov
# run this step only for push events or pull requests from the same repo
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
uses: codecov/codecov-action@v7.0.0
with:
file: ./coverage.xml
fail_ci_if_error: false # Codecov upload is best-effort — transient service outages should not block CI
token: ${{ secrets.CODECOV_TOKEN }}
run: npm ci

- name: Build wheels package
run: |
uv build --wheel --no-sources
- name: Type-check
run: npm run type-check

- name: Lint and format check
run: npm run lint

- name: Unit tests
run: npm test

- name: Verify TOOLS.md is up to date
run: npm run check:tools-docs

- name: Build package
run: npm run build
env:
SKIP_ENV_VALIDATION: '1'

- name: Get package version
id: get_package_version
if: matrix.python-version == '3.10'
if: matrix.node-version == '22'
run: |
VERSION=`uv run python3 -c 'import importlib.metadata; print(importlib.metadata.version("keboola_mcp_server"))'`
VERSION=$(node -p "require('./package.json').version")
TAG="${GITHUB_REF##*/}"
IS_SEMANTIC_TAG=$(echo "$TAG" | grep -q '^v\?[0-9]\+\.[0-9]\+\.[0-9]\+$' && echo true || echo false)
echo "Version = '$VERSION', Tag = '$TAG', is semantic tag = '$IS_SEMANTIC_TAG'"
echo "is_semantic_tag=$IS_SEMANTIC_TAG" >> $GITHUB_OUTPUT
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "version=${VERSION}" >> $GITHUB_OUTPUT

- name: Upload wheel package
id: wheel_artifact_upload
# run this step only for push events or pull requests from the same repo
if: matrix.python-version == '3.10' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
uses: actions/upload-artifact@v7
with:
name: keboola_mcp_server-${{ steps.get_package_version.outputs.version }}-py3-none-any.whl
path: dist/keboola_mcp_server-${{ steps.get_package_version.outputs.version }}-py3-none-any.whl
if-no-files-found: error
compression-level: 0 # wheels are ZIP archives
retention-days: 7
echo "is_semantic_tag=$IS_SEMANTIC_TAG" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

integration_tests:
name: Integration Tests
needs: build
# run this job only for push events (not pull requests)
name: Integration tests
# Runs in parallel with `build` (independent job) so the fast unit lane isn't blocked.
# Push-only: PRs from forks have no access to the project-pool secrets.
if: github.event_name == 'push'
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
# This ensures tests run sequentially, not in parallel
max-parallel: 1
steps:
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
- name: Set up Node
uses: actions/setup-node@v4
with:
python-version: ${{ matrix.python-version }}
node-version: '22'
cache: npm

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install uv
uv sync --frozen --no-editable --extra dev
run: npm ci

- name: Integration tests
# run actual tests only for pushes to the same repository (not forks)
if: github.repository == github.event.repository.full_name
env:
INTEGTEST_STORAGE_TOKENS: ${{ secrets.INTEGTEST_STORAGE_TOKENS }}
INTEGTEST_POOL_STORAGE_API_URL: ${{ vars.INTEGTEST_POOL_STORAGE_API_URL }}
INTEGTEST_STORAGE_TOKEN_STORAGE_BRANCHES: ${{ secrets.INTEGTEST_STORAGE_TOKEN_STORAGE_BRANCHES }}
run: |
uv run tox -e integtests

- name: Skip integration tests for forks
# show a message when tests are skipped for forks
if: github.repository != github.event.repository.full_name
run: |
echo "Integration tests skipped for fork repository"
echo "Dependencies installed successfully - setup is working correctly"

- name: Publish integration test results
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
# don't fail the job if the Checks API call flakes — the tests themselves already ran
continue-on-error: true
# publish results only when actual tests were run (same repository)
if: (always()) && github.repository == github.event.repository.full_name
# Unwrap the project pool: substitute $TEST_KBC_PROJECT_*_TOKEN secrets into
# .github/ci/projects.json -> ./projects.json (mirrors keboola/go-monorepo).
- name: Export Keboola test projects
id: projects
uses: ./.github/actions/export-kbc-projects
with:
name: Integration test results (${{ matrix.python-version }})
path: ./integtest-results.xml
reporter: 'java-junit'
secrets: ${{ toJSON(secrets) }}

deploy_to_pypi:
name: Deploy to pypi.org
- name: Integration tests
env:
# Cross-runner project lease via the shared redis (same key scheme as go-monorepo,
# so leases coordinate with other consumers of the pool).
TEST_MCP_PROJECTS_LOCK_HOST: ${{ vars.TEST_MCP_PROJECTS_LOCK_HOST }}
TEST_MCP_PROJECTS_LOCK_PASSWORD: ${{ secrets.TEST_MCP_PROJECTS_LOCK_PASSWORD }}
TEST_KBC_PROJECTS_FILE: ${{ steps.projects.outputs.projects-file }}
run: npm run test:integ

publish_npm:
name: Publish to npm
needs:
- build
- integration_tests
runs-on: ubuntu-latest
# Publish only on a semantic vX.Y.Z tag that matches package.json's version.
if: |
startsWith(github.ref, 'refs/tags/') &&
needs.build.outputs.is_semantic_tag == 'true' &&
needs.build.outputs.tag == format('v{0}', needs.build.outputs.version)
permissions:
contents: read
id-token: write # npm provenance
steps:
- name: Download wheel package
uses: actions/download-artifact@v8
with:
name: keboola_mcp_server-${{ needs.build.outputs.version }}-py3-none-any.whl
path: dist/
- uses: actions/checkout@v6

- name: Publish package
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
- name: Set up Node
uses: actions/setup-node@v4
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
node-version: '22'
cache: npm
registry-url: 'https://registry.npmjs.org'

- name: Install dependencies
run: npm ci

- name: Build package
run: npm run build
env:
SKIP_ENV_VALIDATION: '1'

- name: Publish
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

register-with-anthropic:
name: Register with Anthropic
needs:
- build
- deploy_to_pypi
- publish_npm
runs-on: ubuntu-latest
if: |
startsWith(github.ref, 'refs/tags/') &&
Expand Down
Loading
Loading