@heroku/mcp-server
Version:
Heroku Platform MCP Server
111 lines (110 loc) • 4.34 kB
JavaScript
import { z } from 'zod';
import { handleCliOutput } from '../utils/handle-cli-output.js';
import { CommandBuilder } from '../utils/command-builder.js';
import { TOOL_COMMAND_MAP } from '../utils/tool-commands.js';
/**
* Schema for listing Heroku apps with filters
*/
export const listAppsOptionsSchema = z.object({
all: z.boolean().optional().describe('Show owned apps and collaborator access. Default: owned only'),
personal: z.boolean().optional().describe('List personal account apps only, ignoring default team'),
space: z.string().optional().describe('Filter by private space name. Excludes team param'),
team: z.string().optional().describe('Filter by team name. Excludes space param')
});
/**
* Register list_apps tool
*
* @param server MCP server instance
* @param herokuRepl Heroku REPL instance
*/
export const registerListAppsTool = (server, herokuRepl) => {
server.tool('list_apps', 'List Heroku apps: owned, collaborator access, team/space filtering', listAppsOptionsSchema.shape, async (options) => {
const command = new CommandBuilder(TOOL_COMMAND_MAP.LIST_APPS)
.addFlags({
all: options.all,
personal: options.personal,
space: options.space,
team: options.team
})
.build();
const output = await herokuRepl.executeCommand(command);
return handleCliOutput(output);
});
};
/**
* Schema for app info retrieval
*/
export const getAppInfoOptionsSchema = z.object({
app: z.string().describe('Target app name. Requires access permissions'),
json: z.boolean().optional().describe('JSON output with full metadata. Default: text format')
});
/**
* Register get_app_info tool
*
* @param server MCP server instance
* @param herokuRepl Heroku REPL instance
*/
export const registerGetAppInfoTool = (server, herokuRepl) => {
server.tool('get_app_info', 'Get app details: config, dynos, addons, access, domains', getAppInfoOptionsSchema.shape, async (options) => {
const command = new CommandBuilder(TOOL_COMMAND_MAP.GET_APP_INFO)
.addFlags({
app: options.app,
json: options.json
})
.build();
const output = await herokuRepl.executeCommand(command);
return handleCliOutput(output);
});
};
/**
* Schema for app creation
*/
export const createAppOptionsSchema = z.object({
app: z.string().optional().describe('App name. Auto-generated if omitted'),
region: z.enum(['us', 'eu']).optional().describe('Region: us/eu. Default: us. Excludes space param'),
space: z.string().optional().describe('Private space name. Inherits region. Excludes region param'),
team: z.string().optional().describe('Team name for ownership')
});
/**
* Register create_app tool
*
* @param server MCP server instance
* @param herokuRepl Heroku REPL instance
*/
export const registerCreateAppTool = (server, herokuRepl) => {
server.tool('create_app', 'Create app: custom name, region (US/EU), team, private space', createAppOptionsSchema.shape, async (options) => {
const command = new CommandBuilder(TOOL_COMMAND_MAP.CREATE_APP)
.addPositionalArguments({ app: options.app })
.addFlags({
region: options.region,
space: options.space,
team: options.team
})
.build();
const output = await herokuRepl.executeCommand(command);
return handleCliOutput(output);
});
};
/**
* Schema for app rename
*/
export const renameAppOptionsSchema = z.object({
app: z.string().describe('Current app name. Requires access'),
newName: z.string().describe('New unique app name')
});
/**
* Register rename_app tool
*
* @param server MCP server instance
* @param herokuRepl Heroku REPL instance
*/
export const registerRenameAppTool = (server, herokuRepl) => {
server.tool('rename_app', 'Rename app: validate and update app name', renameAppOptionsSchema.shape, async (options) => {
const command = new CommandBuilder(TOOL_COMMAND_MAP.RENAME_APP)
.addFlags({ app: options.app })
.addPositionalArguments({ newName: options.newName })
.build();
const output = await herokuRepl.executeCommand(command);
return handleCliOutput(output);
});
};