diff --git a/LICENSE b/LICENSE
index d9f59d42..0a62d25a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2022 TigerGraph Inc.
+ Copyright 2022-2026 TigerGraph Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index aadd9897..6cde56da 100644
--- a/README.md
+++ b/README.md
@@ -1,66 +1,233 @@
# pyTigerGraph
-pyTigerGraph is a Python package for connecting to TigerGraph databases. Check out the documentation [here](https://docs.tigergraph.com/pytigergraph/current/intro/).
+pyTigerGraph is a Python client for [TigerGraph](https://www.tigergraph.com/) databases. It wraps the REST++ and GSQL APIs and provides both a synchronous and an asynchronous interface.
-[](https://pepy.tech/project/pyTigergraph)
-[](https://pepy.tech/project/pyTigergraph)
-[](https://pepy.tech/project/pyTigergraph)
+Full documentation:
-## Quickstart
+Downloads: [](https://pepy.tech/project/pyTigergraph) | [](https://pepy.tech/project/pyTigergraph) | [](https://pepy.tech/project/pyTigergraph)
+
+---
+
+## Installation
+
+### Base package
-### Installing pyTigerGraph
-This section walks you through installing pyTigerGraph on your machine.
+```sh
+pip install pyTigerGraph
+```
-#### Prerequisites
-* Python 3+
-* If you wish to use the GDS functionality, install `torch` ahead of time.
+### Optional extras
-#### Install _pyTigerGraph_
+| Extra | What it adds | Install command |
+|-------|-------------|-----------------|
+| `gds` | Graph Data Science — data loaders for PyTorch Geometric, DGL, and Pandas | `pip install 'pyTigerGraph[gds]'` |
+| `mcp` | Model Context Protocol server — installs [`pyTigerGraph-mcp`](https://github.com/tigergraph/tigergraph-mcp) (convenience alias) | `pip install 'pyTigerGraph[mcp]'` |
+| `fast` | [orjson](https://github.com/ijl/orjson) JSON backend — 2–10× faster parsing, releases the GIL under concurrent load | `pip install 'pyTigerGraph[fast]'` |
-To download _pyTigerGraph_, run the following command in the command line or use the appropriate tool of your development environment (anaconda, PyCharm, etc.).:
+Extras can be combined:
```sh
-pip3 install pyTigerGraph
+pip install 'pyTigerGraph[fast,gds,mcp]'
```
-#### Install _pyTigerGraph[gds]_
+#### `[gds]` prerequisites
-To utilize the Graph Data Science Functionality, there are a few options:
-* To use the GDS functions with **PyTorch Geometric**, install `torch` and `PyTorch Geometric` according to their instructions:
+Install `torch` before installing the `gds` extra:
- 1) [Install Torch](https://pytorch.org/get-started/locally/)
+1. [Install Torch](https://pytorch.org/get-started/locally/)
+2. Optionally [Install PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html) or [Install DGL](https://www.dgl.ai/pages/start.html)
+3. `pip install 'pyTigerGraph[gds]'`
- 2) [Install PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html)
+#### `[fast]` — orjson JSON backend
- 3) Install pyTigerGraph with:
- ```sh
- pip3 install 'pyTigerGraph[gds]'
- ```
+`orjson` is a Rust-backed JSON library that is detected and used automatically when installed. No code changes are required. It improves throughput in two ways:
-* To use the GDS functions with **DGL**, install `torch` and `dgl` according to their instructions:
+- **Faster parsing** — 2–10× vs stdlib `json`
+- **GIL release** — threads parse responses concurrently instead of serialising on the GIL
- 1) [Install Torch](https://pytorch.org/get-started/locally/)
+If `orjson` is not installed the library falls back to stdlib `json` transparently.
- 2) [Install DGL](https://www.dgl.ai/pages/start.html)
+---
- 3) Install pyTigerGraph with:
- ```sh
- pip3 install 'pyTigerGraph[gds]'
- ```
+## Quickstart
-* To use the GDS functions without needing to produce output in the format supported by PyTorch Geometric or DGL.
-This makes the data loaders output *Pandas dataframes*:
-```sh
-pip3 install 'pyTigerGraph[gds]'
+### Synchronous connection
+
+```python
+from pyTigerGraph import TigerGraphConnection
+
+conn = TigerGraphConnection(
+ host="http://localhost",
+ graphname="my_graph",
+ username="tigergraph",
+ password="tigergraph",
+)
+
+print(conn.echo())
```
-Once the package is installed, you can import it like any other Python package:
+Use as a context manager to ensure the underlying HTTP session is closed:
-```py
-import pyTigerGraph as tg
+```python
+with TigerGraphConnection(host="http://localhost", graphname="my_graph") as conn:
+ result = conn.runInstalledQuery("my_query", {"param": "value"})
```
-### Getting Started with Core Functions
+
+### Asynchronous connection
+
+`AsyncTigerGraphConnection` exposes the same API as `TigerGraphConnection` but with `async`/`await` syntax. It uses [aiohttp](https://docs.aiohttp.org/) internally and shares a single connection pool across all concurrent tasks, making it significantly more efficient than threaded sync code at high concurrency.
+
+```python
+import asyncio
+from pyTigerGraph import AsyncTigerGraphConnection
+
+async def main():
+ async with AsyncTigerGraphConnection(
+ host="http://localhost",
+ graphname="my_graph",
+ username="tigergraph",
+ password="tigergraph",
+ ) as conn:
+ result = await conn.runInstalledQuery("my_query", {"param": "value"})
+ print(result)
+
+asyncio.run(main())
+```
+
+### Token-based authentication
+
+```python
+conn = TigerGraphConnection(
+ host="http://localhost",
+ graphname="my_graph",
+ gsqlSecret="my_secret", # generates a session token automatically
+)
+```
+
+### HTTPS / TigerGraph Cloud
+
+```python
+conn = TigerGraphConnection(
+ host="https://my-instance.i.tgcloud.io",
+ graphname="my_graph",
+ username="tigergraph",
+ password="tigergraph",
+ tgCloud=True,
+)
+```
+
+---
+
+## Connection parameters
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `host` | `str` | `"http://127.0.0.1"` | Server URL including scheme (`http://` or `https://`) |
+| `graphname` | `str` | `""` | Target graph name |
+| `username` | `str` | `"tigergraph"` | Database username |
+| `password` | `str` | `"tigergraph"` | Database password |
+| `gsqlSecret` | `str` | `""` | GSQL secret for token-based auth (preferred over username/password) |
+| `apiToken` | `str` | `""` | Pre-obtained REST++ API token |
+| `jwtToken` | `str` | `""` | JWT token for customer-managed authentication |
+| `restppPort` | `int\|str` | `"9000"` | REST++ port (auto-fails over to `14240/restpp` for TigerGraph 4.x) |
+| `gsPort` | `int\|str` | `"14240"` | GSQL server port |
+| `certPath` | `str` | `None` | Path to CA certificate for HTTPS |
+| `tgCloud` | `bool` | `False` | Set to `True` for TigerGraph Cloud instances |
+
+---
+
+## Performance notes
+
+### Synchronous mode (`TigerGraphConnection`)
+
+- Each thread gets its own dedicated HTTP session and connection pool, so concurrent threads never block each other.
+- Install `pyTigerGraph[fast]` to activate the `orjson` backend and reduce JSON parsing overhead under concurrent load.
+- Use `ThreadPoolExecutor` to run queries in parallel:
+
+```python
+from concurrent.futures import ThreadPoolExecutor, as_completed
+
+with TigerGraphConnection(...) as conn:
+ with ThreadPoolExecutor(max_workers=16) as executor:
+ futures = [executor.submit(conn.runInstalledQuery, "q", {"p": v}) for v in values]
+ for f in as_completed(futures):
+ print(f.result())
+```
+
+### Asynchronous mode (`AsyncTigerGraphConnection`)
+
+- Uses a single `aiohttp.ClientSession` with an unbounded connection pool shared across all concurrent coroutines — no GIL, no thread-scheduling overhead.
+- Typically achieves higher QPS and lower tail latency than the threaded sync mode for I/O-bound workloads.
+
+```python
+import asyncio
+from pyTigerGraph import AsyncTigerGraphConnection
+
+async def main():
+ async with AsyncTigerGraphConnection(...) as conn:
+ tasks = [conn.runInstalledQuery("q", {"p": v}) for v in values]
+ results = await asyncio.gather(*tasks)
+
+asyncio.run(main())
+```
+
+---
+
+## Graph Data Science (GDS)
+
+The `gds` sub-module provides data loaders that stream vertex and edge data from TigerGraph directly into PyTorch Geometric, DGL, or Pandas DataFrames for machine learning workflows.
+
+Install requirements, then access via `conn.gds`:
+
+```python
+conn = TigerGraphConnection(host="...", graphname="...")
+loader = conn.gds.vertexLoader(attributes=["feat", "label"], batch_size=1024)
+for batch in loader:
+ train(batch)
+```
+
+See the [GDS documentation](https://docs.tigergraph.com/pytigergraph/current/gds/) for full details.
+
+---
+
+## MCP Server
+
+The TigerGraph MCP server is now a standalone package: **[pyTigerGraph-mcp](https://github.com/tigergraph/tigergraph-mcp)**. It exposes TigerGraph operations as tools for AI agents and LLM applications (Claude Desktop, Cursor, Copilot, etc.).
+
+```sh
+# Recommended — install the standalone package directly
+pip install pyTigerGraph-mcp
+
+# Or via the pyTigerGraph convenience alias (installs pyTigerGraph-mcp automatically)
+pip install 'pyTigerGraph[mcp]'
+
+# Start the server (reads connection config from environment variables)
+tigergraph-mcp
+```
+
+For full setup instructions, available tools, configuration examples, and multi-profile support, see the **[pyTigerGraph-mcp README](https://github.com/tigergraph/tigergraph-mcp#readme)**.
+
+> **Migrating from `pyTigerGraph.mcp`?** Update your imports:
+> ```python
+> # Old
+> from pyTigerGraph.mcp import serve, ConnectionManager
+> # New
+> from tigergraph_mcp import serve, ConnectionManager
+> ```
+
+---
+
+## Getting started video
[](https://www.youtube.com/watch?v=2BcC3C-qfX4)
-The video above is a good starting place for learning the core functions of pyTigerGraph. [This Google Colab notebook](https://colab.research.google.com/drive/1JhYcnGVWT51KswcXZzyPzKqCoPP5htcC) is the companion notebook to the video.
+Companion notebook: [Google Colab](https://colab.research.google.com/drive/1JhYcnGVWT51KswcXZzyPzKqCoPP5htcC)
+
+---
+
+## Links
+
+- [Documentation](https://docs.tigergraph.com/pytigergraph/current/intro/)
+- [PyPI](https://pypi.org/project/pyTigerGraph/)
+- [GitHub Issues](https://github.com/tigergraph/pyTigerGraph/issues)
+- [Source](https://github.com/tigergraph/pyTigerGraph)
diff --git a/build.sh b/build.sh
index 2c067641..5e173613 100755
--- a/build.sh
+++ b/build.sh
@@ -1,10 +1,57 @@
-#! /bin/bash
+#!/usr/bin/env bash
+set -euo pipefail
-echo ---- Removing old dist ----
-rm -rf dist
+usage() {
+ cat <&2; usage >&2; exit 1 ;;
+ esac
+ shift
+done
+
+if $DO_BUILD; then
+ echo "---- Removing old dist ----"
+ rm -rf dist
+
+ echo "---- Building new package ----"
+ python3 -m build
+fi
+
+if $DO_UPLOAD; then
+ if [[ ! -d dist ]] || [[ -z "$(ls dist/)" ]]; then
+ echo "Error: dist/ is empty or missing. Run with --build first." >&2
+ exit 1
+ fi
+
+ echo "---- Uploading to PyPI ----"
+ python3 -m twine upload dist/*
+fi
diff --git a/pyTigerGraph/__init__.py b/pyTigerGraph/__init__.py
index 844db559..ca018e34 100644
--- a/pyTigerGraph/__init__.py
+++ b/pyTigerGraph/__init__.py
@@ -1,8 +1,13 @@
+from importlib.metadata import version as _pkg_version, PackageNotFoundError
+
from pyTigerGraph.pyTigerGraph import TigerGraphConnection
from pyTigerGraph.pytgasync.pyTigerGraph import AsyncTigerGraphConnection
from pyTigerGraph.common.exception import TigerGraphException
-__version__ = "2.0.0"
+try:
+ __version__ = _pkg_version("pyTigerGraph")
+except PackageNotFoundError:
+ __version__ = "2.0.1"
__license__ = "Apache 2"
diff --git a/pyTigerGraph/common/base.py b/pyTigerGraph/common/base.py
index 53e96422..ef78b5f0 100644
--- a/pyTigerGraph/common/base.py
+++ b/pyTigerGraph/common/base.py
@@ -15,6 +15,18 @@
from typing import Union
from urllib.parse import urlparse
+# orjson is an optional Rust-backed JSON library that:
+# - parses/serialises 2–10× faster than stdlib json
+# - releases the GIL during parsing, eliminating inter-thread contention on
+# multi-threaded workloads where all threads parse responses simultaneously
+# Fall back to stdlib transparently when orjson is not installed.
+try:
+ import orjson as _orjson
+ _HAS_ORJSON = True
+except ImportError:
+ _orjson = None
+ _HAS_ORJSON = False
+
from pyTigerGraph.common.exception import TigerGraphException
@@ -112,6 +124,7 @@ def __init__(self, host: str = "http://127.0.0.1", graphname: str = "",
# Detect auth mode automatically by checking if jwtToken or apiToken is provided
self.authHeader = self._set_auth_header()
+ self.authMode = "token" if (self.jwtToken or self.apiToken) else "pwd"
# TODO Eliminate version and use gsqlVersion only, meaning TigerGraph server version
if gsqlVersion:
@@ -152,6 +165,12 @@ def __init__(self, host: str = "http://127.0.0.1", graphname: str = "",
self.certPath = certPath
self.sslPort = str(sslPort)
+ # SSL verify value — depends only on useCert/certPath which are fixed after init,
+ # so we compute it once here rather than on every request in _prep_req.
+ # Note: for http, certPath="" (not None) so the condition is True → False; for
+ # https, useCert=True → False. The verify=True branch in _prep_req is unreachable.
+ self.verify = False if (self.useCert or self.certPath) else True
+
# TODO Remove gcp parameter
if gcp:
warnings.warn("The `gcp` parameter is deprecated.",
@@ -212,6 +231,10 @@ def __init__(self, host: str = "http://127.0.0.1", graphname: str = "",
self.asynchronous = False
+ # Pre-build per-authMode header dicts so _prep_req avoids repeating
+ # the isinstance/string-comparison chain on every request.
+ self._refresh_auth_headers()
+
logger.debug("exit: __init__")
def _set_auth_header(self):
@@ -223,6 +246,34 @@ def _set_auth_header(self):
else:
return {"Authorization": "Basic {0}".format(self.base64_credential)}
+ def _refresh_auth_headers(self) -> None:
+ """Pre-build per-authMode header dicts used by every request.
+
+ Called once at __init__ and again after getToken() updates the
+ credentials. Eliminates per-request isinstance checks and string
+ formatting in _prep_req's hot path.
+
+ Two dicts are kept because authMode can be either "token" or "pwd":
+ - "token": JWT > apiToken (tuple or str) > Basic
+ - "pwd": JWT > Basic
+ The "X-User-Agent" header is baked in so _prep_req skips that update too.
+ """
+ # ---- token mode ----
+ if isinstance(self.jwtToken, str) and self.jwtToken.strip():
+ token_val = "Bearer " + self.jwtToken
+ elif isinstance(self.apiToken, tuple):
+ token_val = "Bearer " + self.apiToken[0]
+ elif isinstance(self.apiToken, str) and self.apiToken.strip():
+ token_val = "Bearer " + self.apiToken
+ else:
+ token_val = "Basic " + self.base64_credential
+
+ # ---- pwd mode ----
+ pwd_val = ("Bearer " + self.jwtToken) if self.jwtToken else ("Basic " + self.base64_credential)
+
+ self._cached_token_auth = {"Authorization": token_val, "X-User-Agent": "pyTigerGraph"}
+ self._cached_pwd_auth = {"Authorization": pwd_val, "X-User-Agent": "pyTigerGraph"}
+
def _verify_jwt_token_support(self):
try:
# Check JWT support for RestPP server
@@ -281,34 +332,11 @@ def _prep_req(self, authMode, headers, url, method, data):
if logger.level == logging.DEBUG:
logger.debug("params: " + self._locals(locals()))
- _headers = {}
-
- # If JWT token is provided, always use jwtToken as token
- if authMode == "token":
- if isinstance(self.jwtToken, str) and self.jwtToken.strip() != "":
- token = self.jwtToken
- elif isinstance(self.apiToken, tuple):
- token = self.apiToken[0]
- elif isinstance(self.apiToken, str) and self.apiToken.strip() != "":
- token = self.apiToken
- else:
- token = None
-
- if token:
- self.authHeader = {'Authorization': "Bearer " + token}
- _headers = self.authHeader
- else:
- self.authHeader = {
- 'Authorization': 'Basic {0}'.format(self.base64_credential)}
- _headers = self.authHeader
- self.authMode = "pwd"
- else:
- if self.jwtToken:
- _headers = {'Authorization': "Bearer " + self.jwtToken}
- else:
- _headers = {'Authorization': 'Basic {0}'.format(
- self.base64_credential)}
- self.authMode = "pwd"
+ # Shallow-copy the pre-built header dict (auth + X-User-Agent already included).
+ # _refresh_auth_headers() keeps these current after every getToken() call.
+ _headers = dict(
+ self._cached_token_auth if authMode == "token" else self._cached_pwd_auth
+ )
if headers:
_headers.update(headers)
@@ -323,25 +351,28 @@ def _prep_req(self, authMode, headers, url, method, data):
else:
_data = None
- if self.useCert is True or self.certPath is not None:
- verify = False
- else:
- verify = True
-
- _headers.update({"X-User-Agent": "pyTigerGraph"})
logger.debug("exit: _prep_req")
- return _headers, _data, verify
+ return _headers, _data, self.verify
- def _parse_req(self, res, jsonResponse, strictJson, skipCheck, resKey):
+ def _parse_req(self, data: Union[bytes, str], jsonResponse, strictJson, skipCheck, resKey):
logger.debug("entry: _parse_req")
if jsonResponse:
try:
- res = json.loads(res.text, strict=strictJson)
- except:
- raise TigerGraphException("Cannot parse json: " + res.text)
+ if _HAS_ORJSON and strictJson:
+ # orjson accepts bytes directly (no decode step), parses 2–10× faster
+ # than stdlib, and releases the GIL — eliminating inter-thread contention
+ # when multiple threads parse responses simultaneously.
+ res = _orjson.loads(data)
+ else:
+ # strictJson=False allows control characters; orjson is always strict,
+ # so fall back to stdlib for that case.
+ res = json.loads(data, strict=strictJson)
+ except Exception:
+ text = data.decode("utf-8", errors="replace") if isinstance(data, bytes) else data
+ raise TigerGraphException("Cannot parse json: " + text)
else:
- res = res.text
+ res = data.decode("utf-8", errors="replace") if isinstance(data, bytes) else data
if not skipCheck:
self._error_check(res)
diff --git a/pyTigerGraph/common/edge.py b/pyTigerGraph/common/edge.py
index 71e3ec10..879763b5 100644
--- a/pyTigerGraph/common/edge.py
+++ b/pyTigerGraph/common/edge.py
@@ -177,30 +177,22 @@ def _dumps(data) -> str:
Returns:
The JSON to be sent to the endpoint.
"""
- ret = ""
- if isinstance(data, dict):
- c1 = 0
- for k1, v1 in data.items():
- if c1 > 0:
- ret += ","
- if k1 == ___trgvtxids:
- # Dealing with the (possibly multiple instances of) edge details
- # v1 should be a dict of lists
- c2 = 0
- for k2, v2 in v1.items():
- if c2 > 0:
- ret += ","
- c3 = 0
- for v3 in v2:
- if c3 > 0:
- ret += ","
- ret += json.dumps(k2) + ':' + json.dumps(v3)
- c3 += 1
- c2 += 1
- else:
- ret += json.dumps(k1) + ':' + _dumps(data[k1])
- c1 += 1
- return "{" + ret + "}"
+ if not isinstance(data, dict):
+ return json.dumps(data)
+ parts = []
+ for k1, v1 in data.items():
+ if k1 == ___trgvtxids:
+ # Dealing with the (possibly multiple instances of) edge details.
+ # v1 is a dict mapping target vertex ID -> list of attribute dicts.
+ # Each list entry becomes a separate JSON key:value pair (same key repeated
+ # for MultiEdge), so we cannot use json.dumps on v1 directly.
+ for k2, v2 in v1.items():
+ k2_encoded = json.dumps(k2)
+ for v3 in v2:
+ parts.append(k2_encoded + ":" + json.dumps(v3))
+ else:
+ parts.append(json.dumps(k1) + ":" + _dumps(v1))
+ return "{" + ",".join(parts) + "}"
def _prep_upsert_edges(sourceVertexType,
edgeType,
@@ -248,8 +240,8 @@ def _prep_upsert_edge_dataframe(df, from_id, to_id, attributes):
for index in df.index:
json_up.append(json.loads(df.loc[index].to_json()))
json_up[-1] = (
- index if from_id is None else json_up[-1][from_id],
- index if to_id is None else json_up[-1][to_id],
+ index if not from_id else json_up[-1][from_id],
+ index if not to_id else json_up[-1][to_id],
json_up[-1] if attributes is None
else {target: json_up[-1][source] for target, source in attributes.items()}
)
diff --git a/pyTigerGraph/common/gsql.py b/pyTigerGraph/common/gsql.py
index 2b179e2e..917a11cf 100644
--- a/pyTigerGraph/common/gsql.py
+++ b/pyTigerGraph/common/gsql.py
@@ -60,6 +60,69 @@ def clean_res(resp: list) -> str:
return string_without_ansi
+_GSQL_ERROR_PATTERNS = [
+ "Encountered \"",
+ "SEMANTIC ERROR",
+ "Syntax Error",
+ "Failed to create",
+ "does not exist",
+ "is not a valid",
+ "already exists",
+ "Invalid syntax",
+]
+
+
+def _wrap_gsql_result(result, skipCheck: bool = False):
+ """Wrap a gsql() string result into a dict matching 4.x REST response format.
+
+ Args:
+ result: The raw string returned by ``gsql()``.
+ skipCheck: If ``False`` (default), raises ``TigerGraphException`` when
+ an error pattern is detected — consistent with ``_error_check``
+ on the 4.x REST path. If ``True``, returns the dict with
+ ``"error": True`` without raising.
+ """
+ msg = str(result) if result else ""
+ has_error = any(p in msg for p in _GSQL_ERROR_PATTERNS)
+ if has_error and not skipCheck:
+ raise TigerGraphException(msg)
+ return {
+ "error": has_error,
+ "message": msg,
+ }
+
+
+def _parse_graph_list(gsql_output):
+ """Parse ``SHOW GRAPH *`` output into a list of dicts matching 4.x REST format."""
+ output = str(gsql_output) if gsql_output else ""
+ graphs = []
+ for line in output.splitlines():
+ stripped = line.strip().lstrip("- ").strip()
+ if not stripped.startswith("Graph "):
+ continue
+ paren_start = stripped.find("(")
+ name = stripped[6:paren_start].strip() if paren_start > 6 else stripped[6:].strip()
+ if not name or name == "*":
+ continue
+ vertices = []
+ edges = []
+ if paren_start != -1:
+ paren_end = stripped.rfind(")")
+ inner = stripped[paren_start + 1:paren_end] if paren_end > paren_start else ""
+ for token in inner.split(","):
+ token = token.strip()
+ if token.endswith(":v"):
+ vertices.append(token[:-2])
+ elif token.endswith(":e"):
+ edges.append(token[:-2])
+ graphs.append({
+ "GraphName": name,
+ "VertexTypes": vertices,
+ "EdgeTypes": edges,
+ })
+ return graphs
+
+
def _prep_get_udf(ExprFunctions: bool = True, ExprUtil: bool = True):
urls = {} # urls when using TG 4.x
alt_urls = {} # urls when using TG 3.x
diff --git a/pyTigerGraph/common/loading.py b/pyTigerGraph/common/loading.py
index c12c6f56..137e70e4 100644
--- a/pyTigerGraph/common/loading.py
+++ b/pyTigerGraph/common/loading.py
@@ -70,9 +70,10 @@ def _prep_run_loading_job(gsUrl: str,
def _prep_abort_loading_jobs(gsUrl: str, graphname: str, jobIds: list[str], pauseJob: bool):
'''url builder for abortLoadingJob()'''
+ job_params = "&".join("jobId=" + jobId for jobId in jobIds)
url = gsUrl + "/gsql/v1/loading-jobs/abort?graph=" + graphname
- for jobId in jobIds:
- url += "&jobId=" + jobId
+ if job_params:
+ url += "&" + job_params
if pauseJob:
url += "&isPause=true"
return url
@@ -91,16 +92,46 @@ def _prep_resume_loading_job(gsUrl: str, jobId: str):
url = gsUrl + "/gsql/v1/loading-jobs/resume/" + jobId
return url
-def _prep_get_loading_jobs_status(gsUrl: str, jobIds: list[str]):
- '''url builder for getLoadingJobStatus()
- TODO: verify that this is correct
- '''
- url = gsUrl + "/gsql/v1/loading-jobs/status/jobId"
- for jobId in jobIds:
- url += "&jobId=" + jobId
+def _prep_get_loading_jobs_status(gsUrl: str, graphname: str, jobIds: list[str]):
+ '''url builder for getLoadingJobsStatus()'''
+ job_params = "&".join("jobId=" + jobId for jobId in jobIds)
+ url = gsUrl + "/gsql/v1/loading-jobs/status?graph=" + graphname
+ if job_params:
+ url += "&" + job_params
return url
-def _prep_get_loading_job_status(gsUrl: str, jobId: str):
+def _prep_get_loading_job_status(gsUrl: str, graphname: str, jobId: str):
'''url builder for getLoadingJobStatus()'''
- url = gsUrl + "/gsql/v1/loading-jobs/status/" + jobId
- return url
\ No newline at end of file
+ url = gsUrl + "/gsql/v1/loading-jobs/status/" + jobId + "?graph=" + graphname
+ return url
+
+
+# ---- Data Source helpers ----
+
+def _prep_data_source_url(gsUrl: str, graphname: str = None):
+ '''url builder for getDataSources() and createDataSource()'''
+ url = gsUrl + "/gsql/v1/data-sources"
+ if graphname:
+ url += "?graph=" + graphname
+ return url
+
+
+def _prep_data_source_by_name(gsUrl: str, dsName: str, graphname: str = None):
+ '''url builder for getDataSource(), dropDataSource(), updateDataSource()'''
+ url = gsUrl + "/gsql/v1/data-sources/" + dsName
+ if graphname:
+ url += "?graph=" + graphname
+ return url
+
+
+def _prep_drop_all_data_sources(gsUrl: str, graphname: str = None):
+ '''url builder for dropAllDataSources()'''
+ url = gsUrl + "/gsql/v1/data-sources/dropAll"
+ if graphname:
+ url += "?graph=" + graphname
+ return url
+
+
+def _prep_sample_data_url(gsUrl: str):
+ '''url builder for previewSampleData()'''
+ return gsUrl + "/gsql/v1/sample-data"
\ No newline at end of file
diff --git a/pyTigerGraph/common/query.py b/pyTigerGraph/common/query.py
index 7b5b3655..efaed2b8 100644
--- a/pyTigerGraph/common/query.py
+++ b/pyTigerGraph/common/query.py
@@ -20,7 +20,11 @@
logger = logging.getLogger(__name__)
# TODO getQueries() # List _all_ query names
-def _parse_get_installed_queries(fmt, ret):
+def _parse_get_installed_queries(fmt, ret, graphname: str = ""):
+ prefix = f"GET /query/{graphname}/" if graphname else "GET /query/"
+ if fmt == "list":
+ return [ep[len(prefix):] for ep in ret if ep.startswith(prefix)]
+ ret = {ep: v for ep, v in ret.items() if ep.startswith(prefix)}
if fmt == "json":
ret = json.dumps(ret)
if fmt == "df":
@@ -57,37 +61,33 @@ def _parse_query_parameters(params: dict) -> str:
logger.debug("entry: _parseQueryParameters")
logger.debug("params: " + str(params))
- ret = ""
+ parts = []
for k, v in params.items():
if isinstance(v, tuple):
if len(v) == 2 and isinstance(v[1], str):
- ret += k + "=" + str(v[0]) + "&" + k + \
- ".type=" + _safe_char(v[1]) + "&"
+ parts.append(k + "=" + str(v[0]))
+ parts.append(k + ".type=" + _safe_char(v[1]))
else:
raise TigerGraphException(
"Invalid parameter value: (vertex_primary_id, vertex_type)"
" was expected.")
elif isinstance(v, list):
- i = 0
- for vv in v:
+ for i, vv in enumerate(v):
if isinstance(vv, tuple):
if len(vv) == 2 and isinstance(vv[1], str):
- ret += k + "[" + str(i) + "]=" + _safe_char(vv[0]) + "&" + \
- k + "[" + str(i) + "].type=" + vv[1] + "&"
+ parts.append(k + "[" + str(i) + "]=" + _safe_char(vv[0]))
+ parts.append(k + "[" + str(i) + "].type=" + vv[1])
else:
raise TigerGraphException(
"Invalid parameter value: (vertex_primary_id, vertex_type)"
" was expected.")
else:
- ret += k + "=" + _safe_char(vv) + "&"
- i += 1
+ parts.append(k + "=" + _safe_char(vv))
elif isinstance(v, datetime):
- ret += k + "=" + \
- _safe_char(v.strftime("%Y-%m-%d %H:%M:%S")) + "&"
+ parts.append(k + "=" + _safe_char(v.strftime("%Y-%m-%d %H:%M:%S")))
else:
- ret += k + "=" + _safe_char(v) + "&"
- if ret:
- ret = ret[:-1]
+ parts.append(k + "=" + _safe_char(v))
+ ret = "&".join(parts)
if logger.level == logging.DEBUG:
logger.debug("return: " + str(ret))
diff --git a/pyTigerGraph/mcp/MCP_README.md b/pyTigerGraph/mcp/MCP_README.md
deleted file mode 100644
index a111ee58..00000000
--- a/pyTigerGraph/mcp/MCP_README.md
+++ /dev/null
@@ -1,393 +0,0 @@
-# pyTigerGraph MCP Support
-
-pyTigerGraph now includes Model Context Protocol (MCP) support, allowing AI agents to interact with TigerGraph through the MCP standard. All MCP tools use pyTigerGraph's async APIs for optimal performance.
-
-## Installation
-
-To use MCP functionality, install pyTigerGraph with the `mcp` extra:
-
-```bash
-pip install pyTigerGraph[mcp]
-```
-
-This will install:
-- `mcp>=1.0.0` - The MCP SDK
-- `pydantic>=2.0.0` - For data validation
-- `click` - For the CLI entry point
-- `python-dotenv>=1.0.0` - For loading .env files
-
-## Usage
-
-### Running the MCP Server
-
-You can run the MCP server as a standalone process:
-
-```bash
-tigergraph-mcp
-```
-
-With a custom .env file:
-
-```bash
-tigergraph-mcp --env-file /path/to/.env
-```
-
-With verbose logging:
-
-```bash
-tigergraph-mcp -v # INFO level
-tigergraph-mcp -vv # DEBUG level
-```
-
-Or programmatically:
-
-```python
-from pyTigerGraph.mcp import serve
-import asyncio
-
-asyncio.run(serve())
-```
-
-### Configuration
-
-The MCP server reads connection configuration from environment variables. You can set these either directly as environment variables or in a `.env` file.
-
-#### Using a .env File (Recommended)
-
-Create a `.env` file in your project directory:
-
-```bash
-# .env
-TG_HOST=http://localhost
-TG_GRAPHNAME=MyGraph # Optional - can be omitted if database has multiple graphs
-TG_USERNAME=tigergraph
-TG_PASSWORD=tigergraph
-TG_RESTPP_PORT=9000
-TG_GS_PORT=14240
-```
-
-The server will automatically load the `.env` file if it exists. Environment variables take precedence over `.env` file values.
-
-You can also specify a custom path to the `.env` file:
-
-```bash
-tigergraph-mcp --env-file /path/to/custom/.env
-```
-
-#### Environment Variables
-
-The following environment variables are supported:
-
-- `TG_HOST` - TigerGraph host (default: http://127.0.0.1)
-- `TG_GRAPHNAME` - Graph name (optional - can be omitted if database has multiple graphs. Use `tigergraph__list_graphs` tool to see available graphs)
-- `TG_USERNAME` - Username (default: tigergraph)
-- `TG_PASSWORD` - Password (default: tigergraph)
-- `TG_SECRET` - GSQL secret (optional)
-- `TG_API_TOKEN` - API token (optional)
-- `TG_JWT_TOKEN` - JWT token (optional)
-- `TG_RESTPP_PORT` - REST++ port (default: 9000)
-- `TG_GS_PORT` - GSQL port (default: 14240)
-- `TG_SSL_PORT` - SSL port (default: 443)
-- `TG_TGCLOUD` - Whether using TigerGraph Cloud (default: False)
-- `TG_CERT_PATH` - Path to certificate (optional)
-
-### Using with Existing Connection
-
-You can also use MCP with an existing `TigerGraphConnection` (sync) or `AsyncTigerGraphConnection`:
-
-**With Sync Connection:**
-```python
-from pyTigerGraph import TigerGraphConnection
-
-conn = TigerGraphConnection(
- host="http://localhost",
- graphname="MyGraph",
- username="tigergraph",
- password="tigergraph"
-)
-
-# Enable MCP support for this connection
-# This creates an async connection internally for MCP tools
-conn.start_mcp_server()
-```
-
-**With Async Connection (Recommended):**
-```python
-from pyTigerGraph import AsyncTigerGraphConnection
-from pyTigerGraph.mcp import ConnectionManager
-
-conn = AsyncTigerGraphConnection(
- host="http://localhost",
- graphname="MyGraph",
- username="tigergraph",
- password="tigergraph"
-)
-
-# Set as default for MCP tools
-ConnectionManager.set_default_connection(conn)
-```
-
-This sets the connection as the default for MCP tools. Note that MCP tools use async APIs internally, so using `AsyncTigerGraphConnection` directly is more efficient.
-
-## Available Tools
-
-The MCP server provides the following tools:
-
-### Global Schema Operations (Database Level)
-These operations work with the global schema that spans across the entire TigerGraph database.
-
-- `tigergraph__get_global_schema` - Get the complete global schema (all global vertex/edge types, graphs, and members) via GSQL 'LS' command
-
-### Graph Operations (Database Level)
-These operations manage individual graphs within the TigerGraph database. A database can contain multiple graphs.
-
-- `tigergraph__list_graphs` - List all graph names in the database (names only, no details)
-- `tigergraph__create_graph` - Create a new graph with its schema (vertex types, edge types)
-- `tigergraph__drop_graph` - Drop (delete) a graph and its schema
-- `tigergraph__clear_graph_data` - Clear all data from a graph (keeps schema structure)
-
-### Schema Operations (Graph Level)
-These operations work with the schema and objects of a specific graph.
-
-- `tigergraph__get_graph_schema` - Get the schema of a specific graph as structured JSON (vertex/edge types and attributes only)
-- `tigergraph__show_graph_details` - Show details of a graph: schema, queries, loading jobs, data sources. Use `detail_type` to filter (`schema`, `query`, `loading_job`, `data_source`) or omit for all
-
-### Node Operations
-- `tigergraph__add_node` - Add a single node
-- `tigergraph__add_nodes` - Add multiple nodes
-- `tigergraph__get_node` - Get a single node
-- `tigergraph__get_nodes` - Get multiple nodes
-- `tigergraph__delete_node` - Delete a single node
-- `tigergraph__delete_nodes` - Delete multiple nodes
-- `tigergraph__has_node` - Check if a node exists
-- `tigergraph__get_node_edges` - Get all edges connected to a node
-
-### Edge Operations
-- `tigergraph__add_edge` - Add a single edge
-- `tigergraph__add_edges` - Add multiple edges
-- `tigergraph__get_edge` - Get a single edge
-- `tigergraph__get_edges` - Get multiple edges
-- `tigergraph__delete_edge` - Delete a single edge
-- `tigergraph__delete_edges` - Delete multiple edges
-- `tigergraph__has_edge` - Check if an edge exists
-
-### Query Operations
-- `tigergraph__run_query` - Run an interpreted query
-- `tigergraph__run_installed_query` - Run an installed query
-- `tigergraph__install_query` - Install a query
-- `tigergraph__drop_query` - Drop (delete) an installed query
-- `tigergraph__show_query` - Show query text
-- `tigergraph__get_query_metadata` - Get query metadata
-- `tigergraph__is_query_installed` - Check if a query is installed
-- `tigergraph__get_neighbors` - Get neighbor vertices of a node
-
-### Loading Job Operations
-- `tigergraph__create_loading_job` - Create a loading job from structured config (file mappings, node/edge mappings)
-- `tigergraph__run_loading_job_with_file` - Execute a loading job with a data file
-- `tigergraph__run_loading_job_with_data` - Execute a loading job with inline data string
-- `tigergraph__get_loading_jobs` - Get all loading jobs for the graph
-- `tigergraph__get_loading_job_status` - Get status of a specific loading job
-- `tigergraph__drop_loading_job` - Drop a loading job
-
-### Statistics Operations
-- `tigergraph__get_vertex_count` - Get vertex count
-- `tigergraph__get_edge_count` - Get edge count
-- `tigergraph__get_node_degree` - Get the degree (number of edges) of a node
-
-### GSQL Operations
-- `tigergraph__gsql` - Execute raw GSQL command
-- `tigergraph__generate_gsql` - Generate a GSQL query from a natural language description (requires LLM configuration)
-- `tigergraph__generate_cypher` - Generate an openCypher query from a natural language description (requires LLM configuration)
-
-### Vector Schema Operations
-- `tigergraph__add_vector_attribute` - Add a vector attribute to a vertex type (DIMENSION, METRIC: COSINE/L2/IP)
-- `tigergraph__drop_vector_attribute` - Drop a vector attribute from a vertex type
-- `tigergraph__list_vector_attributes` - List vector attributes (name, dimension, index type, data type, metric) by parsing `LS` output; optionally filter by vertex type
-- `tigergraph__get_vector_index_status` - Check vector index rebuild status (Ready_for_query/Rebuild_processing)
-
-### Vector Data Operations
-- `tigergraph__upsert_vectors` - Upsert multiple vertices with vector data using REST API (batch support)
-- `tigergraph__load_vectors_from_csv` - Bulk-load vectors from a CSV/delimited file via a GSQL loading job (creates job, runs with file, drops job)
-- `tigergraph__load_vectors_from_json` - Bulk-load vectors from a JSON Lines (.jsonl) file via a GSQL loading job with `JSON_FILE="true"` (creates job, runs with file, drops job)
-- `tigergraph__search_top_k_similarity` - Perform vector similarity search using `vectorSearch()` function
-- `tigergraph__fetch_vector` - Fetch vertices with vector data using GSQL `PRINT WITH VECTOR`
-
-**Note:** Vector attributes can ONLY be fetched via GSQL queries with `PRINT v WITH VECTOR;` - they cannot be retrieved via REST API.
-
-### Data Source Operations
-- `tigergraph__create_data_source` - Create a new data source (S3, GCS, Azure Blob, local)
-- `tigergraph__update_data_source` - Update an existing data source
-- `tigergraph__get_data_source` - Get information about a data source
-- `tigergraph__drop_data_source` - Drop a data source
-- `tigergraph__get_all_data_sources` - Get all data sources
-- `tigergraph__drop_all_data_sources` - Drop all data sources
-- `tigergraph__preview_sample_data` - Preview sample data from a file
-
-### Discovery & Navigation
-- `tigergraph__discover_tools` - Search for tools by description, use case, or keywords
-- `tigergraph__get_workflow` - Get step-by-step workflow templates for common tasks (e.g., `data_loading`, `schema_creation`, `graph_exploration`)
-- `tigergraph__get_tool_info` - Get detailed information about a specific tool (parameters, examples, related tools)
-
-## Backward Compatibility
-
-All existing pyTigerGraph APIs continue to work as before. MCP support is completely optional and does not affect existing code. The MCP functionality is only available when:
-
-1. The `mcp` extra is installed
-2. You explicitly use MCP-related imports or methods
-
-## Example: Using with MCP Clients
-
-### Using MultiServerMCPClient
-
-```python
-from langchain_mcp_adapters import MultiServerMCPClient
-from pathlib import Path
-from dotenv import dotenv_values
-import asyncio
-
-# Load environment variables
-env_dict = dotenv_values(dotenv_path=Path(".env").expanduser().resolve())
-
-# Configure the client
-client = MultiServerMCPClient(
- {
- "tigergraph-mcp": {
- "transport": "stdio",
- "command": "tigergraph-mcp",
- "args": ["-vv"], # Enable debug logging
- "env": env_dict,
- },
- }
-)
-
-# Get tools and use them
-tools = asyncio.run(client.get_tools())
-# Tools are now available for use
-```
-
-### Using MCP Client SDK Directly
-
-```python
-import asyncio
-from mcp import ClientSession, StdioServerParameters
-from mcp.client.stdio import stdio_client
-
-async def call_tool():
- # Configure server parameters
- server_params = StdioServerParameters(
- command="tigergraph-mcp",
- args=["-vv"], # Enable debug logging
- env=None, # Uses .env file or environment variables
- )
-
- async with stdio_client(server_params) as (read, write):
- async with ClientSession(read, write) as session:
- await session.initialize()
-
- # List available tools
- tools = await session.list_tools()
- print(f"Available tools: {[t.name for t in tools.tools]}")
-
- # Call a tool
- result = await session.call_tool(
- "tigergraph__list_graphs",
- arguments={}
- )
-
- # Print result
- for content in result.content:
- print(content.text)
-
-asyncio.run(call_tool())
-```
-
-**Note:** When using `MultiServerMCPClient` or similar MCP clients with stdio transport, the `args` parameter is required. For the `tigergraph-mcp` command (which is a standalone entry point), set `args` to an empty list `[]`. If you need to pass arguments to the command, include them in the list (e.g., `["-v"]` for verbose mode, `["-vv"]` for debug mode).
-
-## LLM-Friendly Features
-
-The MCP server is designed to help AI agents work effectively with TigerGraph.
-
-### Structured Responses
-
-Every tool response follows a consistent JSON structure:
-
-```json
-{
- "success": true,
- "operation": "get_node",
- "summary": "Found vertex 'p123' of type 'Person'",
- "data": { ... },
- "suggestions": [
- "View connected edges: get_node_edges(...)",
- "Find neighbors: get_neighbors(...)"
- ],
- "metadata": { "graph_name": "MyGraph" }
-}
-```
-
-Error responses include actionable recovery hints:
-
-```json
-{
- "success": false,
- "operation": "get_node",
- "error": "Vertex not found",
- "suggestions": [
- "Verify the vertex_id is correct",
- "Check vertex type with show_graph_details()"
- ]
-}
-```
-
-### Rich Tool Descriptions
-
-Each tool includes detailed descriptions with:
-- **Use cases** — when to call this tool
-- **Common workflows** — step-by-step patterns
-- **Tips** — best practices and gotchas
-- **Warnings** — safety notes for destructive operations
-- **Related tools** — what to call next
-
-### Token Optimization
-
-Responses are designed for efficient LLM token usage:
-- No echoing of input parameters (the LLM already knows what it sent)
-- Only returns new information (results, counts, boolean answers)
-- Clean text output with no decorative formatting
-
-## Tool Discovery Workflow
-
-The MCP server includes discovery tools to help AI agents find the right tool for a task:
-
-```python
-# 1. Discover tools for a task
-result = await session.call_tool(
- "tigergraph__discover_tools",
- arguments={"query": "how to add data to the graph"}
-)
-# Returns: ranked list of relevant tools with use cases
-
-# 2. Get a workflow template
-result = await session.call_tool(
- "tigergraph__get_workflow",
- arguments={"workflow_type": "data_loading"}
-)
-# Returns: step-by-step guide with tool calls
-
-# 3. Get detailed tool info
-result = await session.call_tool(
- "tigergraph__get_tool_info",
- arguments={"tool_name": "tigergraph__add_node"}
-)
-# Returns: full documentation, examples, related tools
-```
-
-## Notes
-
-- **Async APIs**: All MCP tools use pyTigerGraph's async APIs (`AsyncTigerGraphConnection`) for optimal performance
-- **Transport**: The MCP server uses stdio transport by default
-- **Structured Responses**: All tools return structured JSON responses with `success`, `operation`, `summary`, `data`, `suggestions`, and `metadata` fields. Error responses include recovery hints and contextual suggestions
-- **Error Detection**: GSQL operations include error detection for syntax and semantic errors (since `conn.gsql()` does not raise Python exceptions for GSQL failures)
-- **Connection Management**: The connection manager automatically creates async connections from environment variables
-- **Performance**: Async APIs for non-blocking I/O; `v.outdegree()` for O(1) degree counting; batch operations for multiple vertices/edges
-
diff --git a/pyTigerGraph/mcp/__init__.py b/pyTigerGraph/mcp/__init__.py
index b5b4dbd8..853bac00 100644
--- a/pyTigerGraph/mcp/__init__.py
+++ b/pyTigerGraph/mcp/__init__.py
@@ -1,18 +1,44 @@
# Copyright 2025 TigerGraph Inc.
# Licensed under the Apache License, Version 2.0.
# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-"""Model Context Protocol (MCP) support for TigerGraph.
+"""Deprecated MCP shim — the MCP server has moved to the `pyTigerGraph-mcp` package.
-This module provides MCP server capabilities for TigerGraph, allowing
-AI agents to interact with TigerGraph through the Model Context Protocol.
+Install the standalone package::
+
+ pip install pyTigerGraph-mcp
+
+Or continue using the convenience alias (which installs `pyTigerGraph-mcp` automatically)::
+
+ pip install pyTigerGraph[mcp]
+
+Update your imports::
+
+ # Old
+ from pyTigerGraph.mcp import serve, MCPServer, ConnectionManager
+
+ # New
+ from tigergraph_mcp import serve, MCPServer, ConnectionManager
"""
-from .server import serve, MCPServer
-from .connection_manager import get_connection, ConnectionManager
+import warnings
+
+warnings.warn(
+ "pyTigerGraph.mcp is deprecated and will be removed in a future release. "
+ "The MCP server now lives in the 'pyTigerGraph-mcp' package. "
+ "Install it with: pip install pyTigerGraph-mcp "
+ "Update imports from 'pyTigerGraph.mcp' to 'tigergraph_mcp'.",
+ DeprecationWarning,
+ stacklevel=2,
+)
+
+try:
+ from tigergraph_mcp import serve, MCPServer, get_connection, ConnectionManager # noqa: F401
+except ImportError as e:
+ raise ImportError(
+ "Could not import 'tigergraph_mcp'. "
+ "Install it with: pip install pyTigerGraph-mcp"
+ ) from e
__all__ = [
"serve",
@@ -20,4 +46,3 @@
"get_connection",
"ConnectionManager",
]
-
diff --git a/pyTigerGraph/mcp/connection_manager.py b/pyTigerGraph/mcp/connection_manager.py
deleted file mode 100644
index f87c02dd..00000000
--- a/pyTigerGraph/mcp/connection_manager.py
+++ /dev/null
@@ -1,184 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""Connection manager for MCP server.
-
-Manages AsyncTigerGraphConnection instances for MCP tools.
-"""
-
-import os
-import logging
-from pathlib import Path
-from typing import Optional, Dict, Any
-from pyTigerGraph import AsyncTigerGraphConnection
-from pyTigerGraph.common.exception import TigerGraphException
-
-logger = logging.getLogger(__name__)
-
-# Try to load dotenv if available
-try:
- from dotenv import load_dotenv
- _dotenv_available = True
-except ImportError:
- _dotenv_available = False
-
-
-def _load_env_file(env_path: Optional[str] = None) -> None:
- """Load environment variables from .env file if available.
-
- Args:
- env_path: Optional path to .env file. If not provided, looks for .env in current directory.
- """
- if not _dotenv_available:
- return
-
- if env_path:
- env_file = Path(env_path).expanduser().resolve()
- else:
- # Look for .env in current directory and parent directories
- current_dir = Path.cwd()
- env_file = None
- for directory in [current_dir] + list(current_dir.parents):
- potential_env = directory / ".env"
- if potential_env.exists():
- env_file = potential_env
- break
-
- if env_file is None:
- # Also check in the directory where the script is running
- env_file = Path(".env")
-
- if env_file and env_file.exists():
- load_dotenv(env_file, override=False) # Don't override existing env vars
- logger.debug(f"Loaded environment variables from {env_file}")
- elif env_path:
- logger.warning(f"Specified .env file not found: {env_path}")
-
-
-class ConnectionManager:
- """Manages TigerGraph connections for MCP tools."""
-
- _default_connection: Optional[AsyncTigerGraphConnection] = None
-
- @classmethod
- def get_default_connection(cls) -> Optional[AsyncTigerGraphConnection]:
- """Get the default connection instance."""
- return cls._default_connection
-
- @classmethod
- def set_default_connection(cls, conn: AsyncTigerGraphConnection) -> None:
- """Set the default connection instance."""
- cls._default_connection = conn
-
- @classmethod
- def create_connection_from_env(cls, env_path: Optional[str] = None) -> AsyncTigerGraphConnection:
- """Create a connection from environment variables.
-
- Automatically loads variables from a .env file if it exists (requires python-dotenv).
- Environment variables take precedence over .env file values.
-
- Reads the following environment variables:
- - TG_HOST: TigerGraph host (default: http://127.0.0.1)
- - TG_GRAPHNAME: Graph name (optional - can be set later or use list_graphs tool)
- - TG_USERNAME: Username (default: tigergraph)
- - TG_PASSWORD: Password (default: tigergraph)
- - TG_SECRET: GSQL secret (optional)
- - TG_API_TOKEN: API token (optional)
- - TG_JWT_TOKEN: JWT token (optional)
- - TG_RESTPP_PORT: REST++ port (default: 9000)
- - TG_GS_PORT: GSQL port (default: 14240)
- - TG_SSL_PORT: SSL port (default: 443)
- - TG_TGCLOUD: Whether using TigerGraph Cloud (default: False)
- - TG_CERT_PATH: Path to certificate (optional)
-
- Args:
- env_path: Optional path to .env file. If not provided, searches for .env in current and parent directories.
- """
- # Load .env file if available
- _load_env_file(env_path)
-
- host = os.getenv("TG_HOST", "http://127.0.0.1")
- graphname = os.getenv("TG_GRAPHNAME", "") # Optional - can be empty
- username = os.getenv("TG_USERNAME", "tigergraph")
- password = os.getenv("TG_PASSWORD", "tigergraph")
- gsql_secret = os.getenv("TG_SECRET", "")
- api_token = os.getenv("TG_API_TOKEN", "")
- jwt_token = os.getenv("TG_JWT_TOKEN", "")
- restpp_port = os.getenv("TG_RESTPP_PORT", "9000")
- gs_port = os.getenv("TG_GS_PORT", "14240")
- ssl_port = os.getenv("TG_SSL_PORT", "443")
- tg_cloud = os.getenv("TG_TGCLOUD", "false").lower() == "true"
- cert_path = os.getenv("TG_CERT_PATH", None)
-
- # TG_GRAPHNAME is now optional - can be set later or use list_graphs tool
-
- conn = AsyncTigerGraphConnection(
- host=host,
- graphname=graphname,
- username=username,
- password=password,
- gsqlSecret=gsql_secret if gsql_secret else "",
- apiToken=api_token if api_token else "",
- jwtToken=jwt_token if jwt_token else "",
- restppPort=restpp_port,
- gsPort=gs_port,
- sslPort=ssl_port,
- tgCloud=tg_cloud,
- certPath=cert_path,
- )
-
- cls._default_connection = conn
- return conn
-
-
-def get_connection(
- graph_name: Optional[str] = None,
- connection_config: Optional[Dict[str, Any]] = None,
-) -> AsyncTigerGraphConnection:
- """Get or create an async TigerGraph connection.
-
- Args:
- graph_name: Name of the graph. If provided, will create a new connection.
- connection_config: Connection configuration dict. If provided, will create a new connection.
-
- Returns:
- AsyncTigerGraphConnection instance.
- """
- # If connection config is provided, create a new connection
- if connection_config:
- return AsyncTigerGraphConnection(
- host=connection_config.get("host", "http://127.0.0.1"),
- graphname=connection_config.get("graphname", graph_name or ""),
- username=connection_config.get("username", "tigergraph"),
- password=connection_config.get("password", "tigergraph"),
- gsqlSecret=connection_config.get("gsqlSecret", ""),
- apiToken=connection_config.get("apiToken", ""),
- jwtToken=connection_config.get("jwtToken", ""),
- restppPort=connection_config.get("restppPort", "9000"),
- gsPort=connection_config.get("gsPort", "14240"),
- sslPort=connection_config.get("sslPort", "443"),
- tgCloud=connection_config.get("tgCloud", False),
- certPath=connection_config.get("certPath", None),
- )
-
- # If graph_name is provided, try to get/create connection for that graph
- if graph_name:
- # For now, use default connection but set graphname
- conn = ConnectionManager.get_default_connection()
- if conn is None:
- conn = ConnectionManager.create_connection_from_env()
- # Update graphname if different
- if conn.graphname != graph_name:
- conn.graphname = graph_name
- return conn
-
- # Return default connection or create from env
- conn = ConnectionManager.get_default_connection()
- if conn is None:
- conn = ConnectionManager.create_connection_from_env()
- return conn
-
diff --git a/pyTigerGraph/mcp/main.py b/pyTigerGraph/mcp/main.py
deleted file mode 100644
index 75056af4..00000000
--- a/pyTigerGraph/mcp/main.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""Main entry point for TigerGraph MCP server."""
-
-import logging
-import sys
-import click
-import asyncio
-from pathlib import Path
-
-from .server import serve
-
-
-@click.command()
-@click.option("-v", "--verbose", count=True)
-@click.option("--env-file", type=click.Path(exists=True, path_type=Path), default=None,
- help="Path to .env file (default: searches for .env in current and parent directories)")
-def main(verbose: bool, env_file: Path = None) -> None:
- """TigerGraph MCP Server - TigerGraph functionality for MCP
-
- The server will automatically load environment variables from a .env file
- if python-dotenv is installed and a .env file is found.
- """
-
- logging_level = logging.WARN
- if verbose == 1:
- logging_level = logging.INFO
- elif verbose >= 2:
- logging_level = logging.DEBUG
-
- logging.basicConfig(level=logging_level, stream=sys.stderr)
-
- # Ensure mcp.server.lowlevel.server respects the WARNING level
- logging.getLogger('mcp.server.lowlevel.server').setLevel(logging.WARNING)
-
- # Load .env file (automatically searches if not specified)
- from .connection_manager import _load_env_file
- if env_file:
- _load_env_file(str(env_file))
- else:
- # Automatically search for .env file
- _load_env_file()
-
- asyncio.run(serve())
-
-
-if __name__ == "__main__":
- main()
-
diff --git a/pyTigerGraph/mcp/response_formatter.py b/pyTigerGraph/mcp/response_formatter.py
deleted file mode 100644
index 00b99f5e..00000000
--- a/pyTigerGraph/mcp/response_formatter.py
+++ /dev/null
@@ -1,315 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""Structured response formatting for MCP tools.
-
-This module provides utilities for creating consistent, LLM-friendly responses
-from MCP tools. It ensures responses are both machine-readable and human-friendly.
-"""
-
-import json
-from typing import Any, Dict, List, Optional
-from datetime import datetime
-from pydantic import BaseModel
-from mcp.types import TextContent
-
-
-class ToolResponse(BaseModel):
- """Structured response format for all MCP tools.
-
- This format provides:
- - Clear success/failure indication
- - Structured data for parsing
- - Human-readable summary
- - Contextual suggestions for next steps
- - Rich metadata
- """
- success: bool
- operation: str
- data: Optional[Dict[str, Any]] = None
- summary: str
- metadata: Optional[Dict[str, Any]] = None
- suggestions: Optional[List[str]] = None
- error: Optional[str] = None
- error_code: Optional[str] = None
- timestamp: str = None
-
- def __init__(self, **data):
- if 'timestamp' not in data:
- data['timestamp'] = datetime.utcnow().isoformat() + 'Z'
- super().__init__(**data)
-
-
-def format_response(
- success: bool,
- operation: str,
- summary: str,
- data: Optional[Dict[str, Any]] = None,
- suggestions: Optional[List[str]] = None,
- error: Optional[str] = None,
- error_code: Optional[str] = None,
- metadata: Optional[Dict[str, Any]] = None,
-) -> List[TextContent]:
- """Create a structured response for MCP tools.
-
- Args:
- success: Whether the operation succeeded
- operation: Name of the operation (tool name without prefix)
- summary: Human-readable summary message
- data: Structured result data
- suggestions: List of suggested next steps or actions
- error: Error message if success=False
- error_code: Optional error code for categorization
- metadata: Additional context (graph_name, timing, etc.)
-
- Returns:
- List of TextContent with both JSON and formatted text
-
- Example:
- >>> format_response(
- ... success=True,
- ... operation="add_node",
- ... summary="Node added successfully",
- ... data={"vertex_id": "user1", "vertex_type": "Person"},
- ... suggestions=["Use 'get_node' to verify", "Use 'add_edge' to connect"]
- ... )
- """
-
- response = ToolResponse(
- success=success,
- operation=operation,
- summary=summary,
- data=data,
- suggestions=suggestions,
- error=error,
- error_code=error_code,
- metadata=metadata
- )
-
- # Create structured JSON output
- json_output = response.model_dump_json(indent=2, exclude_none=True)
-
- # Create human-readable format
- text_parts = [f"**{summary}**"]
-
- # Add data section
- if data:
- text_parts.append(f"\n**Data:**\n```json\n{json.dumps(data, indent=2, default=str)}\n```")
-
- # Add suggestions
- if suggestions and len(suggestions) > 0:
- text_parts.append("\n**💡 Suggestions:**")
- for i, suggestion in enumerate(suggestions, 1):
- text_parts.append(f"{i}. {suggestion}")
-
- # Add error details
- if error:
- text_parts.append(f"\n**❌ Error Details:**\n{error}")
- if error_code:
- text_parts.append(f"\n**Error Code:** {error_code}")
-
- # Add metadata footer
- if metadata:
- text_parts.append(f"\n**Metadata:** {json.dumps(metadata, default=str)}")
-
- text_output = "\n".join(text_parts)
-
- # Combine both formats
- full_output = f"```json\n{json_output}\n```\n\n{text_output}"
-
- return [TextContent(type="text", text=full_output)]
-
-
-def format_success(
- operation: str,
- summary: str,
- data: Optional[Dict[str, Any]] = None,
- suggestions: Optional[List[str]] = None,
- metadata: Optional[Dict[str, Any]] = None,
-) -> List[TextContent]:
- """Convenience method for successful operations."""
- return format_response(
- success=True,
- operation=operation,
- summary=summary,
- data=data,
- suggestions=suggestions,
- metadata=metadata
- )
-
-
-def format_error(
- operation: str,
- error: Exception,
- context: Optional[Dict[str, Any]] = None,
- suggestions: Optional[List[str]] = None,
-) -> List[TextContent]:
- """Format an error response with contextual recovery hints.
-
- Args:
- operation: Name of the failed operation
- error: The exception that occurred
- context: Context information (parameters, state, etc.)
- suggestions: Optional manual suggestions (auto-generated if not provided)
-
- Returns:
- Formatted error response with recovery hints
- """
-
- error_str = str(error)
- error_lower = error_str.lower()
-
- # Auto-generate suggestions based on error type if not provided
- if suggestions is None:
- suggestions = []
-
- # Schema/type errors
- if any(term in error_lower for term in ["vertex type", "edge type", "type not found"]):
- suggestions.extend([
- "The specified type may not exist in the schema",
- "Call 'show_graph_details' to see available vertex and edge types",
- "Call 'list_graphs' to ensure you're using the correct graph"
- ])
-
- # Attribute errors
- elif any(term in error_lower for term in ["attribute", "column", "field"]):
- suggestions.extend([
- "One or more attributes may not match the schema definition",
- "Call 'show_graph_details' to see required attributes and their types",
- "Check that attribute names are spelled correctly"
- ])
-
- # Connection errors
- elif any(term in error_lower for term in ["connection", "timeout", "unreachable"]):
- suggestions.extend([
- "Unable to connect to TigerGraph server",
- "Verify TG_HOST environment variable is correct",
- "Check network connectivity and firewall settings",
- "Ensure TigerGraph server is running"
- ])
-
- # Authentication errors
- elif any(term in error_lower for term in ["auth", "token", "permission", "forbidden"]):
- suggestions.extend([
- "Authentication failed - check credentials",
- "Verify TG_USERNAME and TG_PASSWORD environment variables",
- "For TigerGraph Cloud, ensure TG_API_TOKEN is set",
- "Check if user has required permissions for this operation"
- ])
-
- # Query errors
- elif any(term in error_lower for term in ["syntax", "parse", "query"]):
- suggestions.extend([
- "Query syntax error detected",
- "For GSQL: Use 'INTERPRET QUERY () FOR GRAPH { ... }'",
- "For Cypher: Use 'INTERPRET OPENCYPHER QUERY () FOR GRAPH { ... }'",
- "Call 'show_graph_details' to understand the schema before writing queries"
- ])
-
- # Vector errors
- elif any(term in error_lower for term in ["vector", "dimension", "embedding"]):
- suggestions.extend([
- "Vector operation error",
- "Ensure vector dimensions match the attribute definition",
- "Call 'get_vector_index_status' to check if index is ready",
- "Verify vector attribute exists with 'show_graph_details'"
- ])
-
- # Generic suggestions
- if len(suggestions) == 0:
- suggestions.extend([
- "Check the error message for specific details",
- "Call 'show_graph_details' to understand the current graph structure",
- "Verify all required parameters are provided correctly"
- ])
-
- # Determine error code
- error_code = None
- if "connection" in error_lower or "timeout" in error_lower:
- error_code = "CONNECTION_ERROR"
- elif "auth" in error_lower or "permission" in error_lower:
- error_code = "AUTHENTICATION_ERROR"
- elif "type" in error_lower:
- error_code = "SCHEMA_ERROR"
- elif "attribute" in error_lower:
- error_code = "ATTRIBUTE_ERROR"
- elif "syntax" in error_lower or "parse" in error_lower:
- error_code = "SYNTAX_ERROR"
- else:
- error_code = "OPERATION_ERROR"
-
- return format_response(
- success=False,
- operation=operation,
- summary=f"❌ Failed to {operation.replace('_', ' ')}",
- error=error_str,
- error_code=error_code,
- metadata=context,
- suggestions=suggestions
- )
-
-
-def gsql_has_error(result_str: str) -> bool:
- """Check whether a GSQL result string indicates a failure.
-
- ``conn.gsql()`` does **not** raise an exception when a GSQL command fails;
- instead, the error message is returned as a plain string. This helper
- inspects the result for well-known error patterns so callers can
- distinguish success from failure.
- """
- error_patterns = [
- "Encountered \"",
- "SEMANTIC ERROR",
- "Syntax Error",
- "Failed to create",
- "does not exist",
- "is not a valid",
- "already exists",
- "Invalid syntax",
- ]
- return any(p in result_str for p in error_patterns)
-
-
-def format_list_response(
- operation: str,
- items: List[Any],
- item_type: str = "items",
- summary_template: Optional[str] = None,
- suggestions: Optional[List[str]] = None,
- metadata: Optional[Dict[str, Any]] = None,
-) -> List[TextContent]:
- """Format a response containing a list of items.
-
- Args:
- operation: Name of the operation
- items: List of items to return
- item_type: Type of items (for summary message)
- summary_template: Optional custom summary (use {count} and {type} placeholders)
- suggestions: Optional suggestions
- metadata: Optional metadata
-
- Returns:
- Formatted response
- """
-
- count = len(items)
-
- if summary_template:
- summary = summary_template.format(count=count, type=item_type)
- else:
- summary = f"✅ Found {count} {item_type}"
-
- return format_success(
- operation=operation,
- summary=summary,
- data={
- "count": count,
- item_type: items
- },
- suggestions=suggestions,
- metadata=metadata
- )
diff --git a/pyTigerGraph/mcp/server.py b/pyTigerGraph/mcp/server.py
deleted file mode 100644
index 21ed936c..00000000
--- a/pyTigerGraph/mcp/server.py
+++ /dev/null
@@ -1,273 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""MCP Server implementation for TigerGraph."""
-
-import logging
-from typing import Dict, List
-from mcp.server import Server
-from mcp.server.stdio import stdio_server
-from mcp.types import Tool, TextContent
-
-from .tool_names import TigerGraphToolName
-from pyTigerGraph.common.exception import TigerGraphException
-from .tools import (
- get_all_tools,
- # Global schema operations (database level)
- get_global_schema,
- # Graph operations (database level)
- list_graphs,
- create_graph,
- drop_graph,
- clear_graph_data,
- # Schema operations (graph level)
- get_graph_schema,
- show_graph_details,
- # Node tools
- add_node,
- add_nodes,
- get_node,
- get_nodes,
- delete_node,
- delete_nodes,
- has_node,
- get_node_edges,
- # Edge tools
- add_edge,
- add_edges,
- get_edge,
- get_edges,
- delete_edge,
- delete_edges,
- has_edge,
- # Query tools
- run_query,
- run_installed_query,
- install_query,
- drop_query,
- show_query,
- get_query_metadata,
- is_query_installed,
- get_neighbors,
- # Loading job tools
- create_loading_job,
- run_loading_job_with_file,
- run_loading_job_with_data,
- get_loading_jobs,
- get_loading_job_status,
- drop_loading_job,
- # Statistics tools
- get_vertex_count,
- get_edge_count,
- get_node_degree,
- # GSQL tools
- gsql,
- generate_gsql,
- generate_cypher,
- # Vector schema tools
- add_vector_attribute,
- drop_vector_attribute,
- list_vector_attributes,
- get_vector_index_status,
- # Vector data tools
- upsert_vectors,
- load_vectors_from_csv,
- load_vectors_from_json,
- search_top_k_similarity,
- fetch_vector,
- # Data Source tools
- create_data_source,
- update_data_source,
- get_data_source,
- drop_data_source,
- get_all_data_sources,
- drop_all_data_sources,
- preview_sample_data,
- # Discovery tools
- discover_tools,
- get_workflow,
- get_tool_info,
-)
-
-logger = logging.getLogger(__name__)
-
-
-class MCPServer:
- """MCP Server for TigerGraph."""
-
- def __init__(self, name: str = "TigerGraph-MCP"):
- """Initialize the MCP server."""
- self.server = Server(name)
- self._setup_handlers()
-
- def _setup_handlers(self):
- """Setup MCP server handlers."""
-
- @self.server.list_tools()
- async def list_tools() -> List[Tool]:
- """List all available tools."""
- return get_all_tools()
-
- @self.server.call_tool()
- async def call_tool(name: str, arguments: Dict) -> List[TextContent]:
- """Handle tool calls."""
- try:
- match name:
- # Global schema operations (database level)
- case TigerGraphToolName.GET_GLOBAL_SCHEMA:
- return await get_global_schema(**arguments)
- # Graph operations (database level)
- case TigerGraphToolName.LIST_GRAPHS:
- return await list_graphs(**arguments)
- case TigerGraphToolName.CREATE_GRAPH:
- return await create_graph(**arguments)
- case TigerGraphToolName.DROP_GRAPH:
- return await drop_graph(**arguments)
- case TigerGraphToolName.CLEAR_GRAPH_DATA:
- return await clear_graph_data(**arguments)
- # Schema operations (graph level)
- case TigerGraphToolName.GET_GRAPH_SCHEMA:
- return await get_graph_schema(**arguments)
- case TigerGraphToolName.SHOW_GRAPH_DETAILS:
- return await show_graph_details(**arguments)
- # Node operations
- case TigerGraphToolName.ADD_NODE:
- return await add_node(**arguments)
- case TigerGraphToolName.ADD_NODES:
- return await add_nodes(**arguments)
- case TigerGraphToolName.GET_NODE:
- return await get_node(**arguments)
- case TigerGraphToolName.GET_NODES:
- return await get_nodes(**arguments)
- case TigerGraphToolName.DELETE_NODE:
- return await delete_node(**arguments)
- case TigerGraphToolName.DELETE_NODES:
- return await delete_nodes(**arguments)
- case TigerGraphToolName.HAS_NODE:
- return await has_node(**arguments)
- case TigerGraphToolName.GET_NODE_EDGES:
- return await get_node_edges(**arguments)
- # Edge operations
- case TigerGraphToolName.ADD_EDGE:
- return await add_edge(**arguments)
- case TigerGraphToolName.ADD_EDGES:
- return await add_edges(**arguments)
- case TigerGraphToolName.GET_EDGE:
- return await get_edge(**arguments)
- case TigerGraphToolName.GET_EDGES:
- return await get_edges(**arguments)
- case TigerGraphToolName.DELETE_EDGE:
- return await delete_edge(**arguments)
- case TigerGraphToolName.DELETE_EDGES:
- return await delete_edges(**arguments)
- case TigerGraphToolName.HAS_EDGE:
- return await has_edge(**arguments)
- # Query operations
- case TigerGraphToolName.RUN_QUERY:
- return await run_query(**arguments)
- case TigerGraphToolName.RUN_INSTALLED_QUERY:
- return await run_installed_query(**arguments)
- case TigerGraphToolName.INSTALL_QUERY:
- return await install_query(**arguments)
- case TigerGraphToolName.DROP_QUERY:
- return await drop_query(**arguments)
- case TigerGraphToolName.SHOW_QUERY:
- return await show_query(**arguments)
- case TigerGraphToolName.GET_QUERY_METADATA:
- return await get_query_metadata(**arguments)
- case TigerGraphToolName.IS_QUERY_INSTALLED:
- return await is_query_installed(**arguments)
- case TigerGraphToolName.GET_NEIGHBORS:
- return await get_neighbors(**arguments)
- # Loading job operations
- case TigerGraphToolName.CREATE_LOADING_JOB:
- return await create_loading_job(**arguments)
- case TigerGraphToolName.RUN_LOADING_JOB_WITH_FILE:
- return await run_loading_job_with_file(**arguments)
- case TigerGraphToolName.RUN_LOADING_JOB_WITH_DATA:
- return await run_loading_job_with_data(**arguments)
- case TigerGraphToolName.GET_LOADING_JOBS:
- return await get_loading_jobs(**arguments)
- case TigerGraphToolName.GET_LOADING_JOB_STATUS:
- return await get_loading_job_status(**arguments)
- case TigerGraphToolName.DROP_LOADING_JOB:
- return await drop_loading_job(**arguments)
- # Statistics operations
- case TigerGraphToolName.GET_VERTEX_COUNT:
- return await get_vertex_count(**arguments)
- case TigerGraphToolName.GET_EDGE_COUNT:
- return await get_edge_count(**arguments)
- case TigerGraphToolName.GET_NODE_DEGREE:
- return await get_node_degree(**arguments)
- # GSQL operations
- case TigerGraphToolName.GSQL:
- return await gsql(**arguments)
- case TigerGraphToolName.GENERATE_GSQL:
- return await generate_gsql(**arguments)
- case TigerGraphToolName.GENERATE_CYPHER:
- return await generate_cypher(**arguments)
- # Vector schema operations
- case TigerGraphToolName.ADD_VECTOR_ATTRIBUTE:
- return await add_vector_attribute(**arguments)
- case TigerGraphToolName.DROP_VECTOR_ATTRIBUTE:
- return await drop_vector_attribute(**arguments)
- case TigerGraphToolName.LIST_VECTOR_ATTRIBUTES:
- return await list_vector_attributes(**arguments)
- case TigerGraphToolName.GET_VECTOR_INDEX_STATUS:
- return await get_vector_index_status(**arguments)
- # Vector data operations
- case TigerGraphToolName.UPSERT_VECTORS:
- return await upsert_vectors(**arguments)
- case TigerGraphToolName.LOAD_VECTORS_FROM_CSV:
- return await load_vectors_from_csv(**arguments)
- case TigerGraphToolName.LOAD_VECTORS_FROM_JSON:
- return await load_vectors_from_json(**arguments)
- case TigerGraphToolName.SEARCH_TOP_K_SIMILARITY:
- return await search_top_k_similarity(**arguments)
- case TigerGraphToolName.FETCH_VECTOR:
- return await fetch_vector(**arguments)
- # Data Source operations
- case TigerGraphToolName.CREATE_DATA_SOURCE:
- return await create_data_source(**arguments)
- case TigerGraphToolName.UPDATE_DATA_SOURCE:
- return await update_data_source(**arguments)
- case TigerGraphToolName.GET_DATA_SOURCE:
- return await get_data_source(**arguments)
- case TigerGraphToolName.DROP_DATA_SOURCE:
- return await drop_data_source(**arguments)
- case TigerGraphToolName.GET_ALL_DATA_SOURCES:
- return await get_all_data_sources(**arguments)
- case TigerGraphToolName.DROP_ALL_DATA_SOURCES:
- return await drop_all_data_sources(**arguments)
- case TigerGraphToolName.PREVIEW_SAMPLE_DATA:
- return await preview_sample_data(**arguments)
- # Discovery operations
- case TigerGraphToolName.DISCOVER_TOOLS:
- return await discover_tools(**arguments)
- case TigerGraphToolName.GET_WORKFLOW:
- return await get_workflow(**arguments)
- case TigerGraphToolName.GET_TOOL_INFO:
- return await get_tool_info(**arguments)
- case _:
- raise ValueError(f"Unknown tool: {name}")
- except TigerGraphException as e:
- logger.exception("Error in tool execution")
- error_msg = e.message if hasattr(e, 'message') else str(e)
- error_code = f" (Code: {e.code})" if hasattr(e, 'code') and e.code else ""
- return [TextContent(type="text", text=f"❌ TigerGraph Error{error_code} due to: {error_msg}")]
- except Exception as e:
- logger.exception("Error in tool execution")
- return [TextContent(type="text", text=f"❌ Error due to: {str(e)}")]
-
-
-async def serve() -> None:
- """Serve the MCP server."""
- server = MCPServer()
- options = server.server.create_initialization_options()
- async with stdio_server() as (read_stream, write_stream):
- await server.server.run(read_stream, write_stream, options, raise_exceptions=True)
-
diff --git a/pyTigerGraph/mcp/tool_metadata.py b/pyTigerGraph/mcp/tool_metadata.py
deleted file mode 100644
index 409e5ddb..00000000
--- a/pyTigerGraph/mcp/tool_metadata.py
+++ /dev/null
@@ -1,528 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""Tool metadata for enhanced LLM guidance."""
-
-from typing import List, Dict, Any, Optional
-from pydantic import BaseModel
-from enum import Enum
-
-
-class ToolCategory(str, Enum):
- """Categories for organizing tools."""
- SCHEMA = "schema"
- DATA = "data"
- QUERY = "query"
- VECTOR = "vector"
- LOADING = "loading"
- DISCOVERY = "discovery"
- UTILITY = "utility"
-
-
-class ToolMetadata(BaseModel):
- """Enhanced metadata for tools to help LLMs understand usage patterns."""
- category: ToolCategory
- prerequisites: List[str] = []
- related_tools: List[str] = []
- common_next_steps: List[str] = []
- use_cases: List[str] = []
- complexity: str = "basic" # basic, intermediate, advanced
- examples: List[Dict[str, Any]] = []
- keywords: List[str] = [] # For discovery
-
-
-# Define metadata for each tool
-TOOL_METADATA: Dict[str, ToolMetadata] = {
- # Schema Operations
- "tigergraph__show_graph_details": ToolMetadata(
- category=ToolCategory.SCHEMA,
- prerequisites=[],
- related_tools=["tigergraph__get_graph_schema"],
- common_next_steps=["tigergraph__add_node", "tigergraph__add_edge", "tigergraph__run_query"],
- use_cases=[
- "Getting a full listing of a graph (schema, queries, jobs)",
- "Understanding the structure of a graph before writing queries",
- "Discovering available vertex and edge types",
- "First step in any graph interaction workflow"
- ],
- complexity="basic",
- keywords=["schema", "structure", "show", "understand", "explore", "queries", "jobs"],
- examples=[
- {
- "description": "Show everything under default graph",
- "parameters": {}
- },
- {
- "description": "Show everything under a specific graph",
- "parameters": {"graph_name": "SocialGraph"}
- }
- ]
- ),
-
- "tigergraph__list_graphs": ToolMetadata(
- category=ToolCategory.SCHEMA,
- prerequisites=[],
- related_tools=["tigergraph__show_graph_details", "tigergraph__create_graph"],
- common_next_steps=["tigergraph__show_graph_details"],
- use_cases=[
- "Discovering what graphs exist in the database",
- "First step when connecting to a new TigerGraph instance",
- "Verifying a graph was created successfully"
- ],
- complexity="basic",
- keywords=["list", "graphs", "discover", "available"],
- examples=[{"description": "List all graphs", "parameters": {}}]
- ),
-
- "tigergraph__create_graph": ToolMetadata(
- category=ToolCategory.SCHEMA,
- prerequisites=[],
- related_tools=["tigergraph__list_graphs", "tigergraph__show_graph_details"],
- common_next_steps=["tigergraph__show_graph_details", "tigergraph__add_node"],
- use_cases=[
- "Creating a new graph from scratch",
- "Setting up a graph with specific vertex and edge types",
- "Initializing a new project or data model"
- ],
- complexity="intermediate",
- keywords=["create", "new", "graph", "initialize", "setup"],
- examples=[
- {
- "description": "Create a social network graph",
- "parameters": {
- "graph_name": "SocialGraph",
- "vertex_types": [
- {
- "name": "Person",
- "attributes": [
- {"name": "name", "type": "STRING"},
- {"name": "age", "type": "INT"}
- ]
- }
- ],
- "edge_types": [
- {
- "name": "FOLLOWS",
- "from_vertex": "Person",
- "to_vertex": "Person"
- }
- ]
- }
- }
- ]
- ),
-
- "tigergraph__get_graph_schema": ToolMetadata(
- category=ToolCategory.SCHEMA,
- prerequisites=[],
- related_tools=["tigergraph__show_graph_details"],
- common_next_steps=["tigergraph__add_node", "tigergraph__run_query"],
- use_cases=[
- "Getting raw JSON schema for programmatic processing",
- "Detailed schema inspection for advanced use cases"
- ],
- complexity="intermediate",
- keywords=["schema", "json", "raw", "detailed"],
- examples=[{"description": "Get raw schema", "parameters": {}}]
- ),
-
- # Node Operations
- "tigergraph__add_node": ToolMetadata(
- category=ToolCategory.DATA,
- prerequisites=["tigergraph__show_graph_details"],
- related_tools=["tigergraph__add_nodes", "tigergraph__get_node", "tigergraph__delete_node"],
- common_next_steps=["tigergraph__get_node", "tigergraph__add_edge", "tigergraph__get_node_edges"],
- use_cases=[
- "Creating a single vertex in the graph",
- "Updating an existing vertex's attributes",
- "Adding individual entities (users, products, etc.)"
- ],
- complexity="basic",
- keywords=["add", "create", "insert", "node", "vertex", "single"],
- examples=[
- {
- "description": "Add a person node",
- "parameters": {
- "vertex_type": "Person",
- "vertex_id": "user123",
- "attributes": {"name": "Alice", "age": 30, "city": "San Francisco"}
- }
- },
- {
- "description": "Add a product node",
- "parameters": {
- "vertex_type": "Product",
- "vertex_id": "prod456",
- "attributes": {"name": "Laptop", "price": 999.99, "category": "Electronics"}
- }
- }
- ]
- ),
-
- "tigergraph__add_nodes": ToolMetadata(
- category=ToolCategory.DATA,
- prerequisites=["tigergraph__show_graph_details"],
- related_tools=["tigergraph__add_node", "tigergraph__get_nodes"],
- common_next_steps=["tigergraph__get_vertex_count", "tigergraph__add_edges"],
- use_cases=[
- "Batch loading multiple vertices efficiently",
- "Importing data from CSV or JSON",
- "Initial data population"
- ],
- complexity="basic",
- keywords=["add", "create", "insert", "batch", "multiple", "bulk", "nodes", "vertices"],
- examples=[
- {
- "description": "Add multiple person nodes",
- "parameters": {
- "vertex_type": "Person",
- "vertices": [
- {"id": "user1", "name": "Alice", "age": 30},
- {"id": "user2", "name": "Bob", "age": 25},
- {"id": "user3", "name": "Carol", "age": 35}
- ]
- }
- }
- ]
- ),
-
- "tigergraph__get_node": ToolMetadata(
- category=ToolCategory.DATA,
- prerequisites=[],
- related_tools=["tigergraph__get_nodes", "tigergraph__has_node"],
- common_next_steps=["tigergraph__get_node_edges", "tigergraph__delete_node"],
- use_cases=[
- "Retrieving a specific vertex by ID",
- "Verifying a vertex was created",
- "Checking vertex attributes"
- ],
- complexity="basic",
- keywords=["get", "retrieve", "fetch", "read", "node", "vertex", "single"],
- examples=[
- {
- "description": "Get a person node",
- "parameters": {
- "vertex_type": "Person",
- "vertex_id": "user123"
- }
- }
- ]
- ),
-
- "tigergraph__get_nodes": ToolMetadata(
- category=ToolCategory.DATA,
- prerequisites=[],
- related_tools=["tigergraph__get_node", "tigergraph__get_vertex_count"],
- common_next_steps=["tigergraph__get_edges"],
- use_cases=[
- "Retrieving multiple vertices of a type",
- "Exploring graph data",
- "Data export and analysis"
- ],
- complexity="basic",
- keywords=["get", "retrieve", "fetch", "list", "multiple", "nodes", "vertices"],
- examples=[
- {
- "description": "Get all person nodes (limited)",
- "parameters": {
- "vertex_type": "Person",
- "limit": 100
- }
- }
- ]
- ),
-
- # Edge Operations
- "tigergraph__add_edge": ToolMetadata(
- category=ToolCategory.DATA,
- prerequisites=["tigergraph__add_node", "tigergraph__show_graph_details"],
- related_tools=["tigergraph__add_edges", "tigergraph__get_edge"],
- common_next_steps=["tigergraph__get_node_edges", "tigergraph__get_neighbors"],
- use_cases=[
- "Creating a relationship between two vertices",
- "Connecting entities in the graph",
- "Building graph structure"
- ],
- complexity="basic",
- keywords=["add", "create", "connect", "relationship", "edge", "link"],
- examples=[
- {
- "description": "Create a friendship edge",
- "parameters": {
- "edge_type": "FOLLOWS",
- "from_vertex_type": "Person",
- "from_vertex_id": "user1",
- "to_vertex_type": "Person",
- "to_vertex_id": "user2",
- "attributes": {"since": "2024-01-15"}
- }
- }
- ]
- ),
-
- "tigergraph__add_edges": ToolMetadata(
- category=ToolCategory.DATA,
- prerequisites=["tigergraph__add_nodes", "tigergraph__show_graph_details"],
- related_tools=["tigergraph__add_edge"],
- common_next_steps=["tigergraph__get_edge_count"],
- use_cases=[
- "Batch loading multiple edges",
- "Building graph structure efficiently",
- "Importing relationship data"
- ],
- complexity="basic",
- keywords=["add", "create", "batch", "multiple", "edges", "relationships", "bulk"],
- examples=[]
- ),
-
- # Query Operations
- "tigergraph__run_query": ToolMetadata(
- category=ToolCategory.QUERY,
- prerequisites=["tigergraph__show_graph_details"],
- related_tools=["tigergraph__run_installed_query", "tigergraph__get_neighbors"],
- common_next_steps=[],
- use_cases=[
- "Ad-hoc querying without installing",
- "Testing queries before installation",
- "Simple data retrieval operations",
- "Running openCypher or GSQL queries"
- ],
- complexity="intermediate",
- keywords=["query", "search", "find", "select", "interpret", "gsql", "cypher"],
- examples=[
- {
- "description": "Simple GSQL query",
- "parameters": {
- "query_text": "INTERPRET QUERY () FOR GRAPH MyGraph { SELECT v FROM Person:v LIMIT 5; PRINT v; }"
- }
- },
- {
- "description": "openCypher query",
- "parameters": {
- "query_text": "INTERPRET OPENCYPHER QUERY () FOR GRAPH MyGraph { MATCH (n:Person) RETURN n LIMIT 5 }"
- }
- }
- ]
- ),
-
- "tigergraph__get_neighbors": ToolMetadata(
- category=ToolCategory.QUERY,
- prerequisites=[],
- related_tools=["tigergraph__get_node_edges", "tigergraph__run_query"],
- common_next_steps=[],
- use_cases=[
- "Finding vertices connected to a given vertex",
- "1-hop graph traversal",
- "Discovering relationships"
- ],
- complexity="basic",
- keywords=["neighbors", "connected", "adjacent", "traverse", "related"],
- examples=[
- {
- "description": "Get friends of a person",
- "parameters": {
- "vertex_type": "Person",
- "vertex_id": "user1",
- "edge_type": "FOLLOWS"
- }
- }
- ]
- ),
-
- # Vector Operations
- "tigergraph__add_vector_attribute": ToolMetadata(
- category=ToolCategory.VECTOR,
- prerequisites=["tigergraph__show_graph_details"],
- related_tools=["tigergraph__drop_vector_attribute", "tigergraph__get_vector_index_status"],
- common_next_steps=["tigergraph__get_vector_index_status", "tigergraph__upsert_vectors"],
- use_cases=[
- "Adding vector/embedding support to existing vertex types",
- "Setting up semantic search capabilities",
- "Enabling similarity-based queries"
- ],
- complexity="intermediate",
- keywords=["vector", "embedding", "add", "attribute", "similarity", "semantic"],
- examples=[
- {
- "description": "Add embedding attribute for documents",
- "parameters": {
- "vertex_type": "Document",
- "vector_name": "embedding",
- "dimension": 384,
- "metric": "COSINE"
- }
- },
- {
- "description": "Add embedding for products (higher dimension)",
- "parameters": {
- "vertex_type": "Product",
- "vector_name": "feature_vector",
- "dimension": 1536,
- "metric": "L2"
- }
- }
- ]
- ),
-
- "tigergraph__upsert_vectors": ToolMetadata(
- category=ToolCategory.VECTOR,
- prerequisites=["tigergraph__add_vector_attribute", "tigergraph__get_vector_index_status"],
- related_tools=["tigergraph__search_top_k_similarity", "tigergraph__fetch_vector"],
- common_next_steps=["tigergraph__get_vector_index_status", "tigergraph__search_top_k_similarity"],
- use_cases=[
- "Loading embedding vectors into the graph",
- "Updating vector data for vertices",
- "Populating semantic search index"
- ],
- complexity="intermediate",
- keywords=["vector", "embedding", "upsert", "load", "insert", "update"],
- examples=[
- {
- "description": "Upsert document embeddings",
- "parameters": {
- "vertex_type": "Document",
- "vector_attribute": "embedding",
- "vectors": [
- {
- "vertex_id": "doc1",
- "vector": [0.1, 0.2, 0.3],
- "attributes": {"title": "Document 1"}
- }
- ]
- }
- }
- ]
- ),
-
- "tigergraph__search_top_k_similarity": ToolMetadata(
- category=ToolCategory.VECTOR,
- prerequisites=["tigergraph__upsert_vectors", "tigergraph__get_vector_index_status"],
- related_tools=["tigergraph__fetch_vector"],
- common_next_steps=[],
- use_cases=[
- "Finding similar documents or items",
- "Semantic search operations",
- "Recommendation based on similarity"
- ],
- complexity="intermediate",
- keywords=["vector", "search", "similarity", "nearest", "semantic", "find", "similar"],
- examples=[
- {
- "description": "Find similar documents",
- "parameters": {
- "vertex_type": "Document",
- "vector_attribute": "embedding",
- "query_vector": [0.1, 0.2, 0.3],
- "top_k": 10
- }
- }
- ]
- ),
-
- # Loading Operations
- "tigergraph__create_loading_job": ToolMetadata(
- category=ToolCategory.LOADING,
- prerequisites=["tigergraph__show_graph_details"],
- related_tools=["tigergraph__run_loading_job_with_file", "tigergraph__run_loading_job_with_data"],
- common_next_steps=["tigergraph__run_loading_job_with_file", "tigergraph__get_loading_jobs"],
- use_cases=[
- "Setting up data ingestion from CSV/JSON files",
- "Defining how file columns map to vertex/edge attributes",
- "Preparing for bulk data loading"
- ],
- complexity="advanced",
- keywords=["loading", "job", "create", "define", "ingest", "import"],
- examples=[]
- ),
-
- "tigergraph__run_loading_job_with_file": ToolMetadata(
- category=ToolCategory.LOADING,
- prerequisites=["tigergraph__create_loading_job"],
- related_tools=["tigergraph__run_loading_job_with_data", "tigergraph__get_loading_job_status"],
- common_next_steps=["tigergraph__get_loading_job_status", "tigergraph__get_vertex_count"],
- use_cases=[
- "Loading data from CSV or JSON files",
- "Bulk import of graph data",
- "ETL operations"
- ],
- complexity="intermediate",
- keywords=["loading", "job", "run", "file", "import", "bulk"],
- examples=[]
- ),
-
- # Statistics
- "tigergraph__get_vertex_count": ToolMetadata(
- category=ToolCategory.UTILITY,
- prerequisites=[],
- related_tools=["tigergraph__get_edge_count", "tigergraph__get_nodes"],
- common_next_steps=[],
- use_cases=[
- "Verifying data was loaded",
- "Monitoring graph size",
- "Data validation"
- ],
- complexity="basic",
- keywords=["count", "statistics", "size", "vertex", "node", "total"],
- examples=[
- {
- "description": "Count all vertices",
- "parameters": {}
- },
- {
- "description": "Count specific vertex type",
- "parameters": {"vertex_type": "Person"}
- }
- ]
- ),
-
- "tigergraph__get_edge_count": ToolMetadata(
- category=ToolCategory.UTILITY,
- prerequisites=[],
- related_tools=["tigergraph__get_vertex_count"],
- common_next_steps=[],
- use_cases=[
- "Verifying relationships were created",
- "Monitoring graph connectivity",
- "Data validation"
- ],
- complexity="basic",
- keywords=["count", "statistics", "size", "edge", "relationship", "total"],
- examples=[]
- ),
-}
-
-
-def get_tool_metadata(tool_name: str) -> Optional[ToolMetadata]:
- """Get metadata for a specific tool."""
- return TOOL_METADATA.get(tool_name)
-
-
-def get_tools_by_category(category: ToolCategory) -> List[str]:
- """Get all tool names in a specific category."""
- return [
- tool_name for tool_name, metadata in TOOL_METADATA.items()
- if metadata.category == category
- ]
-
-
-def search_tools_by_keywords(keywords: List[str]) -> List[str]:
- """Search for tools matching any of the provided keywords."""
- matching_tools = []
- keywords_lower = [k.lower() for k in keywords]
-
- for tool_name, metadata in TOOL_METADATA.items():
- # Check if any keyword matches
- for keyword in keywords_lower:
- if any(keyword in mk.lower() for mk in metadata.keywords):
- matching_tools.append(tool_name)
- break
- # Also check in use cases
- if any(keyword in uc.lower() for uc in metadata.use_cases):
- matching_tools.append(tool_name)
- break
-
- return matching_tools
diff --git a/pyTigerGraph/mcp/tool_names.py b/pyTigerGraph/mcp/tool_names.py
deleted file mode 100644
index 2ca7ee81..00000000
--- a/pyTigerGraph/mcp/tool_names.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""Tool names for TigerGraph MCP tools."""
-
-from enum import Enum
-
-
-class TigerGraphToolName(str, Enum):
- """Enumeration of all available TigerGraph MCP tool names."""
-
- # Global Schema Operations (Database level - operates on global schema)
- GET_GLOBAL_SCHEMA = "tigergraph__get_global_schema"
-
- # Graph Operations (Database level - operates on graphs within the database)
- LIST_GRAPHS = "tigergraph__list_graphs"
- CREATE_GRAPH = "tigergraph__create_graph"
- DROP_GRAPH = "tigergraph__drop_graph"
- CLEAR_GRAPH_DATA = "tigergraph__clear_graph_data"
-
- # Schema Operations (Graph level - operates on schema within a specific graph)
- GET_GRAPH_SCHEMA = "tigergraph__get_graph_schema"
- SHOW_GRAPH_DETAILS = "tigergraph__show_graph_details"
-
- # Node Operations
- ADD_NODE = "tigergraph__add_node"
- ADD_NODES = "tigergraph__add_nodes"
- GET_NODE = "tigergraph__get_node"
- GET_NODES = "tigergraph__get_nodes"
- DELETE_NODE = "tigergraph__delete_node"
- DELETE_NODES = "tigergraph__delete_nodes"
- HAS_NODE = "tigergraph__has_node"
- GET_NODE_EDGES = "tigergraph__get_node_edges"
-
- # Edge Operations
- ADD_EDGE = "tigergraph__add_edge"
- ADD_EDGES = "tigergraph__add_edges"
- GET_EDGE = "tigergraph__get_edge"
- GET_EDGES = "tigergraph__get_edges"
- DELETE_EDGE = "tigergraph__delete_edge"
- DELETE_EDGES = "tigergraph__delete_edges"
- HAS_EDGE = "tigergraph__has_edge"
-
- # Query Operations
- RUN_QUERY = "tigergraph__run_query"
- RUN_INSTALLED_QUERY = "tigergraph__run_installed_query"
- INSTALL_QUERY = "tigergraph__install_query"
- DROP_QUERY = "tigergraph__drop_query"
- SHOW_QUERY = "tigergraph__show_query"
- GET_QUERY_METADATA = "tigergraph__get_query_metadata"
- IS_QUERY_INSTALLED = "tigergraph__is_query_installed"
- GET_NEIGHBORS = "tigergraph__get_neighbors"
-
- # Loading Job Operations
- CREATE_LOADING_JOB = "tigergraph__create_loading_job"
- RUN_LOADING_JOB_WITH_FILE = "tigergraph__run_loading_job_with_file"
- RUN_LOADING_JOB_WITH_DATA = "tigergraph__run_loading_job_with_data"
- GET_LOADING_JOBS = "tigergraph__get_loading_jobs"
- GET_LOADING_JOB_STATUS = "tigergraph__get_loading_job_status"
- DROP_LOADING_JOB = "tigergraph__drop_loading_job"
-
- # Statistics
- GET_VERTEX_COUNT = "tigergraph__get_vertex_count"
- GET_EDGE_COUNT = "tigergraph__get_edge_count"
- GET_NODE_DEGREE = "tigergraph__get_node_degree"
-
- # GSQL Operations
- GSQL = "tigergraph__gsql"
- GENERATE_GSQL = "tigergraph__generate_gsql"
- GENERATE_CYPHER = "tigergraph__generate_cypher"
-
- # Vector Schema Operations
- ADD_VECTOR_ATTRIBUTE = "tigergraph__add_vector_attribute"
- DROP_VECTOR_ATTRIBUTE = "tigergraph__drop_vector_attribute"
- LIST_VECTOR_ATTRIBUTES = "tigergraph__list_vector_attributes"
- GET_VECTOR_INDEX_STATUS = "tigergraph__get_vector_index_status"
-
- # Vector Data Operations
- UPSERT_VECTORS = "tigergraph__upsert_vectors"
- LOAD_VECTORS_FROM_CSV = "tigergraph__load_vectors_from_csv"
- LOAD_VECTORS_FROM_JSON = "tigergraph__load_vectors_from_json"
- SEARCH_TOP_K_SIMILARITY = "tigergraph__search_top_k_similarity"
- FETCH_VECTOR = "tigergraph__fetch_vector"
-
- # Data Source Operations
- CREATE_DATA_SOURCE = "tigergraph__create_data_source"
- UPDATE_DATA_SOURCE = "tigergraph__update_data_source"
- GET_DATA_SOURCE = "tigergraph__get_data_source"
- DROP_DATA_SOURCE = "tigergraph__drop_data_source"
- GET_ALL_DATA_SOURCES = "tigergraph__get_all_data_sources"
- DROP_ALL_DATA_SOURCES = "tigergraph__drop_all_data_sources"
- PREVIEW_SAMPLE_DATA = "tigergraph__preview_sample_data"
-
- # Discovery and Navigation Operations
- DISCOVER_TOOLS = "tigergraph__discover_tools"
- GET_WORKFLOW = "tigergraph__get_workflow"
- GET_TOOL_INFO = "tigergraph__get_tool_info"
-
- @classmethod
- def from_value(cls, value: str) -> "TigerGraphToolName":
- """Get enum from string value."""
- for tool in cls:
- if tool.value == value:
- return tool
- raise ValueError(f"Unknown tool name: {value}")
-
diff --git a/pyTigerGraph/mcp/tools/__init__.py b/pyTigerGraph/mcp/tools/__init__.py
deleted file mode 100644
index 3922142e..00000000
--- a/pyTigerGraph/mcp/tools/__init__.py
+++ /dev/null
@@ -1,298 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""MCP tools for TigerGraph."""
-
-from .schema_tools import (
- # Global schema operations (database level)
- get_global_schema_tool,
- get_global_schema,
- # Graph operations (database level)
- list_graphs_tool,
- create_graph_tool,
- drop_graph_tool,
- clear_graph_data_tool,
- list_graphs,
- create_graph,
- drop_graph,
- clear_graph_data,
- # Schema operations (graph level)
- get_graph_schema_tool,
- show_graph_details_tool,
- get_graph_schema,
- show_graph_details,
-)
-from .node_tools import (
- add_node_tool,
- add_nodes_tool,
- get_node_tool,
- get_nodes_tool,
- delete_node_tool,
- delete_nodes_tool,
- has_node_tool,
- get_node_edges_tool,
- add_node,
- add_nodes,
- get_node,
- get_nodes,
- delete_node,
- delete_nodes,
- has_node,
- get_node_edges,
-)
-from .edge_tools import (
- add_edge_tool,
- add_edges_tool,
- get_edge_tool,
- get_edges_tool,
- delete_edge_tool,
- delete_edges_tool,
- has_edge_tool,
- add_edge,
- add_edges,
- get_edge,
- get_edges,
- delete_edge,
- delete_edges,
- has_edge,
-)
-from .query_tools import (
- run_query_tool,
- run_installed_query_tool,
- install_query_tool,
- drop_query_tool,
- show_query_tool,
- get_query_metadata_tool,
- is_query_installed_tool,
- get_neighbors_tool,
- run_query,
- run_installed_query,
- install_query,
- drop_query,
- show_query,
- get_query_metadata,
- is_query_installed,
- get_neighbors,
-)
-from .data_tools import (
- create_loading_job_tool,
- run_loading_job_with_file_tool,
- run_loading_job_with_data_tool,
- get_loading_jobs_tool,
- get_loading_job_status_tool,
- drop_loading_job_tool,
- create_loading_job,
- run_loading_job_with_file,
- run_loading_job_with_data,
- get_loading_jobs,
- get_loading_job_status,
- drop_loading_job,
-)
-from .statistics_tools import (
- get_vertex_count_tool,
- get_edge_count_tool,
- get_node_degree_tool,
- get_vertex_count,
- get_edge_count,
- get_node_degree,
-)
-from .gsql_tools import (
- gsql_tool,
- gsql,
- generate_gsql_query_tool,
- generate_gsql,
- generate_cypher_query_tool,
- generate_cypher,
-)
-from .vector_tools import (
- # Vector schema tools
- add_vector_attribute_tool,
- drop_vector_attribute_tool,
- list_vector_attributes_tool,
- get_vector_index_status_tool,
- add_vector_attribute,
- drop_vector_attribute,
- list_vector_attributes,
- get_vector_index_status,
- # Vector data tools
- upsert_vectors_tool,
- load_vectors_from_csv_tool,
- load_vectors_from_json_tool,
- search_top_k_similarity_tool,
- fetch_vector_tool,
- upsert_vectors,
- load_vectors_from_csv,
- load_vectors_from_json,
- search_top_k_similarity,
- fetch_vector,
-)
-from .datasource_tools import (
- create_data_source_tool,
- update_data_source_tool,
- get_data_source_tool,
- drop_data_source_tool,
- get_all_data_sources_tool,
- drop_all_data_sources_tool,
- preview_sample_data_tool,
- create_data_source,
- update_data_source,
- get_data_source,
- drop_data_source,
- get_all_data_sources,
- drop_all_data_sources,
- preview_sample_data,
-)
-from .discovery_tools import (
- discover_tools_tool,
- get_workflow_tool,
- get_tool_info_tool,
- discover_tools,
- get_workflow,
- get_tool_info,
-)
-from .tool_registry import get_all_tools
-
-__all__ = [
- # Global schema operations (database level)
- "get_global_schema_tool",
- "get_global_schema",
- # Graph operations (database level)
- "list_graphs_tool",
- "create_graph_tool",
- "drop_graph_tool",
- "clear_graph_data_tool",
- "list_graphs",
- "create_graph",
- "drop_graph",
- "clear_graph_data",
- # Schema operations (graph level)
- "get_graph_schema_tool",
- "show_graph_details_tool",
- "get_graph_schema",
- "show_graph_details",
- # Node tools
- "add_node_tool",
- "add_nodes_tool",
- "get_node_tool",
- "get_nodes_tool",
- "delete_node_tool",
- "delete_nodes_tool",
- "has_node_tool",
- "get_node_edges_tool",
- "add_node",
- "add_nodes",
- "get_node",
- "get_nodes",
- "delete_node",
- "delete_nodes",
- "has_node",
- "get_node_edges",
- # Edge tools
- "add_edge_tool",
- "add_edges_tool",
- "get_edge_tool",
- "get_edges_tool",
- "delete_edge_tool",
- "delete_edges_tool",
- "has_edge_tool",
- "add_edge",
- "add_edges",
- "get_edge",
- "get_edges",
- "delete_edge",
- "delete_edges",
- "has_edge",
- # Query tools
- "run_query_tool",
- "run_installed_query_tool",
- "install_query_tool",
- "drop_query_tool",
- "show_query_tool",
- "get_query_metadata_tool",
- "is_query_installed_tool",
- "get_neighbors_tool",
- "run_query",
- "run_installed_query",
- "install_query",
- "drop_query",
- "show_query",
- "get_query_metadata",
- "is_query_installed",
- "get_neighbors",
- # Loading job tools
- "create_loading_job_tool",
- "run_loading_job_with_file_tool",
- "run_loading_job_with_data_tool",
- "get_loading_jobs_tool",
- "get_loading_job_status_tool",
- "drop_loading_job_tool",
- "create_loading_job",
- "run_loading_job_with_file",
- "run_loading_job_with_data",
- "get_loading_jobs",
- "get_loading_job_status",
- "drop_loading_job",
- # Statistics tools
- "get_vertex_count_tool",
- "get_edge_count_tool",
- "get_node_degree_tool",
- "get_vertex_count",
- "get_edge_count",
- "get_node_degree",
- # GSQL tools
- "gsql_tool",
- "gsql",
- "generate_gsql_query_tool",
- "generate_gsql",
- "generate_cypher_query_tool",
- "generate_cypher",
- # Vector schema tools
- "add_vector_attribute_tool",
- "drop_vector_attribute_tool",
- "list_vector_attributes_tool",
- "get_vector_index_status_tool",
- "add_vector_attribute",
- "drop_vector_attribute",
- "list_vector_attributes",
- "get_vector_index_status",
- # Vector data tools
- "upsert_vectors_tool",
- "load_vectors_from_csv_tool",
- "load_vectors_from_json_tool",
- "search_top_k_similarity_tool",
- "fetch_vector_tool",
- "upsert_vectors",
- "load_vectors_from_csv",
- "load_vectors_from_json",
- "search_top_k_similarity",
- "fetch_vector",
- # Data Source tools
- "create_data_source_tool",
- "update_data_source_tool",
- "get_data_source_tool",
- "drop_data_source_tool",
- "get_all_data_sources_tool",
- "drop_all_data_sources_tool",
- "preview_sample_data_tool",
- "create_data_source",
- "update_data_source",
- "get_data_source",
- "drop_data_source",
- "get_all_data_sources",
- "drop_all_data_sources",
- "preview_sample_data",
- # Discovery tools
- "discover_tools_tool",
- "get_workflow_tool",
- "get_tool_info_tool",
- "discover_tools",
- "get_workflow",
- "get_tool_info",
- # Registry
- "get_all_tools",
-]
-
diff --git a/pyTigerGraph/mcp/tools/data_tools.py b/pyTigerGraph/mcp/tools/data_tools.py
deleted file mode 100644
index 77b589c1..00000000
--- a/pyTigerGraph/mcp/tools/data_tools.py
+++ /dev/null
@@ -1,626 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""Data loading tools for MCP.
-
-These tools use the non-deprecated loading job APIs:
-- createLoadingJob - Create a loading job from structured config or GSQL
-- runLoadingJobWithFile - Execute loading job with a file
-- runLoadingJobWithData - Execute loading job with data string
-- getLoadingJobs - List all loading jobs
-- getLoadingJobStatus - Get status of a loading job
-- dropLoadingJob - Drop a loading job
-"""
-
-import json
-from typing import List, Optional, Dict, Any, Union
-from pydantic import BaseModel, Field
-from mcp.types import Tool, TextContent
-
-from ..tool_names import TigerGraphToolName
-from ..connection_manager import get_connection
-from ..response_formatter import format_success, format_error, gsql_has_error
-from pyTigerGraph.common.exception import TigerGraphException
-
-
-# =============================================================================
-# Input Models for Loading Job Configuration
-# =============================================================================
-
-class NodeMapping(BaseModel):
- """Mapping configuration for loading vertices."""
- vertex_type: str = Field(..., description="Target vertex type name.")
- attribute_mappings: Dict[str, Union[str, int]] = Field(
- ...,
- description="Map of attribute name to column index (int) or header name (string). Must include the primary key. Example: {'id': 0, 'name': 1} or {'id': 'user_id', 'name': 'user_name'}"
- )
-
-
-class EdgeMapping(BaseModel):
- """Mapping configuration for loading edges."""
- edge_type: str = Field(..., description="Target edge type name.")
- source_column: Union[str, int] = Field(..., description="Column for source vertex ID (string for header name, int for column index).")
- target_column: Union[str, int] = Field(..., description="Column for target vertex ID (string for header name, int for column index).")
- attribute_mappings: Optional[Dict[str, Union[str, int]]] = Field(
- default_factory=dict,
- description="Map of attribute name to column. Optional for edges without attributes."
- )
-
-
-class FileConfig(BaseModel):
- """Configuration for a single data file in a loading job."""
- file_alias: str = Field(..., description="Alias for the file (used in DEFINE FILENAME).")
- file_path: Optional[str] = Field(None, description="Path to the file. If not provided, data will be passed at runtime.")
- separator: str = Field(",", description="Field separator character.")
- header: str = Field("true", description="Whether the file has a header row ('true' or 'false').")
- eol: str = Field("\\n", description="End-of-line character.")
- quote: Optional[str] = Field(None, description="Quote character for CSV (e.g., 'DOUBLE' for double quotes).")
- node_mappings: List[NodeMapping] = Field(
- default_factory=list,
- description="List of vertex loading mappings. Example: [{'vertex_type': 'Person', 'attribute_mappings': {'id': 0, 'name': 1}}]"
- )
- edge_mappings: List[EdgeMapping] = Field(
- default_factory=list,
- description="List of edge loading mappings."
- )
-
-
-class CreateLoadingJobToolInput(BaseModel):
- """Input schema for creating a loading job."""
- graph_name: Optional[str] = Field(None, description="Name of the graph. If not provided, uses default connection.")
- job_name: str = Field(..., description="Name for the loading job.")
- files: List[FileConfig] = Field(
- ...,
- description="List of file configurations. Each file must have a 'file_alias' and 'node_mappings' and/or 'edge_mappings'. Example: [{'file_alias': 'f1', 'node_mappings': [...]}]"
- )
- run_job: bool = Field(False, description="If True, run the loading job immediately after creation.")
- drop_after_run: bool = Field(False, description="If True, drop the job after running (only applies if run_job=True).")
-
-
-# =============================================================================
-# Input Models for Other Operations
-# =============================================================================
-
-class RunLoadingJobWithFileToolInput(BaseModel):
- """Input schema for running a loading job with a file."""
- graph_name: Optional[str] = Field(None, description="Name of the graph. If not provided, uses default connection.")
- file_path: str = Field(..., description="Absolute path to the data file to load. Example: '/home/user/data/persons.csv'")
- file_tag: str = Field(..., description="The name of file variable in the loading job (DEFINE FILENAME ).")
- job_name: str = Field(..., description="The name of the loading job to run.")
- separator: Optional[str] = Field(None, description="Data value separator. Default is comma. For JSON data, don't specify.")
- eol: Optional[str] = Field(None, description="End-of-line character. Default is '\\n'. Supports '\\r\\n'.")
- timeout: int = Field(16000, description="Timeout in milliseconds. Set to 0 for system-wide timeout.")
- size_limit: int = Field(128000000, description="Maximum size for input file in bytes (default 128MB).")
-
-
-class RunLoadingJobWithDataToolInput(BaseModel):
- """Input schema for running a loading job with inline data."""
- graph_name: Optional[str] = Field(None, description="Name of the graph. If not provided, uses default connection.")
- data: str = Field(..., description="The data string to load (CSV, JSON, etc.). Example: 'user1,Alice\\nuser2,Bob'")
- file_tag: str = Field(..., description="The name of file variable in the loading job (DEFINE FILENAME ).")
- job_name: str = Field(..., description="The name of the loading job to run.")
- separator: Optional[str] = Field(None, description="Data value separator. Default is comma. For JSON data, don't specify.")
- eol: Optional[str] = Field(None, description="End-of-line character. Default is '\\n'. Supports '\\r\\n'.")
- timeout: int = Field(16000, description="Timeout in milliseconds. Set to 0 for system-wide timeout.")
- size_limit: int = Field(128000000, description="Maximum size for input data in bytes (default 128MB).")
-
-
-class GetLoadingJobsToolInput(BaseModel):
- """Input schema for listing loading jobs."""
- graph_name: Optional[str] = Field(None, description="Name of the graph. If not provided, uses default connection.")
-
-
-class GetLoadingJobStatusToolInput(BaseModel):
- """Input schema for getting loading job status."""
- graph_name: Optional[str] = Field(None, description="Name of the graph. If not provided, uses default connection.")
- job_id: str = Field(..., description="The ID of the loading job to check status.")
-
-
-class DropLoadingJobToolInput(BaseModel):
- """Input schema for dropping a loading job."""
- graph_name: Optional[str] = Field(None, description="Name of the graph. If not provided, uses default connection.")
- job_name: str = Field(..., description="The name of the loading job to drop.")
-
-
-# =============================================================================
-# Tool Definitions
-# =============================================================================
-
-create_loading_job_tool = Tool(
- name=TigerGraphToolName.CREATE_LOADING_JOB,
- description="""Create a loading job from structured configuration.
-The job defines how to load data from files into vertices and edges.
-Each file config specifies: file alias, separator, header, EOL, and mappings.
-Node mappings define which columns map to vertex attributes.
-Edge mappings define source/target columns and edge attributes.
-Optionally run the job immediately and drop it after execution.""",
- inputSchema=CreateLoadingJobToolInput.model_json_schema(),
-)
-
-run_loading_job_with_file_tool = Tool(
- name=TigerGraphToolName.RUN_LOADING_JOB_WITH_FILE,
- description="Execute a loading job with a data file. The file is uploaded to TigerGraph and loaded according to the specified loading job definition.",
- inputSchema=RunLoadingJobWithFileToolInput.model_json_schema(),
-)
-
-run_loading_job_with_data_tool = Tool(
- name=TigerGraphToolName.RUN_LOADING_JOB_WITH_DATA,
- description="Execute a loading job with inline data string. The data is posted to TigerGraph and loaded according to the specified loading job definition.",
- inputSchema=RunLoadingJobWithDataToolInput.model_json_schema(),
-)
-
-get_loading_jobs_tool = Tool(
- name=TigerGraphToolName.GET_LOADING_JOBS,
- description="Get a list of all loading jobs defined for the current graph.",
- inputSchema=GetLoadingJobsToolInput.model_json_schema(),
-)
-
-get_loading_job_status_tool = Tool(
- name=TigerGraphToolName.GET_LOADING_JOB_STATUS,
- description="Get the status of a specific loading job by its job ID.",
- inputSchema=GetLoadingJobStatusToolInput.model_json_schema(),
-)
-
-drop_loading_job_tool = Tool(
- name=TigerGraphToolName.DROP_LOADING_JOB,
- description="Drop (delete) a loading job from the graph.",
- inputSchema=DropLoadingJobToolInput.model_json_schema(),
-)
-
-
-# =============================================================================
-# Helper Functions
-# =============================================================================
-
-def _format_column(column: Union[str, int]) -> str:
- """Format column reference for GSQL loading job."""
- if isinstance(column, int):
- return f"${column}"
- return f'$"{column}"'
-
-
-def _generate_loading_job_gsql(
- graph_name: str,
- job_name: str,
- files: List[Dict[str, Any]],
-) -> str:
- """Generate GSQL script for creating a loading job."""
-
- # Build DEFINE FILENAME statements
- define_files = []
- for file_config in files:
- alias = file_config["file_alias"]
- path = file_config.get("file_path")
- if path:
- define_files.append(f'DEFINE FILENAME {alias} = "{path}";')
- else:
- define_files.append(f"DEFINE FILENAME {alias};")
-
- # Build LOAD statements for each file
- load_statements = []
- for file_config in files:
- alias = file_config["file_alias"]
- separator = file_config.get("separator", ",")
- header = file_config.get("header", "true")
- eol = file_config.get("eol", "\\n")
- quote = file_config.get("quote")
-
- # Build USING clause
- using_parts = [
- f'SEPARATOR="{separator}"',
- f'HEADER="{header}"',
- f'EOL="{eol}"'
- ]
- if quote:
- using_parts.append(f'QUOTE="{quote}"')
- using_clause = "USING " + ", ".join(using_parts) + ";"
-
- # Build mapping statements
- mapping_statements = []
-
- # Node mappings
- for node_mapping in file_config.get("node_mappings", []):
- vertex_type = node_mapping["vertex_type"]
- attr_mappings = node_mapping["attribute_mappings"]
-
- # Format attribute values
- attr_values = ", ".join(
- _format_column(col) for col in attr_mappings.values()
- )
- mapping_statements.append(
- f"TO VERTEX {vertex_type} VALUES({attr_values})"
- )
-
- # Edge mappings
- for edge_mapping in file_config.get("edge_mappings", []):
- edge_type = edge_mapping["edge_type"]
- source_col = _format_column(edge_mapping["source_column"])
- target_col = _format_column(edge_mapping["target_column"])
- attr_mappings = edge_mapping.get("attribute_mappings", {})
-
- # Format attribute values
- if attr_mappings:
- attr_values = ", ".join(
- _format_column(col) for col in attr_mappings.values()
- )
- all_values = f"{source_col}, {target_col}, {attr_values}"
- else:
- all_values = f"{source_col}, {target_col}"
-
- mapping_statements.append(
- f"TO EDGE {edge_type} VALUES({all_values})"
- )
-
- # Combine into LOAD statement
- if mapping_statements:
- load_stmt = f"LOAD {alias}\n " + ",\n ".join(mapping_statements) + f"\n {using_clause}"
- load_statements.append(load_stmt)
-
- # Build the complete GSQL script
- define_section = " # Define files\n " + "\n ".join(define_files)
- load_section = " # Load data\n " + "\n ".join(load_statements)
-
- gsql_script = f"""USE GRAPH {graph_name}
-
-CREATE LOADING JOB {job_name} FOR GRAPH {graph_name} {{
-{define_section}
-
-{load_section}
-}}"""
-
- return gsql_script
-
-
-# =============================================================================
-# Tool Implementations
-# =============================================================================
-
-async def create_loading_job(
- job_name: str,
- files: List[Dict[str, Any]],
- run_job: bool = False,
- drop_after_run: bool = False,
- graph_name: Optional[str] = None,
-) -> List[TextContent]:
- """Create a loading job from structured configuration."""
- try:
- conn = get_connection(graph_name=graph_name)
-
- # Generate the GSQL script
- gsql_script = _generate_loading_job_gsql(
- graph_name=conn.graphname,
- job_name=job_name,
- files=files
- )
-
- # Add RUN and DROP commands if requested
- if run_job:
- gsql_script += f"\n\nRUN LOADING JOB {job_name}"
- if drop_after_run:
- gsql_script += f"\n\nDROP JOB {job_name}"
-
- result = await conn.gsql(gsql_script)
- result_str = str(result) if result else ""
-
- if gsql_has_error(result_str):
- return format_error(
- operation="create_loading_job",
- error=TigerGraphException(result_str),
- context={
- "job_name": job_name,
- "graph_name": conn.graphname,
- "gsql_script": gsql_script,
- },
- suggestions=[
- "Check that vertex/edge types referenced in the job exist in the schema",
- "Use show_graph_details() to verify the current schema",
- "Ensure file paths and column mappings are correct",
- ],
- )
-
- status_parts = []
- if run_job:
- if drop_after_run:
- status_parts.append("Job created, executed, and dropped (one-time load)")
- else:
- status_parts.append("Job created and executed")
- else:
- status_parts.append("Job created successfully")
-
- return format_success(
- operation="create_loading_job",
- summary=f"Success: Loading job '{job_name}' " + ", ".join(status_parts),
- data={
- "job_name": job_name,
- "file_count": len(files),
- "executed": run_job,
- "dropped": drop_after_run,
- "gsql_script": gsql_script,
- "result": result_str,
- },
- suggestions=[s for s in [
- f"Run the job: run_loading_job_with_file(job_name='{job_name}', ...)" if not run_job else "Job already executed",
- "List all jobs: get_loading_jobs()",
- f"Get status: get_loading_job_status(job_name='{job_name}')" if not drop_after_run else None,
- "Tip: Loading jobs are the recommended way to bulk-load data"
- ] if s is not None],
- metadata={
- "graph_name": conn.graphname,
- "operation_type": "DDL"
- }
- )
-
- except Exception as e:
- return format_error(
- operation="create_loading_job",
- error=e,
- context={
- "job_name": job_name,
- "file_count": len(files),
- "graph_name": graph_name or "default"
- }
- )
-
-
-async def run_loading_job_with_file(
- file_path: str,
- file_tag: str,
- job_name: str,
- separator: Optional[str] = None,
- eol: Optional[str] = None,
- timeout: int = 16000,
- size_limit: int = 128000000,
- graph_name: Optional[str] = None,
-) -> List[TextContent]:
- """Execute a loading job with a data file."""
- try:
- conn = get_connection(graph_name=graph_name)
- result = await conn.runLoadingJobWithFile(
- filePath=file_path,
- fileTag=file_tag,
- jobName=job_name,
- sep=separator,
- eol=eol,
- timeout=timeout,
- sizeLimit=size_limit
- )
- if result:
- return format_success(
- operation="run_loading_job_with_file",
- summary=f"Success: Loading job '{job_name}' executed successfully with file '{file_path}'",
- data={
- "job_name": job_name,
- "file_path": file_path,
- "file_tag": file_tag,
- "result": result
- },
- suggestions=[
- f"Check status: get_loading_job_status(job_id='')",
- "Verify loaded data with: get_vertex_count() or get_edge_count()",
- "List all jobs: get_loading_jobs()"
- ],
- metadata={"graph_name": conn.graphname}
- )
- else:
- return format_error(
- operation="run_loading_job_with_file",
- error=ValueError("Loading job returned no result"),
- context={
- "job_name": job_name,
- "file_path": file_path,
- "file_tag": file_tag,
- "graph_name": graph_name or "default"
- },
- suggestions=[
- "Check if the job name is correct",
- "Verify the file_tag matches the loading job definition",
- "Ensure the loading job exists: get_loading_jobs()"
- ]
- )
- except Exception as e:
- return format_error(
- operation="run_loading_job_with_file",
- error=e,
- context={
- "job_name": job_name,
- "file_path": file_path,
- "graph_name": graph_name or "default"
- }
- )
-
-
-async def run_loading_job_with_data(
- data: str,
- file_tag: str,
- job_name: str,
- separator: Optional[str] = None,
- eol: Optional[str] = None,
- timeout: int = 16000,
- size_limit: int = 128000000,
- graph_name: Optional[str] = None,
-) -> List[TextContent]:
- """Execute a loading job with inline data string."""
- try:
- conn = get_connection(graph_name=graph_name)
- result = await conn.runLoadingJobWithData(
- data=data,
- fileTag=file_tag,
- jobName=job_name,
- sep=separator,
- eol=eol,
- timeout=timeout,
- sizeLimit=size_limit
- )
- if result:
- data_preview = data[:100] + "..." if len(data) > 100 else data
- return format_success(
- operation="run_loading_job_with_data",
- summary=f"Success: Loading job '{job_name}' executed successfully with inline data",
- data={
- "job_name": job_name,
- "file_tag": file_tag,
- "data_preview": data_preview,
- "data_size": len(data),
- "result": result
- },
- suggestions=[
- "Verify loaded data: get_vertex_count() or get_edge_count()",
- "Tip: For large datasets, use 'run_loading_job_with_file' instead",
- "List all jobs: get_loading_jobs()"
- ],
- metadata={"graph_name": conn.graphname}
- )
- else:
- return format_error(
- operation="run_loading_job_with_data",
- error=ValueError("Loading job returned no result"),
- context={
- "job_name": job_name,
- "file_tag": file_tag,
- "data_size": len(data),
- "graph_name": graph_name or "default"
- },
- suggestions=[
- "Check if the job name is correct",
- "Verify the file_tag matches the loading job definition",
- "Ensure the loading job exists: get_loading_jobs()"
- ]
- )
- except Exception as e:
- return format_error(
- operation="run_loading_job_with_data",
- error=e,
- context={
- "job_name": job_name,
- "data_size": len(data),
- "graph_name": graph_name or "default"
- }
- )
-
-
-async def get_loading_jobs(
- graph_name: Optional[str] = None,
-) -> List[TextContent]:
- """Get a list of all loading jobs for the current graph."""
- try:
- conn = get_connection(graph_name=graph_name)
- result = await conn.getLoadingJobs()
- if result:
- job_count = len(result) if isinstance(result, list) else 1
- return format_success(
- operation="get_loading_jobs",
- summary=f"Found {job_count} loading job(s) for graph '{conn.graphname}'",
- data={
- "jobs": result,
- "count": job_count
- },
- suggestions=[
- "Run a job: run_loading_job_with_file(...) or run_loading_job_with_data(...)",
- "Create new job: create_loading_job(...)",
- "Check job status: get_loading_job_status(job_id='')"
- ],
- metadata={"graph_name": conn.graphname}
- )
- else:
- return format_success(
- operation="get_loading_jobs",
- summary=f"Success: No loading jobs found for graph '{conn.graphname}'",
- suggestions=[
- "Create a loading job: create_loading_job(...)",
- "Tip: Loading jobs are used for bulk data ingestion"
- ],
- metadata={"graph_name": conn.graphname}
- )
- except Exception as e:
- return format_error(
- operation="get_loading_jobs",
- error=e,
- context={"graph_name": graph_name or "default"}
- )
-
-
-async def get_loading_job_status(
- job_id: str,
- graph_name: Optional[str] = None,
-) -> List[TextContent]:
- """Get the status of a specific loading job."""
- try:
- conn = get_connection(graph_name=graph_name)
- result = await conn.getLoadingJobStatus(jobId=job_id)
- if result:
- return format_success(
- operation="get_loading_job_status",
- summary=f"Success: Loading job status for '{job_id}'",
- data={
- "job_id": job_id,
- "status": result
- },
- suggestions=[
- "List all jobs: get_loading_jobs()",
- "Tip: Use this to monitor long-running loading jobs"
- ],
- metadata={"graph_name": conn.graphname}
- )
- else:
- return format_error(
- operation="get_loading_job_status",
- error=ValueError("No status found for loading job"),
- context={
- "job_id": job_id,
- "graph_name": graph_name or "default"
- },
- suggestions=[
- "Verify the job_id is correct",
- "List all jobs: get_loading_jobs()"
- ]
- )
- except Exception as e:
- return format_error(
- operation="get_loading_job_status",
- error=e,
- context={
- "job_id": job_id,
- "graph_name": graph_name or "default"
- }
- )
-
-
-async def drop_loading_job(
- job_name: str,
- graph_name: Optional[str] = None,
-) -> List[TextContent]:
- """Drop a loading job from the graph."""
- try:
- conn = get_connection(graph_name=graph_name)
- result = await conn.dropLoadingJob(jobName=job_name)
-
- return format_success(
- operation="drop_loading_job",
- summary=f"Success: Loading job '{job_name}' dropped successfully",
- data={
- "job_name": job_name,
- "result": result
- },
- suggestions=[
- "Warning: This operation is permanent and cannot be undone",
- "Verify deletion: get_loading_jobs()",
- "Create a new job: create_loading_job(...)"
- ],
- metadata={
- "graph_name": conn.graphname,
- "destructive": True
- }
- )
- except Exception as e:
- return format_error(
- operation="drop_loading_job",
- error=e,
- context={
- "job_name": job_name,
- "graph_name": graph_name or "default"
- }
- )
diff --git a/pyTigerGraph/mcp/tools/datasource_tools.py b/pyTigerGraph/mcp/tools/datasource_tools.py
deleted file mode 100644
index c8d0b46a..00000000
--- a/pyTigerGraph/mcp/tools/datasource_tools.py
+++ /dev/null
@@ -1,362 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""Data source operation tools for MCP."""
-
-from typing import List, Optional, Dict, Any
-from pydantic import BaseModel, Field
-from mcp.types import Tool, TextContent
-
-from ..tool_names import TigerGraphToolName
-from ..connection_manager import get_connection
-
-
-class CreateDataSourceToolInput(BaseModel):
- """Input schema for creating a data source."""
- data_source_name: str = Field(..., description="Name of the data source.")
- data_source_type: str = Field(..., description="Type of data source: 's3', 'gcs', 'azure_blob', or 'local'.")
- config: Dict[str, Any] = Field(..., description="Configuration for the data source (e.g., bucket, credentials).")
-
-
-class UpdateDataSourceToolInput(BaseModel):
- """Input schema for updating a data source."""
- data_source_name: str = Field(..., description="Name of the data source to update.")
- config: Dict[str, Any] = Field(..., description="Updated configuration for the data source.")
-
-
-class GetDataSourceToolInput(BaseModel):
- """Input schema for getting a data source."""
- data_source_name: str = Field(..., description="Name of the data source.")
-
-
-class DropDataSourceToolInput(BaseModel):
- """Input schema for dropping a data source."""
- data_source_name: str = Field(..., description="Name of the data source to drop.")
-
-
-class GetAllDataSourcesToolInput(BaseModel):
- """Input schema for getting all data sources."""
- # No parameters needed - returns all data sources
-
-
-class DropAllDataSourcesToolInput(BaseModel):
- """Input schema for dropping all data sources."""
- confirm: bool = Field(False, description="Must be True to confirm dropping all data sources.")
-
-
-class PreviewSampleDataToolInput(BaseModel):
- """Input schema for previewing sample data."""
- data_source_name: str = Field(..., description="Name of the data source.")
- file_path: str = Field(..., description="Path to the file within the data source.")
- num_rows: int = Field(10, description="Number of sample rows to preview.")
- graph_name: Optional[str] = Field(None, description="Name of the graph context. If not provided, uses default connection.")
-
-
-create_data_source_tool = Tool(
- name=TigerGraphToolName.CREATE_DATA_SOURCE,
- description="Create a new data source for loading data (S3, GCS, Azure Blob, or local).",
- inputSchema=CreateDataSourceToolInput.model_json_schema(),
-)
-
-update_data_source_tool = Tool(
- name=TigerGraphToolName.UPDATE_DATA_SOURCE,
- description="Update an existing data source configuration.",
- inputSchema=UpdateDataSourceToolInput.model_json_schema(),
-)
-
-get_data_source_tool = Tool(
- name=TigerGraphToolName.GET_DATA_SOURCE,
- description="Get information about a specific data source.",
- inputSchema=GetDataSourceToolInput.model_json_schema(),
-)
-
-drop_data_source_tool = Tool(
- name=TigerGraphToolName.DROP_DATA_SOURCE,
- description="Drop (delete) a data source.",
- inputSchema=DropDataSourceToolInput.model_json_schema(),
-)
-
-get_all_data_sources_tool = Tool(
- name=TigerGraphToolName.GET_ALL_DATA_SOURCES,
- description="Get information about all data sources.",
- inputSchema=GetAllDataSourcesToolInput.model_json_schema(),
-)
-
-drop_all_data_sources_tool = Tool(
- name=TigerGraphToolName.DROP_ALL_DATA_SOURCES,
- description="Drop all data sources. WARNING: This is a destructive operation.",
- inputSchema=DropAllDataSourcesToolInput.model_json_schema(),
-)
-
-preview_sample_data_tool = Tool(
- name=TigerGraphToolName.PREVIEW_SAMPLE_DATA,
- description="Preview sample data from a file in a data source.",
- inputSchema=PreviewSampleDataToolInput.model_json_schema(),
-)
-
-
-async def create_data_source(
- data_source_name: str,
- data_source_type: str,
- config: Dict[str, Any],
-) -> List[TextContent]:
- """Create a new data source."""
- from ..response_formatter import format_success, format_error, gsql_has_error
-
- try:
- conn = get_connection()
-
- config_str = ", ".join([f'{k}="{v}"' for k, v in config.items()])
-
- gsql_cmd = f"CREATE DATA_SOURCE {data_source_type.upper()} {data_source_name}"
- if config_str:
- gsql_cmd += f" = ({config_str})"
-
- result = await conn.gsql(gsql_cmd)
- result_str = str(result) if result else ""
-
- if gsql_has_error(result_str):
- return format_error(
- operation="create_data_source",
- error=Exception(f"Could not create data source:\n{result_str}"),
- context={"data_source_name": data_source_name, "data_source_type": data_source_type},
- )
-
- return format_success(
- operation="create_data_source",
- summary=f"Data source '{data_source_name}' of type '{data_source_type}' created successfully",
- data={"data_source_name": data_source_name, "result": result_str},
- suggestions=[
- f"View data source: get_data_source(data_source_name='{data_source_name}')",
- "List all data sources: get_all_data_sources()",
- ],
- )
- except Exception as e:
- return format_error(
- operation="create_data_source",
- error=e,
- context={"data_source_name": data_source_name},
- )
-
-
-async def update_data_source(
- data_source_name: str,
- config: Dict[str, Any],
-) -> List[TextContent]:
- """Update an existing data source."""
- from ..response_formatter import format_success, format_error, gsql_has_error
-
- try:
- conn = get_connection()
-
- config_str = ", ".join([f'{k}="{v}"' for k, v in config.items()])
- gsql_cmd = f"ALTER DATA_SOURCE {data_source_name} = ({config_str})"
-
- result = await conn.gsql(gsql_cmd)
- result_str = str(result) if result else ""
-
- if gsql_has_error(result_str):
- return format_error(
- operation="update_data_source",
- error=Exception(f"Could not update data source:\n{result_str}"),
- context={"data_source_name": data_source_name},
- )
-
- return format_success(
- operation="update_data_source",
- summary=f"Data source '{data_source_name}' updated successfully",
- data={"data_source_name": data_source_name, "result": result_str},
- )
- except Exception as e:
- return format_error(
- operation="update_data_source",
- error=e,
- context={"data_source_name": data_source_name},
- )
-
-
-async def get_data_source(
- data_source_name: str,
-) -> List[TextContent]:
- """Get information about a data source."""
- from ..response_formatter import format_success, format_error, gsql_has_error
-
- try:
- conn = get_connection()
-
- result = await conn.gsql(f"SHOW DATA_SOURCE {data_source_name}")
- result_str = str(result) if result else ""
-
- if gsql_has_error(result_str):
- return format_error(
- operation="get_data_source",
- error=Exception(f"Could not retrieve data source:\n{result_str}"),
- context={"data_source_name": data_source_name},
- )
-
- return format_success(
- operation="get_data_source",
- summary=f"Data source '{data_source_name}' details",
- data={"data_source_name": data_source_name, "details": result_str},
- )
- except Exception as e:
- return format_error(
- operation="get_data_source",
- error=e,
- context={"data_source_name": data_source_name},
- )
-
-
-async def drop_data_source(
- data_source_name: str,
-) -> List[TextContent]:
- """Drop a data source."""
- from ..response_formatter import format_success, format_error, gsql_has_error
-
- try:
- conn = get_connection()
-
- result = await conn.gsql(f"DROP DATA_SOURCE {data_source_name}")
- result_str = str(result) if result else ""
-
- if gsql_has_error(result_str):
- return format_error(
- operation="drop_data_source",
- error=Exception(f"Could not drop data source:\n{result_str}"),
- context={"data_source_name": data_source_name},
- )
-
- return format_success(
- operation="drop_data_source",
- summary=f"Data source '{data_source_name}' dropped successfully",
- data={"data_source_name": data_source_name, "result": result_str},
- suggestions=["List remaining: get_all_data_sources()"],
- metadata={"destructive": True},
- )
- except Exception as e:
- return format_error(
- operation="drop_data_source",
- error=e,
- context={"data_source_name": data_source_name},
- )
-
-
-async def get_all_data_sources(**kwargs) -> List[TextContent]:
- """Get all data sources."""
- from ..response_formatter import format_success, format_error, gsql_has_error
-
- try:
- conn = get_connection()
-
- result = await conn.gsql("SHOW DATA_SOURCE *")
- result_str = str(result) if result else ""
-
- if gsql_has_error(result_str):
- return format_error(
- operation="get_all_data_sources",
- error=Exception(f"Could not retrieve data sources:\n{result_str}"),
- context={},
- )
-
- return format_success(
- operation="get_all_data_sources",
- summary="All data sources retrieved",
- data={"details": result_str},
- suggestions=["Create a data source: create_data_source(...)"],
- )
- except Exception as e:
- return format_error(
- operation="get_all_data_sources",
- error=e,
- context={},
- )
-
-
-async def drop_all_data_sources(
- confirm: bool = False,
-) -> List[TextContent]:
- """Drop all data sources."""
- from ..response_formatter import format_success, format_error, gsql_has_error
-
- if not confirm:
- return format_error(
- operation="drop_all_data_sources",
- error=ValueError("Confirmation required"),
- context={},
- suggestions=[
- "Set confirm=True to proceed with this destructive operation",
- "This will drop ALL data sources",
- ],
- )
-
- try:
- conn = get_connection()
-
- result = await conn.gsql("DROP DATA_SOURCE *")
- result_str = str(result) if result else ""
-
- if gsql_has_error(result_str):
- return format_error(
- operation="drop_all_data_sources",
- error=Exception(f"Could not drop all data sources:\n{result_str}"),
- context={},
- )
-
- return format_success(
- operation="drop_all_data_sources",
- summary="All data sources dropped successfully",
- data={"result": result_str},
- metadata={"destructive": True},
- )
- except Exception as e:
- return format_error(
- operation="drop_all_data_sources",
- error=e,
- context={},
- )
-
-
-async def preview_sample_data(
- data_source_name: str,
- file_path: str,
- num_rows: int = 10,
- graph_name: Optional[str] = None,
-) -> List[TextContent]:
- """Preview sample data from a file."""
- from ..response_formatter import format_success, format_error, gsql_has_error
-
- try:
- conn = get_connection(graph_name=graph_name)
-
- gsql_cmd = (
- f"USE GRAPH {conn.graphname}\n"
- f'SHOW DATA_SOURCE {data_source_name} FILE "{file_path}" LIMIT {num_rows}'
- )
-
- result = await conn.gsql(gsql_cmd)
- result_str = str(result) if result else ""
-
- if gsql_has_error(result_str):
- return format_error(
- operation="preview_sample_data",
- error=Exception(f"Could not preview data:\n{result_str}"),
- context={"data_source_name": data_source_name, "file_path": file_path},
- )
-
- return format_success(
- operation="preview_sample_data",
- summary=f"Sample data from '{file_path}' (first {num_rows} rows)",
- data={"data_source_name": data_source_name, "file_path": file_path, "preview": result_str},
- metadata={"graph_name": conn.graphname},
- )
- except Exception as e:
- return format_error(
- operation="preview_sample_data",
- error=e,
- context={"data_source_name": data_source_name, "file_path": file_path},
- )
-
diff --git a/pyTigerGraph/mcp/tools/discovery_tools.py b/pyTigerGraph/mcp/tools/discovery_tools.py
deleted file mode 100644
index a09f6605..00000000
--- a/pyTigerGraph/mcp/tools/discovery_tools.py
+++ /dev/null
@@ -1,611 +0,0 @@
-# Copyright 2025 TigerGraph Inc.
-# Licensed under the Apache License, Version 2.0.
-# See the LICENSE file or https://www.apache.org/licenses/LICENSE-2.0
-#
-# Permission is granted to use, copy, modify, and distribute this software
-# under the License. The software is provided "AS IS", without warranty.
-
-"""Discovery and navigation tools for LLMs.
-
-These tools help LLMs discover the right tools for their tasks and understand
-common workflows.
-"""
-
-import json
-from typing import List, Optional
-from pydantic import BaseModel, Field
-from mcp.types import Tool, TextContent
-
-from ..tool_names import TigerGraphToolName
-from ..tool_metadata import TOOL_METADATA, ToolCategory, search_tools_by_keywords, get_tools_by_category
-from ..response_formatter import format_success, format_list_response
-
-
-class ToolDiscoveryInput(BaseModel):
- """Input for discovering relevant tools."""
- task_description: str = Field(
- ...,
- description=(
- "Describe what you want to accomplish in natural language.\n"
- "Examples:\n"
- " - 'add multiple users to the graph'\n"
- " - 'find similar documents using embeddings'\n"
- " - 'understand the graph structure'\n"
- " - 'load data from a CSV file'"
- )
- )
- category: Optional[str] = Field(
- None,
- description=(
- "Filter by category: 'schema', 'data', 'query', 'vector', 'loading', 'utility'.\n"
- "Leave empty to search all categories."
- )
- )
- limit: int = Field(
- 5,
- description="Maximum number of tools to return (default: 5)"
- )
-
-
-class GetWorkflowInput(BaseModel):
- """Input for getting workflow templates."""
- workflow_type: str = Field(
- ...,
- description=(
- "Type of workflow to retrieve:\n"
- " - 'create_graph': Set up a new graph with schema\n"
- " - 'load_data': Import data into an existing graph\n"
- " - 'query_data': Query and analyze graph data\n"
- " - 'vector_search': Set up and use vector similarity search\n"
- " - 'graph_analysis': Analyze graph structure and statistics\n"
- " - 'setup_connection': Initial connection setup and verification"
- )
- )
-
-
-class GetToolInfoInput(BaseModel):
- """Input for getting detailed information about a specific tool."""
- tool_name: str = Field(
- ...,
- description=(
- "Name of the tool to get information about.\n"
- "Example: 'tigergraph__add_node' or 'tigergraph__search_top_k_similarity'"
- )
- )
-
-
-# Tool definitions
-discover_tools_tool = Tool(
- name=TigerGraphToolName.DISCOVER_TOOLS,
- description=(
- "Discover which TigerGraph tools are relevant for your task.\n\n"
- "**Use this tool when:**\n"
- " - You're unsure which tool to use for your goal\n"
- " - You want to explore available capabilities\n"
- " - You need suggestions for accomplishing a task\n\n"
- "**Returns:**\n"
- " - List of recommended tools with descriptions\n"
- " - Use cases and complexity ratings\n"
- " - Prerequisites and related tools\n"
- " - Example parameters\n\n"
- "**Example:**\n"
- " task_description: 'I want to add multiple users to the graph'"
- ),
- inputSchema=ToolDiscoveryInput.model_json_schema(),
-)
-
-get_workflow_tool = Tool(
- name=TigerGraphToolName.GET_WORKFLOW,
- description=(
- "Get a step-by-step workflow template for common TigerGraph tasks.\n\n"
- "**Use this tool when:**\n"
- " - You need to complete a complex multi-step task\n"
- " - You want to follow best practices\n"
- " - You're new to TigerGraph and need guidance\n\n"
- "**Returns:**\n"
- " - Ordered list of tools to use\n"
- " - Example parameters for each step\n"
- " - Explanations of what each step accomplishes\n\n"
- "**Available workflows:** create_graph, load_data, query_data, vector_search, graph_analysis, setup_connection"
- ),
- inputSchema=GetWorkflowInput.model_json_schema(),
-)
-
-get_tool_info_tool = Tool(
- name=TigerGraphToolName.GET_TOOL_INFO,
- description=(
- "Get detailed information about a specific TigerGraph tool.\n\n"
- "**Use this tool when:**\n"
- " - You want to understand a tool's capabilities\n"
- " - You need examples of how to use a tool\n"
- " - You want to know prerequisites or related tools\n\n"
- "**Returns:**\n"
- " - Detailed tool description\n"
- " - Use cases and examples\n"
- " - Prerequisites and related tools\n"
- " - Common next steps"
- ),
- inputSchema=GetToolInfoInput.model_json_schema(),
-)
-
-
-# Workflow templates
-WORKFLOWS = {
- "setup_connection": {
- "name": "Setup and Verify Connection",
- "description": "Initial setup to verify connection and explore available graphs",
- "steps": [
- {
- "step": 1,
- "tool": "tigergraph__list_graphs",
- "description": "List all available graphs to see what exists",
- "parameters": {},
- "rationale": "First, discover what graphs are available in your TigerGraph instance"
- },
- {
- "step": 2,
- "tool": "tigergraph__show_graph_details",
- "description": "Get detailed schema of a specific graph",
- "parameters": {"graph_name": "