Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions packages/server/src/ibmi-mcp-server/tools/source-code/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# IBM i Source Code Tools

TypeScript-based tools for AI-assisted source code access, compilation, and debugging on IBM i.

## Overview

These tools enable AI agents to:
- **Read** source code from IBM i source files
- **Compile** RPG, CL, and COBOL programs
- **Retrieve** compilation errors from job logs

## Tools

### 1. read_source_member

Reads IBM i source code from a source physical file member.

**Input:**
```typescript
{
library: string; // Library containing the source (e.g., 'MYLIB')
source_file: string; // Source file name (e.g., 'QRPGLESRC')
member: string; // Member name (e.g., 'MYPGM')
include_line_numbers?: boolean; // Default: true
}
```

**Output:**
- Source code with line numbers
- Metadata (source type, line count, last modified)
- Execution time

**Example:**
```
Input: { library: 'MYLIB', source_file: 'QRPGLESRC', member: 'MYPGM' }
Output: Complete source code with metadata
```

### 2. compile_source

Compiles IBM i source code into modules or programs.

**Input:**
```typescript
{
library: string; // Library with source
source_file: string; // Source file name
member: string; // Member to compile
target_library?: string; // Where to put compiled object
compile_type: 'RPGLE' | 'SQLRPGLE' | 'CL' | 'CLLE' | 'CBL' | 'CBLLE';
compile_options?: string; // Additional options (e.g., 'DBGVIEW(*SOURCE)')
create_program?: boolean; // Create program vs module (default: false)
}
```

**Output:**
- Success/failure status
- Compile command executed
- Job information
- Execution time

**Example:**
```
Input: { library: 'MYLIB', source_file: 'QRPGLESRC', member: 'MYPGM', compile_type: 'RPGLE' }
Output: Compilation status and job details
```

### 3. get_compile_errors

Retrieves compilation error messages from the current job log.

**Input:**
```typescript
{
min_severity?: number; // Minimum severity (0-99), default: 20
max_messages?: number; // Max messages to return, default: 100
message_type_filter?: string[]; // Filter by message types
}
```

**Output:**
- Array of error messages with:
- Message ID (e.g., RNF7030)
- Severity level
- Message text
- Detailed help text
- Source program/library

**Example:**
```
Input: { min_severity: 30, max_messages: 50 }
Output: List of errors and warnings from recent compile
```

## Configuration

### Enable Source Code Tools

**Environment Variable:**
```bash
IBMI_ENABLE_SOURCE_TOOLS=true
```

**In .env file:**
```bash
# Enable source code tools
IBMI_ENABLE_SOURCE_TOOLS=true

# Note: compile_source requires write access
IBMI_EXECUTE_SQL_READONLY=false # ⚠️ Security: enables write operations
```

### Security Considerations

- **read_source_member**: Read-only, safe for production
- **compile_source**: Requires write access (IBMI_EXECUTE_SQL_READONLY=false)
- **get_compile_errors**: Read-only, safe for production

⚠️ **Only enable in development/test environments** unless you have strict security controls.

## Usage Example

### AI-Assisted Compile Workflow

```
1. Agent: "Read source for MYPGM"
Tool: read_source_member

2. Agent: "Compile this program"
Tool: compile_source

3. If compile fails:
Tool: get_compile_errors

4. Agent analyzes errors and suggests fixes

5. Agent: "Update source with fixes"
(Would use update_source_member - not yet implemented)

6. Repeat until successful
```

## Implementation Details

- **Pattern**: Single-file .tool.ts following factory pattern
- **Error Handling**: McpError with proper error codes
- **Logging**: Structured logging with RequestContext
- **Database Access**: Via IBMiConnectionPool
- **Response Formatting**: Custom formatters for each tool

## Related Documentation

- [SOURCE_CODE_TOOLS_DESIGN.md](../../../../../../SOURCE_CODE_TOOLS_DESIGN.md) - Complete design specification
- [SOURCE_CODE_TOOLS_SUMMARY.md](../../../../../../SOURCE_CODE_TOOLS_SUMMARY.md) - Quick reference
- [packages/server/README.md](../../../../README.md) - Server documentation

## Future Enhancements

- **update_source_member**: Modify source code programmatically
- **get_spool_file_content**: Read detailed compile listings
- **create_source_member**: Create new source members
- **AI compile loop**: Automated compile-fix-recompile orchestration

## Testing

Run tests:
```bash
npm test -- packages/server/src/ibmi-mcp-server/tools/source-code/
```

## Contributing

When adding new tools:
1. Create .tool.ts file following existing pattern
2. Export from index.ts
3. Add to getAllToolDefinitions() in parent index.ts
4. Update this README
5. Add tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/**
* Compile from IFS Tool
*
* Compiles IBM i source code from IFS stream file using SRCSTMF parameter.
* Works with source written via write_source_to_ifs tool.
*
* @module compileFromIfs.tool
*/

import { z } from "zod";
import type { ContentBlock } from "@modelcontextprotocol/sdk/types.js";
import { JsonRpcErrorCode, McpError } from "../../../types-global/errors.js";
import type { RequestContext } from "../../../utils/index.js";
import { logger } from "../../../utils/internal/logger.js";
import { IBMiConnectionPool } from "../../services/connectionPool.js";
import { defineTool } from "../../../mcp-server/tools/utils/tool-factory.js";
import type { SdkContext } from "../../../mcp-server/tools/utils/types.js";

// =============================================================================
// Schemas
// =============================================================================

