This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch gremlin-mcp
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit f9a1cec15be0ed48a62dc607fbbc0f7a0e6c1435
Author: Stephen Mallette <[email protected]>
AuthorDate: Thu Oct 9 13:26:33 2025 -0400

    Improved tool definitions
---
 docs/src/reference/gremlin-applications.asciidoc   |  3 +-
 gremlin-mcp/src/main/javascript/README.md          |  7 +-
 .../src/main/javascript/src/handlers/tools.ts      | 76 +++++++++++++---------
 3 files changed, 50 insertions(+), 36 deletions(-)

diff --git a/docs/src/reference/gremlin-applications.asciidoc 
b/docs/src/reference/gremlin-applications.asciidoc
index ca3a21a71d..f6e1ffdd63 100644
--- a/docs/src/reference/gremlin-applications.asciidoc
+++ b/docs/src/reference/gremlin-applications.asciidoc
@@ -3031,8 +3031,7 @@ input schema, and a result schema. When connected to a 
Gremlin MCP server, the a
 The Gremlin MCP server sits alongside Gremlin Server (or any 
TinkerPop‑compatible endpoint) and forwards tool calls to
 the graph via standard Gremlin traversals.
 
-IMPORTANT: Gremlin MCP is currently available for experimental use only. It is 
under active development and its features
-may change.
+IMPORTANT: This MCP server is designed for development and trusted 
environments.
 
 WARNING: Gremlin MCP can modify the graph to which it is connected. To prevent 
such changes, ensure that Gremlin MCP is
 configured to work against a read-only instance of the graph. Gremlin Server 
hosted graphs can configure their graph
diff --git a/gremlin-mcp/src/main/javascript/README.md 
b/gremlin-mcp/src/main/javascript/README.md
index 7751e49a90..ee68d40c45 100644
--- a/gremlin-mcp/src/main/javascript/README.md
+++ b/gremlin-mcp/src/main/javascript/README.md
@@ -367,6 +367,7 @@ GREMLIN_SCHEMA_INCLUDE_COUNTS="true"          # Include 
vertex/edge counts in sc
 - 🔒 Use behind a firewall in production
 - 🔑 Enable strong authentication on your Gremlin server
 - 📊 Monitor query patterns and resource usage
+- 👓 Consider using a read-only graph configuration if you do not expect or 
desire mutations
 - 🛡️ Consider a query proxy for additional security controls
 - 🔄 Keep dependencies updated
 
@@ -484,11 +485,7 @@ The server implements intelligent schema discovery with 
enumeration detection:
 
 ### Contributing
 
