Skip to content

Releases: cubrid-lab/cubrid-mcp-server

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 08:48
bc22ba6

Upgrade notes

  • make verify now runs the live integration lane (#404) — it runs make check, the offline
    make test and then the run-owned make integration lane, so it needs Docker and fails when
    CUBRID never becomes ready or every live test skips. The live lane starts only after the offline
    tests pass, also under make -j. make test stays offline; run make check test for the
    former offline-only make verify. Development tooling only; the MCP tools are unchanged.

Added

  • make verify writes an aggregate summary (#449) — scripts/run_verify.py now runs the
    three phases and always writes test-results/verify/summary.json (overall PASS/FAIL/
    INCOMPLETE and each phase's status, NOT_RUN when it never started, and exit code), copies of
    the lanes' JUnit files under junit/<lane>.xml and the live environment.json, with the
    configured connection passwords masked, also after a failure or a signal. A missing, unreadable
    or non-string lane status, an all-skipped live lane or a file that cannot be copied is never
    PASS. The first SIGINT, SIGTERM or SIGHUP is forwarded to the running phase and the run exits
    128 + the signal number; make integration now execs its runner, so a SIGTERM to make reaches
    it, and the runner also cleans up on SIGHUP. New scripts/report_results.py aggregate
    subcommand. Development tooling only; the MCP tools are unchanged.
  • make: run the live integration lane in make verify (#447) (e694f56)
  • make: write an aggregate verification summary under test-results/verify (#450) (3260e38), refs #449

CI

  • Every new issue starts in status: needs triage (#418) — the issue forms add the
    label and the new issue-triage.yml workflow adds it to every opened issue. Priority and
    size are still set by a maintainer, who removes the label after triage.

Tests

  • Named-connection invalid values keep the prefixed variable name (#434) — tests/test_config.py now checks that an invalid PORT, MCP_MAX_ROWS, MCP_MAX_CHARS, MCP_MAX_SQL_LENGTH or MCP_QUERY_TIMEOUT on a named connection raises ConfigError naming the full CUBRID_<NAME>_... variable and not the bare default one, and that a blank required named variable is reported like a missing one. Test-only; config.py is unchanged. Refs #434.

Full Changelog: v0.5.6...v0.6.0

v0.5.6

Choose a tag to compare

@github-actions github-actions released this 25 Oct 02:08
de97800

Changed

  • Invalid boolean settings name their variable (#435) — CUBRID_MCP_READONLY, CUBRID_MCP_WRITE and CUBRID_MCP_AUDIT_LOG (and the CUBRID_<NAME>_ forms for named connections) reported only expected a boolean-like value, got 'x', so with several connections the operator had to guess which variable was wrong. The error now reads <VARIABLE> must be a boolean (1/true/yes/on or 0/false/no/off), got 'x', matching the integer settings. Accepted aliases are unchanged, and _parse_bool(value) without a key keeps its old message. Refs #435.

Fixed

  • config: name the variable when a boolean setting is invalid (#443) (8fc77db), refs #435

Documentation

  • agents: align independent MCP safety review and local title rules (7592c53)
  • agents: clarify MCP safety review and local PR policy (#442) (7592c53)

Full Changelog: v0.5.5...v0.5.6

v0.5.5

Choose a tag to compare

@github-actions github-actions released this 25 Oct 00:55
cd83c12

Fixed

  • Audit logger invariants raise RuntimeError instead of asserting (#429) — AppContext.audit_for() and server._audit() guarded the audit logger that AppContext.__post_init__ always sets with assert, which python -O strips. Both now raise RuntimeError naming the missing field; behavior is unchanged while the invariant holds, so no tool output changes. Refs #429.

  • Suppressed connection close failures are logged (#431) — AppContext.close() re-raises only the first close failure and silently dropped any later one, hiding secondary shutdown problems. Each suppressed failure is now logged at WARNING through the module logger with the connection name and the exception type only (driver error text can echo connection details, so it is deliberately left out). The first error is still raised unchanged. Refs #431.

  • context: log suppressed connection close failures (#439) (9924e2c)
  • context: raise RuntimeError for unset audit loggers instead of asserting (#438) (b6d8bb0)
  • reporting: redact credentials embedded in environment command metadata (#433) (702aac6)

Security

  • environment.json masks credentials embedded in the recorded command (#426) — scripts/report_results.py stored the --command string, which make integration builds from its overridable --pytest value, verbatim, so a password assignment, an authenticated database URL or a token option in it would have reached the test-results/integration/environment.json CI artifact. It now masks the password of URL userinfo (scheme://user:***@host, percent-encoded passwords included) and the value of a password, passwd, pwd, token, secret, api_key or apikey assignment or option, case-insensitively and as a key suffix (PASSWORD=***, CUBRID_PASSWORD=***, --password=***, --password ***), and keeps every other byte of the command. The schema, statuses, exit codes and the arguments pytest receives are unchanged. The masking is a best-effort safety net with documented limits (for example -p secret is not recognized); CONTRIBUTING.md and the module docstring now say to supply secrets through environment variables or the CI secret store, not the command line. Refs #426.

Documentation

  • The table-resolution and schema helpers in server.py are documented (#432) — _schema_definitions and _list_indexes were the last of the private helpers behind the schema tools without a docstring; each now states what it returns and how the table is selected by owner and name. Docstrings only: no behavior, signature or tool output changes. Refs #432.
  • server: document the table-resolution and schema helper functions (#440) (c645e6c), refs #432

CI

  • The temporary last-release-sha override is removed after 0.5.4 (#388) — v0.5.4 is published, and the release run verified it against the cookbook, so the next candidate starts from that tag. The override for the abandoned 0.5.3 (8633db6) is no longer needed, and leaving it in place would make a later recovery start from a stale base (RELEASING.md, "A merged X.Y.Z is abandoned"). Refs #388.

Full Changelog: v0.5.4...v0.5.5

v0.5.4

Choose a tag to compare

@github-actions github-actions released this 24 Oct 12:13
982a943

Upgrade notes

  • 0.5.3 was never published (#388). Its cookbook candidate verification failed: the pinned cookbook commit predated the cookbook's pandas 3.1 golden fix (cubrid-lab/cubrid-cookbook-python#444), so no 0.5.3 tag, GitHub Release or PyPI version exists. 0.5.4 contains every 0.5.3 change below; upgrade from 0.5.2 directly to 0.5.4.

  • make integration needs Docker Compose and reads CUBRID_TEST_PORT (#402) — it now runs docker compose ... up --detach --wait, so it needs the Docker Compose plugin (v2 or later) as well as Docker. Its port is CUBRID_TEST_PORT (default 33000), the variable make docker-up already uses, so a CUBRID_TEST_PORT exported for a running lab also moves make integration onto that port (pass another one). INTEGRATION_PORT still works and wins when set. Refs #402.

  • The dev extra is now the dev dependency group (#398) — pip install -e ".[dev]" no longer installs the contributor tools, and the published wheel and sdist no longer carry a dev extra (their runtime dependencies are unchanged). Install them with make install or python -m pip install -e . --group dev, which needs pip 25.1 or later (python -m pip install --upgrade pip). Refs #398.

  • make integration now starts its own CUBRID (#379) — it used to run pytest -m integration against a CUBRID you had already started. It now starts a uniquely named cubrid/cubrid:$(CUBRID_VERSION) container (default 11.4) with demodb, publishes the broker on host port $(INTEGRATION_PORT) (default 33000), waits for demodb, runs the integration tests against 127.0.0.1:$(INTEGRATION_PORT)/demodb as dba (exported CUBRID_* connection variables are ignored), and always removes that container, so it needs Docker. The old behavior is make integration-local. make test now also measures coverage and fails below fail_under = 95 and writes coverage.xml, make lint checks the whole repository (ruff check .) instead of cubrid_mcp_server/ and tests/, and make check adds format-check, so all three can fail where they passed before.

Added

  • A persistent local CUBRID with Docker Compose (#378) — a root docker-compose.yml defines one service, cubrid (cubrid/cubrid:${CUBRID_VERSION:-11.4}, demodb, CUBRID_DB_HOST=localhost, the broker on 127.0.0.1:${CUBRID_TEST_PORT:-33000} only, a csql ... SELECT 1 health check, a named data volume, no fixed container name), and new Make targets drive it for manual work: make docker-up starts it and waits until it is healthy, make docker-down removes the container and network but keeps the data volume, and make docker-clean also deletes the volume. make integration, make verify and the CI live jobs are unchanged: the live lane still starts and always removes its own run-owned container. CONTRIBUTING.md, AGENTS.md and the README development section (English and Korean) document the targets and how to point make integration-local at the service. Refs #378.

  • make test and make integration write test-results/ summaries (#405) — both targets now write a gitignored test-results/ tree: make test writes test-results/test/junit.xml, coverage.xml and summary.json (the root coverage.xml that CI uploads to Codecov is still written, as a copy), and make integration writes test-results/integration/junit.xml, summary.json, environment.json (Python, package and CUBRID versions, platform and command; never credentials) and, on a failure, logs/cubrid.log. The new scripts/report_results.py builds summary.json (schema_version, status of PASS, FAIL or INCOMPLETE, counts, coverage) from the JUnit and coverage files, and each command ends with a one-line console summary of the same status and counts. A lane that collected nothing, or in which no test passed, is INCOMPLETE, never PASS. Exit statuses, test selection and the coverage floor are unchanged, and make clean removes test-results/. Refs #405.

Changed

  • make integration runs the docker-compose.yml service as a run-owned Compose project (#402) — scripts/run_integration.py no longer starts its own docker run container: it runs docker compose --file docker-compose.yml --project-name cubrid-mcp-it-<random id> up --detach --wait cubrid with CUBRID_VERSION and CUBRID_TEST_PORT set, so make integration and make docker-up share one service definition (image tag, demodb, health check, shared-memory size, loopback-only broker). Readiness is the service's csql ... SELECT 1 health check instead of a separate docker exec probe loop; on a failure it prints docker compose logs cubrid; and it always runs down --volumes on that project, which removes its container, network and named volumes, on success, failure, a readiness failure, SIGINT or SIGTERM (the cleanup runs in its own session, so a second Ctrl-C cannot stop it). The exit-status rules are unchanged. The project name is unique per run, so concurrent runs on different ports, and a make docker-up lab, are never touched. CUBRID_TEST_PORT is now the one port variable of both lanes, with INTEGRATION_PORT kept as a compatibility override. The offline tests use a fake docker compose; CONTRIBUTING.md, AGENTS.md, the README development section (English and Korean) and the docker-compose.yml comments describe it. Refs #402.

  • Contributor tools are a PEP 735 dev dependency group; the Makefile runs them through one interpreter (#398) — the pins that were in [project.optional-dependencies].dev (including ruff==0.17.1 and mypy==2.4.0) now live unchanged in [dependency-groups].dev; there is no lock file and no other group. make install runs $(PYTHON) -m pip install -e . --group dev, and pytest, ruff and mypy run as $(PYTHON) -m ... instead of bare executables, so an activated virtualenv is honored even when PATH lists another one. PYTHON ?= python3, PYTEST, RUFF and MYPY are all overridable (make lint PYTHON=.venv/bin/python, make lint RUFF=ruff); the commands, flags, coverage floor and Docker lifecycle of check, test, verify and integration are unchanged. Refs #398.

  • The Makefile is the canonical local and CI interface (#379) — new targets: doctor (checks Python 3.11+, ruff, mypy, pytest, pytest-cov and pytest-asyncio; exits non-zero without a success line if any is missing), format-check (ruff format --check .), verify (check + test, no Docker), integration (the run-owned Docker lifecycle in scripts/run_integration.py: start, readiness wait, pytest -m integration -v, the container log on a failure, removal of the container and its anonymous volumes on success, failure, SIGINT or SIGTERM; the first failing step's exit status, or 128 + the signal number) and integration-local. lint, format-check and typecheck use the same paths and flags as CI, format is the only target that edits files, test takes extra pytest arguments in PYTEST_ARGS, clean also removes .ruff_cache, .coverage, coverage.xml and htmlcov (all gitignored, generated files), and make help groups the targets by purpose. Refs #379.

  • remove the App-owned agent-review publisher (#397) (e5bf28e)

Fixed

  • A failed service-log save no longer skips make integration cleanup — with --results-dir, an I/O error while writing logs/cubrid.log after a failing lane used to escape before docker compose down --volumes, leaving the run-owned project behind and losing the lane's exit status. Saving the log is now best-effort: the error is printed as a warning, the project is still taken down and the summary keeps the lane's status.
  • test: never let a failed service-log save skip make integration cleanup (#416) (dec3942)

Documentation

  • Install instructions use the dev group (#398) — CONTRIBUTING.md, the README (English and Korean), scripts/doctor.py and THIRD_PARTY_LICENSES.md replace .[dev] with --group dev, document the pip 25.1 requirement and the Makefile overrides, and explain that the dev table's cubrid-mcp-server attribution is hand-kept because installed metadata does not record group membership. Refs #398.

  • Make targets in the contributor docs (#379) — CONTRIBUTING.md, AGENTS.md, the PR template and the README development section (English and Korean) describe the new targets, the make integration variables and that CI calls the same targets. Refs #379.

  • agents: require repository-local issue authoring checks (#383) (4f04af7)
  • enforce repository-local issue drafting (4f04af7)
  • ko: replace the mistranslated redaction term in the Korean docs (#389) (2621d8b), refs #290
  • triage: clarify PR overlap and execution precedence (440166a), refs #410

CI

  • The release workflow verifies against the current cookbook, and preparation restarts after the abandoned 0.5.3 (#388) — 0.5.3 was not published: its cookbook candidate verification failed at the pinned cook...
Read more

v0.5.2

Choose a tag to compare

@github-actions github-actions released this 21 Oct 13:07
bdeb394

Fixed

  • The db_serial column probe keeps transport failures and its connection (#351) — list_serials resolves whether db_serial names its column att_name (CUBRID 10.2–11.2) or attr_name (11.4) with a zero-row probe per connection. The probe treated every error as "this spelling is missing": a timeout or a lost connection on the first probe made it try the other spelling, and two such failures were reported as db_serial exposes neither att_name nor attr_name. A successful probe whose read-transaction rollback then failed also cached its answer after that connection had been discarded, so the replacement connection, possibly on another server version, reused it without probing. Now only a server rejection of the column (a ProgrammingError, IntegrityError or DataError that is not a timeout, as CUBRID 10.2, 11.2 and 11.4 raise) moves on to the other spelling; a timeout raises QueryTimeoutError and other failures their sanitized query failed: ... error. If the probe's connection is discarded, the call fails with connection was reset while probing db_serial; retry the request and nothing is cached; the next call probes the new connection. Refs #351.
  • agent-review: do not count empty reply-only Copilot records as review rounds (#349) (6d75295)
  • agent-review: fail closed when a review thread lacks author or resolver identity (#347) (09d746e), refs #346
  • database: preserve serial-probe failures and fence the cache to its connection (#357) (0832251)

Documentation

  • A release review checklist (#355) — RELEASING.md now has a checklist to complete before merging a release PR: the candidate's base and final head, each required check's state at that head (missing, approval required, running, failed or cancelled, successful; only successful counts), the review evidence, the impact classification (including 0.x upgrade notes, the .mcpb/server.json version agreement and the serverInfo tests) and whether there is a user-facing reason to publish (a candidate may stay pending; a documentation-only release stays possible). A release counts as complete when the publisher summaries show publication and post-publication verification for the same tag, SHA and version (published and verified, or a published run followed by a successful verify-only recovery); the package version stays apart from FastMCP, client and registry tool versions, and server.json is not proof of a registry entry. The "start CI after the final bot update" step now describes what happened for candidate #339 (runs waiting as action_required, no check runs) instead of saying that bot pushes start no workflow. CONTRIBUTING.md and AGENTS.md point to the checklist. The release workflows and gates are unchanged. Refs #355.

  • The documentation site says it is built from main (#355) — every page of the site, English and Korean, now opens with a notice that it is development documentation for the main branch, built at a linked commit (from GITHUB_SHA in GitHub Actions; a local build says that no commit was recorded), that it can describe unpublished changes, and that a published version's documentation is the docs/ folder at its tag on the release list. The label never reads __version__, the release manifest or .mcpb/server.json, and the site shows no hardcoded latest version; offline tests render the template for each case. The Quick Start separates the document source, the package release (the version in the source and an open release pull request are not proof of publication) and the one verified combination recorded in #356, and names what was not run. The docs workflow stays build-only on pull requests and is unchanged. Refs #355.

  • The documentation site is grouped by reader (#354) — the navigation now has Start here (the Quick Start), How-to guides (serving several databases), Reference (tools, resources and prompts; configuration settings; Security Model), Help (Troubleshooting, the issue forms and private vulnerability reporting through SECURITY.md) and a 한국어 tab with the same groups, instead of Korean pages nested under Project → Translations. Every English page links its Korean page below the title and every Korean page links back; the documentation home lists the pages by the same groups and warns not to paste secrets or query results into issues. Pages now show previous and next links. CONTRIBUTING.md records the page map (reader, question, group and which content #352 owns) and a convention for writing walkthrough steps. No page is renamed, so existing URLs and anchors keep working; no content, tool, setting or security behavior changes. Refs #354.

  • The Quick Start is one safe, tested walkthrough (#352) — the English and Korean Quick Start now explain how CUBRID, the server and an MCP client fit together and what stdio means, then go step by step: a disposable CUBRID 11.4 Docker lab bound to 127.0.0.1, an install in a virtual environment, a one-time administrator script that creates the sample table mcp_quickstart_books and the account mcp_reader with SELECT on that table only (in one transaction, refusing to touch existing objects), a FastMCP Python client whose mcpServers configuration starts the server with an explicit environment, tool discovery and health_check, a first describe_table/execute_query read and a refused DELETE, real first-connection error messages, and a cleanup script that removes only the walkthrough's own objects, with docker rm -f -v for the lab's volume. The model's account is never dba, and the password stays in an environment variable. Every command and script was run as written on Linux against cubrid/cubrid:11.4; the Claude Desktop, Claude Code and Cursor configurations are kept as optional, unverified examples using the same names. The README (English and Korean) and the documentation home point to the walkthrough first, and the README Quick Start states that its values are placeholders. Refs #352.

  • Agent PR review route: Copilot or a maintainer — maintainer decision of 2026-10-15: a standalone-agent PR needs a substantive submitted Copilot review or a review by a maintainer (a code owner other than the PR's author), recorded on the PR. Reviews by other people or by other AI agents do not satisfy the route. No CI check enforces it; whether to add one is pending (#317). The two-round AI review budget stays. New: under every route, changes to safety.py, database.py, audit.py, server.py and workflows that hold write permissions or perform releases need a review that explicitly assesses their invariants, and a maintainer confirms that disposition before merge. AGENTS.md, CONTRIBUTING.md and the PR template are updated; the Korean docs carry no review rules, so nothing there changes. Refs #317.

  • agents: accept an independent reviewer agent in the standalone-agent review route (#342) (bbfe3a4); superseded before this release by #344: an independent reviewer agent does not satisfy the review route
  • agents: require a Copilot or maintainer review in the standalone-agent route (#344) (3b18cdf), refs #317
  • contributing: clarify good-first-issue access for human contributors (#322) (b319f9f)
  • fix Korean documentation typos and links (#320) (6f49f05)
  • ko: fix a typo and broken cross-page links (#319) (6f49f05)
  • onboarding: provide a safe single-client walkthrough with a prepared read-only lab (#356) (e3e0185), refs #352
  • release: add a release review checklist to the runbook (#360) (f196135), refs #355
  • site: label the site as main-branch documentation with its build commit (#359) (08550e7), refs #355
  • structure: group the site by reader and make Korean pages peers (#358) ([c0f900b](https:...
Read more

v0.5.1

Choose a tag to compare

@github-actions github-actions released this 14 Oct 14:07
f6427f6

Upgrade notes

  • Owner-qualified table names on CUBRID 11.2 and later — all_table_names, filter_table_names, describe_table, table_row_counts, list_class_hierarchy and the cubrid://schema resources now return table names as owner.table (for example dba.product) on CUBRID 11.2 and later. Clients that compared these names with bare table names must accept the owner prefix. Bare names are still accepted as input; one that several owners share now fails as ambiguous unless the configured user owns one of them. CUBRID 10.2 and 11.0 are unchanged.
  • CALL, EVALUATE and KILL in execute_query with CUBRID_MCP_READONLY=0 — these statements now fail before execution. Call stored functions from a SELECT (SELECT my_function(…)) and evaluate expressions with SELECT; KILL is not offered by any tool.
  • INCR, DECR, serial advances and the click counter in execute_query with CUBRID_MCP_READONLY=0 — these forms now fail before execution, as they already did in read-only mode. Use execute_write for writes; read a serial with CURRENT_VALUE.

Removed

  • Test-only helpers left the package — AppContext.single() (cubrid_mcp_server/context.py) and ConnectionRegistry.names (cubrid_mcp_server/config.py) were used only by the tests. The tests now build single-connection contexts with tests/helpers.py:single_context() and read sorted(registry.configs). The server never called either helper, so its behavior is unchanged.

Fixed

  • MCP serverInfo.version is the package version — the server passes __version__ to FastMCP, which otherwise reported its own version (for example 4.1.0) in the initialize response.
  • Audit category ignores leading comments — the audit log reads the statement category after stripping comments and string literals, as it already did for table identifiers, so -- note\nSELECT … and /* note */ SELECT … are SELECT and /* SELECT */ DELETE … is DELETE. Literals and comments are found with the same CUBRID-aware scanner as the SQL checks (// line comments, \r line ends, unterminated block comments or literals), under both no_backslash_escapes settings, and double-quoted text that is not a plain name is treated as a literal, so audit records never echo text that is a literal or comment under either reading.
  • SQL checks read comments, quotes and statements with CUBRID's rules — ensure_read_only, ensure_query_statement and ensure_write_allowed no longer rely on sqlparse's generic lexer for comment, quote and ; boundaries, which differ from CUBRID's: sqlparse does not know CUBRID's // line comments, reads # and $$ … $$ as a comment and a string where CUBRID does not, and reads a backslash as a quote escape. A comment that names an allowed keyword can no longer hide the real leading statement, and input that CUBRID would split into more than one statement is rejected. Input with an unterminated quote, or whose quote boundaries depend on the server parameter no_backslash_escapes, is now rejected; escape a quote by doubling the quote character used ('' or "").
  • The release workflow is named publish-pypi.yml again — .github/workflows/release.yml is renamed back to .github/workflows/publish-pypi.yml, the workflow filename registered as the PyPI Trusted Publisher (environment pypi). Under the release.yml name the 0.5.0 run failed at the PyPI upload with "Publisher with matching claims was not found". The shared release scripts (release_detect.py, release_summary.py, verify_dist.py, pypi_duplicate_guard.py, reconcile_release_labels.py) are again byte-identical with pycubrid and sqlalchemy-cubrid, and RELEASING.md, CONTRIBUTING.md, AGENTS.md, the Makefile and the workflow tests name publish-pypi.yml. RELEASING.md notes that the filename is the Trusted Publisher identity and must not be renamed. No runtime change.
  • #-prefixed names are one name in the side-effect check — CUBRID accepts # as the first character of an identifier, but the check read SELECT #incr(cnt) FROM t as # followed by INCR( and rejected it as the INCR side effect. #incr, #decr, #next_value and the like are now read as single names and pass the check (on CUBRID 10.2 and 11.4 the server then reports Function #incr is undefined unless such a function exists). INCR(, x.next_value and the other side-effect forms are still rejected, including next to a # name (SELECT #a, INCR(cnt) …) and after a lone #.
  • CUBRID_MCP_MAX_CHARS limits the first row of a result — execute_query always returned the first row whole and capped each value on its own, so a 60-column SELECT of REPEAT('x', 3990) returned 239,688 characters with truncated: false against the default cap of 4000. The cap now runs across the values of every row: rows are returned while they fit whole, and a first row that alone is longer is cut to the cap (later values become …, so it can exceed the cap by one character per later column). truncated is now true whenever a value was cut, including a single long value in a one-row result. A later row is left out rather than cut. docs/TOOLS.md and docs/ko/TOOLS.md describe how the cap is counted.
  • Table metadata is selected by owner on CUBRID 11.2 and later — the schema tools ignored the owner, so with dba.t and other.t both present, all_table_names() returned ['t', 't'], describe_table('t') merged the columns of both tables, and table_row_counts() failed for every table, because an unqualified "t" resolves to the configured user's own t. The tools now read db_class.owner_name, filter db_attribute, db_index, db_index_key and db_direct_super_class on owner and table name, count "OWNER"."table", and return owner.table. An unqualified name resolves to the only table with that name, or to the configured user's own table, and otherwise fails as ambiguous. Whether the catalog has owner columns is probed once per connection, like the db_serial column; CUBRID 10.2 and 11.0 keep unqualified names. docs/TOOLS.md and docs/ko/TOOLS.md describe the naming, and their examples show the 11.4 output.
  • explain_query reads, recovers and caps like execute_query — it found the leading keyword by splitting on whitespace, so SELECT*FROM t and (SELECT 1) were rejected with "only accepts SELECT or WITH" although execute_query runs them; it now reads the keyword with the same rules as the SQL checks. A statement the server rejects (ProgrammingError, IntegrityError, DataError) no longer discards the connection held for SET TRACE ON … SHOW TRACE: the transaction is rolled back and the session kept, as execute_query already did, so a mistyped query no longer forces a reconnect. The plan is now capped at CUBRID_MCP_MAX_CHARS and the result has a new truncated field. docs/TOOLS.md and docs/ko/TOOLS.md describe the behavior.
  • audit: determine SQL category after stripping leading comments (#283) (2c64d0f)
  • safety: reject CALL and KILL in execute_query when read-only mode is off (#304) (49bb56f)
  • safety: reject INCR and serial advances in execute_query with the whitelist off (#305) (56c97eb), refs #301 #303
  • safety: reject INCR, DECR and serial advances in read-only queries (#302) (d17bf58)
  • server: report the package version in MCP serverInfo (#288) (55fea91)
  • tools: cap the first result row at the configured character limit (#306) (094b3c6)
  • tools: parse, keep connections and cap output in explain_query like execute_query (#308) (75dd0cc)
  • tools: qualify table metadata by owner on CUBRID 11.2 and later (#307) (6f8a60f)

Security

  • Read-only mode rejects INCR, DECR, serial advances and the click counter — ensure_read_only checked only sqlparse keyword tokens, so SELECT INCR(cnt) …, DECR(…), serial.NEXT_VALUE, serial.NEXTVAL, SERIAL_NEXT_VALUE(serial, n) and SELECT … WITH INCREMENT FOR col / WITH DECREMENT FOR col passed execute_query and explain_query and changed data. The rollback after the read does not undo these changes, and a SELECT-only grant does not prevent them: on CUBRID 11.4 a user granted only SELECT on a table increased a column in it and advanced a serial it had no grant on. Read-only mode now rejects these names when they are called or used as an attribute, in any case, bare or quoted ("INCR", [incr], `incr`), with whitespace or comments in between, anywhere in the statement, including subqueries and CTEs; ...
Read more

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 11:29
836209f

Upgrade notes

  • table_row_counts result shape (#239) — clients that read the result as a list must read result["tables"] instead, and check truncated after a call without table_names.
  • EXPLAIN in execute_query (#250) — it never ran on CUBRID; use the explain_query tool for execution plans.
  • execute_query with CUBRID_MCP_READONLY=0 (#235) — clients that sent INSERT/UPDATE/DELETE through execute_query with the whitelist disabled must use execute_write (enable it with CUBRID_MCP_WRITE=1, or CUBRID_<NAME>_MCP_WRITE=1 per connection). Those statements, COMMIT/ROLLBACK, PREPARE/EXECUTE/DEALLOCATE, DO and multi-statement input now fail before execution.
  • Python 3.10 users must either upgrade to Python 3.11 or later, or stay on the last cubrid-mcp-server release that supports 3.10 together with a driver release compatible with 3.10. Newer pycubrid lines (1.10+) do not support Python 3.10, and this project does not imply otherwise.

Added

  • add MCP Registry server.json (f04c32e)
  • add MCP Registry server.json for official listing (c45bfd5)
  • CUBRID Skills — domain knowledge resources + expert prompts + tool hints + server instructions (#162) (#163) (79927e5)
  • VHS terminal demo script — MCP server programmatic interaction (#165) (685a437)

Changed

  • table_row_counts caps the default call instead of failing beyond 50 tables (#239) — behaviour change. With no table_names on a database with more than 50 user tables the tool raised too many tables requested, although its docstring promised a capped result and the explore_unknown_db prompt tells the model to call it. The default call now counts the first 50 user tables in name order. The result is now an object, {"tables": [...], "truncated": bool, "total_tables": int}, matching how execute_query signals truncation: tables holds the per-table entries the tool returned before, truncated is true when the default call left tables out, and total_tables is the number of user tables. An explicit list of more than 50 names still raises; [] still counts nothing. TOOLS.md (EN/KO) and the explore_unknown_db prompt describe the cap and how to fetch the rest.
  • EXPLAIN removed from execute_query's read-only whitelist (#250) — CUBRID has no EXPLAIN statement: on CUBRID 11.4.6, EXPLAIN SELECT 1 (and EXPLAIN PLAN FOR …) fail at the server with a syntax error, so the whitelist admitted a statement that could never run. READ_ONLY_KEYWORDS is now SELECT, SHOW, DESC, DESCRIBE, WITH, and with CUBRID_MCP_READONLY=1 (the default) EXPLAIN … is rejected before it reaches the database with UnsafeSQLError; with CUBRID_MCP_READONLY=0 it is not a rejected keyword and still fails at the server. README (EN/KO), SECURITY.md, the Security Model (EN/KO), Troubleshooting (EN/KO), the docs home page, ROADMAP and the cubrid://agent-guide resource no longer list it and point to the explain_query tool, which already uses CUBRID's SET TRACE/SHOW TRACE. A live integration test pins both the server rejection and the whitelist rejection. No other lexer change.
  • execute_query stays read-only when CUBRID_MCP_READONLY=0 (#235) — behaviour change. CUBRID_MCP_READONLY=0 now only relaxes the read-only whitelist; it no longer lets execute_query run writes. With the whitelist off, execute_query rejects any statement that leads with a write, DDL or transaction-control keyword (INSERT, UPDATE, DELETE, REPLACE, MERGE, CREATE, ALTER, DROP, TRUNCATE, RENAME, GRANT, REVOKE, COMMIT, ROLLBACK, SAVEPOINT, SET, PREPARE, EXECUTE, DEALLOCATE, DO) before it reaches the database, with an UnsafeSQLError pointing to execute_write; the first word of the leading keyword is matched, so CREATE OR REPLACE … is rejected like CREATE. Multi-statement input (multi-statement SQL is not allowed in execute_query) and empty input — including comment-only or ;-only input — are rejected too, as in read-only mode. The keyword check is a guardrail; the read-only database account remains the security boundary. Previously such a statement ran, was reported as query failed: InterfaceError and cost the shared connection. As a backstop, a statement that returns no result set (cursor.description is None) is rolled back and fails with ValueError: statement produced no result set; execute_query is read-only, use execute_write …, and the connection is kept instead of discarded. With CUBRID_MCP_READONLY=1 (the default) the whitelist and its error messages are unchanged. Result-set reads (SELECT, WITH … SELECT, SHOW, DESC, and with the whitelist off also CALLs that return a value) are unaffected.
  • Python 3.11 is now the minimum supported version (#221) — requires-python is >=3.11 (Python 3.10 reached upstream end of life on 2026-10-01); the 3.10 classifier is removed, Ruff targets py311 and mypy 3.11. Python 3.14 is added to the classifiers and the unit/full-integration matrices after the offline suite, Ruff and mypy passed on 3.14. The lowest-direct job and Codecov upload moved to 3.11; lowest-direct resolves FastMCP 3.0.0, pycubrid 1.4.0 and sqlparse 0.5.0 and passes. No MCP, SQL-safety, timeout or audit behavior changes; direct dependency floors are unchanged.

Fixed

  • explain_query discards the connection when trace cleanup fails (#264) — after the statement ran, explain_query turns tracing off and rolls back on the shared connection. A failure to open the cleanup cursor, run SET TRACE OFF or roll back was only logged, so the next tool call could reuse a session that still had tracing on or a transaction open. Any such failure now discards the connection under the connection lock, so the next call reconnects; cleanup stops at the first failed step, and after a query timeout no cleanup statement is sent. A successful query still returns its plan, a query error is still the error the client sees, and a healthy cleanup keeps the connection. TOOLS.md and the Security Model (EN/KO) describe the behavior.
  • list_class_hierarchy resolves table names like the other schema tools (#240) — the tool bound the raw table_name, so matching was case-sensitive and an unknown name, system class or view silently returned []. It now uses _resolve_table: names match user tables case-insensitively (and are bound as the stored name), and an unknown table raises unknown table: '…'; a whitespace-only name raises table name must not be empty. Omitting table_name (or passing "") still lists every class. The TOOLS.md signature (EN/KO) is corrected from list_class_hierarchy(connection=None, ...) to list_class_hierarchy(table_name=None, connection=None).
  • Reads end their transaction on the shared connection (#234) — the server never enables autocommit and pycubrid defaults to autocommit=False, so every fetch_all/fetch_many (all read tools, including execute_query and its truncated early return) left a transaction open until a later write committed, explain_query rolled back or the connection was dropped. That kept table locks (blocking DDL from other sessions) and, under REPEATABLE READ, a stale snapshot that hid rows committed by other sessions. Each read now rolls back once after its rows are collected, inside the connection lock; a failed rollback discards the connection so the next call reconnects. A failed read caused by a statement the server rejected (pycubrid ProgrammingError, IntegrityError or DataError) now rolls back and keeps the session instead of reconnecting; timeouts, OperationalError, InterfaceError, InternalError, the generic DatabaseError and non-driver errors still discard it. execute_write commit/rollback and explain_query trace cleanup are unchanged. With CUBRID_MCP_READONLY=0, a write that also returns a result set is now rolled back instead of left pending; use execute_write for writes.
  • Accept leading comments in explain_query (#178) — SELECT/WITH statements now use the same comment normalization as the read-only safety checker before the leading-token gate.
  • Blank required connection values are rejected (#177) — an empty or whitespace-only CUBRID_HOST, CUBRID_USER, or CUBRID_DATABASE (and the CUBRID_<NAME>_* equivalents) now raises ConfigError at configuration time, like a missing variable, instead of producing a broken connection config. An empty CUBRID_PASSWORD remains allowed.
  • Port range validation (#176) — CUBRID_PORT (and CUBRID_<NAME>_PORT) must now be an integer in the TCP port range 1..65535; out-of-range values such as 0 or 65536 raise ConfigError at configuration time instead of failing later at connect time.
  • db_serial column probe is reset on reconnect (#181) — the cached att_name/attr_name probe result used by list_serials is now cleared whenever the connection is discarded or closed, so a reconnect that lands on a different CUBRID version (e.g. 11.2 → 11.4 after a broker failover) re-probes instead of reusing the stale column name.
  • Cursor-creation failures take the normal recovery path (#180) — connection.cursor() is now created inside the recovery block of both Database.cursor() and execute_write(), so a failure opening a cursor (e.g. on a connection the broker already closed) is sur...
Read more

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 10:07
b6305f1

Added

  • Published to PyPI: uvx cubrid-mcp-server and pipx run cubrid-mcp-server now install from PyPI instead of a git checkout.
  • Documentation site (mkdocs-material) deployed to https://cubrid-lab.github.io/cubrid-mcp-server/ — quickstart, full tool reference, security model, multi-connection guide, and troubleshooting.
  • Korean README translation (docs/README.ko.md), matching the sibling repositories' translation set.
  • upstream-canary.yml now also tracks fastmcp@latest in addition to pycubrid@main, giving the eventual fastmcp 4.x migration decision CI evidence. The dependency pin intentionally remains >=3.0,<4 until that canary is green.
  • THIRD_PARTY_LICENSES.md (full runtime license inventory, pip-licenses generated) and a NOTICE file (original implementation, no third-party code embedded) added.
  • Official MCP Registry readiness: PyPI ownership-verification marker (mcp-name: io.github.cubrid-lab/cubrid-mcp-server) embedded in README, and glama.json added at the repo root for Glama registry indexing.

Changed

  • Development Status classifier raised from 2 - Pre-Alpha to 4 - Beta: the server has shipped multi-database connections, opt-in write mode, audit logging, Resources, and Prompts since 0.3.0.
  • [project.urls] now includes Documentation and Changelog links, matching the sibling repositories.
  • Framework :: AsyncIO trove classifier added for registry/search discoverability.

Fixed

  • README: removed a duplicated CUBRID_MCP_WRITE row in the configuration table.

v0.3.1

Choose a tag to compare

@yeongseon yeongseon released this 02 Sep 04:40
39c4047

Fixed

  • execute_write now honours the per-call connection argument instead of always writing to the default connection, matching the read tools and the v0.3.0 multi-connection contract. (#136)
  • Per-connection CUBRID_<NAME>_MCP_WRITE and CUBRID_<NAME>_MCP_AUDIT_LOG settings are now actually applied: write-enablement is enforced against the target connection's config (a connection with writes off refuses even when another enables them), the execute_write tool is registered whenever any connection opts into write mode, and audit logging is resolved per connection so a named connection's MCP_AUDIT_LOG takes effect independently of the default. execute_write calls are now audited as well. (#137)

v0.3.0

Choose a tag to compare

@yeongseon yeongseon released this 01 Sep 02:19

v0.3.0 — Advanced Features (epic #23)

Added

  • Schema as MCP Resources — cubrid://schema index + cubrid://schema/{table} template, reusing the read-only catalog queries. (#124)
  • Opt-in audit logging (CUBRID_MCP_AUDIT_LOG) — one redaction-safe JSON record per statement to stderr; raw SQL/params/literals never logged. (#127)
  • MCP Prompt templates — summarize_table, explain_query, inspect_schema, find_index_candidates (guidance-only, no DB access). (#125)
  • Multi-database connections — CUBRID_CONNECTIONS + CUBRID_<NAME>_*, per-call connection argument; single-DB setups unchanged. (#126)
  • Opt-in write mode (CUBRID_MCP_WRITE=1) — execute_write tool for a single atomic INSERT/UPDATE/DELETE; disabled and unregistered by default. (#123)
  • ROADMAP.md (#96) and repo-local CONTRIBUTING.md (#94).

Security

  • Write mode is strictly gated by a DML-only whitelist (ensure_write_allowed); the read-only default path is unchanged. (#123)

Changed

  • Explicit Ruff rule set (select = ["E4","E7","E9","F"]) to stabilize CI across ruff 0.16. (#88)

Full changelog: see CHANGELOG.md.