Skip to content
Closed
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
31 changes: 18 additions & 13 deletions genkit-tools/cli/src/mcp/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import path from 'path';
import z from 'zod';
import { version } from '../utils/version';
import { McpRunToolEvent } from './analytics.js';
import { enrichToolDescription } from './utils.js';

const DOCS_URL =
process.env.GENKIT_DOCS_BUNDLE_URL ??
Expand Down Expand Up @@ -71,24 +72,28 @@ export async function defineDocsTool(server: McpServer) {
readFileSync(DOCS_BUNDLE_FILE_PATH, { encoding: 'utf8' })
) as Record<string, Doc>;

const inputSchema = {
language: z
.enum(['js', 'go', 'python'])
.describe('which language these docs are for (default js).')
.default('js'),
files: z
.array(z.string())
.describe(
'Specific docs files to look up. If empty or not specified an index will be returned. Always lookup index first for exact file names.'
)
.optional(),
};

server.registerTool(
'lookup_genkit_docs',
{
title: 'Genkit Docs',
description:
description: enrichToolDescription(
'Use this to look up documentation for the Genkit AI framework.',
inputSchema: {
language: z
.enum(['js', 'go', 'python'])
.describe('which language these docs are for (default js).')
.default('js'),
files: z
.array(z.string())
.describe(
'Specific docs files to look up. If empty or not specified an index will be returned. Always lookup index first for exact file names.'
)
.optional(),
},
inputSchema
),
inputSchema,
},
async ({ language, files }) => {
await record(new McpRunToolEvent('lookup_genkit_docs'));
Expand Down
32 changes: 20 additions & 12 deletions genkit-tools/cli/src/mcp/flows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@ import z from 'zod';
import { McpRunToolEvent } from './analytics.js';
import {
McpToolOptions,
enrichToolDescription,
getCommonSchema,
resolveProjectRoot,
} from './utils.js';

export function defineFlowTools(server: McpServer, options: McpToolOptions) {
const listFlowsSchema = getCommonSchema(options.explicitProjectRoot);
server.registerTool(
'list_flows',
{
title: 'List Genkit Flows',
description:
description: enrichToolDescription(
'Use this to discover available Genkit flows or inspect the input schema of Genkit flows to know how to successfully call them.',
inputSchema: getCommonSchema(options.explicitProjectRoot),
listFlowsSchema
),
inputSchema: listFlowsSchema,
},
async (opts) => {
await record(new McpRunToolEvent('list_flows'));
Expand Down Expand Up @@ -64,20 +68,24 @@ export function defineFlowTools(server: McpServer, options: McpToolOptions) {
}
);

const runFlowSchema = getCommonSchema(options.explicitProjectRoot, {
flowName: z.string().describe('name of the flow'),
input: z
.string()
.describe(
'Flow input as JSON object encoded as string (it will be passed through `JSON.parse`). Must conform to the schema.'
)
.optional(),
});
server.registerTool(
'run_flow',
{
title: 'Run Flow',
description: 'Runs the flow with the provided input',
inputSchema: getCommonSchema(options.explicitProjectRoot, {
flowName: z.string().describe('name of the flow'),
input: z
.string()
.describe(
'Flow input as JSON object encoded as string (it will be passed through `JSON.parse`). Must conform to the schema.'
)
.optional(),
}),
description: enrichToolDescription(
'Runs the flow with the provided input',
runFlowSchema
),
inputSchema: runFlowSchema,
},
async (opts) => {
await record(new McpRunToolEvent('run_flow'));
Expand Down
32 changes: 21 additions & 11 deletions genkit-tools/cli/src/mcp/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,35 @@ import { z } from 'zod';
import { McpRunToolEvent } from './analytics.js';
import {
McpToolOptions,
enrichToolDescription,
getCommonSchema,
resolveProjectRoot,
} from './utils.js';

export function defineRuntimeTools(server: McpServer, options: McpToolOptions) {
const startRuntimeSchema = getCommonSchema(options.explicitProjectRoot, {
command: z.string().describe('The command to run'),
args: z
.array(z.string())
.describe(
'The array of string args for the command to run. Eg: `["run", "dev"]`.'
),
});

server.registerTool(
'start_runtime',
{
title: 'Starts a Genkit runtime process',
description: `Use this to start a Genkit runtime process (This is typically the entry point to the users app). Once started, the runtime will be picked up by the \`genkit start\` command to power the Dev UI features like model and flow playgrounds. The inputSchema for this tool matches the function prototype for \`NodeJS.child_process.spawn\`.
description: enrichToolDescription(
`Use this to start a Genkit runtime process (This is typically the entry point to the users app). Once started, the runtime will be picked up by the \`genkit start\` command to power the Dev UI features like model and flow playgrounds. The inputSchema for this tool matches the function prototype for \`NodeJS.child_process.spawn\`.

Examples:
{command: "go", args: ["run", "main.go"]}
{command: "npm", args: ["run", "dev"]}
{command: "npm", args: ["run", "dev"], projectRoot: "path/to/project"}`,
inputSchema: getCommonSchema(options.explicitProjectRoot, {
command: z.string().describe('The command to run'),
args: z
.array(z.string())
.describe(
'The array of string args for the command to run. Eg: `["run", "dev"]`.'
),
}),
startRuntimeSchema
),
inputSchema: startRuntimeSchema,
},
async (opts) => {
await record(new McpRunToolEvent('start_runtime'));
Expand Down Expand Up @@ -94,12 +100,16 @@ export function defineRuntimeTools(server: McpServer, options: McpToolOptions) {
title: string,
action: 'kill' | 'restart'
) => {
const inputSchema = getCommonSchema(options.explicitProjectRoot);
server.registerTool(
name,
{
title,
description: `Use this to ${action} an existing runtime that was started using the \`start_runtime\` tool`,
inputSchema: getCommonSchema(options.explicitProjectRoot),
description: enrichToolDescription(
`Use this to ${action} an existing runtime that was started using the \`start_runtime\` tool`,
inputSchema
),
inputSchema,
},
async (opts) => {
await record(new McpRunToolEvent(name));
Expand Down
21 changes: 13 additions & 8 deletions genkit-tools/cli/src/mcp/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,28 @@ import z from 'zod';
import { McpRunToolEvent } from './analytics.js';
import {
McpToolOptions,
enrichToolDescription,
getCommonSchema,
resolveProjectRoot,
} from './utils.js';

export function defineTraceTools(server: McpServer, options: McpToolOptions) {
const inputSchema = getCommonSchema(options.explicitProjectRoot, {
traceId: z
.string()
.describe(
'trace id (typically returned after running a flow or other actions)'
),
});
server.registerTool(
'get_trace',
{
title: 'Get Genkit Trace',
description: 'Returns the trace details',
inputSchema: getCommonSchema(options.explicitProjectRoot, {
traceId: z
.string()
.describe(
'trace id (typically returned after running a flow or other actions)'
),
}),
description: enrichToolDescription(
'Returns the trace details.',
inputSchema
),
inputSchema,
},
async (opts) => {
await record(new McpRunToolEvent('get_trace'));
Expand Down
21 changes: 13 additions & 8 deletions genkit-tools/cli/src/mcp/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,29 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { ContentBlock } from '@modelcontextprotocol/sdk/types';
import z from 'zod';
import { McpRunToolEvent } from './analytics.js';
import { enrichToolDescription } from './utils.js';

import { GENKIT_CONTEXT as GoContext } from '../commands/init-ai-tools/context/go.js';
import { GENKIT_CONTEXT as JsContext } from '../commands/init-ai-tools/context/nodejs.js';

export async function defineUsageGuideTool(server: McpServer) {
const inputSchema = {
language: z
.enum(['js', 'go'])
.describe('which language this usage guide is for')
.default('js')
.optional(),
};

server.registerTool(
'get_usage_guide',
{
title: 'Genkit Instructions',
description:
description: enrichToolDescription(
'Use this tool to look up the Genkit usage guide before implementing any AI feature',
inputSchema: {
language: z
.enum(['js', 'go'])
.describe('which language this usage guide is for')
.default('js')
.optional(),
},
inputSchema
),
inputSchema,
},
async ({ language }) => {
await record(new McpRunToolEvent('get_usage_guide'));
Expand Down
25 changes: 20 additions & 5 deletions genkit-tools/cli/src/mcp/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,37 @@ export interface McpToolOptions {
manager: McpRuntimeManager;
}

export const PROJECT_ROOT_DESCRIPTION =
'The path to the current project root (a.k.a workspace directory or project directory)';

export function getCommonSchema(
explicitProjectRoot: boolean,
shape: z.ZodRawShape = {}
): z.ZodRawShape {
return !explicitProjectRoot
? shape
: {
projectRoot: z
.string()
.describe(
'The path to the current project root (a.k.a workspace directory or project directory)'
),
projectRoot: z.string().describe(PROJECT_ROOT_DESCRIPTION),
...shape,
};
}

export function enrichToolDescription(
baseDescription: string,
schema: z.ZodRawShape
): string {
const args = Object.entries(schema)
.map(([key, value]) => {
const desc = value.description;
return desc ? `${key}: ${desc}` : key;
})
.join('; ');

if (args.length === 0) return baseDescription;

return `${baseDescription} Arguments: ${args}`;
Comment on lines +47 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation joins argument descriptions with a semicolon, which can be hard to read, especially with longer descriptions. Using a markdown list would improve readability for both developers and LLMs that might consume this description.

This change formats the arguments as a markdown list, which is clearer and more structured.

  const entries = Object.entries(schema);
  if (entries.length === 0) {
    return baseDescription;
  }

  const argsList = entries
    .map(([key, value]) => {
      const desc = value.description;
      return `\n- ${key}${desc ? `: ${desc}` : ''}`;
    })
    .join('');

  return `${baseDescription}\n\nArguments:${argsList}`;

}

export function resolveProjectRoot(
explicitProjectRoot: boolean,
opts: {
Expand Down
Loading