diff --git a/CHANGES.md b/CHANGES.md index 27de914..b01d980 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,8 @@ update to [MCP spec 2025-06-18]. - OCI: Added building OCI standard image `cratedb-mcp` - OCI: Added building OCI image `cratedb-mcpo` for Open WebUI +- Prompt: Added instructions (system prompt) to MCP server. + Thanks, @hammerhead and @WalBeh. [FastMCP 2.10]: https://github.com/jlowin/fastmcp/releases/tag/v2.10.0 [MCP spec 2025-06-18]: https://modelcontextprotocol.io/specification/2025-06-18/changelog diff --git a/cratedb_mcp/__main__.py b/cratedb_mcp/__main__.py index 53432ee..87ad6a8 100644 --- a/cratedb_mcp/__main__.py +++ b/cratedb_mcp/__main__.py @@ -1,3 +1,6 @@ +import importlib.resources + +from cratedb_about.instruction import GeneralInstructions from fastmcp import FastMCP from fastmcp.tools import Tool @@ -10,19 +13,13 @@ query_sql, ) -# Create FastMCP application object. -mcp: FastMCP = FastMCP(__appname__) - +instructions_general = GeneralInstructions().render() +instructions_mcp = (importlib.resources.files("cratedb_mcp") / "instructions.md").read_text() -# ------------------------------------------ -# Health / Status -# ------------------------------------------ -mcp.add_tool( - Tool.from_function( - fn=get_cluster_health, - description="Return the health of the CrateDB cluster.", - tags={"health", "monitoring", "status"}, - ) +# Create FastMCP application object. +mcp: FastMCP = FastMCP( + name=__appname__, + instructions=instructions_mcp + instructions_general, ) @@ -31,15 +28,17 @@ # ------------------------------------------ mcp.add_tool( Tool.from_function( - fn=query_sql, - description="Send an SQL query to CrateDB. Only 'SELECT' queries are allowed.", + fn=get_table_metadata, + description="Return column schema and metadata for all tables stored in CrateDB. " + "Use it to inquire entities you don't know about.", tags={"text-to-sql"}, ) ) mcp.add_tool( Tool.from_function( - fn=get_table_metadata, - description="Return an aggregation of all CrateDB's schema, tables and their metadata.", + fn=query_sql, + description="Send an SQL query to CrateDB and return results. " + "Only 'SELECT' queries are allowed.", tags={"text-to-sql"}, ) ) @@ -64,3 +63,15 @@ tags={"documentation"}, ) ) + + +# ------------------------------------------ +# Health / Status +# ------------------------------------------ +mcp.add_tool( + Tool.from_function( + fn=get_cluster_health, + description="Return the health of the CrateDB cluster.", + tags={"health", "monitoring", "status"}, + ) +) diff --git a/cratedb_mcp/instructions.md b/cratedb_mcp/instructions.md new file mode 100644 index 0000000..19d6562 --- /dev/null +++ b/cratedb_mcp/instructions.md @@ -0,0 +1,25 @@ +## Tool instructions + +Use all available tools from the CrateDB MCP server `cratedb-mcp` or derived +applications for gathering accurate information. + +You have the following tools available: + +1. `get_table_metadata`: Return table and column schema information for all tables stored in CrateDB. Use it when you need to discover entities and database metadata you are unfamiliar with. +2. `query_sql`: Execute an SQL query on CrateDB and return the results. +3. `get_cratedb_documentation_index`: Return the table of contents for the curated CrateDB documentation index. Use it whenever you need to verify CrateDB-specific syntax. +4. `fetch_cratedb_docs`: Given a link returned by `get_cratedb_documentation_index`, fetch the full content of that documentation page. + +Those rules are applicable when using the available tools: + +- First use `get_table_metadata` to find out about tables stored in the database and their + column names and types. Next, use `query_sql` to execute a parameterised SQL query that + returns only the columns you need (avoid `SELECT *`) and, where appropriate, add a + `LIMIT` clause to keep result sets concise. + +- First use `get_cratedb_documentation_index` to find out about curated documentation resources + about CrateDB. Then, use `fetch_cratedb_docs` to retrieve individual pages that + can be quoted verbatim when answering questions about CrateDB. + +After fetching data, reason about the output and provide a concise interpretation before +formulating the final answer. diff --git a/cratedb_mcp/tool.py b/cratedb_mcp/tool.py index ed557aa..29127cc 100644 --- a/cratedb_mcp/tool.py +++ b/cratedb_mcp/tool.py @@ -5,14 +5,6 @@ from cratedb_mcp.util.sql import sql_is_permitted -# ------------------------------------------ -# Health / Status -# ------------------------------------------ -def get_cluster_health() -> dict: - """Query sys.health ordered by severity.""" - return query_cratedb(Queries.HEALTH) - - # ------------------------------------------ # Text-to-SQL # ------------------------------------------ @@ -58,3 +50,11 @@ def fetch_cratedb_docs(link: str) -> str: if not documentation_index.url_permitted(link): raise ValueError(f"Link is not permitted: {link}") return documentation_index.client.get(link, timeout=Settings.http_timeout()).text + + +# ------------------------------------------ +# Health / Status +# ------------------------------------------ +def get_cluster_health() -> dict: + """Query sys.health ordered by severity.""" + return query_cratedb(Queries.HEALTH) diff --git a/pyproject.toml b/pyproject.toml index ef32366..2d783fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ dependencies = [ "attrs", "cachetools<7", "click<9", - "cratedb-about==0.0.5", + "cratedb-about==0.0.6", "fastmcp>=2.7,<2.11", "hishel<0.2", "pueblo==0.0.11", @@ -94,6 +94,15 @@ optional-dependencies.test = [ scripts.cratedb-mcp = "cratedb_mcp.cli:cli" +[tool.setuptools] +include-package-data = true + +[tool.setuptools.package-data] +cratedb_mcp = [ "*.md" ] + +[tool.setuptools.packages.find] +namespaces = false + [tool.ruff] line-length = 100 @@ -132,9 +141,6 @@ lint.per-file-ignores."tests/*" = [ "S101", # Allow use of `assert`. ] -[tool.setuptools.packages.find] -namespaces = false - [tool.pytest.ini_options] addopts = """ -rfEXs -p pytester --strict-markers --verbosity=3 diff --git a/tests/test_instructions.py b/tests/test_instructions.py new file mode 100644 index 0000000..daec78e --- /dev/null +++ b/tests/test_instructions.py @@ -0,0 +1,13 @@ +from cratedb_mcp.__main__ import mcp + + +def test_instructions(): + instructions_text = mcp.instructions + + # MCP instructions. + assert "Tool instructions" in instructions_text + + # General instructions. + assert "Things to remember when working with CrateDB" in instructions_text + assert "Rules for writing SQL queries" in instructions_text + assert "Core writing principles" in instructions_text