const CompileFromIfsInputSchema = z.object({
library: z
.string()
.min(1, "Library name cannot be empty.")
.max(10, "Library name cannot exceed 10 characters.")
.describe("Target library for compiled object"),
object_name: z
.string()
.min(1, "Object name cannot be empty.")
.max(10, "Object name cannot exceed 10 characters.")
.describe("Name of the compiled object (program/module)"),
ifs_path: z
.string()
.min(1, "IFS path cannot be empty.")
.describe("IFS path to the source file (e.g., '/home/user/src/MYPROG.sqlrpgle')"),
compile_type: z
.enum(["SQLRPGLE", "RPGLE", "CLLE", "CBLLE"])
.describe("Type of compilation"),
setup_program: z
.string()
.optional()
.default("DB")
.describe("Library setup program to call before compilation (default: 'DB')"),
});

const CompileFromIfsOutputSchema = z.object({
success: z.boolean().describe("Whether compilation was successful."),
library: z.string().optional(),
objectName: z.string().optional(),
ifsPath: z.string().optional(),
executionTime: z.number().optional().describe("Execution time in milliseconds"),
message: z.string().optional(),
error: z
.object({
code: z.string().describe("Error code"),
message: z.string().describe("Error message"),
details: z.record(z.unknown()).optional().describe("Error details"),
})
.optional()
.describe("Error information if compilation failed."),
});

type CompileFromIfsInput = z.infer<typeof CompileFromIfsInputSchema>;
type CompileFromIfsOutput = z.infer<typeof CompileFromIfsOutputSchema>;

// =============================================================================
// Business Logic
// =============================================================================

export async function compileFromIfsLogic(
params: CompileFromIfsInput,
appContext: RequestContext,
_sdkContext: SdkContext,
): Promise<CompileFromIfsOutput> {
logger.debug(
{ ...appContext, toolInput: params },
"Processing compile from IFS logic."
);

const startTime = Date.now();
const { library, object_name, ifs_path, compile_type, setup_program } = params;

const pool = IBMiConnectionPool.getInstance();
const job = await pool.getJob();

try {
// Determine compile command based on type
let compileCmd = "";
switch (compile_type) {
case "SQLRPGLE":
compileCmd = `CRTSQLRPGI OBJ(${library}/${object_name}) SRCSTMF('${ifs_path}') COMMIT(*NONE) DBGVIEW(*SOURCE)`;
break;
case "RPGLE":
compileCmd = `CRTBNDRPG PGM(${library}/${object_name}) SRCSTMF('${ifs_path}') DBGVIEW(*SOURCE)`;
break;
case "CLLE":
compileCmd = `CRTBNDCL PGM(${library}/${object_name}) SRCSTMF('${ifs_path}') DBGVIEW(*SOURCE)`;
break;
case "CBLLE":
compileCmd = `CRTBNDCBL PGM(${library}/${object_name}) SRCSTMF('${ifs_path}') DBGVIEW(*SOURCE)`;
break;
default:
throw new McpError(
JsonRpcErrorCode.InvalidParams,
`Unsupported compile type: ${compile_type}`
);
}

logger.info(
{ ...appContext, library, objectName: object_name, ifsPath: ifs_path, compileType: compile_type },
"Compiling from IFS stream file"
);

// Build CL program that calls setup, then compiles
const clSource = `
PGM
CALL PGM(${library}/${setup_program})
MONMSG MSGID(CPF0000)
${compileCmd}
ENDPGM
`;

// Create temp CL in QTEMP
const tempMember = `CMP${Date.now().toString().substring(7)}`;

// Create CL source file if needed
try {
await job.query(`CREATE TABLE QTEMP.QCLSRC LIKE QSYS.QCLSRC`, {});
} catch (e) {
// Already exists, ignore
}

// Write CL source
await job.query(
`INSERT INTO QTEMP.QCLSRC (SRCSEQ, SRCDAT, SRCDTA) VALUES (?, ?, ?)`,
{ parameters: [1.00, 0, clSource] }
);

// Create and call the CL program
await job.query(`CALL QSYS2.QCMDEXC('CRTBNDCL PGM(QTEMP/${tempMember}) SRCFILE(QTEMP/QCLSRC)')`);
await job.query(`CALL QSYS2.QCMDEXC('CALL PGM(QTEMP/${tempMember})')`);

const executionTime = Date.now() - startTime;

logger.info(
{ ...appContext, library, objectName: object_name, executionTime },
"Successfully compiled from IFS"
);

return {
success: true,
library,
objectName: object_name,
ifsPath: ifs_path,
executionTime,
message: `Program ${object_name} compiled successfully from ${ifs_path}`,
};
} catch (error) {
const executionTime = Date.now() - startTime;
logger.error(
{ ...appContext, error, library, objectName: object_name, executionTime },
"Failed to compile from IFS"
);

throw new McpError(
JsonRpcErrorCode.InternalError,
`Compilation failed: ${error instanceof Error ? error.message : String(error)}`,
{
library,
objectName: object_name,
ifsPath: ifs_path,
error: String(error),
}
);
} finally {
await pool.returnJob(job);
}
}

// =============================================================================
// Tool Registration
// =============================================================================

export const compileFromIfsTool = defineTool({
name: "compile_from_ifs",
description: `Compile IBM i source code from IFS stream file with library setup. Calls the setup program (default: DB) to configure the library list, then compiles using SRCSTMF parameter. This is the modern approach for IBM i development.`,
inputSchema: CompileFromIfsInputSchema,
logic: compileFromIfsLogic,
});
Loading