Inspect PDF Digital Signatures
inspect_signaturesExamine digital signature fields in a PDF document. Get total signature count, signed/unsigned breakdown, and details per field including signer name, reason, and signing time.
Instructions
Examine digital signature fields in a PDF document.
Args:
file_path (string): Absolute path to a local PDF file
response_format ('markdown' | 'json'): Output format (default: 'markdown')
Returns: Total signature field count, signed/unsigned breakdown, and details for each field (signer name, reason, location, signing time, filter/subFilter).
Note: This tool inspects signature field structure only. Cryptographic signature verification is not performed.
Examples:
Check if a PDF has been digitally signed
Inspect signer information and signing dates
Verify signature field structure
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | Absolute path to a local PDF file (e.g., "/path/to/document.pdf") | |
| response_format | No | Output format: "markdown" for human-readable, "json" for structured data | markdown |
Implementation Reference
- Registration function for the 'inspect_signatures' tool. Defines the tool metadata, input schema, and the async handler that calls analyzeSignatures and formats the result.
export function registerInspectSignatures(server: McpServer): void { server.registerTool( 'inspect_signatures', { title: 'Inspect PDF Digital Signatures', description: `Examine digital signature fields in a PDF document. Args: - file_path (string): Absolute path to a local PDF file - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: Total signature field count, signed/unsigned breakdown, and details for each field (signer name, reason, location, signing time, filter/subFilter). Note: This tool inspects signature field structure only. Cryptographic signature verification is not performed. Examples: - Check if a PDF has been digitally signed - Inspect signer information and signing dates - Verify signature field structure`, inputSchema: InspectSignaturesSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }, async (params: InspectSignaturesInput) => { try { const analysis = await analyzeSignatures(params.file_path); const raw = params.response_format === ResponseFormat.JSON ? JSON.stringify(analysis, null, 2) : formatSignaturesMarkdown(analysis); const { text } = truncateIfNeeded(raw); return { content: [{ type: 'text' as const, text }] }; } catch (error) { const err = handleStructuredError(error); return { content: [{ type: 'text' as const, text: JSON.stringify(err, null, 2) }], isError: true, }; } }, ); } - src/services/pdflib-service.ts:348-437 (handler)Core implementation of signature analysis. Loads the PDF with pdf-lib, iterates AcroForm fields looking for 'Sig' type fields, extracts signature metadata (signer name, reason, location, time, filter, subFilter), and returns a SignaturesAnalysis.
export async function analyzeSignatures(filePath: string): Promise<SignaturesAnalysis> { return withSuppressedPdfLibLogs(() => analyzeSignaturesImpl(filePath)); } async function analyzeSignaturesImpl(filePath: string): Promise<SignaturesAnalysis> { const doc = await loadWithPdfLib(filePath); const fields: SignatureFieldInfo[] = []; try { const acroForm = doc.catalog.getAcroForm(); if (!acroForm) { return { totalFields: 0, signedCount: 0, unsignedCount: 0, fields: [], note: 'No AcroForm found in the document.', }; } const allFields = acroForm.getAllFields(); for (const [field, _ref] of allFields) { const ftName = field.dict.lookupMaybe(PDFName.of('FT'), PDFName); if (!ftName || ftName.decodeText() !== 'Sig') continue; const fieldName = field.getFullyQualifiedName() ?? field.getPartialName() ?? '(unnamed)'; const vObj = field.dict.get(PDFName.of('V')); let isSigned = false; let signerName: string | null = null; let reason: string | null = null; let location: string | null = null; let contactInfo: string | null = null; let signingTime: string | null = null; let filter: string | null = null; let subFilter: string | null = null; // If V exists, the field has been signed if (vObj) { isSigned = true; let sigDict: PDFDict | undefined; if (vObj instanceof PDFRef) { const resolved = doc.context.lookup(vObj); if (resolved instanceof PDFDict) sigDict = resolved; } else if (vObj instanceof PDFDict) { sigDict = vObj; } if (sigDict) { signerName = extractStringFromDict(sigDict, 'Name'); reason = extractStringFromDict(sigDict, 'Reason'); location = extractStringFromDict(sigDict, 'Location'); contactInfo = extractStringFromDict(sigDict, 'ContactInfo'); signingTime = extractStringFromDict(sigDict, 'M'); const filterObj = sigDict.lookupMaybe(PDFName.of('Filter'), PDFName); filter = filterObj?.decodeText() ?? null; const subFilterObj = sigDict.lookupMaybe(PDFName.of('SubFilter'), PDFName); subFilter = subFilterObj?.decodeText() ?? null; } } fields.push({ fieldName, isSigned, signerName, reason, location, contactInfo, signingTime, filter, subFilter, }); } } catch { // Some PDFs may have malformed AcroForm } const signedCount = fields.filter((f) => f.isSigned).length; return { totalFields: fields.length, signedCount, unsignedCount: fields.length - signedCount, fields, note: 'Cryptographic signature verification is not performed. Only field structure is inspected.', }; } - src/schemas/tier2.ts:41-47 (schema)Zod schema for inspect_signatures input: file_path (string) and response_format (markdown|json).
/** inspect_signatures */ export const InspectSignaturesSchema = z .object({ file_path: FilePathSchema, response_format: ResponseFormatSchema, }) .strict(); - src/types.ts:223-243 (schema)TypeScript type definitions for SignatureFieldInfo (per-field details) and SignaturesAnalysis (aggregate result including totalFields, signedCount, unsignedCount, fields array, and note).
/** Signature field information */ export interface SignatureFieldInfo { fieldName: string; isSigned: boolean; signerName: string | null; reason: string | null; location: string | null; contactInfo: string | null; signingTime: string | null; filter: string | null; subFilter: string | null; } /** inspect_signatures output */ export interface SignaturesAnalysis { totalFields: number; signedCount: number; unsignedCount: number; fields: SignatureFieldInfo[]; note: string; } - src/tools/index.ts:19-19 (registration)Import of registerInspectSignatures in the main tool index.
import { registerInspectSignatures } from './tier2/inspect-signatures.js'; - src/tools/index.ts:45-45 (registration)Registration call for inspect_signatures in the registerAllTools function.
registerInspectSignatures(server); - src/utils/formatter.ts:299-328 (helper)Formats the SignaturesAnalysis into a human-readable Markdown string with total fields, signed/unsigned counts, and per-field details.
export function formatSignaturesMarkdown(analysis: SignaturesAnalysis): string { const lines: string[] = ['# Digital Signature Analysis', '']; lines.push(`- **Total Signature Fields**: ${analysis.totalFields}`); lines.push(`- **Signed**: ${analysis.signedCount}`); lines.push(`- **Unsigned**: ${analysis.unsignedCount}`); if (analysis.totalFields === 0) { lines.push('', 'No signature fields found in this document.'); lines.push('', `> ${analysis.note}`); return lines.join('\n'); } lines.push('', '## Signature Fields', ''); for (const field of analysis.fields) { lines.push(`### ${field.fieldName}`, ''); lines.push(`- **Signed**: ${field.isSigned ? 'Yes' : 'No'}`); if (field.signerName) lines.push(`- **Signer**: ${field.signerName}`); if (field.reason) lines.push(`- **Reason**: ${field.reason}`); if (field.location) lines.push(`- **Location**: ${field.location}`); if (field.signingTime) lines.push(`- **Signing Time**: ${field.signingTime}`); if (field.filter) lines.push(`- **Filter**: ${field.filter}`); if (field.subFilter) lines.push(`- **SubFilter**: ${field.subFilter}`); lines.push(''); } lines.push(`> ${analysis.note}`); return lines.join('\n'); } - Helper function to extract a string value from a PDFDict by key name, used to read signature field metadata.
function extractStringFromDict(dict: PDFDict, key: string): string | null { const obj = dict.get(PDFName.of(key)); if (obj instanceof PDFString) return obj.decodeText(); if (obj instanceof PDFHexString) return obj.decodeText(); if (obj instanceof PDFName) return obj.decodeText(); return null; }