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
11 changes: 7 additions & 4 deletions node-version/src/handlers/platformHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export interface PageRotation {
readonly amount: number;
}

export interface Redaction {
readonly pageIndex: number;
readonly boundingBox: number[];
readonly label?: string | undefined;
}

export interface PdfMetadata {
readonly title?: string;
readonly author?: string;
Expand Down Expand Up @@ -435,10 +441,7 @@ export class PlatformHandler {
return body;
}

async redactPdf(
fileBytes: Buffer,
redactions: { pageIndex: number; boundingBox: number[] }[],
): Promise<Buffer> {
async redactPdf(fileBytes: Buffer, redactions: Redaction[]): Promise<Buffer> {
const file = createBytesFile(ContentType.PDF, fileBytes, 'input.pdf');
const { body } = await this._client.run('transformations', file, {
method: 'redact',
Expand Down
10 changes: 9 additions & 1 deletion node-version/src/tools/pii.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { z } from 'zod';
import type { AppContext } from '../context.js';
import { getDep } from '../context.js';
import { handleToolError, UserFacingError } from '../errors.js';
import type { Redaction } from '../handlers/platformHandler.js';
import { outputTargetSchema, singleFileInputSchema } from '../models.js';
import { jsonResult } from './jsonOutput.js';

Expand Down Expand Up @@ -38,6 +39,13 @@ const _boundingBoxAreaSchema = z.object({
.array(z.number())
.length(4)
.describe('Bounding box coordinates [x0, y0, width, height]'),
label: z
.string()
.optional()
.describe(
'Optional text rendered onto the redaction box in the output, left-aligned and ' +
'trimmed to fit the width of the box',
),
});

function _successResult(
Expand Down Expand Up @@ -144,7 +152,7 @@ export function register(server: McpServer, context: AppContext): void {

const inputBytes = filesHandler.read(args.inputPath);

let redactions: { pageIndex: number; boundingBox: number[] }[];
let redactions: Redaction[];

if (args.piiJsonFile !== undefined) {
const jsonBytes = filesHandler.read(args.piiJsonFile);
Expand Down
50 changes: 50 additions & 0 deletions node-version/tests/pii.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,56 @@ describe('PII tools', () => {
]);
});

it('passes redaction labels through to the platform', async () => {
filesHandlerMock.read.mockReturnValue(Buffer.from('pdf-bytes'));
platformHandlerMock.redactPdf.mockResolvedValue(Buffer.from('redacted-pdf'));
filesHandlerMock.write.mockReturnValue(path.join(tmpDir, 'doc-redacted.pdf'));

const redactions = [
{ pageIndex: 0, boundingBox: [10, 20, 30, 40], label: 'label' },
{ pageIndex: 1, boundingBox: [5, 5, 15, 15] },
];

await caller.call(
'redact_pdf',
{ inputPath: path.join(tmpDir, 'doc.pdf'), redactions },
{ expectedResult: { outputFilename: 'doc-redacted.pdf', redactionCount: 2 } },
);

// Labels are optional per redaction: the first carries one, the second is sent without.
expect(platformHandlerMock.redactPdf).toHaveBeenCalledWith(
Buffer.from('pdf-bytes'),
redactions,
);
});

it('does not label redactions derived from piiJsonFile', async () => {
const piiResult = {
PIIBoxes: [
{ PIIType: 'EMAIL', confidence: 0.9, pageIndex: 0, boundingBox: [10, 20, 30, 40] },
],
};
filesHandlerMock.read
.mockReturnValueOnce(Buffer.from('pdf-bytes'))
.mockReturnValueOnce(Buffer.from(JSON.stringify(piiResult)));
platformHandlerMock.redactPdf.mockResolvedValue(Buffer.from('redacted-pdf'));
filesHandlerMock.write.mockReturnValue(path.join(tmpDir, 'doc-redacted.pdf'));

await caller.call(
'redact_pdf',
{
inputPath: path.join(tmpDir, 'doc.pdf'),
piiJsonFile: path.join(tmpDir, 'doc-pii.json'),
},
{ expectedResult: { outputFilename: 'doc-redacted.pdf', redactionCount: 1 } },
);

// Auto-redaction output is unchanged: no label is derived from the detected PII type.
expect(platformHandlerMock.redactPdf).toHaveBeenCalledWith(Buffer.from('pdf-bytes'), [
{ pageIndex: 0, boundingBox: [10, 20, 30, 40] },
]);
});

it('returns error when neither redactions nor piiJsonFile provided', async () => {
filesHandlerMock.read.mockReturnValue(Buffer.from('pdf-bytes'));

Expand Down