Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .skills/tinker-dev/references/gremlin-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Use gremlin-mcp when you need to:
|------------------------------|-----------------|--------------------------------------------|
| `translate_gremlin_query` | No | Translate Gremlin between language variants |
| `format_gremlin_query` | No | Format a Gremlin query with gremlint |
| `search_gremlin_docs` | No | Search TinkerPop docs for a given version |
| `get_gremlin_doc_page` | No | Read one docs page as Markdown |
| `get_graph_status` | Yes | Check database connectivity |
| `get_graph_schema` | Yes | Get complete graph structure |
| `run_gremlin_query` | Yes | Execute a Gremlin traversal |
Expand All @@ -27,9 +29,16 @@ Use gremlin-mcp when you need to:
## Offline Mode

When `GREMLIN_MCP_ENDPOINT` is not set, graph tools are not registered but
`translate_gremlin_query` and `format_gremlin_query` remain fully available. This is
`translate_gremlin_query`, `format_gremlin_query` and the docs tools remain fully available. This is
useful for query translation and formatting without a running database.

## TinkerPop Docs

The docs tools require a `version` from the enum in their schema (the bundled version plus any
added with `gremlin-mcp docs install <version>` into `~/.gremlin-mcp/docs/`; restart to pick up
new installs). Bundled docs come from `target/docs/markdown`, so run `bin/process-docs.sh`
(`--dryRun` is enough locally) before building gremlin-mcp to test them.

## Translation

The `translate_gremlin_query` tool supports these targets:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Fixed `CountStrategy` so that it no longer adds a `limit()` before `count().is(P)` when the capped count can pass the predicate and is then observed downstream, e.g. `out().count().is(gt(2))`.
* Removed the per-request `userAgent` option from all drivers so the user agent is sent only as the `User-Agent` header controlled by `enableUserAgentOnConnect`.
* Fixed `gremlin-mcp` to include the server's reason when a query fails rather than only reporting that it failed.
* Added `search_gremlin_docs` and `get_gremlin_doc_page` tools to `gremlin-mcp` that serve the reference documentation and Gremlin Semantics for a chosen TinkerPop version.

[[release-4-0-0-beta-3]]
=== TinkerPop 4.0.0-beta.3 (July 20, 2026)
Expand Down
19 changes: 19 additions & 0 deletions docs/site/home/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

> Apache TinkerPop is a graph computing framework and the home of Gremlin, the graph traversal language implemented by many graph databases and graph processors. This file indexes the project site and points to the documentation index for each supported release.

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

Documentation is published per release under `https://tinkerpop.apache.org/docs/<version>/`, and each release carries its own page-level index at `https://tinkerpop.apache.org/docs/<version>/llms.txt`. The path `https://tinkerpop.apache.org/docs/current/` resolves to the most recent official release and therefore changes as releases are made. Versions 3.7.7 and 3.8.2 are the first to publish an index of this form. Later releases of both lines do the same, while earlier releases are not backfilled, so a version at or above one of those two is required.

Every version's index covers the same body of documentation, pinned to that version by its URL: the reference documentation describing the Gremlin language and every step, the upgrade documentation recording what changed in each release, recipes offering reusable traversal patterns, tutorials introducing the property graph structure and the Gremlin Console, the provider documentation defining the semantics a graph system must implement and the semantics of Gremlin itself, and the IO documentation specifying the GraphBinary, GraphSON, GraphML, and Gryo serialization formats. Selecting a version and reading its index leads to any of them.
Expand Down
5 changes: 4 additions & 1 deletion docs/src/dev/developer/release.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,11 @@ the help of a PMC member for those steps.
... Consider direct deploy with `npm publish --otp <otp>` from `gremlin-js/gremlin-javascript/` - if using this method, ensure the `npm` version aligns with the one expected by the branch.
.. `mvn deploy -pl :gremlint -DskipTests -Dnpm`
... Consider direct deploy with `npm publish --otp <otp>` from `gremlin-js/gremlint/` - if using this method, ensure the `npm` version aligns with the one expected by the branch.
.. `bin/process-docs.sh` to build the documentation for the tag, since the `clean` above removed it. `gremlin-mcp`
bundles the reference and Gremlin Semantics pages of this build into its package, and its deploy fails if they are
missing or were built for another version.
.. `mvn deploy -pl :gremlin-mcp -DskipTests -Dnpm`
... Consider direct deploy with `npm publish --otp <otp>` from `gremlin-js/gremlin-mcp/` - if using this method, ensure the `npm` version aligns with the one expected by the branch. Note: `gremlin-javascript` must be published before `gremlin-mcp` as it is a dependency.
... Consider direct deploy with `npm publish --otp <otp>` from `gremlin-js/gremlin-mcp/` - if using this method, ensure the `npm` version aligns with the one expected by the branch. Note: `gremlin-javascript` must be published before `gremlin-mcp` as it is a dependency. A direct `npm publish` only warns when the docs are missing, so confirm its output reports the docs pages it bundled.
.. `git tag gremlin-go/vxx.yy.zz`
.. `git push origin gremlin-go/vxx.yy.zz`
.. `GOPROXY=proxy.golang.org go list -m github.com/apache/tinkerpop/gremlin-go/v3@vxx.yy.xx`
Expand Down
42 changes: 39 additions & 3 deletions docs/src/reference/gremlin-applications.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -2375,10 +2375,11 @@ input schema, and a result schema. When connected to a Gremlin MCP server, the a
* Execute Gremlin traversals
* Translate Gremlin queries between language variants
* Format Gremlin queries for readability
* Look up the TinkerPop documentation for a specific TinkerPop version