-1. Follow the Apache TinkerPop [contribution 
guidelines](https://github.com/apache/tinkerpop/blob/master/CONTRIBUTING.asciidoc)
-2. Run `npm run validate` before committing
-3. Add tests for new functionality
-4. Update documentation for user-facing changes
-5. Ensure all tests pass
+Follow the Apache TinkerPop [contribution 
guidelines](https://github.com/apache/tinkerpop/blob/master/CONTRIBUTING.asciidoc)
 
 ### Testing Strategy
 
diff --git a/gremlin-mcp/src/main/javascript/src/handlers/tools.ts 
b/gremlin-mcp/src/main/javascript/src/handlers/tools.ts
index a331bd8c70..b64f7869c8 100644
--- a/gremlin-mcp/src/main/javascript/src/handlers/tools.ts
+++ b/gremlin-mcp/src/main/javascript/src/handlers/tools.ts
@@ -42,12 +42,43 @@ import {
  * Input validation schemas for tool parameters.
  */
 
-const exportInputSchema = z.object({
-  traversal_query: z.string(),
-  format: z.enum(['graphson', 'json', 'csv']),
-  max_depth: z.number().optional(),
-  include_properties: z.array(z.string()).optional(),
-  exclude_properties: z.array(z.string()).optional(),
+const exportInputBase = z.object({
+  traversal_query: z
+    .string()
+    .min(1, 'traversal_query must not be empty')
+    .max(10000, 'traversal_query is too long')
+    .describe(
+      'Gremlin traversal query to define the subgraph that will normally use 
the subgraph() step to gather data'
+    ),
+  format: z
+    .enum(['graphson', 'json', 'csv'])
+    .default('graphson')
+    .describe('The output format for the exported data'),
+  include_properties: z
+    .array(z.string().min(1))
+    .optional()
+    .describe('Properties to include in the export'),
+  exclude_properties: z
+    .array(z.string().min(1))
+    .optional()
+    .describe('Properties to exclude from the export'),
+});
+
+const exportInputSchema = exportInputBase.refine(
+  ({ include_properties, exclude_properties }) => {
+    if (!include_properties || !exclude_properties) return true;
+    const s = new Set(include_properties);
+    return !exclude_properties.some(p => s.has(p));
+  },
+  { message: 'include_properties and exclude_properties must not overlap' }
+);
+
+// Parameterless tools: strict empty object
+const emptyInputSchema = z.object({}).strict();
+
+// Run Gremlin Query input
+const runQueryInputSchema = z.object({
+  query: z.string().min(1).max(10000).describe('The Gremlin query to execute'),
 });
 
 /**
@@ -72,7 +103,7 @@ export function registerEffectToolHandlers(
     {
       title: 'Get Graph Status',
       description: 'Get the connection status of the Gremlin graph database',
-      inputSchema: {},
+      inputSchema: emptyInputSchema.shape,
     },
     () =>
       Effect.runPromise(
@@ -95,7 +126,7 @@ export function registerEffectToolHandlers(
       title: 'Get Graph Schema',
       description:
         'Get the complete schema of the graph including vertex labels, edge 
labels, and relationship patterns',
-      inputSchema: {},
+      inputSchema: emptyInputSchema.shape,
     },
     () =>
       Effect.runPromise(
@@ -115,7 +146,7 @@ export function registerEffectToolHandlers(
     {
       title: 'Refresh Schema Cache',
       description: 'Force an immediate refresh of the graph schema cache',
-      inputSchema: {},
+      inputSchema: emptyInputSchema.shape,
     },
     () =>
       Effect.runPromise(
@@ -137,12 +168,10 @@ export function registerEffectToolHandlers(
     {
       title: 'Run Gremlin Query',
       description: 'Execute a Gremlin traversal query against the graph 
database',
-      inputSchema: {
-        query: z.string().describe('The Gremlin query to execute'),
-      },
+      inputSchema: runQueryInputSchema.shape,
     },
     (args: unknown) => {
-      const { query } = z.object({ query: z.string() }).parse(args);
+      const { query } = runQueryInputSchema.parse(args);
       return Effect.runPromise(pipe(createQueryEffect(query), 
Effect.provide(runtime)));
     }
   );
@@ -153,28 +182,17 @@ export function registerEffectToolHandlers(
     {
       title: 'Export Subgraph',
       description: 'Export a subgraph based on a traversal query to various 
formats',
-      inputSchema: {
-        traversal_query: z.string().describe('Gremlin traversal query to 
define the subgraph'),
-        format: z
-          .enum(['graphson', 'json', 'csv'])
-          .describe('The output format for the exported data'),
-        max_depth: z.number().optional().describe('Maximum traversal depth for 
the subgraph'),
-        include_properties: z
-          .array(z.string())
-          .optional()
-          .describe('Properties to include in the export'),
-        exclude_properties: z
-          .array(z.string())
-          .optional()
-          .describe('Properties to exclude from the export'),
-      },
+      inputSchema: exportInputBase.shape,
     },
     (args: unknown) =>
       Effect.runPromise(
         pipe(
           createValidatedToolEffect(
             exportInputSchema,
-            input => Effect.andThen(GremlinService, service => 
exportSubgraph(service, input)),
+            rawInput => {
+              const input = exportInputBase.parse(rawInput);
+              return Effect.andThen(GremlinService, service => 
exportSubgraph(service, input));
+            },
             'Export Subgraph'
           )(args),
           Effect.provide(runtime)

Reply via email to