Best practices for authoring Genkit tooling, including CLI commands and MCP server tools. Covers naming conventions, architectural patterns, and consistency guidelines.
Consistency in naming helps users and agents navigate the tooling.
Use kebab-case with colon separators for subcommands.
noun:verb or category:actionflow:run, eval:run, initflowName) but standard format in help text (<flowName>).Use snake_case for tool names to align with MCP standards.
verb_nounlist_flows, run_flow, list_genkit_docs, read_genkit_docsCommands are implemented in cli/src/commands/ using commander.
Most commands require interacting with the user's project runtime. Use the runWithManager utility to handle the lifecycle of the runtime process.
import { runWithManager } from '../utils/manager-utils';
// ... command definition ...
.action(async (arg, options) => {
await runWithManager(await findProjectRoot(), async (manager) => {
// Interact with manager here
const result = await manager.runAction({ key: arg });
});
});
logger from @genkit-ai/tools-common/utils.flow:run), provide a --stream flag and pipe output to stdout.MCP tools in cli/src/mcp/ follow two distinct patterns: Static and Runtime.
These tools do not require a running Genkit project context.
defineDocsTool(server: McpServer)server instance.These tools interact with a specific Genkit project's runtime.
defineRuntimeTools(server: McpServer, options: McpToolOptions)options containing manager (process manager) and projectRoot.getCommonSchema(options.explicitProjectRoot, ...) to ensure the tool can accept a projectRoot argument when required (e.g., in multi-project environments).// Runtime tool definition pattern
server.registerTool(
'my_runtime_tool',
{
inputSchema: getCommonSchema(options.explicitProjectRoot, {
myArg: z.string(),
}),
},
async (opts) => {
// Resolve project root before action
const rootOrError = resolveProjectRoot(
options.explicitProjectRoot,
opts,
options.projectRoot
);
if (typeof rootOrError !== 'string') return rootOrError;
// access manager via options.manager
}
);
MCP tools should generally catch errors and return them as content blocks with isError: true rather than throwing exceptions, which ensures the client receives a structured error response.
try {
// operation
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return {
isError: true,
content: [{ type: 'text', text: `Error: ${message}` }],
};
}