The Gremlin MCP server sits alongside Gremlin Server (or any TinkerPop‑compatible endpoint) and forwards tool calls to
the graph via standard Gremlin traversals. The translation and formatting tools are utility tools that work
independently and do not require a graph connection.
the graph via standard Gremlin traversals. The translation, formatting and documentation tools are utility tools that
work independently and do not require a graph connection.

IMPORTANT: This MCP server is designed for development and trusted environments.

Expand Down Expand Up @@ -2407,6 +2408,8 @@ The Gremlin MCP server exposes these tools:
* `refresh_schema_cache` — Forces schema discovery to run again when the graph has changed.
* `translate_gremlin_query` — Translates a Gremlin query to a target language variant with optional normalization.
* `format_gremlin_query` — Formats a Gremlin query using gremlint.
* `search_gremlin_docs` — Searches the reference documentation and Gremlin Semantics for a given TinkerPop version.
* `get_gremlin_doc_page` — Returns one page of that documentation as Markdown.

=== Schema discovery

Expand Down Expand Up @@ -2512,14 +2515,47 @@ For example, the assistant may execute a traversal like the following:
g.V().hasLabel('person').has('age', gt(30)).out('knows').values('name')
----

[[gremlin-mcp-docs]]
=== Gremlin documentation

An assistant writes better Gremlin when it can check the documentation for the version of TinkerPop its graph runs. The
`search_gremlin_docs` and `get_gremlin_doc_page` tools serve the Markdown form of this reference documentation
and the link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#gremlin-semantics[Gremlin Semantics] section of the
provider documentation. Search ranks pages by their titles, summaries and text, so a step name such as `mergeV` or a
concept such as "numeric type promotion" leads to the relevant pages, and each page links to related pages by an id that
the assistant can pass back to `get_gremlin_doc_page`. For a large page, a search result also names the section that
best matches the query, and passing that section's `page#anchor` form to `get_gremlin_doc_page` returns only that
section rather than the whole page.

Both tools require a `version`, and their input schema lists the versions that are available. There is no default
because the documentation differs between versions, and documentation for the wrong version leads an assistant to
write Gremlin that its graph does not support. The tool descriptions therefore direct the assistant to ask the user for
the TinkerPop version of the target graph when it does not know it.

Each Gremlin MCP release includes the documentation for its own TinkerPop version. Documentation for other versions is
added with the `docs install` command, which downloads the pages for that version from the TinkerPop website, and the
`docs list` command shows what is available:

[source,text]
----
npx gremlin-mcp docs install 3.8.2
npx gremlin-mcp docs list
----

Installed versions are written to `~/.gremlin-mcp/docs/<version>/`, and installing a version again replaces it. The
Gremlin MCP server reads the available versions when it starts, so it must be restarted before a newly installed version
is offered to the assistant. For a host without internet access, the versions can be installed on another machine and
the `~/.gremlin-mcp/docs/` directory copied to the same location on that host. Markdown documentation is published
from TinkerPop 3.7.7 and 3.8.2 onward, so earlier versions cannot be installed.

