diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml
new file mode 100644
index 00000000..b323fab4
--- /dev/null
+++ b/.github/workflows/copilot-setup-steps.yml
@@ -0,0 +1,44 @@
+name: 'Copilot Setup Steps'
+
+# Run automatically when changed, and allow manual validation from the Actions tab.
+on:
+ workflow_dispatch:
+ push:
+ paths:
+ - .github/workflows/copilot-setup-steps.yml
+ pull_request:
+ paths:
+ - .github/workflows/copilot-setup-steps.yml
+
+jobs:
+ # Must be named exactly `copilot-setup-steps` for Copilot to pick it up.
+ copilot-setup-steps:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install bun
+ uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
+
+ - name: Install dependencies
+ run: bun install
+
+ - name: Install pre-commit format hook for Copilot agent
+ run: |
+ mkdir -p .git/copilot-hooks
+ cat > .git/copilot-hooks/pre-commit << 'EOF'
+ #!/bin/sh
+ # Auto-format all staged files before committing.
+ # Runs nx format:write (Prettier) and re-stages any files it changed.
+ if command -v bunx >/dev/null 2>&1; then
+ bunx nx format:write --uncommitted
+ else
+ npx nx format:write --uncommitted
+ fi
+ git update-index --again
+ EOF
+ chmod +x .git/copilot-hooks/pre-commit
diff --git a/.husky/pre-commit b/.husky/pre-commit
index d87790a9..9279dff7 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,3 +1,7 @@
-npx nx format:write --uncommitted
+if command -v bunx >/dev/null 2>&1; then
+ bunx nx format:write --uncommitted
+else
+ npx nx format:write --uncommitted
+fi
git update-index --again
diff --git a/packages/adt-mcp/src/lib/mock/fixtures.ts b/packages/adt-mcp/src/lib/mock/fixtures.ts
index 3adba5c5..055eb280 100644
--- a/packages/adt-mcp/src/lib/mock/fixtures.ts
+++ b/packages/adt-mcp/src/lib/mock/fixtures.ts
@@ -101,6 +101,19 @@ export const fixtures = {
},
},
+ // Transport create response – returned for POST /sap/bc/adt/cts/transportrequests
+ transportCreate: {
+ root: {
+ request: {
+ trkorr: 'DEVK900099',
+ as4text: 'New transport',
+ as4user: 'DEVELOPER',
+ trstatus: 'D',
+ trfunction: 'K',
+ },
+ },
+ },
+
atcRun: {
worklistId: 'WL_001',
id: 'RUN_001',
@@ -177,4 +190,276 @@ export const fixtures = {
],
},
},
+
+ // Grep / content search results – returned for GET .../informationsystem/search?userannotation=userwhere
+ grepResults: {
+ objectReference: [
+ {
+ name: 'ZCL_EXAMPLE',
+ type: 'CLAS/OC',
+ uri: '/sap/bc/adt/oo/classes/zcl_example',
+ description: 'Example class',
+ packageName: 'ZPACKAGE',
+ },
+ ],
+ },
+
+ // DDIC table definition – returned for GET /sap/bc/adt/ddic/tables/{name}
+ tableDefinition: {
+ blueSource: {
+ name: 'MARA',
+ description: 'General Material Data',
+ type: 'TABL/DT',
+ element: [
+ { name: 'MANDT', type: 'CLNT', length: '3', description: 'Client' },
+ {
+ name: 'MATNR',
+ type: 'CHAR',
+ length: '18',
+ description: 'Material Number',
+ },
+ {
+ name: 'MBRSH',
+ type: 'CHAR',
+ length: '1',
+ description: 'Industry Sector',
+ },
+ ],
+ },
+ },
+
+ // Data preview result – returned for POST /sap/bc/adt/datapreview/freestyle
+ tableContents: {
+ columns: {
+ column: [
+ { name: 'MANDT', type: 'C', length: '3' },
+ { name: 'MATNR', type: 'C', length: '18' },
+ { name: 'MBRSH', type: 'C', length: '1' },
+ ],
+ },
+ rows: {
+ row: [
+ {
+ cell: [
+ { _text: '100' },
+ { _text: 'Z_EXAMPLE_MATERIAL' },
+ { _text: 'A' },
+ ],
+ },
+ ],
+ },
+ },
+
+ // Navigation target – returned for GET /sap/bc/adt/navigation/target
+ navigationTarget: {
+ objectReference: {
+ uri: '/sap/bc/adt/oo/classes/zcl_example',
+ type: 'CLAS/OC',
+ name: 'ZCL_EXAMPLE',
+ description: 'Example class',
+ },
+ },
+
+ // Usages / references – returned for GET .../informationsystem/usages
+ usagesResult: {
+ usages: {
+ usage: [
+ {
+ uri: '/sap/bc/adt/programs/programs/zprog_example',
+ name: 'ZPROG_EXAMPLE',
+ type: 'PROG',
+ location: 'line 42',
+ },
+ ],
+ },
+ },
+
+ // Call hierarchy callers – returned for GET .../informationsystem/callers
+ callersResult: {
+ callers: {
+ caller: [
+ {
+ uri: '/sap/bc/adt/programs/programs/zprog_main',
+ name: 'ZPROG_MAIN',
+ type: 'PROG',
+ },
+ ],
+ },
+ },
+
+ // Call hierarchy callees – returned for GET .../informationsystem/callees
+ calleesResult: {
+ callees: {
+ callee: [
+ {
+ uri: '/sap/bc/adt/functions/groups/zfugr_util',
+ name: 'ZFUGR_UTIL',
+ type: 'FUGR',
+ },
+ ],
+ },
+ },
+
+ // Inactive objects – returned for GET /sap/bc/adt/activation/inactive_objects
+ inactiveObjects: {
+ objectReference: [
+ {
+ name: 'ZCL_EXAMPLE',
+ type: 'CLAS/OC',
+ uri: '/sap/bc/adt/oo/classes/zcl_example',
+ description: 'Example class',
+ },
+ ],
+ },
+
+ // Function group metadata – returned for GET /sap/bc/adt/functions/groups/{name}
+ functionGroup: {
+ abapFunctionGroup: {
+ name: 'ZFUGR_UTIL',
+ type: 'FUGR',
+ description: 'Utility function group',
+ language: 'EN',
+ masterLanguage: 'EN',
+ packageRef: { uri: '/sap/bc/adt/packages/zpackage' },
+ },
+ },
+
+ // Function module metadata – returned for GET /sap/bc/adt/functions/groups/{g}/fmodules/{fm}
+ functionModule: {
+ abapFunctionModule: {
+ name: 'Z_MY_FUNCTION',
+ type: 'FUGR/FF',
+ description: 'My utility function module',
+ processingType: 'normal',
+ remoteEnabledMode: 'notRemoteEnabled',
+ parameters: {
+ importParameters: {
+ parameter: [
+ {
+ name: 'IV_INPUT',
+ type: 'TYPE',
+ associatedType: 'STRING',
+ optional: true,
+ },
+ ],
+ },
+ exportParameters: {
+ parameter: [
+ {
+ name: 'EV_OUTPUT',
+ type: 'TYPE',
+ associatedType: 'STRING',
+ },
+ ],
+ },
+ },
+ },
+ },
+
+ // Object structure – returned for GET {objectUri}/objectstructure
+ objectStructure: {
+ objectStructure: {
+ objectReference: {
+ uri: '/sap/bc/adt/oo/classes/zcl_example',
+ type: 'CLAS/OC',
+ name: 'ZCL_EXAMPLE',
+ description: 'Example class',
+ },
+ includes: {
+ include: [
+ {
+ uri: '/sap/bc/adt/oo/classes/zcl_example/includes/definitions',
+ type: 'CLAS/OC/D',
+ name: 'ZCL_EXAMPLE',
+ },
+ {
+ uri: '/sap/bc/adt/oo/classes/zcl_example/includes/implementations',
+ type: 'CLAS/OC/M',
+ name: 'ZCL_EXAMPLE',
+ },
+ ],
+ },
+ },
+ },
+
+ // Type hierarchy – returned for GET /sap/bc/adt/oo/typeinfo
+ typeHierarchy: {
+ typeInfo: {
+ objectReference: {
+ uri: '/sap/bc/adt/oo/classes/zcl_example',
+ type: 'CLAS/OC',
+ name: 'ZCL_EXAMPLE',
+ },
+ superClasses: {
+ superClass: [
+ {
+ uri: '/sap/bc/adt/oo/classes/object',
+ type: 'CLAS/OC',
+ name: 'OBJECT',
+ },
+ ],
+ },
+ interfaces: {
+ interface: [],
+ },
+ },
+ },
+
+ // Pretty-printed source – returned for POST /sap/bc/adt/prettyprinter/prettifySource
+ prettySource:
+ 'CLASS zcl_example DEFINITION PUBLIC FINAL CREATE PUBLIC.\n PUBLIC SECTION.\n METHODS: do_something.\nENDCLASS.\n',
+
+ // Installed software components – GET /sap/bc/adt/system/softwarecomponents
+ softwareComponents: {
+ softwareComponents: {
+ softwareComponent: [
+ {
+ name: 'SAP_BASIS',
+ release: '756',
+ patchLevel: '0012',
+ description: 'SAP Basis Component',
+ },
+ {
+ name: 'SAP_ABA',
+ release: '756',
+ patchLevel: '0012',
+ description: 'Cross-Application Component',
+ },
+ ],
+ },
+ },
+
+ // abapGit exportable objects – GET /sap/bc/adt/abapgit/objects
+ gitObjects: {
+ abapgitObjects: {
+ object: [
+ {
+ name: 'ZCL_EXAMPLE',
+ type: 'CLAS',
+ uri: '/sap/bc/adt/oo/classes/zcl_example',
+ },
+ {
+ name: 'ZPROG_EXAMPLE',
+ type: 'PROG',
+ uri: '/sap/bc/adt/programs/programs/zprog_example',
+ },
+ ],
+ },
+ },
+
+ // abapGit export – GET /sap/bc/adt/abapgit/repos/{name}/export
+ gitExport: {
+ files: [
+ {
+ path: 'src/zcl_example.clas.abap',
+ content:
+ 'CLASS zcl_example DEFINITION PUBLIC FINAL CREATE PUBLIC.\nENDCLASS.',
+ },
+ {
+ path: 'src/zcl_example.clas.xml',
+ content:
+ '',
+ },
+ ],
+ },
};
diff --git a/packages/adt-mcp/src/lib/mock/server.ts b/packages/adt-mcp/src/lib/mock/server.ts
index 50ef44c3..a168cce1 100644
--- a/packages/adt-mcp/src/lib/mock/server.ts
+++ b/packages/adt-mcp/src/lib/mock/server.ts
@@ -59,7 +59,20 @@ function matchRoute(
};
}
- // Quick search
+ // Grep / content search (userannotation=userwhere) – must come before general search
+ if (
+ m === 'GET' &&
+ url.startsWith('/sap/bc/adt/repository/informationsystem/search') &&
+ url.includes('userannotation=userwhere')
+ ) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.grepResults),
+ contentType: 'application/json',
+ };
+ }
+
+ // Quick search (general – name pattern)
if (
m === 'GET' &&
url.startsWith('/sap/bc/adt/repository/informationsystem/search')
@@ -71,6 +84,91 @@ function matchRoute(
};
}
+ // Usages / find-references
+ if (
+ m === 'GET' &&
+ url.startsWith('/sap/bc/adt/repository/informationsystem/usages')
+ ) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.usagesResult),
+ contentType: 'application/json',
+ };
+ }
+
+ // Call hierarchy – callers
+ if (
+ m === 'GET' &&
+ url.startsWith('/sap/bc/adt/repository/informationsystem/callers')
+ ) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.callersResult),
+ contentType: 'application/json',
+ };
+ }
+
+ // Call hierarchy – callees
+ if (
+ m === 'GET' &&
+ url.startsWith('/sap/bc/adt/repository/informationsystem/callees')
+ ) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.calleesResult),
+ contentType: 'application/json',
+ };
+ }
+
+ // Navigation target – find definition
+ if (m === 'GET' && url.startsWith('/sap/bc/adt/navigation/target')) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.navigationTarget),
+ contentType: 'application/json',
+ };
+ }
+
+ // Data preview – get_table_contents and run_query
+ if (m === 'POST' && url.startsWith('/sap/bc/adt/datapreview/freestyle')) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.tableContents),
+ contentType: 'application/json',
+ };
+ }
+
+ // DDIC tables – get_table (specific path, before generic DDIC)
+ if (m === 'GET' && url.startsWith('/sap/bc/adt/ddic/tables/')) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.tableDefinition),
+ contentType: 'application/json',
+ };
+ }
+
+ // CTS – create transport
+ if (
+ m === 'POST' &&
+ url.startsWith('/sap/bc/adt/cts/transportrequests') &&
+ !url.includes('/sap/bc/adt/cts/transportrequests/')
+ ) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.transportCreate),
+ contentType: 'application/json',
+ };
+ }
+
+ // CTS – release transport (_action=RELEASE)
+ if (
+ m === 'POST' &&
+ /\/sap\/bc\/adt\/cts\/transportrequests\/\w+/.test(url) &&
+ url.includes('_action=RELEASE')
+ ) {
+ return { status: 200, body: '', contentType: 'text/plain' };
+ }
+
// CTS – list transports
if (
m === 'GET' &&
@@ -160,6 +258,18 @@ function matchRoute(
return { status: 200, body: '', contentType: 'text/plain' };
}
+ // Inactive objects – GET /sap/bc/adt/activation/inactive_objects
+ if (
+ m === 'GET' &&
+ url.startsWith('/sap/bc/adt/activation/inactive_objects')
+ ) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.inactiveObjects),
+ contentType: 'application/json',
+ };
+ }
+
// Activation – POST /sap/bc/adt/activation
if (m === 'POST' && url.startsWith('/sap/bc/adt/activation')) {
return {
@@ -169,6 +279,30 @@ function matchRoute(
};
}
+ // Object create – POST to object-type paths (programs, classes, interfaces, functions, packages)
+ if (
+ m === 'POST' &&
+ (url.startsWith('/sap/bc/adt/programs/programs') ||
+ url.startsWith('/sap/bc/adt/oo/classes') ||
+ url.startsWith('/sap/bc/adt/oo/interfaces') ||
+ url.startsWith('/sap/bc/adt/functions/groups') ||
+ url.startsWith('/sap/bc/adt/packages'))
+ ) {
+ return { status: 200, body: '', contentType: 'text/plain' };
+ }
+
+ // Object delete – DELETE to object-type paths
+ if (
+ m === 'DELETE' &&
+ (url.startsWith('/sap/bc/adt/programs/programs/') ||
+ url.startsWith('/sap/bc/adt/oo/classes/') ||
+ url.startsWith('/sap/bc/adt/oo/interfaces/') ||
+ url.startsWith('/sap/bc/adt/functions/groups/') ||
+ url.startsWith('/sap/bc/adt/packages/'))
+ ) {
+ return { status: 204, body: '', contentType: 'text/plain' };
+ }
+
// Syntax check – POST /sap/bc/adt/checkruns
if (m === 'POST' && url.startsWith('/sap/bc/adt/checkruns')) {
return {
@@ -187,6 +321,127 @@ function matchRoute(
};
}
+ // Function module source – GET .../fmodules/{name}/source/main
+ if (
+ m === 'GET' &&
+ url.includes('/fmodules/') &&
+ url.includes('/source/main')
+ ) {
+ return {
+ status: 200,
+ body: fixtures.sourceCode,
+ contentType: 'text/plain',
+ };
+ }
+
+ // Function modules metadata – GET /sap/bc/adt/functions/groups/{g}/fmodules/{fm}
+ if (m === 'GET' && url.includes('/fmodules/')) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.functionModule),
+ contentType: 'application/json',
+ };
+ }
+
+ // Function group source – GET /sap/bc/adt/functions/groups/{name}/source/main
+ if (
+ m === 'GET' &&
+ url.startsWith('/sap/bc/adt/functions/groups/') &&
+ url.includes('/source/main')
+ ) {
+ return {
+ status: 200,
+ body: fixtures.sourceCode,
+ contentType: 'text/plain',
+ };
+ }
+
+ // Function group metadata – GET /sap/bc/adt/functions/groups/{name}
+ // Must exclude objectstructure, source, and fmodule sub-paths
+ if (
+ m === 'GET' &&
+ url.startsWith('/sap/bc/adt/functions/groups/') &&
+ !url.includes('/objectstructure') &&
+ !url.includes('/source/') &&
+ !url.includes('/fmodules/')
+ ) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.functionGroup),
+ contentType: 'application/json',
+ };
+ }
+
+ // Object structure – GET {objectUri}/objectstructure
+ if (m === 'GET' && url.includes('/objectstructure')) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.objectStructure),
+ contentType: 'application/json',
+ };
+ }
+
+ // Type hierarchy – GET /sap/bc/adt/oo/typeinfo
+ if (m === 'GET' && url.startsWith('/sap/bc/adt/oo/typeinfo')) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.typeHierarchy),
+ contentType: 'application/json',
+ };
+ }
+
+ // Pretty printer – POST /sap/bc/adt/prettyprinter/prettifySource
+ if (
+ m === 'POST' &&
+ url.startsWith('/sap/bc/adt/prettyprinter/prettifySource')
+ ) {
+ return {
+ status: 200,
+ body: fixtures.prettySource,
+ contentType: 'text/plain',
+ };
+ }
+
+ // Software components – GET /sap/bc/adt/system/softwarecomponents
+ if (m === 'GET' && url.startsWith('/sap/bc/adt/system/softwarecomponents')) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.softwareComponents),
+ contentType: 'application/json',
+ };
+ }
+
+ // Service binding publish – POST/DELETE /sap/bc/adt/businessservices/bindings/{name}/publishedstates
+ if (
+ (m === 'POST' || m === 'DELETE') &&
+ url.includes('/sap/bc/adt/businessservices/bindings/') &&
+ url.includes('/publishedstates')
+ ) {
+ return { status: 200, body: '', contentType: 'text/plain' };
+ }
+
+ // abapGit exportable objects – GET /sap/bc/adt/abapgit/objects
+ if (m === 'GET' && url.startsWith('/sap/bc/adt/abapgit/objects')) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.gitObjects),
+ contentType: 'application/json',
+ };
+ }
+
+ // abapGit export – GET /sap/bc/adt/abapgit/repos/{name}/export
+ if (
+ m === 'GET' &&
+ url.includes('/sap/bc/adt/abapgit/repos/') &&
+ url.includes('/export')
+ ) {
+ return {
+ status: 200,
+ body: JSON.stringify(fixtures.gitExport),
+ contentType: 'application/json',
+ };
+ }
+
// CSRF token fetch (used by write operations)
if (m === 'HEAD') {
return {
diff --git a/packages/adt-mcp/src/lib/tools/activate-package.ts b/packages/adt-mcp/src/lib/tools/activate-package.ts
new file mode 100644
index 00000000..b59b5d44
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/activate-package.ts
@@ -0,0 +1,118 @@
+/**
+ * Tool: activate_package – batch-activate all inactive objects in a package
+ *
+ * 1. Lists inactive objects in the package via GET /sap/bc/adt/activation/inactive_objects
+ * 2. Activates them all in a single batch POST to /sap/bc/adt/activation
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { extractObjectReferences } from './utils';
+import type { InferTypedSchema } from '@abapify/adt-schemas';
+import { adtcore } from '@abapify/adt-schemas';
+
+type ObjectReferencesBody = Extract<
+ InferTypedSchema,
+ { objectReferences: unknown }
+>;
+
+export function registerActivatePackageTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'activate_package',
+ 'Batch-activate all inactive objects in a package. Returns the count and list of activated objects.',
+ {
+ ...connectionShape,
+ packageName: z.string().describe('ABAP package name (e.g. ZPACKAGE)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const packageName = args.packageName.toUpperCase();
+
+ // Step 1: Get list of inactive objects in the package
+ const params = new URLSearchParams({ packageName });
+ const inactiveResult = await client.fetch(
+ `/sap/bc/adt/activation/inactive_objects?${params.toString()}`,
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ const objects = extractObjectReferences(inactiveResult);
+
+ if (objects.length === 0) {
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: 'no_inactive_objects',
+ packageName,
+ message: 'No inactive objects found in this package',
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ }
+
+ // Step 2: Activate all inactive objects in a single batch call
+ const body: ObjectReferencesBody = {
+ objectReferences: {
+ objectReference: objects.map((o) => ({
+ uri: o.uri ?? '',
+ type: o.type ?? '',
+ name: (o.name ?? '').toUpperCase(),
+ })),
+ },
+ };
+
+ await client.adt.activation.activate.post(
+ { method: 'activate', preauditRequested: true },
+ body,
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: 'activated',
+ packageName,
+ count: objects.length,
+ objects: objects.map((o) => ({
+ name: o.name,
+ type: o.type,
+ uri: o.uri,
+ })),
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Activate package failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/call-hierarchy.ts b/packages/adt-mcp/src/lib/tools/call-hierarchy.ts
new file mode 100644
index 00000000..464a4372
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/call-hierarchy.ts
@@ -0,0 +1,162 @@
+/**
+ * Tools: get_callers_of + get_callees_of – call hierarchy navigation
+ *
+ * get_callers_of: traverse the call hierarchy upward (who calls this
+ * method/function/subroutine).
+ * get_callees_of: traverse the call hierarchy downward (what does this
+ * method/function/subroutine call).
+ *
+ * ADT endpoints:
+ * GET /sap/bc/adt/repository/informationsystem/callers
+ * GET /sap/bc/adt/repository/informationsystem/callees
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUri } from './utils';
+
+const callHierarchyShape = {
+ ...connectionShape,
+ objectName: z
+ .string()
+ .describe('Name of the ABAP object (class, function group, program)'),
+ objectType: z
+ .string()
+ .optional()
+ .describe('Object type (e.g. CLAS, FUGR, PROG)'),
+ objectUri: z
+ .string()
+ .optional()
+ .describe(
+ 'Direct ADT URI of the object (skips name resolution if provided)',
+ ),
+ maxResults: z
+ .number()
+ .optional()
+ .describe('Maximum number of results (default: 50)'),
+};
+
+async function fetchCallHierarchy(
+ client: ReturnType,
+ endpoint: 'callers' | 'callees',
+ objectName: string,
+ objectType: string | undefined,
+ objectUri: string | undefined,
+ maxResults: number,
+): Promise<{ objectUri: string; result: unknown } | null> {
+ const resolvedUri =
+ objectUri ?? (await resolveObjectUri(client, objectName, objectType));
+ if (!resolvedUri) return null;
+
+ const params = new URLSearchParams({
+ objectUri: resolvedUri,
+ maxResults: String(maxResults),
+ });
+
+ const result = await client.fetch(
+ `/sap/bc/adt/repository/informationsystem/${endpoint}?${params.toString()}`,
+ { method: 'GET', headers: { Accept: 'application/json' } },
+ );
+
+ return { objectUri: resolvedUri, result };
+}
+
+type CallHierarchyToolConfig = {
+ endpoint: 'callers' | 'callees';
+ failureLabel: string;
+ resultKey: 'callers' | 'callees';
+ toolDescription: string;
+ toolName: 'get_callers_of' | 'get_callees_of';
+};
+
+function registerCallHierarchyTool(
+ server: McpServer,
+ ctx: ToolContext,
+ config: CallHierarchyToolConfig,
+): void {
+ server.tool(
+ config.toolName,
+ config.toolDescription,
+ callHierarchyShape,
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const res = await fetchCallHierarchy(
+ client,
+ config.endpoint,
+ args.objectName,
+ args.objectType,
+ args.objectUri,
+ args.maxResults ?? 50,
+ );
+ if (!res) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Object '${args.objectName}' not found`,
+ },
+ ],
+ };
+ }
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ objectName: args.objectName,
+ objectUri: res.objectUri,
+ [config.resultKey]: res.result,
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `${config.failureLabel}: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
+
+export function registerGetCallersOfTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ registerCallHierarchyTool(server, ctx, {
+ toolName: 'get_callers_of',
+ toolDescription:
+ 'Find all callers (upward call hierarchy) of an ABAP method, function module, or subroutine',
+ endpoint: 'callers',
+ resultKey: 'callers',
+ failureLabel: 'Get callers failed',
+ });
+}
+
+export function registerGetCalleesOfTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ registerCallHierarchyTool(server, ctx, {
+ toolName: 'get_callees_of',
+ toolDescription:
+ 'Find all callees (downward call hierarchy) of an ABAP method, function module, or subroutine',
+ endpoint: 'callees',
+ resultKey: 'callees',
+ failureLabel: 'Get callees failed',
+ });
+}
diff --git a/packages/adt-mcp/src/lib/tools/clone-object.ts b/packages/adt-mcp/src/lib/tools/clone-object.ts
new file mode 100644
index 00000000..edaac131
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/clone-object.ts
@@ -0,0 +1,249 @@
+/**
+ * Tool: clone_object – copy an ABAP object to a new name
+ *
+ * Creates a new object of the same type, copies the source code from the
+ * original, and optionally activates the clone.
+ *
+ * Supports PROG, CLAS, INTF (source-based objects). For other types falls
+ * back to an error asking for manual creation.
+ *
+ * ADT approach: create new object → copy source → activate clone
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { createLockService } from '@abapify/adt-locks';
+import type { ToolContext } from '../types';
+import {
+ createAdtObject,
+ isSourceBackedObjectType,
+ SOURCE_BACKED_OBJECT_TYPES,
+ type SourceBackedObjectType,
+} from './object-creation';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUri, resolveObjectUriFromType } from './utils';
+
+async function getSourceCode(
+ client: ReturnType,
+ objectType: SourceBackedObjectType,
+ objectName: string,
+): Promise {
+ const name = objectName.toLowerCase();
+ switch (objectType) {
+ case 'PROG':
+ return (await client.adt.programs.programs.source.main.get(
+ name,
+ )) as string;
+ case 'CLAS':
+ return (await client.adt.oo.classes.source.main.get(name)) as string;
+ case 'INTF':
+ return (await client.adt.oo.interfaces.source.main.get(name)) as string;
+ }
+}
+
+async function resolveCloneDescription(
+ client: ReturnType,
+ sourceType: SourceBackedObjectType,
+ sourceName: string,
+ targetDescription?: string,
+): Promise {
+ if (targetDescription) {
+ return targetDescription;
+ }
+
+ const sourceUri = resolveObjectUriFromType(sourceType, sourceName);
+ if (!sourceUri) {
+ return `Copy of ${sourceName}`;
+ }
+
+ try {
+ const meta = (await client.fetch(sourceUri, {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ })) as Record;
+ const desc =
+ (meta as Record>)?.abapClass
+ ?.description ??
+ (meta as Record>)?.abapProgram
+ ?.description ??
+ (meta as Record>)?.abapInterface
+ ?.description;
+
+ return desc ? `Copy of ${String(desc)}` : `Copy of ${sourceName}`;
+ } catch {
+ return `Copy of ${sourceName}`;
+ }
+}
+
+async function copySourceToClone(
+ client: ReturnType,
+ resolvedTargetUri: string,
+ sourceCode: string,
+ targetName: string,
+ sourceType: SourceBackedObjectType,
+ transport?: string,
+): Promise {
+ const lockService = createLockService(client);
+ let lockHandle: string | undefined;
+
+ try {
+ const lockResult = await lockService.lock(resolvedTargetUri, {
+ transport,
+ objectName: targetName,
+ objectType: sourceType,
+ });
+ lockHandle = lockResult.handle;
+
+ const putParams = new URLSearchParams({
+ lockHandle,
+ ...(transport ? { corrNr: transport } : {}),
+ });
+
+ await client.fetch(
+ `${resolvedTargetUri}/source/main?${putParams.toString()}`,
+ {
+ method: 'PUT',
+ headers: { 'Content-Type': 'text/plain' },
+ body: sourceCode,
+ },
+ );
+ } finally {
+ if (lockHandle) {
+ try {
+ await lockService.unlock(resolvedTargetUri, { lockHandle });
+ } catch {
+ // ignore unlock errors in error paths
+ }
+ }
+ }
+}
+
+export function registerCloneObjectTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'clone_object',
+ 'Copy an ABAP object to a new name. Supported types: PROG, CLAS, INTF. Creates the new object and copies the source code.',
+ {
+ ...connectionShape,
+ sourceObjectName: z
+ .string()
+ .describe('Name of the source object to copy'),
+ sourceObjectType: z
+ .string()
+ .describe('Object type of the source: PROG, CLAS, or INTF'),
+ targetObjectName: z.string().describe('Name for the new (cloned) object'),
+ targetDescription: z
+ .string()
+ .optional()
+ .describe(
+ 'Description for the clone (defaults to source description with "Copy of" prefix)',
+ ),
+ targetPackage: z
+ .string()
+ .optional()
+ .describe('Package for the clone (defaults to same package as source)'),
+ transport: z
+ .string()
+ .optional()
+ .describe('Transport request number for the clone'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const sourceType = args.sourceObjectType.toUpperCase().split('/')[0];
+ const sourceName = args.sourceObjectName.toUpperCase();
+ const targetName = args.targetObjectName.toUpperCase();
+
+ if (!isSourceBackedObjectType(sourceType)) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Object type '${sourceType}' is not supported for cloning. Supported types: ${SOURCE_BACKED_OBJECT_TYPES.join(', ')}`,
+ },
+ ],
+ };
+ }
+
+ const description = await resolveCloneDescription(
+ client,
+ sourceType,
+ sourceName,
+ args.targetDescription,
+ );
+
+ // 2. Get source code
+ const sourceCode = await getSourceCode(client, sourceType, sourceName);
+
+ // 3. Create the target object
+ await createAdtObject(client, {
+ objectType: sourceType,
+ objectName: targetName,
+ description,
+ packageName: args.targetPackage,
+ transport: args.transport,
+ });
+
+ const resolvedTargetUri =
+ resolveObjectUriFromType(sourceType, targetName) ??
+ (await resolveObjectUri(client, targetName, sourceType));
+
+ if (!resolvedTargetUri) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Could not resolve URI for target object '${targetName}'`,
+ },
+ ],
+ };
+ }
+
+ await copySourceToClone(
+ client,
+ resolvedTargetUri,
+ sourceCode,
+ targetName,
+ sourceType,
+ args.transport,
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: 'cloned',
+ sourceObject: { name: sourceName, type: sourceType },
+ targetObject: {
+ name: targetName,
+ type: sourceType,
+ description,
+ uri: resolvedTargetUri,
+ },
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Clone object failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/create-object.ts b/packages/adt-mcp/src/lib/tools/create-object.ts
new file mode 100644
index 00000000..b35ea3ae
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/create-object.ts
@@ -0,0 +1,109 @@
+/**
+ * Tool: create_object – create a new ABAP object
+ *
+ * Supports creating the most common ABAP object types using the typed ADT contracts:
+ * PROG (program), CLAS (class), INTF (interface), FUGR (function group), DEVC (package)
+ *
+ * Uses the respective typed CRUD contract for each object type so that all
+ * XML serialisation goes through the schema pipeline (no manual XML building).
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import {
+ createAdtObject,
+ CREATE_OBJECT_TYPES,
+ isCreateObjectType,
+} from './object-creation';
+import { connectionShape } from './shared-schemas';
+
+export function registerCreateObjectTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'create_object',
+ 'Create a new ABAP object. Supported types: PROG (program), CLAS (class), INTF (interface), FUGR (function group), and DEVC (package).',
+ {
+ ...connectionShape,
+ objectName: z
+ .string()
+ .describe(
+ 'Name of the new object (uppercase, e.g. ZCL_MY_CLASS, ZPACKAGE)',
+ ),
+ objectType: z
+ .string()
+ .describe('Object type: PROG, CLAS, INTF, FUGR, or DEVC'),
+ description: z.string().describe('Short description of the object'),
+ packageName: z
+ .string()
+ .optional()
+ .describe(
+ 'Package to assign the object to (required for non-local objects)',
+ ),
+ transport: z
+ .string()
+ .optional()
+ .describe(
+ 'Transport request number (required for transportable objects)',
+ ),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const objectType = args.objectType.toUpperCase();
+ const objectName = args.objectName.toUpperCase();
+
+ if (!isCreateObjectType(objectType)) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Object type '${objectType}' is not supported. Supported types: ${CREATE_OBJECT_TYPES.join(', ')}`,
+ },
+ ],
+ };
+ }
+
+ await createAdtObject(client, {
+ objectType,
+ objectName,
+ description: args.description,
+ packageName: args.packageName,
+ transport: args.transport,
+ });
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: 'created',
+ objectName,
+ objectType,
+ description: args.description,
+ packageName: args.packageName?.toUpperCase(),
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Create object failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/create-package.ts b/packages/adt-mcp/src/lib/tools/create-package.ts
new file mode 100644
index 00000000..4c6a12d9
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/create-package.ts
@@ -0,0 +1,112 @@
+/**
+ * Tool: create_package – create a new ABAP development package (DEVC)
+ *
+ * Dedicated tool for package creation with package-specific options:
+ * - packageType: 'development' (default), 'structure', or 'main'
+ * - parentPackage: super-package URI
+ * - Transport (optional, omit for local $TMP packages)
+ *
+ * ADT endpoint: POST /sap/bc/adt/packages
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerCreatePackageTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'create_package',
+ 'Create a new ABAP development package (DEVC). Omit transport for local ($TMP) packages.',
+ {
+ ...connectionShape,
+ packageName: z
+ .string()
+ .describe('Package name (e.g. ZPACKAGE, $TMP_TEST)'),
+ description: z.string().describe('Short description of the package'),
+ parentPackage: z
+ .string()
+ .optional()
+ .describe(
+ 'Parent package name (e.g. ZROOT). If omitted the package is created at the top level.',
+ ),
+ packageType: z
+ .enum(['development', 'structure', 'main'])
+ .optional()
+ .describe('Package type (default: development)'),
+ transport: z
+ .string()
+ .optional()
+ .describe(
+ 'Transport request number. Omit for local packages (e.g. $-prefixed names).',
+ ),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const packageName = args.packageName.toUpperCase();
+ const packageType = args.packageType ?? 'development';
+
+ const queryOptions = args.transport ? { corrNr: args.transport } : {};
+
+ const pkgBody = {
+ package: {
+ name: packageName,
+ type: 'DEVC/K',
+ description: args.description,
+ language: 'EN',
+ masterLanguage: 'EN',
+ attributes: { packageType },
+ superPackage: args.parentPackage
+ ? {
+ uri: `/sap/bc/adt/packages/${args.parentPackage.toUpperCase()}`,
+ }
+ : {},
+ extensionAlias: {},
+ switch: {},
+ applicationComponent: {},
+ transport: {},
+ translation: {},
+ useAccesses: {},
+ packageInterfaces: {},
+ subPackages: {},
+ },
+ };
+
+ await client.adt.packages.post(queryOptions, pkgBody);
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: 'created',
+ packageName,
+ packageType,
+ description: args.description,
+ parentPackage: args.parentPackage?.toUpperCase(),
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Create package failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/cts-create-transport.ts b/packages/adt-mcp/src/lib/tools/cts-create-transport.ts
index 27451f33..10d0014e 100644
--- a/packages/adt-mcp/src/lib/tools/cts-create-transport.ts
+++ b/packages/adt-mcp/src/lib/tools/cts-create-transport.ts
@@ -3,18 +3,36 @@
*
* CLI equivalent: `adt cts tr create`
*
- * Note: The underlying ADT client transport service `create()` method is not
- * yet implemented. This tool returns a clear error until it is.
+ * Uses the transportrequests.create() contract with the transportmanagmentCreate schema
+ * to build and POST the XML request body.
*/
import { z } from 'zod';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { ToolContext } from '../types';
import { connectionShape } from './shared-schemas';
+import type { InferTypedSchema } from '@abapify/adt-schemas';
+import { transportmanagmentCreate } from '@abapify/adt-schemas';
+
+type CreateBody = InferTypedSchema;
+
+function getRecord(value: unknown): Record | undefined {
+ return value && typeof value === 'object'
+ ? (value as Record)
+ : undefined;
+}
+
+function getStringField(
+ value: Record | undefined,
+ key: string,
+): string | undefined {
+ const rawValue = value?.[key];
+ return typeof rawValue === 'string' ? rawValue : undefined;
+}
export function registerCtsCreateTransportTool(
server: McpServer,
- _ctx: ToolContext,
+ ctx: ToolContext,
): void {
server.tool(
'cts_create_transport',
@@ -28,19 +46,65 @@ export function registerCtsCreateTransportTool(
.describe(
'Transport type: K (Workbench) or W (Customizing). Default: K',
),
- target: z.string().optional().describe('Target system (default: LOCAL)'),
+ target: z.string().optional().describe('Target system'),
project: z.string().optional().describe('CTS project name'),
},
- async (_args) => {
- return {
- isError: true,
- content: [
- {
- type: 'text' as const,
- text: 'Create transport is not supported: the underlying ADT client transport service "create" method is not yet implemented.',
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ const body: CreateBody = {
+ root: {
+ request: {
+ desc: args.description,
+ type: args.type ?? 'K',
+ ...(args.target ? { target: args.target } : {}),
+ ...(args.project ? { cts_project: args.project } : {}),
+ },
},
- ],
- };
+ };
+
+ const response = await client.adt.cts.transportrequests.create(body);
+
+ // Extract transport number from response
+ const data = getRecord(response);
+ const request =
+ getRecord(getRecord(data?.root)?.request) ??
+ getRecord(data?.request) ??
+ data;
+ const transportNumber =
+ getStringField(request, 'trkorr') ??
+ getStringField(request, 'number') ??
+ '';
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: 'created',
+ transport: transportNumber,
+ description: args.description,
+ type: args.type ?? 'K',
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Create transport failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
},
);
}
diff --git a/packages/adt-mcp/src/lib/tools/cts-release-transport.ts b/packages/adt-mcp/src/lib/tools/cts-release-transport.ts
index ee68c427..9b9c40f8 100644
--- a/packages/adt-mcp/src/lib/tools/cts-release-transport.ts
+++ b/packages/adt-mcp/src/lib/tools/cts-release-transport.ts
@@ -3,8 +3,7 @@
*
* CLI equivalent: `adt cts tr release `
*
- * Note: The underlying ADT client transport service `release()` method is not
- * yet implemented. This tool returns a clear error until it is.
+ * POSTs to /sap/bc/adt/cts/transportrequests/{trkorr}?_action=RELEASE
*/
import { z } from 'zod';
@@ -14,7 +13,7 @@ import { connectionShape } from './shared-schemas';
export function registerCtsReleaseTransportTool(
server: McpServer,
- _ctx: ToolContext,
+ ctx: ToolContext,
): void {
server.tool(
'cts_release_transport',
@@ -25,16 +24,45 @@ export function registerCtsReleaseTransportTool(
.string()
.describe('Transport number to release (e.g. S0DK900001)'),
},
- async (_args) => {
- return {
- isError: true,
- content: [
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ await client.fetch(
+ `/sap/bc/adt/cts/transportrequests/${args.transport}?_action=RELEASE`,
{
- type: 'text' as const,
- text: 'Transport release is not supported: the ADT client transports.release() method is not yet implemented.',
+ method: 'POST',
+ headers: {
+ 'Content-Type':
+ 'application/vnd.sap.adt.transportorganizer.v1+xml',
+ Accept: 'application/vnd.sap.adt.transportorganizer.v1+xml',
+ },
},
- ],
- };
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ { status: 'released', transport: args.transport },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Release transport failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
},
);
}
diff --git a/packages/adt-mcp/src/lib/tools/delete-object.ts b/packages/adt-mcp/src/lib/tools/delete-object.ts
new file mode 100644
index 00000000..aec3c802
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/delete-object.ts
@@ -0,0 +1,140 @@
+/**
+ * Tool: delete_object – delete an ABAP object
+ *
+ * Deletes an ABAP object using the appropriate typed CRUD contract.
+ * Supports the same types as create_object: PROG, CLAS, INTF, FUGR, DEVC.
+ *
+ * For other object types, falls back to resolving the URI and using
+ * a direct DELETE request.
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUri } from './utils';
+
+type AdtClient = ReturnType;
+type QueryOptions = { corrNr?: string };
+type DeleteOperation = (
+ client: AdtClient,
+ objectName: string,
+ queryOptions: QueryOptions,
+) => Promise;
+
+const deleteOperations: Record = {
+ PROG: (client, objectName, queryOptions) =>
+ client.adt.programs.programs.delete(objectName.toLowerCase(), queryOptions),
+ CLAS: (client, objectName, queryOptions) =>
+ client.adt.oo.classes.delete(objectName.toLowerCase(), queryOptions),
+ INTF: (client, objectName, queryOptions) =>
+ client.adt.oo.interfaces.delete(objectName.toLowerCase(), queryOptions),
+ FUGR: (client, objectName, queryOptions) =>
+ client.adt.functions.groups.delete(objectName.toLowerCase(), queryOptions),
+ DEVC: (client, objectName, queryOptions) =>
+ client.adt.packages.delete(objectName, queryOptions),
+};
+
+function getDeleteOperation(objectType?: string): DeleteOperation | undefined {
+ return objectType ? deleteOperations[objectType] : undefined;
+}
+
+async function deleteByResolvedUri(
+ client: AdtClient,
+ objectName: string,
+ objectType: string | undefined,
+ transport: string | undefined,
+): Promise {
+ const uri = await resolveObjectUri(client, objectName, objectType);
+ if (!uri) {
+ return false;
+ }
+
+ const params = new URLSearchParams();
+ if (transport) {
+ params.set('corrNr', transport);
+ }
+
+ const queryString = params.toString();
+ const deleteUri = queryString ? `${uri}?${queryString}` : uri;
+ await client.fetch(deleteUri, { method: 'DELETE' });
+ return true;
+}
+
+export function registerDeleteObjectTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'delete_object',
+ 'Delete an ABAP object. Supports PROG, CLAS, INTF, FUGR, DEVC and falls back to direct URI deletion for other types.',
+ {
+ ...connectionShape,
+ objectName: z.string().describe('Name of the ABAP object to delete'),
+ objectType: z
+ .string()
+ .optional()
+ .describe('Object type (e.g. CLAS, PROG, INTF, FUGR, DEVC)'),
+ transport: z
+ .string()
+ .optional()
+ .describe(
+ 'Transport request number (required for transportable objects)',
+ ),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const objectName = args.objectName.toUpperCase();
+ const objectType = args.objectType?.toUpperCase();
+ const queryOptions = args.transport ? { corrNr: args.transport } : {};
+
+ const deleteOperation = getDeleteOperation(objectType);
+ if (deleteOperation) {
+ await deleteOperation(client, objectName, queryOptions);
+ } else {
+ const deleted = await deleteByResolvedUri(
+ client,
+ objectName,
+ objectType,
+ args.transport,
+ );
+ if (!deleted) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Object '${objectName}' not found`,
+ },
+ ],
+ };
+ }
+ }
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ { status: 'deleted', objectName, objectType },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Delete object failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/find-definition.ts b/packages/adt-mcp/src/lib/tools/find-definition.ts
new file mode 100644
index 00000000..26dcae1b
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/find-definition.ts
@@ -0,0 +1,110 @@
+/**
+ * Tool: find_definition – navigate to the definition of an ABAP symbol
+ *
+ * Uses the ADT navigation endpoint to resolve where a symbol (class, method,
+ * data element, etc.) is defined.
+ *
+ * ADT endpoint: GET /sap/bc/adt/navigation/target
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUri } from './utils';
+
+export function registerFindDefinitionTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'find_definition',
+ 'Navigate to the definition of an ABAP symbol (class, method, type, function module, etc.)',
+ {
+ ...connectionShape,
+ objectName: z
+ .string()
+ .describe('Name of the symbol or object to navigate to'),
+ objectType: z
+ .string()
+ .optional()
+ .describe(
+ 'Object type to narrow the search (e.g. CLAS, PROG, DTEL, TABL)',
+ ),
+ parentObjectName: z
+ .string()
+ .optional()
+ .describe(
+ 'Parent object name (e.g. class name when looking for a method)',
+ ),
+ parentObjectType: z
+ .string()
+ .optional()
+ .describe('Parent object type (e.g. CLAS)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ const params = new URLSearchParams({
+ objectName: args.objectName,
+ });
+
+ if (args.objectType) params.set('objectType', args.objectType);
+ if (args.parentObjectName) params.set('context', args.parentObjectName);
+ if (args.parentObjectType)
+ params.set('contextType', args.parentObjectType);
+
+ const result = await client.fetch(
+ `/sap/bc/adt/navigation/target?${params.toString()}`,
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ // If result is empty, try resolving via search
+ if (!result) {
+ const uri = await resolveObjectUri(
+ client,
+ args.objectName,
+ args.objectType,
+ );
+ if (uri) {
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ { objectName: args.objectName, uri },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ }
+ }
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(result, null, 2),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Find definition failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/find-references.ts b/packages/adt-mcp/src/lib/tools/find-references.ts
new file mode 100644
index 00000000..d9ce904d
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/find-references.ts
@@ -0,0 +1,109 @@
+/**
+ * Tool: find_references – find all usages (where-used) of an ABAP symbol
+ *
+ * Uses the ADT repository information system usages endpoint to find
+ * all references to a given object or symbol.
+ *
+ * ADT endpoint: GET /sap/bc/adt/repository/informationsystem/usages
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUri } from './utils';
+
+export function registerFindReferencesTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'find_references',
+ 'Find all usages (where-used) of an ABAP object or symbol. Returns a list of locations where the object is referenced.',
+ {
+ ...connectionShape,
+ objectName: z
+ .string()
+ .describe('Name of the ABAP object to find references for'),
+ objectType: z
+ .string()
+ .optional()
+ .describe('Object type (e.g. CLAS, PROG, DTEL, TABL)'),
+ objectUri: z
+ .string()
+ .optional()
+ .describe(
+ 'Direct ADT URI of the object (skips name resolution if provided)',
+ ),
+ maxResults: z
+ .number()
+ .optional()
+ .describe('Maximum number of results (default: 100)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const maxResults = args.maxResults ?? 100;
+
+ // Resolve the object URI
+ let objectUri = args.objectUri;
+ if (!objectUri) {
+ objectUri = await resolveObjectUri(
+ client,
+ args.objectName,
+ args.objectType,
+ );
+ if (!objectUri) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Object '${args.objectName}' not found`,
+ },
+ ],
+ };
+ }
+ }
+
+ const params = new URLSearchParams({
+ objectUri,
+ objectName: args.objectName,
+ maxResults: String(maxResults),
+ });
+ if (args.objectType) params.set('objectType', args.objectType);
+
+ const result = await client.fetch(
+ `/sap/bc/adt/repository/informationsystem/usages?${params.toString()}`,
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ { objectName: args.objectName, objectUri, results: result },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Find references failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/function-tools.ts b/packages/adt-mcp/src/lib/tools/function-tools.ts
new file mode 100644
index 00000000..853821e0
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/function-tools.ts
@@ -0,0 +1,142 @@
+/**
+ * Tools: get_function_group + get_function – ABAP function group and module access
+ *
+ * get_function_group: retrieve function group metadata and optionally source.
+ * get_function: retrieve function module metadata and optionally source.
+ *
+ * ADT endpoints:
+ * GET /sap/bc/adt/functions/groups/{groupName}
+ * GET /sap/bc/adt/functions/groups/{groupName}/source/main
+ * GET /sap/bc/adt/functions/groups/{groupName}/fmodules/{fmName}
+ * GET /sap/bc/adt/functions/groups/{groupName}/fmodules/{fmName}/source/main
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+function formatMetadataResult(
+ metadata: unknown,
+ source: string | undefined,
+): string {
+ return JSON.stringify(
+ source === undefined ? { metadata } : { metadata, source },
+ null,
+ 2,
+ );
+}
+
+export function registerGetFunctionGroupTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_function_group',
+ 'Read ABAP function group metadata (description, includes). Optionally includes source code.',
+ {
+ ...connectionShape,
+ groupName: z.string().describe('Function group name (e.g. ZFUGR_UTIL)'),
+ includeSource: z
+ .boolean()
+ .optional()
+ .describe(
+ 'Whether to also return the main include source code (default: false)',
+ ),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const name = args.groupName.toLowerCase();
+
+ const metadata = await client.adt.functions.groups.get(name);
+
+ let source: string | undefined;
+ if (args.includeSource) {
+ source = (await client.adt.functions.groups.source.main.get(
+ name,
+ )) as string;
+ }
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: formatMetadataResult(metadata, source),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get function group failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
+
+export function registerGetFunctionTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_function',
+ 'Read ABAP function module metadata (parameters, exceptions) and optionally its source code.',
+ {
+ ...connectionShape,
+ groupName: z.string().describe('Function group name (e.g. ZFUGR_UTIL)'),
+ functionName: z
+ .string()
+ .describe('Function module name (e.g. Z_MY_FUNCTION)'),
+ includeSource: z
+ .boolean()
+ .optional()
+ .describe('Whether to also return the source code (default: false)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const groupName = args.groupName.toLowerCase();
+ const fmName = args.functionName.toLowerCase();
+
+ const metadata = await client.adt.functions.groups.fmodules.get(
+ groupName,
+ fmName,
+ );
+
+ let source: string | undefined;
+ if (args.includeSource) {
+ source = (await client.fetch(
+ `/sap/bc/adt/functions/groups/${groupName}/fmodules/${fmName}/source/main`,
+ { method: 'GET', headers: { Accept: 'text/plain' } },
+ )) as string;
+ }
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: formatMetadataResult(metadata, source),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get function failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/get-installed-components.ts b/packages/adt-mcp/src/lib/tools/get-installed-components.ts
new file mode 100644
index 00000000..ec45db85
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/get-installed-components.ts
@@ -0,0 +1,155 @@
+/**
+ * Tool: get_installed_components – list installed SAP software components
+ *
+ * Returns the list of software components installed on the SAP system
+ * together with their versions and release information.
+ *
+ * ADT endpoint: GET /sap/bc/adt/system/softwarecomponents
+ */
+
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerGetInstalledComponentsTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_installed_components',
+ 'List all software components installed on the SAP system with their version and release information.',
+ {
+ ...connectionShape,
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ const result = await client.fetch(
+ '/sap/bc/adt/system/softwarecomponents',
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(result, null, 2),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get installed components failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
+
+/**
+ * Tool: get_features – probe the SAP system for available ADT features
+ *
+ * Checks the discovery endpoint and probes system-specific endpoints to
+ * determine which features are available: abapGit, RAP, AMDP, UI5, ATC, etc.
+ *
+ * ADT endpoint: GET /sap/bc/adt/discovery (augmented with feature probing)
+ */
+export function registerGetFeaturesTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_features',
+ 'Probe the SAP system for available ADT features (abapGit, RAP, AMDP, UI5, ATC, CTS, etc.).',
+ {
+ ...connectionShape,
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ // Fetch the discovery document to understand available services
+ const discovery = (await client.fetch('/sap/bc/adt/discovery', {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ })) as {
+ workspaces?: Array<{
+ title: string;
+ collections?: Array<{ href: string; title: string }>;
+ }>;
+ };
+
+ // Extract all service paths from the discovery document
+ const services = new Set();
+ if (discovery?.workspaces) {
+ for (const ws of discovery.workspaces) {
+ for (const col of ws.collections ?? []) {
+ services.add(col.href);
+ }
+ }
+ }
+
+ // Feature detection heuristics based on known ADT paths
+ const serviceList = [...services];
+ const features: Record = {
+ atc:
+ services.has('/sap/bc/adt/atc') ||
+ serviceList.some((s) => s.includes('/atc')),
+ cts: serviceList.some((s) => s.includes('/cts')),
+ aunit: serviceList.some(
+ (s) => s.includes('/abapunit') || s.includes('/aunit'),
+ ),
+ abapgit: serviceList.some((s) => s.includes('/abapgit')),
+ rap: serviceList.some(
+ (s) => s.includes('/businessservices') || s.includes('/rap'),
+ ),
+ ui5: serviceList.some(
+ (s) => s.includes('/ui5') || s.includes('/bsp'),
+ ),
+ classicBadi: serviceList.some((s) => s.includes('/enhancements')),
+ prettyPrinter: serviceList.some((s) => s.includes('/prettyprinter')),
+ dataPreview: serviceList.some((s) => s.includes('/datapreview')),
+ navigation: serviceList.some((s) => s.includes('/navigation')),
+ };
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ features,
+ discoveryServices: [...services].sort((a, b) =>
+ a.localeCompare(b),
+ ),
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get features failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/get-object-structure.ts b/packages/adt-mcp/src/lib/tools/get-object-structure.ts
new file mode 100644
index 00000000..19b2c8a9
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/get-object-structure.ts
@@ -0,0 +1,115 @@
+/**
+ * Tool: get_object_structure – retrieve the structural tree of an ABAP object
+ *
+ * Returns the object explorer tree including includes, methods, attributes,
+ * and other sub-elements depending on the object type.
+ *
+ * ADT endpoint: GET {objectUri}/objectstructure
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUriFromType, resolveObjectUri } from './utils';
+
+/**
+ * Dispatch to the appropriate typed CRUD contract's objectstructure() method
+ * based on the object type, or fall back to a raw fetch if unknown.
+ */
+async function fetchObjectStructure(
+ client: ReturnType,
+ objectName: string,
+ objectType: string | undefined,
+ version?: 'active' | 'inactive',
+): Promise {
+ const name = objectName.toLowerCase();
+ const type = objectType?.toUpperCase().split('/')[0];
+
+ const structureOptions = version ? { version } : {};
+
+ switch (type) {
+ case 'PROG':
+ return client.adt.programs.programs.objectstructure(
+ name,
+ structureOptions,
+ );
+ case 'CLAS':
+ return client.adt.oo.classes.objectstructure(name, structureOptions);
+ case 'INTF':
+ return client.adt.oo.interfaces.objectstructure(name, structureOptions);
+ case 'FUGR':
+ return client.adt.functions.groups.objectstructure(
+ name,
+ structureOptions,
+ );
+ default: {
+ // Generic fallback: resolve URI and fetch /objectstructure
+ const uri =
+ type && resolveObjectUriFromType(type, objectName)
+ ? resolveObjectUriFromType(type, objectName)
+ : await resolveObjectUri(client, objectName, objectType);
+
+ if (!uri) throw new Error(`Object '${objectName}' not found`);
+
+ const params = version ? `?version=${version}` : '';
+ return client.fetch(`${uri}/objectstructure${params}`, {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ });
+ }
+ }
+}
+
+export function registerGetObjectStructureTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_object_structure',
+ 'Get the structural tree of an ABAP object (includes, methods, attributes, sub-components).',
+ {
+ ...connectionShape,
+ objectName: z.string().describe('ABAP object name'),
+ objectType: z
+ .string()
+ .optional()
+ .describe('Object type (e.g. CLAS, PROG, INTF, FUGR)'),
+ version: z
+ .enum(['active', 'inactive'])
+ .optional()
+ .describe('Object version to inspect (default: active)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ const result = await fetchObjectStructure(
+ client,
+ args.objectName,
+ args.objectType,
+ args.version,
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(result, null, 2),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get object structure failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/get-table-contents.ts b/packages/adt-mcp/src/lib/tools/get-table-contents.ts
new file mode 100644
index 00000000..c1db0ad5
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/get-table-contents.ts
@@ -0,0 +1,95 @@
+/**
+ * Tool: get_table_contents – read table data with optional WHERE filter
+ *
+ * Uses the ADT data preview freestyle endpoint to execute a SELECT query
+ * against a DDIC table and return the result as JSON.
+ *
+ * ADT endpoint: POST /sap/bc/adt/datapreview/freestyle
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerGetTableContentsTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_table_contents',
+ 'Read data from a DDIC table with optional WHERE filter, column selection, and row limit. WARNING: the WHERE clause is sent as-is to the SAP data preview endpoint — avoid untrusted input.',
+ {
+ ...connectionShape,
+ tableName: z.string().describe('DDIC table name (e.g. MARA, VBAK, T001)'),
+ where: z
+ .string()
+ .optional()
+ .describe('WHERE clause (ABAP SQL syntax, e.g. "MATNR LIKE \'Z%\'")'),
+ columns: z
+ .array(z.string())
+ .optional()
+ .describe(
+ 'Columns to select (default: all columns). Example: ["MATNR","MBRSH"]',
+ ),
+ maxRows: z
+ .number()
+ .optional()
+ .describe('Maximum rows to return (default: 100)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const maxRows = args.maxRows ?? 100;
+
+ const selectColumns =
+ args.columns && args.columns.length > 0
+ ? args.columns.join(', ')
+ : '*';
+
+ const whereClause = args.where ? ` WHERE ${args.where}` : '';
+ const query = `SELECT ${selectColumns} FROM ${args.tableName.toUpperCase()}${whereClause}`;
+
+ const params = new URLSearchParams({
+ rowCount: String(maxRows),
+ outputFormat: 'json',
+ });
+
+ const result = await client.fetch(
+ `/sap/bc/adt/datapreview/freestyle?${params.toString()}`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'text/plain',
+ Accept: 'application/json',
+ },
+ body: query,
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ { table: args.tableName.toUpperCase(), query, data: result },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get table contents failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/get-table.ts b/packages/adt-mcp/src/lib/tools/get-table.ts
new file mode 100644
index 00000000..6836e5d6
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/get-table.ts
@@ -0,0 +1,56 @@
+/**
+ * Tool: get_table – read DDIC table or structure definition
+ *
+ * Fetches the table/structure metadata from the ADT DDIC tables endpoint.
+ * Uses the existing adt-contracts tables contract for typed access.
+ *
+ * ADT endpoint: /sap/bc/adt/ddic/tables/{name}
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerGetTableTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_table',
+ 'Read DDIC table or structure definition (fields, keys, data elements)',
+ {
+ ...connectionShape,
+ tableName: z
+ .string()
+ .describe('DDIC table or structure name (e.g. MARA, VBAK)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const name = args.tableName.toLowerCase();
+
+ const result = await client.adt.ddic.tables.get(name);
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(result, null, 2),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get table failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/get-type-hierarchy.ts b/packages/adt-mcp/src/lib/tools/get-type-hierarchy.ts
new file mode 100644
index 00000000..5c5c1d0d
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/get-type-hierarchy.ts
@@ -0,0 +1,94 @@
+/**
+ * Tool: get_type_hierarchy – retrieve super/sub-types of an ABAP class or interface
+ *
+ * Returns the inheritance hierarchy (superclasses, interfaces implemented,
+ * and optionally subclasses) for a given class or interface.
+ *
+ * ADT endpoint: GET /sap/bc/adt/oo/typeinfo
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerGetTypeHierarchyTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_type_hierarchy',
+ 'Get the type hierarchy (super/sub-types, implemented interfaces) of an ABAP class or interface.',
+ {
+ ...connectionShape,
+ objectName: z
+ .string()
+ .describe('Class or interface name (e.g. ZCL_MY_CLASS, ZIF_MY_INTF)'),
+ objectType: z
+ .enum(['CLAS', 'INTF'])
+ .optional()
+ .describe(
+ 'Object type: CLAS (class) or INTF (interface). Auto-detected if omitted.',
+ ),
+ includeSubTypes: z
+ .boolean()
+ .optional()
+ .describe(
+ 'Whether to include sub-types (subclasses/implementors) in the result (default: false)',
+ ),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const objectName = args.objectName.toUpperCase();
+
+ // Detect type from name if not provided (interfaces often start with ZIF_/IF_)
+ let objectType = args.objectType?.toUpperCase() ?? 'CLAS';
+ if (!args.objectType) {
+ const upper = objectName.toUpperCase();
+ if (upper.startsWith('IF_') || upper.startsWith('ZIF_')) {
+ objectType = 'INTF';
+ }
+ }
+
+ const expand = ['superClasses', 'interfaces'];
+ if (args.includeSubTypes) {
+ expand.push('subClasses');
+ }
+
+ const params = new URLSearchParams({
+ type: objectType === 'INTF' ? 'INTF/OI' : 'CLAS/OC',
+ objectName,
+ expand: expand.join(','),
+ });
+
+ const result = await client.fetch(
+ `/sap/bc/adt/oo/typeinfo?${params.toString()}`,
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(result, null, 2),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get type hierarchy failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/git-tools.ts b/packages/adt-mcp/src/lib/tools/git-tools.ts
new file mode 100644
index 00000000..61246513
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/git-tools.ts
@@ -0,0 +1,116 @@
+/**
+ * Tools: get_git_types + git_export – abapGit integration
+ *
+ * get_git_types: list ABAP objects in a package that can be exported via abapGit.
+ * git_export: export package contents in abapGit XML format (one file per object).
+ *
+ * Requires the abapGit ADT plugin installed on the SAP system.
+ *
+ * ADT endpoints:
+ * GET /sap/bc/adt/abapgit/objects?package={name} – list exportable objects
+ * GET /sap/bc/adt/abapgit/repos/{name}/export – export package
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerGetGitTypesTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'get_git_types',
+ 'List ABAP objects in a package that are eligible for abapGit export. Requires abapGit installed on the SAP system.',
+ {
+ ...connectionShape,
+ packageName: z
+ .string()
+ .describe('ABAP package name to inspect (e.g. ZPACKAGE)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const packageName = args.packageName.toUpperCase();
+
+ const params = new URLSearchParams({ package: packageName });
+ const result = await client.fetch(
+ `/sap/bc/adt/abapgit/objects?${params.toString()}`,
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify({ packageName, objects: result }, null, 2),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Get git types failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
+
+export function registerGitExportTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'git_export',
+ 'Export an ABAP package in abapGit XML format. Requires abapGit installed on the SAP system. Returns a map of file paths to their content.',
+ {
+ ...connectionShape,
+ packageName: z
+ .string()
+ .describe('ABAP package name to export (e.g. ZPACKAGE)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const packageName = args.packageName.toUpperCase();
+
+ const result = await client.fetch(
+ `/sap/bc/adt/abapgit/repos/${packageName}/export`,
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify({ packageName, export: result }, null, 2),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Git export failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/grep-objects.ts b/packages/adt-mcp/src/lib/tools/grep-objects.ts
new file mode 100644
index 00000000..18313d05
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/grep-objects.ts
@@ -0,0 +1,109 @@
+/**
+ * Tool: grep_objects – regex search within a list of ABAP object URIs
+ *
+ * Uses the ADT repository information system search endpoint with
+ * userannotation=userwhere for source code content search.
+ *
+ * ADT endpoint: /sap/bc/adt/repository/informationsystem/search
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUri } from './utils';
+
+export function registerGrepObjectsTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'grep_objects',
+ 'Regex search for a pattern within ABAP object source code. Provide either a list of object URIs or name+type pairs to resolve them.',
+ {
+ ...connectionShape,
+ pattern: z.string().describe('Search pattern (regex or literal string)'),
+ objectUris: z
+ .array(z.string())
+ .optional()
+ .describe(
+ 'List of ADT object URIs to search within (e.g. /sap/bc/adt/oo/classes/zcl_example)',
+ ),
+ objects: z
+ .array(
+ z.object({
+ objectName: z.string().describe('ABAP object name'),
+ objectType: z.string().describe('Object type (e.g. CLAS, PROG)'),
+ }),
+ )
+ .optional()
+ .describe('Objects to search within (resolved to URIs automatically)'),
+ maxResults: z
+ .number()
+ .optional()
+ .describe('Maximum number of results (default: 50)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const maxResults = args.maxResults ?? 50;
+
+ // Resolve URIs from name/type pairs if provided (in parallel)
+ const uris: string[] = args.objectUris ? [...args.objectUris] : [];
+ if (args.objects && args.objects.length > 0) {
+ const resolved = await Promise.all(
+ args.objects.map((obj) =>
+ resolveObjectUri(client, obj.objectName, obj.objectType),
+ ),
+ );
+ for (const uri of resolved) {
+ if (uri) uris.push(uri);
+ }
+ }
+
+ // Build query parameters
+ const params = new URLSearchParams({
+ userannotation: 'userwhere',
+ query: args.pattern,
+ maxResults: String(maxResults),
+ });
+
+ // Add object URI references
+ uris.forEach((uri, i) => {
+ params.set(`objectReferences.${i}.uri`, uri);
+ });
+
+ const result = await client.fetch(
+ `/sap/bc/adt/repository/informationsystem/search?${params.toString()}`,
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ { pattern: args.pattern, results: result },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Grep objects failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/grep-packages.ts b/packages/adt-mcp/src/lib/tools/grep-packages.ts
new file mode 100644
index 00000000..534af490
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/grep-packages.ts
@@ -0,0 +1,88 @@
+/**
+ * Tool: grep_packages – regex search across all objects in a package (and subpackages)
+ *
+ * Uses the ADT repository information system search endpoint with
+ * userannotation=userwhere and packageName parameter for package-scoped search.
+ *
+ * ADT endpoint: /sap/bc/adt/repository/informationsystem/search
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerGrepPackagesTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'grep_packages',
+ 'Regex search for a pattern across all ABAP source code within a package (and optionally its subpackages)',
+ {
+ ...connectionShape,
+ pattern: z.string().describe('Search pattern (regex or literal string)'),
+ packageName: z
+ .string()
+ .describe('ABAP package name to search within (e.g. ZPACKAGE)'),
+ includeSubPackages: z
+ .boolean()
+ .optional()
+ .describe('Also search subpackages (default: true)'),
+ maxResults: z
+ .number()
+ .optional()
+ .describe('Maximum number of results (default: 50)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const maxResults = args.maxResults ?? 50;
+ const includeSubPackages = args.includeSubPackages ?? true;
+
+ const params = new URLSearchParams({
+ userannotation: 'userwhere',
+ query: args.pattern,
+ maxResults: String(maxResults),
+ packageName: args.packageName.toUpperCase(),
+ includeSubpackages: String(includeSubPackages),
+ });
+
+ const result = await client.fetch(
+ `/sap/bc/adt/repository/informationsystem/search?${params.toString()}`,
+ {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ pattern: args.pattern,
+ packageName: args.packageName.toUpperCase(),
+ results: result,
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Grep packages failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/index.ts b/packages/adt-mcp/src/lib/tools/index.ts
index abf17ff1..13e373ac 100644
--- a/packages/adt-mcp/src/lib/tools/index.ts
+++ b/packages/adt-mcp/src/lib/tools/index.ts
@@ -21,8 +21,42 @@ import { registerCheckSyntaxTool } from './check-syntax';
import { registerRunUnitTestsTool } from './run-unit-tests';
import { registerGetTestClassesTool } from './get-test-classes';
import { registerListPackageObjectsTool } from './list-package-objects';
+// New tools – high-priority feature parity (#H1–#H8)
+import { registerGrepObjectsTool } from './grep-objects';
+import { registerGrepPackagesTool } from './grep-packages';
+import { registerGetTableTool } from './get-table';
+import { registerGetTableContentsTool } from './get-table-contents';
+import { registerRunQueryTool } from './run-query';
+import { registerFindDefinitionTool } from './find-definition';
+import { registerFindReferencesTool } from './find-references';
+import {
+ registerGetCallersOfTool,
+ registerGetCalleesOfTool,
+} from './call-hierarchy';
+import { registerCreateObjectTool } from './create-object';
+import { registerDeleteObjectTool } from './delete-object';
+import { registerActivatePackageTool } from './activate-package';
+// Medium-priority feature parity (#M1–#M10)
+import {
+ registerGetFunctionGroupTool,
+ registerGetFunctionTool,
+} from './function-tools';
+import { registerLockObjectTool } from './lock-object';
+import { registerUnlockObjectTool } from './unlock-object';
+import { registerGetObjectStructureTool } from './get-object-structure';
+import { registerGetTypeHierarchyTool } from './get-type-hierarchy';
+import { registerPrettyPrintTool } from './pretty-print';
+import { registerCreatePackageTool } from './create-package';
+import {
+ registerGetInstalledComponentsTool,
+ registerGetFeaturesTool,
+} from './get-installed-components';
+import { registerCloneObjectTool } from './clone-object';
+import { registerPublishServiceBindingTool } from './publish-service-binding';
+import { registerGetGitTypesTool, registerGitExportTool } from './git-tools';
export function registerTools(server: McpServer, ctx: ToolContext): void {
+ // Existing tools
registerDiscoveryTool(server, ctx);
registerSystemInfoTool(server, ctx);
registerSearchObjectsTool(server, ctx);
@@ -40,4 +74,32 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
registerRunUnitTestsTool(server, ctx);
registerGetTestClassesTool(server, ctx);
registerListPackageObjectsTool(server, ctx);
+ // New tools – feature parity with vibing-steampunk (#H1–#H8)
+ registerGrepObjectsTool(server, ctx);
+ registerGrepPackagesTool(server, ctx);
+ registerGetTableTool(server, ctx);
+ registerGetTableContentsTool(server, ctx);
+ registerRunQueryTool(server, ctx);
+ registerFindDefinitionTool(server, ctx);
+ registerFindReferencesTool(server, ctx);
+ registerGetCallersOfTool(server, ctx);
+ registerGetCalleesOfTool(server, ctx);
+ registerCreateObjectTool(server, ctx);
+ registerDeleteObjectTool(server, ctx);
+ registerActivatePackageTool(server, ctx);
+ // Medium-priority tools (#M1–#M10)
+ registerGetFunctionGroupTool(server, ctx);
+ registerGetFunctionTool(server, ctx);
+ registerLockObjectTool(server, ctx);
+ registerUnlockObjectTool(server, ctx);
+ registerGetObjectStructureTool(server, ctx);
+ registerGetTypeHierarchyTool(server, ctx);
+ registerPrettyPrintTool(server, ctx);
+ registerCreatePackageTool(server, ctx);
+ registerGetInstalledComponentsTool(server, ctx);
+ registerGetFeaturesTool(server, ctx);
+ registerCloneObjectTool(server, ctx);
+ registerPublishServiceBindingTool(server, ctx);
+ registerGetGitTypesTool(server, ctx);
+ registerGitExportTool(server, ctx);
}
diff --git a/packages/adt-mcp/src/lib/tools/lock-object.ts b/packages/adt-mcp/src/lib/tools/lock-object.ts
new file mode 100644
index 00000000..2e97f63d
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/lock-object.ts
@@ -0,0 +1,96 @@
+/**
+ * Tool: lock_object – acquire an ADT edit lock on an ABAP object
+ *
+ * Returns the lock handle that must be passed to unlock_object and to
+ * update_source / other write operations that require a prior lock.
+ *
+ * Uses the LockService from @abapify/adt-locks for the full SAP security
+ * session / CSRF handshake required for stateful lock operations.
+ *
+ * ADT endpoint: POST {objectUri}?_action=LOCK
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { createLockService } from '@abapify/adt-locks';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUri } from './utils';
+
+export function registerLockObjectTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'lock_object',
+ 'Acquire an ADT edit lock on an ABAP object and return the lock handle needed for subsequent write operations.',
+ {
+ ...connectionShape,
+ objectName: z.string().describe('ABAP object name'),
+ objectType: z
+ .string()
+ .optional()
+ .describe('Object type (e.g. CLAS, PROG, INTF, FUGR)'),
+ transport: z.string().optional().describe('Transport request number'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ const objectUri = await resolveObjectUri(
+ client,
+ args.objectName,
+ args.objectType,
+ );
+
+ if (!objectUri) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Object '${args.objectName}' not found`,
+ },
+ ],
+ };
+ }
+
+ const lockService = createLockService(client);
+ const lockHandle = await lockService.lock(objectUri, {
+ transport: args.transport,
+ objectName: args.objectName,
+ objectType: args.objectType,
+ });
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: 'locked',
+ objectName: args.objectName.toUpperCase(),
+ objectUri,
+ lockHandle: lockHandle.handle,
+ correlationNumber: lockHandle.correlationNumber,
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Lock object failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/object-creation.ts b/packages/adt-mcp/src/lib/tools/object-creation.ts
new file mode 100644
index 00000000..a844880e
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/object-creation.ts
@@ -0,0 +1,102 @@
+import type { ToolContext } from '../types';
+
+export const CREATE_OBJECT_TYPES = [
+ 'PROG',
+ 'CLAS',
+ 'INTF',
+ 'FUGR',
+ 'DEVC',
+] as const;
+
+export const SOURCE_BACKED_OBJECT_TYPES = ['PROG', 'CLAS', 'INTF'] as const;
+
+export type CreateObjectType = (typeof CREATE_OBJECT_TYPES)[number];
+export type SourceBackedObjectType =
+ (typeof SOURCE_BACKED_OBJECT_TYPES)[number];
+
+type AdtClient = ReturnType;
+
+type CreateObjectArgs = {
+ objectType: CreateObjectType;
+ objectName: string;
+ description: string;
+ packageName?: string;
+ transport?: string;
+};
+
+export function isCreateObjectType(type: string): type is CreateObjectType {
+ return CREATE_OBJECT_TYPES.includes(type.toUpperCase() as CreateObjectType);
+}
+
+export function isSourceBackedObjectType(
+ type: string,
+): type is SourceBackedObjectType {
+ return SOURCE_BACKED_OBJECT_TYPES.includes(
+ type.toUpperCase() as SourceBackedObjectType,
+ );
+}
+
+export async function createAdtObject(
+ client: AdtClient,
+ args: CreateObjectArgs,
+): Promise {
+ const packageRef = args.packageName
+ ? { uri: `/sap/bc/adt/packages/${args.packageName.toUpperCase()}` }
+ : undefined;
+ const queryOptions = args.transport ? { corrNr: args.transport } : {};
+ const commonFields = {
+ name: args.objectName.toUpperCase(),
+ description: args.description,
+ language: 'EN',
+ masterLanguage: 'EN',
+ ...(packageRef ? { packageRef } : {}),
+ };
+
+ switch (args.objectType) {
+ case 'PROG':
+ await client.adt.programs.programs.post(queryOptions, {
+ abapProgram: { ...commonFields, type: 'PROG' },
+ });
+ return;
+
+ case 'CLAS':
+ await client.adt.oo.classes.post(queryOptions, {
+ abapClass: { ...commonFields, type: 'CLAS/OC' },
+ });
+ return;
+
+ case 'INTF':
+ await client.adt.oo.interfaces.post(queryOptions, {
+ abapInterface: { ...commonFields, type: 'INTF/OI' },
+ });
+ return;
+
+ case 'FUGR':
+ await client.adt.functions.groups.post(queryOptions, {
+ abapFunctionGroup: { ...commonFields, type: 'FUGR' },
+ });
+ return;
+
+ case 'DEVC':
+ await client.adt.packages.post(queryOptions, {
+ package: {
+ name: args.objectName.toUpperCase(),
+ type: 'DEVC/K',
+ description: args.description,
+ language: 'EN',
+ masterLanguage: 'EN',
+ attributes: { packageType: 'development' },
+ superPackage: {},
+ extensionAlias: {},
+ switch: {},
+ applicationComponent: {},
+ transport: {},
+ translation: {},
+ useAccesses: {},
+ packageInterfaces: {},
+ subPackages: {},
+ },
+ });
+ return;
+ }
+}
diff --git a/packages/adt-mcp/src/lib/tools/pretty-print.ts b/packages/adt-mcp/src/lib/tools/pretty-print.ts
new file mode 100644
index 00000000..83af80a7
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/pretty-print.ts
@@ -0,0 +1,64 @@
+/**
+ * Tool: pretty_print – format ABAP source code via the SAP pretty-printer
+ *
+ * Sends source code to the SAP ADT pretty-printer and returns the formatted
+ * version. Does not modify the object in the system.
+ *
+ * ADT endpoint: POST /sap/bc/adt/prettyprinter/prettifySource
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerPrettyPrintTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'pretty_print',
+ 'Format ABAP source code via the SAP pretty-printer. Returns the formatted code without modifying the object.',
+ {
+ ...connectionShape,
+ sourceCode: z.string().describe('ABAP source code to format'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ const result = await client.fetch(
+ '/sap/bc/adt/prettyprinter/prettifySource',
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'text/plain',
+ Accept: 'text/plain',
+ },
+ body: args.sourceCode,
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text:
+ typeof result === 'string' ? result : JSON.stringify(result),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Pretty print failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/publish-service-binding.ts b/packages/adt-mcp/src/lib/tools/publish-service-binding.ts
new file mode 100644
index 00000000..97b864c7
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/publish-service-binding.ts
@@ -0,0 +1,92 @@
+/**
+ * Tool: publish_service_binding – publish an OData service binding in the SAP system
+ *
+ * Activates and publishes an OData V2 or V4 service binding, making it
+ * accessible via the SAP Gateway. Requires an existing SRVB object.
+ *
+ * ADT endpoint: POST /sap/bc/adt/businessservices/bindings/{name}/publishedstates
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerPublishServiceBindingTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'publish_service_binding',
+ 'Publish (activate) an OData service binding (SRVB) in SAP to make it accessible via the Gateway.',
+ {
+ ...connectionShape,
+ bindingName: z
+ .string()
+ .describe('Service binding name (e.g. ZUI_MYAPP_O4)'),
+ unpublish: z
+ .boolean()
+ .optional()
+ .describe(
+ 'If true, unpublishes (deactivates) the service binding instead (default: false)',
+ ),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const bindingName = args.bindingName.toUpperCase();
+ const action = args.unpublish ? 'unpublish' : 'publish';
+
+ if (args.unpublish) {
+ // DELETE from publishedstates to unpublish
+ await client.fetch(
+ `/sap/bc/adt/businessservices/bindings/${bindingName}/publishedstates`,
+ {
+ method: 'DELETE',
+ headers: { Accept: 'application/json' },
+ },
+ );
+ } else {
+ // POST to publishedstates to publish
+ await client.fetch(
+ `/sap/bc/adt/businessservices/bindings/${bindingName}/publishedstates`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+ body: JSON.stringify({ bindingName }),
+ },
+ );
+ }
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: action === 'publish' ? 'published' : 'unpublished',
+ bindingName,
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Publish service binding failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/run-query.ts b/packages/adt-mcp/src/lib/tools/run-query.ts
new file mode 100644
index 00000000..cdbaa53b
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/run-query.ts
@@ -0,0 +1,94 @@
+/**
+ * Tool: run_query – execute a freestyle ABAP SQL query
+ *
+ * Uses the ADT data preview freestyle endpoint to execute an arbitrary
+ * ABAP SQL SELECT query and return results as JSON.
+ *
+ * ADT endpoint: POST /sap/bc/adt/datapreview/freestyle
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+
+export function registerRunQueryTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'run_query',
+ 'Execute a freestyle ABAP SQL SELECT query and return results as JSON. Only SELECT statements are supported.',
+ {
+ ...connectionShape,
+ query: z
+ .string()
+ .describe(
+ 'ABAP SQL SELECT statement (e.g. "SELECT * FROM T001 WHERE MANDT = \'100\'")',
+ ),
+ maxRows: z
+ .number()
+ .optional()
+ .describe('Maximum rows to return (default: 100)'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+ const maxRows = args.maxRows ?? 100;
+
+ const trimmedQuery = args.query.trim();
+ if (!trimmedQuery.toUpperCase().startsWith('SELECT')) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: 'Only SELECT statements are supported by the data preview endpoint',
+ },
+ ],
+ };
+ }
+
+ const params = new URLSearchParams({
+ rowCount: String(maxRows),
+ outputFormat: 'json',
+ });
+
+ const result = await client.fetch(
+ `/sap/bc/adt/datapreview/freestyle?${params.toString()}`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'text/plain',
+ Accept: 'application/json',
+ },
+ body: trimmedQuery,
+ },
+ );
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ { query: trimmedQuery, data: result },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Run query failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/src/lib/tools/unlock-object.ts b/packages/adt-mcp/src/lib/tools/unlock-object.ts
new file mode 100644
index 00000000..1188a11b
--- /dev/null
+++ b/packages/adt-mcp/src/lib/tools/unlock-object.ts
@@ -0,0 +1,86 @@
+/**
+ * Tool: unlock_object – release an ADT edit lock on an ABAP object
+ *
+ * Requires the lock handle returned by lock_object (or update_source).
+ *
+ * ADT endpoint: POST {objectUri}?_action=UNLOCK&lockHandle={handle}
+ */
+
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { createLockService } from '@abapify/adt-locks';
+import type { ToolContext } from '../types';
+import { connectionShape } from './shared-schemas';
+import { resolveObjectUri } from './utils';
+
+export function registerUnlockObjectTool(
+ server: McpServer,
+ ctx: ToolContext,
+): void {
+ server.tool(
+ 'unlock_object',
+ 'Release an ADT edit lock acquired with lock_object. Requires the lockHandle returned by lock_object.',
+ {
+ ...connectionShape,
+ objectName: z.string().describe('ABAP object name'),
+ objectType: z
+ .string()
+ .optional()
+ .describe('Object type (e.g. CLAS, PROG, INTF, FUGR)'),
+ lockHandle: z.string().describe('Lock handle returned by lock_object'),
+ },
+ async (args) => {
+ try {
+ const client = ctx.getClient(args);
+
+ const objectUri = await resolveObjectUri(
+ client,
+ args.objectName,
+ args.objectType,
+ );
+
+ if (!objectUri) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Object '${args.objectName}' not found`,
+ },
+ ],
+ };
+ }
+
+ const lockService = createLockService(client);
+ await lockService.unlock(objectUri, { lockHandle: args.lockHandle });
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ status: 'unlocked',
+ objectName: args.objectName.toUpperCase(),
+ objectUri,
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Unlock object failed: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ],
+ };
+ }
+ },
+ );
+}
diff --git a/packages/adt-mcp/tests/integration.test.ts b/packages/adt-mcp/tests/integration.test.ts
index a21c38c6..9431c2a1 100644
--- a/packages/adt-mcp/tests/integration.test.ts
+++ b/packages/adt-mcp/tests/integration.test.ts
@@ -198,46 +198,6 @@ describe('adt-mcp integration tests', () => {
});
});
- // ── cts_create_transport ──────────────────────────────────────
-
- describe('cts_create_transport tool', () => {
- it('returns a not-yet-implemented error', async () => {
- const { raw } = await callTool('cts_create_transport', {
- ...connArgs(),
- description: 'Test transport',
- });
- const result = raw as {
- isError?: boolean;
- content: Array<{ type: string; text: string }>;
- };
- assert.strictEqual(result.isError, true);
- assert.ok(
- result.content[0]?.text.includes('not yet implemented'),
- 'error message should mention not yet implemented',
- );
- });
- });
-
- // ── cts_release_transport ─────────────────────────────────────
-
- describe('cts_release_transport tool', () => {
- it('returns a not-yet-implemented error', async () => {
- const { raw } = await callTool('cts_release_transport', {
- ...connArgs(),
- transport: 'DEVK900001',
- });
- const result = raw as {
- isError?: boolean;
- content: Array<{ type: string; text: string }>;
- };
- assert.strictEqual(result.isError, true);
- assert.ok(
- result.content[0]?.text.includes('not yet implemented'),
- 'error message should mention not yet implemented',
- );
- });
- });
-
// ── cts_delete_transport ───────────────────────────────────────
describe('cts_delete_transport tool', () => {
@@ -413,6 +373,488 @@ describe('adt-mcp integration tests', () => {
});
});
+ // ── cts_create_transport ───────────────────────────────────────
+
+ describe('cts_create_transport tool', () => {
+ it('creates a transport and returns transport number', async () => {
+ const { json } = await callTool('cts_create_transport', {
+ ...connArgs(),
+ description: 'Test transport',
+ type: 'K',
+ });
+ const data = json as { status: string; transport: string };
+ assert.strictEqual(data.status, 'created');
+ });
+ });
+
+ // ── cts_release_transport ──────────────────────────────────────
+
+ describe('cts_release_transport tool', () => {
+ it('releases a transport and returns status', async () => {
+ const { json } = await callTool('cts_release_transport', {
+ ...connArgs(),
+ transport: 'DEVK900001',
+ });
+ const data = json as { status: string; transport: string };
+ assert.strictEqual(data.status, 'released');
+ assert.strictEqual(data.transport, 'DEVK900001');
+ });
+ });
+
+ // ── grep_objects ───────────────────────────────────────────────
+
+ describe('grep_objects tool', () => {
+ it('searches for a pattern within named objects', async () => {
+ const { json } = await callTool('grep_objects', {
+ ...connArgs(),
+ pattern: 'METHOD',
+ objects: [{ objectName: 'ZCL_EXAMPLE', objectType: 'CLAS' }],
+ });
+ const data = json as { pattern: string; results: unknown };
+ assert.strictEqual(data.pattern, 'METHOD');
+ assert.ok(data.results !== undefined);
+ });
+ });
+
+ // ── grep_packages ──────────────────────────────────────────────
+
+ describe('grep_packages tool', () => {
+ it('searches for a pattern across a package', async () => {
+ const { json } = await callTool('grep_packages', {
+ ...connArgs(),
+ pattern: 'METHOD',
+ packageName: 'ZPACKAGE',
+ });
+ const data = json as { pattern: string; packageName: string };
+ assert.strictEqual(data.pattern, 'METHOD');
+ assert.strictEqual(data.packageName, 'ZPACKAGE');
+ });
+ });
+
+ // ── get_table ──────────────────────────────────────────────────
+
+ describe('get_table tool', () => {
+ it('returns DDIC table definition', async () => {
+ const { json } = await callTool('get_table', {
+ ...connArgs(),
+ tableName: 'MARA',
+ });
+ assert.ok(json !== undefined, 'should return table definition');
+ });
+ });
+
+ // ── get_table_contents ─────────────────────────────────────────
+
+ describe('get_table_contents tool', () => {
+ it('returns table data rows', async () => {
+ const { json } = await callTool('get_table_contents', {
+ ...connArgs(),
+ tableName: 'T001',
+ maxRows: 10,
+ });
+ const data = json as { table: string; query: string };
+ assert.strictEqual(data.table, 'T001');
+ assert.ok(data.query.includes('SELECT'));
+ });
+ });
+
+ // ── run_query ──────────────────────────────────────────────────
+
+ describe('run_query tool', () => {
+ it('executes a SQL query and returns results', async () => {
+ const { json } = await callTool('run_query', {
+ ...connArgs(),
+ query: "SELECT * FROM T001 WHERE MANDT = '100'",
+ maxRows: 5,
+ });
+ const data = json as { query: string };
+ assert.ok(data.query.includes('SELECT'));
+ });
+
+ it('rejects non-SELECT statements', async () => {
+ const { raw } = await callTool('run_query', {
+ ...connArgs(),
+ query: "DELETE FROM T001 WHERE MANDT = '100'",
+ });
+ const result = raw as { isError?: boolean };
+ assert.ok(result.isError, 'should return error for non-SELECT statement');
+ });
+ });
+
+ // ── find_definition ────────────────────────────────────────────
+
+ describe('find_definition tool', () => {
+ it('returns navigation target for a symbol', async () => {
+ const { json } = await callTool('find_definition', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ });
+ assert.ok(json !== undefined, 'should return navigation target');
+ });
+ });
+
+ // ── find_references ────────────────────────────────────────────
+
+ describe('find_references tool', () => {
+ it('returns usages for an object', async () => {
+ const { json } = await callTool('find_references', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ });
+ const data = json as { objectName: string };
+ assert.strictEqual(data.objectName, 'ZCL_EXAMPLE');
+ });
+ });
+
+ // ── get_callers_of ─────────────────────────────────────────────
+
+ describe('get_callers_of tool', () => {
+ it('returns callers of an object', async () => {
+ const { json } = await callTool('get_callers_of', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ });
+ const data = json as { objectName: string; callers: unknown };
+ assert.strictEqual(data.objectName, 'ZCL_EXAMPLE');
+ assert.ok(data.callers !== undefined);
+ });
+ });
+
+ // ── get_callees_of ─────────────────────────────────────────────
+
+ describe('get_callees_of tool', () => {
+ it('returns callees of an object', async () => {
+ const { json } = await callTool('get_callees_of', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ });
+ const data = json as { objectName: string; callees: unknown };
+ assert.strictEqual(data.objectName, 'ZCL_EXAMPLE');
+ assert.ok(data.callees !== undefined);
+ });
+ });
+
+ // ── create_object ──────────────────────────────────────────────
+
+ describe('create_object tool', () => {
+ it('creates a CLAS object', async () => {
+ const { json } = await callTool('create_object', {
+ ...connArgs(),
+ objectName: 'ZCL_NEW',
+ objectType: 'CLAS',
+ description: 'New test class',
+ packageName: 'ZPACKAGE',
+ transport: 'DEVK900001',
+ });
+ const data = json as {
+ status: string;
+ objectName: string;
+ objectType: string;
+ };
+ assert.strictEqual(data.status, 'created');
+ assert.strictEqual(data.objectName, 'ZCL_NEW');
+ assert.strictEqual(data.objectType, 'CLAS');
+ });
+
+ it('creates a PROG object', async () => {
+ const { json } = await callTool('create_object', {
+ ...connArgs(),
+ objectName: 'ZPROG_NEW',
+ objectType: 'PROG',
+ description: 'New test program',
+ packageName: 'ZPACKAGE',
+ transport: 'DEVK900001',
+ });
+ const data = json as { status: string; objectType: string };
+ assert.strictEqual(data.status, 'created');
+ assert.strictEqual(data.objectType, 'PROG');
+ });
+
+ it('returns error for unsupported type', async () => {
+ const { raw } = await callTool('create_object', {
+ ...connArgs(),
+ objectName: 'ZFB01',
+ objectType: 'TRAN',
+ description: 'Unsupported type',
+ });
+ const result = raw as { isError?: boolean };
+ assert.ok(result.isError, 'should return error for unsupported type');
+ });
+ });
+
+ // ── delete_object ──────────────────────────────────────────────
+
+ describe('delete_object tool', () => {
+ it('deletes a CLAS object', async () => {
+ const { json } = await callTool('delete_object', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ transport: 'DEVK900001',
+ });
+ const data = json as { status: string; objectName: string };
+ assert.strictEqual(data.status, 'deleted');
+ assert.strictEqual(data.objectName, 'ZCL_EXAMPLE');
+ });
+ });
+
+ // ── activate_package ───────────────────────────────────────────
+
+ describe('activate_package tool', () => {
+ it('activates all inactive objects in a package', async () => {
+ const { json } = await callTool('activate_package', {
+ ...connArgs(),
+ packageName: 'ZPACKAGE',
+ });
+ const data = json as {
+ status: string;
+ packageName: string;
+ count: number;
+ };
+ assert.ok(
+ data.status === 'activated' || data.status === 'no_inactive_objects',
+ 'should return activated or no_inactive_objects status',
+ );
+ assert.strictEqual(data.packageName, 'ZPACKAGE');
+ });
+ });
+
+ // ── get_function_group ─────────────────────────────────────────
+
+ describe('get_function_group tool', () => {
+ it('returns function group metadata', async () => {
+ const { json } = await callTool('get_function_group', {
+ ...connArgs(),
+ groupName: 'ZFUGR_UTIL',
+ });
+ const data = json as { metadata: unknown };
+ assert.ok(data.metadata, 'should return metadata');
+ });
+
+ it('returns function group with source when includeSource is true', async () => {
+ const { json } = await callTool('get_function_group', {
+ ...connArgs(),
+ groupName: 'ZFUGR_UTIL',
+ includeSource: true,
+ });
+ const data = json as { metadata: unknown; source: string };
+ assert.ok(data.metadata, 'should return metadata');
+ assert.ok(typeof data.source === 'string', 'should return source string');
+ });
+ });
+
+ // ── get_function ──────────────────────────────────────────────
+
+ describe('get_function tool', () => {
+ it('returns function module metadata', async () => {
+ const { json } = await callTool('get_function', {
+ ...connArgs(),
+ groupName: 'ZFUGR_UTIL',
+ functionName: 'Z_MY_FUNCTION',
+ });
+ const data = json as { metadata: unknown };
+ assert.ok(data.metadata, 'should return metadata');
+ });
+
+ it('returns function module with source when includeSource is true', async () => {
+ const { json } = await callTool('get_function', {
+ ...connArgs(),
+ groupName: 'ZFUGR_UTIL',
+ functionName: 'Z_MY_FUNCTION',
+ includeSource: true,
+ });
+ const data = json as { metadata: unknown; source: string };
+ assert.ok(data.metadata, 'should return metadata');
+ assert.ok(typeof data.source === 'string', 'should return source string');
+ });
+ });
+
+ // ── lock_object ───────────────────────────────────────────────
+
+ describe('lock_object tool', () => {
+ it('acquires a lock and returns lock handle', async () => {
+ const { json } = await callTool('lock_object', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ });
+ const data = json as { status: string; lockHandle: string };
+ assert.strictEqual(data.status, 'locked');
+ assert.ok(
+ typeof data.lockHandle === 'string',
+ 'should return lockHandle',
+ );
+ });
+ });
+
+ // ── unlock_object ─────────────────────────────────────────────
+
+ describe('unlock_object tool', () => {
+ it('releases a lock', async () => {
+ const { json } = await callTool('unlock_object', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ lockHandle: 'MOCK_LOCK_HANDLE_001',
+ });
+ const data = json as { status: string };
+ assert.strictEqual(data.status, 'unlocked');
+ });
+ });
+
+ // ── get_object_structure ──────────────────────────────────────
+
+ describe('get_object_structure tool', () => {
+ it('returns object structure for a CLAS', async () => {
+ const { json } = await callTool('get_object_structure', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ });
+ assert.ok(json, 'should return object structure');
+ });
+ });
+
+ // ── get_type_hierarchy ────────────────────────────────────────
+
+ describe('get_type_hierarchy tool', () => {
+ it('returns type hierarchy for a class', async () => {
+ const { json } = await callTool('get_type_hierarchy', {
+ ...connArgs(),
+ objectName: 'ZCL_EXAMPLE',
+ objectType: 'CLAS',
+ });
+ assert.ok(json, 'should return type hierarchy');
+ });
+ });
+
+ // ── pretty_print ──────────────────────────────────────────────
+
+ describe('pretty_print tool', () => {
+ it('returns formatted ABAP source code', async () => {
+ const { json } = await callTool('pretty_print', {
+ ...connArgs(),
+ sourceCode: 'class zcl_example definition.\nendclass.',
+ });
+ assert.ok(
+ typeof json === 'string',
+ 'should return formatted source as string',
+ );
+ });
+ });
+
+ // ── create_package ────────────────────────────────────────────
+
+ describe('create_package tool', () => {
+ it('creates a package', async () => {
+ const { json } = await callTool('create_package', {
+ ...connArgs(),
+ packageName: 'ZNEWPKG',
+ description: 'New test package',
+ });
+ const data = json as { status: string; packageName: string };
+ assert.strictEqual(data.status, 'created');
+ assert.strictEqual(data.packageName, 'ZNEWPKG');
+ });
+ });
+
+ // ── get_installed_components ──────────────────────────────────
+
+ describe('get_installed_components tool', () => {
+ it('returns installed software components', async () => {
+ const { json } = await callTool('get_installed_components', connArgs());
+ assert.ok(json, 'should return software components');
+ });
+ });
+
+ // ── get_features ──────────────────────────────────────────────
+
+ describe('get_features tool', () => {
+ it('returns feature detection result', async () => {
+ const { json } = await callTool('get_features', connArgs());
+ const data = json as { features: Record };
+ assert.ok(data.features, 'should return features object');
+ assert.strictEqual(
+ typeof data.features.atc,
+ 'boolean',
+ 'features.atc should be boolean',
+ );
+ });
+ });
+
+ // ── clone_object ──────────────────────────────────────────────
+
+ describe('clone_object tool', () => {
+ it('clones a CLAS object', async () => {
+ const { json } = await callTool('clone_object', {
+ ...connArgs(),
+ sourceObjectName: 'ZCL_EXAMPLE',
+ sourceObjectType: 'CLAS',
+ targetObjectName: 'ZCL_EXAMPLE_COPY',
+ targetDescription: 'Copy of ZCL_EXAMPLE',
+ });
+ const data = json as { status: string; targetObject: { name: string } };
+ assert.strictEqual(data.status, 'cloned');
+ assert.strictEqual(data.targetObject.name, 'ZCL_EXAMPLE_COPY');
+ });
+ });
+
+ // ── publish_service_binding ───────────────────────────────────
+
+ describe('publish_service_binding tool', () => {
+ it('publishes a service binding', async () => {
+ const { json } = await callTool('publish_service_binding', {
+ ...connArgs(),
+ bindingName: 'ZUI_MYAPP_O4',
+ });
+ const data = json as { status: string; bindingName: string };
+ assert.strictEqual(data.status, 'published');
+ assert.strictEqual(data.bindingName, 'ZUI_MYAPP_O4');
+ });
+
+ it('unpublishes a service binding', async () => {
+ const { json } = await callTool('publish_service_binding', {
+ ...connArgs(),
+ bindingName: 'ZUI_MYAPP_O4',
+ unpublish: true,
+ });
+ const data = json as { status: string };
+ assert.strictEqual(data.status, 'unpublished');
+ });
+ });
+
+ // ── get_git_types ─────────────────────────────────────────────
+
+ describe('get_git_types tool', () => {
+ it('returns abapGit-eligible objects', async () => {
+ const { json } = await callTool('get_git_types', {
+ ...connArgs(),
+ packageName: 'ZPACKAGE',
+ });
+ const data = json as { packageName: string; objects: unknown };
+ assert.strictEqual(data.packageName, 'ZPACKAGE');
+ assert.ok(data.objects, 'should return objects');
+ });
+ });
+
+ // ── git_export ────────────────────────────────────────────────
+
+ describe('git_export tool', () => {
+ it('returns package export in abapGit format', async () => {
+ const { json } = await callTool('git_export', {
+ ...connArgs(),
+ packageName: 'ZPACKAGE',
+ });
+ const data = json as { packageName: string; export: unknown };
+ assert.strictEqual(data.packageName, 'ZPACKAGE');
+ assert.ok(data.export, 'should return export data');
+ });
+ });
+
// ── tool listing ───────────────────────────────────────────────
describe('tool listing', () => {
@@ -437,6 +879,34 @@ describe('adt-mcp integration tests', () => {
'run_unit_tests',
'get_test_classes',
'list_package_objects',
+ // High-priority tools (#H1–#H8)
+ 'grep_objects',
+ 'grep_packages',
+ 'get_table',
+ 'get_table_contents',
+ 'run_query',
+ 'find_definition',
+ 'find_references',
+ 'get_callers_of',
+ 'get_callees_of',
+ 'create_object',
+ 'delete_object',
+ 'activate_package',
+ // Medium-priority tools (#M1–#M10)
+ 'get_function_group',
+ 'get_function',
+ 'lock_object',
+ 'unlock_object',
+ 'get_object_structure',
+ 'get_type_hierarchy',
+ 'pretty_print',
+ 'create_package',
+ 'get_installed_components',
+ 'get_features',
+ 'clone_object',
+ 'publish_service_binding',
+ 'get_git_types',
+ 'git_export',
];
for (const name of expected) {
assert.ok(names.has(name), `tool "${name}" should be listed`);