Get AppDynamics application model
appd_get_application_modelFetch business transactions, tiers, nodes, and backends for one application in parallel to anchor root cause analysis workflows.
Instructions
Fetch business transactions, tiers, nodes, and backends for one application in parallel. Anchor for nearly all RCA workflows.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| application | Yes | ||
| include | No | Subset of model components to fetch (default: all four). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| summary | Yes | ||
| evidence | No | ||
| entities | Yes | ||
| timeRange | No | ||
| sourceEndpoints | Yes | ||
| pagination | No | ||
| warnings | Yes | ||
| truncated | Yes |
Implementation Reference
- src/tools/getApplicationModel.ts:57-131 (handler)The main tool handler implementation. Defines the ToolRegistration object with name 'appd_get_application_model', wraps the async handler via wrapHandler, fetches business-transactions, tiers, nodes, and backends from the AppDynamics controller API in parallel (concurrency-limited to 4), and returns a structured envelope with all model components.
export const getApplicationModelTool: ToolRegistration = { name: 'appd_get_application_model', profile: 'read', register(server, services) { server.registerTool( 'appd_get_application_model', { title: 'Get AppDynamics application model', description: 'Fetch business transactions, tiers, nodes, and backends for one application in parallel. Anchor for nearly all RCA workflows.', inputSchema: inputShape, outputSchema: envelopeOutputShape, }, wrapHandler<{ application: AppRef; include?: string[] | undefined }, ModelEvidence>( services.log, 'appd_get_application_model', async (input) => { const include = (input.include ?? ALL_COMPONENTS).filter( (c): c is (typeof ALL_COMPONENTS)[number] => (ALL_COMPONENTS as readonly string[]).includes(c), ); const appPath = appRefToPath(input.application); const limit = pLimit(4); const sourceEndpoints: string[] = []; const fetchComponent = async <T>(component: (typeof ALL_COMPONENTS)[number]): Promise<T[]> => { const path = `applications/${appPath}/${component}`; sourceEndpoints.push(`GET /controller/rest/${path}`); const res = await services.controller.get<T[]>(path); return Array.isArray(res.body) ? res.body : []; }; const [bts, tiers, nodes, backends] = await Promise.all([ include.includes('business-transactions') ? limit(() => fetchComponent<BT>('business-transactions')) : Promise.resolve([] as BT[]), include.includes('tiers') ? limit(() => fetchComponent<Tier>('tiers')) : Promise.resolve([] as Tier[]), include.includes('nodes') ? limit(() => fetchComponent<Node>('nodes')) : Promise.resolve([] as Node[]), include.includes('backends') ? limit(() => fetchComponent<Backend>('backends')) : Promise.resolve([] as Backend[]), ]); const entities: EnvelopeEntity[] = [ { kind: 'application', id: input.application }, ...tiers .filter((t): t is Tier & { id: number } => typeof t.id === 'number') .map((t) => ({ kind: 'tier' as const, id: t.id, ...(t.name ? { name: t.name } : {}) })), ...nodes .filter((n): n is Node & { id: number } => typeof n.id === 'number') .map((n) => ({ kind: 'node' as const, id: n.id, ...(n.name ? { name: n.name } : {}) })), ]; return toToolResult( buildEnvelope({ summary: `Application model: ${bts.length} BTs, ${tiers.length} tiers, ${nodes.length} nodes, ${backends.length} backends.`, evidence: { application: input.application, bts, tiers, nodes, backends, } as ModelEvidence, entities, sourceEndpoints, }), ); }, ), ); }, }; - Input schema definition: expects 'application' (appRefSchema: string name or numeric id) and optional 'include' array selecting which model components to fetch (business-transactions, tiers, nodes, backends).
const inputShape = { application: appRefSchema, include: z .array(z.enum(['business-transactions', 'tiers', 'nodes', 'backends'])) .optional() .describe('Subset of model components to fetch (default: all four).'), }; - src/tools/getApplicationModel.ts:57-131 (registration)Registration: exported 'getApplicationModelTool' of type ToolRegistration with name 'appd_get_application_model', profile 'read', and a register() method that calls server.registerTool().
export const getApplicationModelTool: ToolRegistration = { name: 'appd_get_application_model', profile: 'read', register(server, services) { server.registerTool( 'appd_get_application_model', { title: 'Get AppDynamics application model', description: 'Fetch business transactions, tiers, nodes, and backends for one application in parallel. Anchor for nearly all RCA workflows.', inputSchema: inputShape, outputSchema: envelopeOutputShape, }, wrapHandler<{ application: AppRef; include?: string[] | undefined }, ModelEvidence>( services.log, 'appd_get_application_model', async (input) => { const include = (input.include ?? ALL_COMPONENTS).filter( (c): c is (typeof ALL_COMPONENTS)[number] => (ALL_COMPONENTS as readonly string[]).includes(c), ); const appPath = appRefToPath(input.application); const limit = pLimit(4); const sourceEndpoints: string[] = []; const fetchComponent = async <T>(component: (typeof ALL_COMPONENTS)[number]): Promise<T[]> => { const path = `applications/${appPath}/${component}`; sourceEndpoints.push(`GET /controller/rest/${path}`); const res = await services.controller.get<T[]>(path); return Array.isArray(res.body) ? res.body : []; }; const [bts, tiers, nodes, backends] = await Promise.all([ include.includes('business-transactions') ? limit(() => fetchComponent<BT>('business-transactions')) : Promise.resolve([] as BT[]), include.includes('tiers') ? limit(() => fetchComponent<Tier>('tiers')) : Promise.resolve([] as Tier[]), include.includes('nodes') ? limit(() => fetchComponent<Node>('nodes')) : Promise.resolve([] as Node[]), include.includes('backends') ? limit(() => fetchComponent<Backend>('backends')) : Promise.resolve([] as Backend[]), ]); const entities: EnvelopeEntity[] = [ { kind: 'application', id: input.application }, ...tiers .filter((t): t is Tier & { id: number } => typeof t.id === 'number') .map((t) => ({ kind: 'tier' as const, id: t.id, ...(t.name ? { name: t.name } : {}) })), ...nodes .filter((n): n is Node & { id: number } => typeof n.id === 'number') .map((n) => ({ kind: 'node' as const, id: n.id, ...(n.name ? { name: n.name } : {}) })), ]; return toToolResult( buildEnvelope({ summary: `Application model: ${bts.length} BTs, ${tiers.length} tiers, ${nodes.length} nodes, ${backends.length} backends.`, evidence: { application: input.application, bts, tiers, nodes, backends, } as ModelEvidence, entities, sourceEndpoints, }), ); }, ), ); }, }; - src/tools/index.ts:7-38 (registration)The tool is imported from getApplicationModel.ts and included in the ALL_TOOLS array, which is passed to registerTools() for centralized registration with the MCP server.
import { getApplicationModelTool } from './getApplicationModel.js'; import { getDependencyMapTool } from './getDependencyMap.js'; import { getEventsTool } from './getEvents.js'; import { getHealthRuleViolationsTool } from './getHealthRuleViolations.js'; import { getMetricHierarchyTool } from './getMetricHierarchy.js'; import { getTransactionSnapshotsTool } from './getTransactionSnapshots.js'; import { listApplicationsTool } from './listApplications.js'; import { listHealthRulesTool } from './listHealthRules.js'; import { queryAnalyticsEventsTool } from './queryAnalyticsEvents.js'; import { queryMetricsTool } from './queryMetrics.js'; export const ALL_TOOLS: ToolRegistration[] = [ listApplicationsTool, getApplicationModelTool, getMetricHierarchyTool, queryMetricsTool, getTransactionSnapshotsTool, getHealthRuleViolationsTool, getAnomalyViolationsTool, getEventsTool, listHealthRulesTool, getAlertingConfigTool, queryAnalyticsEventsTool, getDependencyMapTool, ]; export function registerAllTools( server: McpServer, services: Services, ): { registered: string[]; skipped: string[] } { return registerTools(server, services, ALL_TOOLS); } - src/tools/_common.ts:28-62 (helper)wrapHandler utility used to wrap the handler, catching HttpErrors and unexpected errors and converting them to structured error envelopes.
export function wrapHandler<I, R>( log: Logger, toolName: string, handler: (input: I) => Promise<DualToolResult<R>>, ): (input: I) => Promise<DualToolResult<R> | (DualToolResult<R> & { isError: true })> { return async (input) => { try { return await handler(input); } catch (err) { if (err instanceof HttpError) { log.warn( { tool: toolName, kind: err.kind, statusCode: err.statusCode, sourceEndpoint: err.sourceEndpoint, }, 'tool: HttpError', ); return toErrorResult({ summary: `${toolName} failed: ${err.kind}${err.statusCode ? ` (HTTP ${err.statusCode})` : ''}. ${err.hint ?? ''}`.trim(), evidence: { kind: err.kind, message: err.message, hint: err.hint } as unknown as R, sourceEndpoints: [err.sourceEndpoint], }); } log.error({ tool: toolName, err }, 'tool: unexpected error'); const message = err instanceof Error ? err.message : String(err); return toErrorResult({ summary: `${toolName} failed: unexpected error: ${message}`, evidence: { kind: 'internal', message } as unknown as R, }); } }; }