=== Configuring an MCP Client

The MCP client is responsible for launching the Gremlin MCP server and providing connection details for the Gremlin
endpoint the server should use.

Basic connection settings:

* `GREMLIN_MCP_ENDPOINT` — `host:port` or `host:port/traversal_source` for the target Gremlin Server or compatible endpoint (default traversal source: `g`). Optional — when not set, graph tools are not registered but `translate_gremlin_query` and `format_gremlin_query` remain available.
* `GREMLIN_MCP_ENDPOINT` — `host:port` or `host:port/traversal_source` for the target Gremlin Server or compatible endpoint (default traversal source: `g`). Optional — when not set, graph tools are not registered but `translate_gremlin_query`, `format_gremlin_query` and the documentation tools remain available.
* `GREMLIN_MCP_USE_SSL` — set to `true` when TLS is required by the endpoint (default: `false`)
* `GREMLIN_MCP_USERNAME` / `GREMLIN_PASSWORD` — credentials when authentication is enabled (optional)
* `GREMLIN_MCP_IDLE_TIMEOUT` — idle connection timeout in seconds (default: `300`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ String generate(final List<Entry> entries) {
final StringBuilder sb = new StringBuilder();
sb.append("# ").append(projectName).append("\n\n");
sb.append("> ").append(summary).append("\n\n");
// The llms.txt format expects the H1 and summary first, so the license comment follows them.
sb.append(MarkdownConverter.LICENSE_COMMENT);

// Group by book (the directory portion of the relative path).
final Map<String, List<Entry>> byBook = new LinkedHashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,31 @@ public class MarkdownConverter extends StringConverter {
*/
static final String LLMS_ALLOW_OVERSIZE_ATTR = "allow-oversize";

/**
* The ASF license header as an HTML comment, emitted at the top of every rendered book. ASF
* policy asks that human-readable documentation in a distribution carry the header, and an HTML
* comment keeps it out of the rendered page, as {@code docinfo.html} does for the HTML backend.
* {@link MarkdownSplitter} moves it onto every page it splits a book into.
*/
static final String LICENSE_COMMENT = "<!--\n"
+ "Licensed to the Apache Software Foundation (ASF) under one\n"
+ "or more contributor license agreements. See the NOTICE file\n"
+ "distributed with this work for additional information\n"
+ "regarding copyright ownership. The ASF licenses this file\n"
+ "to you under the Apache License, Version 2.0 (the\n"
+ "\"License\"); you may not use this file except in compliance\n"
+ "with the License. You may obtain a copy of the License at\n"
+ "\n"
+ " http://www.apache.org/licenses/LICENSE-2.0\n"
+ "\n"
+ "Unless required by applicable law or agreed to in writing,\n"
+ "software distributed under the License is distributed on an\n"
+ "\"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n"
+ "KIND, either express or implied. See the License for the\n"
+ "specific language governing permissions and limitations\n"
+ "under the License.\n"
+ "-->\n\n";

public MarkdownConverter(final String backend, final Map<String, Object> opts) {
super(backend, opts);
}
Expand Down Expand Up @@ -182,7 +207,7 @@ private String convertDocument(final Document doc) {
// AsciidoctorJ discards a postprocessor's return value for a custom StringConverter backend,
// so the Markdown file would otherwise keep the literal x.y.z. The HTML backend still relies
// on the postprocessor for the same substitution.
return substituteVersion(doc, assembled);
return LICENSE_COMMENT + substituteVersion(doc, assembled);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,21 @@ private static final class Node {
* Splits {@code markdown} into pages. The first page always carries the file name
* {@code indexFileName} (typically {@code index.md}) and holds the document preamble plus the
* top-level structure that fits; deeper sections that individually exceed the budget become
* their own pages named from their anchor id.
* their own pages named from their anchor id. Every page starts with the llms.txt pointer and,
* when the book begins with {@link MarkdownConverter#LICENSE_COMMENT}, the license comment.
*
* @param markdown the full rendered Markdown book
* @param indexFileName the file name for the landing page (e.g. {@code index.md})
* @return the ordered list of pages (always at least one)
*/
List<Page> split(final String markdown, final String indexFileName) {
final Node root = parse(markdown);
// The license comment MarkdownConverter puts at the top of the book belongs on every page,
// not only the landing page that would otherwise inherit the book's preamble.
final boolean licensed = markdown.startsWith(MarkdownConverter.LICENSE_COMMENT);
final String header = llmsPointer + (licensed ? MarkdownConverter.LICENSE_COMMENT : "");
final Node root = parse(licensed
? markdown.substring(MarkdownConverter.LICENSE_COMMENT.length())
: markdown);

// Summary-driven splitting: a section becomes its own page if and only if it carries an
// llms-summary (surfaced as the <!-- llms-summary: ... --> marker). The document root is
Expand Down Expand Up @@ -270,7 +277,7 @@ List<Page> split(final String markdown, final String indexFileName) {
final StringBuilder body = new StringBuilder();
renderPage(plan.owner, body, plan == index, nodeToFile);
final String rewritten = rewriteLinks(body.toString(), plan.fileName, anchorToFile);
pages.add(new Page(plan.fileName, llmsPointer + rewritten, isAllowOversize(plan.owner)));
pages.add(new Page(plan.fileName, header + rewritten, isAllowOversize(plan.owner)));
}
return pages;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,15 @@ public void markdownDoesNotContainHtmlTabWidget() {
assertThat(markdown.contains("CodeRay"), is(false));
}
}

@Test
public void markdownStartsWithTheLicenseComment() {
try (final Asciidoctor asciidoctor = Asciidoctor.Factory.create()) {
asciidoctor.javaConverterRegistry().register(MarkdownConverter.class);
asciidoctor.unregisterAllExtensions();
final String markdown = asciidoctor.convert(DOC, Options.builder().backend("tpmarkdown").build());
assertThat(markdown.startsWith(MarkdownConverter.LICENSE_COMMENT), is(true));
assertThat(markdown.indexOf("Licensed to the Apache"), is(markdown.lastIndexOf("Licensed to the Apache")));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public void emitsH1AndSummaryBlockquote() {
final String out = gen.generate(Arrays.asList(
new LlmsTxtGenerator.Entry("dev/io/index.md", "IO Reference", "How IO works.")));
assertThat(out, containsString("# Apache TinkerPop Documentation\n"));
// H1 and summary first, as llms.txt expects, then the license comment.
assertThat(out.startsWith("# Apache TinkerPop Documentation\n"), is(true));
assertThat(out, containsString("\n\n" + MarkdownConverter.LICENSE_COMMENT + "## "));
assertThat(out, containsString("> Docs for a graph computing framework.\n"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ private static MarkdownSplitter.Page page(final List<MarkdownSplitter.Page> page
// <h1>). The document root (index.md) owns the preamble and the doctitle section's content up to
// the first summarized sibling.

@Test
public void licenseCommentIsCarriedOntoEveryPageAfterThePointer() {
final String md = MarkdownConverter.LICENSE_COMMENT + "preamble\n\n" + heading("io", 1, "IO Reference")
+ "\nintro\n\n" + summarized("a", 1, "A", "About A.") + "\ncontent a\n\n"
+ summarized("b", 1, "B", "About B.") + "\ncontent b\n";
final List<MarkdownSplitter.Page> pages = new MarkdownSplitter(50_000, "4.0.0").split(md, "index.md");
assertThat(pages.size(), is(3));
final String pointer = MarkdownSplitter.llmsPointer("4.0.0");
for (final MarkdownSplitter.Page p : pages) {
assertThat(p.getFileName(), p.getContent().startsWith(pointer + MarkdownConverter.LICENSE_COMMENT), is(true));
assertThat(p.getContent().indexOf("Licensed to the Apache"),
is(p.getContent().lastIndexOf("Licensed to the Apache")));
}
}

@Test
public void bookWithoutLicenseCommentGetsNone() {
final String md = "preamble\n\n" + heading("io", 1, "IO Reference") + "\nintro\n";
final List<MarkdownSplitter.Page> pages = new MarkdownSplitter(50_000, "4.0.0").split(md, "index.md");
assertThat(pages.get(0).getContent(), not(containsString("Licensed to the Apache")));
}

@Test
public void bookWithNoSummariesIsASinglePage() {
final String md = "preamble\n\n" + heading("io", 1, "IO Reference") + "\nintro\n\n"
Expand Down
1 change: 1 addition & 0 deletions gremlin-js/gremlin-mcp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ yarn-error.log*

# Build output
dist/
gremlin-docs/
build/

# TypeScript
Expand Down
Loading
Loading