document-management について
document-managementはServiceNowの文書を扱うスキルで、添付ファイル、テンプレート、文書生成、承認を扱います。スクリプト例に加え、添付ファイルのアップロード、ダウンロード、一覧表示、削除に使うツールを説明します。
主な機能
- 01レコードの添付ファイル管理
- 02テンプレート作成と文書生成
- 03HTMLからPDFを生成
- 04文書の承認とバージョン管理
- 05添付ファイルのアクセス権と制限を確認
活用シーン
- 01ServiceNowレコードへのファイル添付や取得
- 02テンプレートから文書やPDFを生成
- 03文書の承認とバージョン更新を管理
MCP 対応
添付ファイルのアップロード、ダウンロード、一覧表示、削除に加え、テーブル照会やスクリプト実行など、MCPクライアント向けのServiceNowツールを説明します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ serac-labs/ serac --skill document-managementインストール後、Claude Code で/document-managementを入力して読み込まれたか確認します。
詳細
ServiceNow のドキュメント管理
ドキュメント管理では、添付ファイル、テンプレート、ドキュメントの生成を扱います。
ドキュメントアーキテクチャ
Record
├── Attachments (sys_attachment)
│ └── Attachment Data (sys_attachment_doc)
├── Generated Documents
└── Document Templates
主要なテーブル
| テーブル | 用途 |
|---|---|
sys_attachment | 添付ファイルのメタデータ |
sys_attachment_doc | 添付ファイルの内容 |
sys_report_template | レポートテンプレート |
dms_document | ドキュメントレコード |
添付ファイル(ES5)
添付ファイルをアップロードする
// Attach file to record (ES5 ONLY!)
function attachFile(tableName, recordSysId, fileName, contentType, content) {
var attachment = new GlideSysAttachment()
// Content can be base64 encoded string
var attachmentSysId = attachment.write(tableName, recordSysId, fileName, contentType, content)
return attachmentSysId
}
// Example
var base64Content = "SGVsbG8gV29ybGQh" // Base64 encoded
attachFile("incident", incidentSysId, "notes.txt", "text/plain", base64Content)
添付ファイルを読み取る
// Read attachment content (ES5 ONLY!)
function getAttachmentContent(attachmentSysId) {
var attachment = new GlideSysAttachment()
var content = attachment.getContent(attachmentSysId)
return content
}
// Get attachment as base64
function getAttachmentBase64(attachmentSysId) {
var attachment = new GlideSysAttachment()
var bytes = attachment.getBytes(attachmentSysId)
// Convert to base64
var base64 = GlideBase64.encode(bytes)
return base64
}
添付ファイルを一覧表示する
// Get all attachments for record (ES5 ONLY!)
function getRecordAttachments(tableName, recordSysId) {
var attachments = []
var gr = new GlideRecord("sys_attachment")
gr.addQuery("table_name", tableName)
gr.addQuery("table_sys_id", recordSysId)
gr.query()
while (gr.next()) {
attachments.push({
sys_id: gr.getUniqueValue(),
file_name: gr.getValue("file_name"),
content_type: gr.getValue("content_type"),
size_bytes: gr.getValue("size_bytes"),
created_on: gr.getValue("sys_created_on"),
created_by: gr.sys_created_by.getDisplayValue(),
})
}
return attachments
}
添付ファイルをコピーする
// Copy attachments between records (ES5 ONLY!)
function copyAttachments(sourceTable, sourceSysId, targetTable, targetSysId) {
var attachment = new GlideSysAttachment()
attachment.copy(sourceTable, sourceSysId, targetTable, targetSysId)
}
// Copy specific attachment
function copyOneAttachment(attachmentSysId, targetTable, targetSysId) {
var source = new GlideRecord("sys_attachment")
if (!source.get(attachmentSysId)) {
return null
}
var attachment = new GlideSysAttachment()
var content = attachment.getBytes(attachmentSysId)
return attachment.write(
targetTable,
targetSysId,
source.getValue("file_name"),
source.getValue("content_type"),
content,
)
}
ドキュメントテンプレート(ES5)
ドキュメントテンプレートを作成する
// Create document template (ES5 ONLY!)
var template = new GlideRecord("sys_report_template")
template.initialize()
template.setValue("name", "Incident Summary Report")
template.setValue("table", "incident")
template.setValue("description", "Summary report for incident resolution")
// Template content with placeholders
template.setValue(
"template",
"<html>\n" +
"<head><title>Incident Summary</title></head>\n" +
"<body>\n" +
"<h1>Incident Summary Report</h1>\n" +
"<h2>${number}</h2>\n" +
"<p><strong>Description:</strong> ${short_description}</p>\n" +
"<p><strong>Caller:</strong> ${caller_id.name}</p>\n" +
"<p><strong>Priority:</strong> ${priority}</p>\n" +
"<p><strong>State:</strong> ${state}</p>\n" +
"<p><strong>Resolution:</strong> ${close_notes}</p>\n" +
"<p><strong>Resolved By:</strong> ${resolved_by.name}</p>\n" +
"<p><strong>Resolved On:</strong> ${resolved_at}</p>\n" +
"</body>\n" +
"</html>",
)
template.insert()
テンプレートからドキュメントを生成する
// Generate document from template (ES5 ONLY!)
function generateDocumentFromTemplate(templateName, recordSysId) {
// Get template
var template = new GlideRecord("sys_report_template")
if (!template.get("name", templateName)) {
return null
}
// Get record
var tableName = template.getValue("table")
var gr = new GlideRecord(tableName)
if (!gr.get(recordSysId)) {
return null
}
// Process template
var content = template.getValue("template")
content = processTemplateVariables(content, gr)
return content
}
function processTemplateVariables(template, gr) {
// Replace ${field} and ${field.property} patterns
var pattern = /\$\{([^}]+)\}/g
var match
while ((match = pattern.exec(template)) !== null) {
var fieldPath = match[1]
var value = getFieldValue(gr, fieldPath)
template = template.replace(match[0], value)
}
return template
}
function getFieldValue(gr, fieldPath) {
var parts = fieldPath.split(".")
var value = gr
for (var i = 0; i < parts.length; i++) {
if (!value) return ""
if (i < parts.length - 1) {
// Reference field - get referenced record
value = value[parts[i]].getRefRecord()
} else {
// Final field
if (value.isValidField && value.isValidField(parts[i])) {
value = value[parts[i]].getDisplayValue() || value.getValue(parts[i])
} else {
value = ""
}
}
}
return value || ""
}
PDF の生成(ES5)
PDF を生成する
// Generate PDF from HTML (ES5 ONLY!)
function generatePDF(htmlContent, fileName) {
try {
var pdfCreator = new sn_pdfgeneratorutils.PDFGenerationAPI()
var pdfBytes = pdfCreator.convertToPDF(htmlContent)
// Return as base64
return {
success: true,
content: GlideBase64.encode(pdfBytes),
fileName: fileName || "document.pdf",
}
} catch (e) {
gs.error("PDF generation failed: " + e.message)
return {
success: false,
error: e.message,
}
}
}
// Generate and attach PDF to record
function generateAndAttachPDF(templateName, recordSysId, fileName) {
// Generate HTML from template
var html = generateDocumentFromTemplate(templateName, recordSysId)
if (!html) {
return { success: false, error: "Template generation failed" }
}
// Convert to PDF
var pdf = generatePDF(html, fileName)
if (!pdf.success) {
return pdf
}
// Get table name from template
var template = new GlideRecord("sys_report_template")
template.get("name", templateName)
var tableName = template.getValue("table")
// Attach PDF
var attachmentSysId = attachFile(tableName, recordSysId, fileName, "application/pdf", pdf.content)
return {
success: true,
attachment_sys_id: attachmentSysId,
}
}
ドキュメントワークフロー(ES5)
ドキュメントの承認
// Create document for approval (ES5 ONLY!)
function submitDocumentForApproval(documentSysId, approvers) {
var doc = new GlideRecord("dms_document")
if (!doc.get(documentSysId)) {
return { success: false, message: "Document not found" }
}
// Update state
doc.setValue("state", "pending_approval")
doc.update()
// Create approval records
for (var i = 0; i < approvers.length; i++) {
var approval = new GlideRecord("sysapproval_approver")
approval.initialize()
approval.setValue("sysapproval", documentSysId)
approval.setValue("approver", approvers[i])
approval.setValue("state", "requested")
approval.insert()
}
// Notify approvers
gs.eventQueue("document.approval.requested", doc, approvers.join(","), "")
return { success: true }
}
バージョン管理
// Create new document version (ES5 ONLY!)
function createDocumentVersion(documentSysId, newContent, changeNotes) {
var doc = new GlideRecord("dms_document")
if (!doc.get(documentSysId)) {
return null
}
// Get current version
var currentVersion = parseInt(doc.getValue("version"), 10) || 1
var newVersion = currentVersion + 1
// Archive current version
var archive = new GlideRecord("dms_document_version")
archive.initialize()
archive.setValue("document", documentSysId)
archive.setValue("version", currentVersion)
archive.setValue("content", doc.getValue("content"))
archive.setValue("created_by", gs.getUserID())
archive.insert()
// Update document with new version
doc.setValue("version", newVersion)
doc.setValue("content", newContent)
doc.work_notes = "Version " + newVersion + ": " + changeNotes
doc.update()
return {
document_sys_id: documentSysId,
version: newVersion,
}
}
// Get document version history
function getDocumentVersions(documentSysId) {
var versions = []
var version = new GlideRecord("dms_document_version")
version.addQuery("document", documentSysId)
version.orderByDesc("version")
version.query()
while (version.next()) {
versions.push({
version: version.getValue("version"),
created_on: version.getValue("sys_created_on"),
created_by: version.sys_created_by.getDisplayValue(),
})
}
return versions
}
添付ファイルのセキュリティ(ES5)
添付ファイルへのアクセスを確認する
// Check if user can access attachment (ES5 ONLY!)
function canAccessAttachment(attachmentSysId) {
var attachment = new GlideRecord("sys_attachment")
if (!attachment.get(attachmentSysId)) {
return false
}
// Check access to parent record
var parentTable = attachment.getValue("table_name")
var parentSysId = attachment.getValue("table_sys_id")
var parent = new GlideRecord(parentTable)
return parent.get(parentSysId) // Returns false if no read access
}
ファイルの出し入れ:添付ファイルツール
この行より上はすべてスクリプト側の機能です。あるレコードから別のレコードへ添付ファイルをコピーしたり、
すでに添付されているファイルを読み取ったりできます。一方で、通常求められる次の2つの操作はどちらもできません。**エージェントが持っているファイルをレコードに取り込むこと、またはインスタンスからファイルを取り出してエージェントに渡すことです。**バックグラウンドスクリプトからエージェントのファイルシステムにはアクセスできず、数メガバイトものバイト列をエージェントに返すこともできません。代わりに、6つのツールが /api/now/attachment 経由で処理します。
| ツール | 方向 | 受け取るもの |
|---|---|---|
snow_upload_attachment | 入力 | content 内の base64 |
snow_file_upload | 入力 | file_data 内の base64。同じエンドポイントで、パラメーター名が異なります |
snow_attach_file | 入力 | ローカルパス。読み取って multipart で送信します |
snow_get_attachments | 出力 | table_name + table_sys_id。メタデータのみを返します |
snow_file_download | 出力 | attachment_sys_id。base64 とコンテンツタイプを返します |
snow_delete_attachment | — | attachment_sys_id。完全に削除します |
base64 またはパス
snow_upload_attachment と snow_file_upload は同じエンドポイントを同じセマンティクスで呼び出します。違いはパラメーター名(content/table_sys_id と file_data/record_sys_id)だけです。どちらもファイルを読み取りません。すでに手元にあるバイト列を渡します。
snow_attach_file はディスクからファイルを読み取るツールです。最初にファイルのサイズを確認し、max_size_mb を超える場合は拒否します。この値のデフォルトは25で、インスタンス側の制限ではなく、クライアント側のガードです。インスタンスには com.glide.attachment.max_size(MB、デフォルトは1024ですが、実際にはもっと低く設定されていることがよくあります)による独自の上限があり、さらに glide.attachment.extensions により、受け付ける拡張子が制限されます。25 MB 以内でもインスタンスの上限を超えるファイルは、ガードではなく API で 400 エラーになります。
base64 にするとサイズが3分の1増えるため、24 MB のファイルはリクエストボディが32 MBになります。大きなファイルを送信する場合は、snow_attach_file を優先してください。multipart ではバイト列がそのまま送信されます。
ファイルを読み戻す
snow_get_attachments が返すのはメタデータのみです。sys_id、file_name、size_bytes、content_type が含まれます。ファイルの内容を取得するには、必ず2回呼び出します。
const list = await snow_get_attachments({ table_name: "incident", table_sys_id: incSysId })
const shot = list.attachments.find((a) => a.content_type.indexOf("image/") === 0)
const file = await snow_file_download({ attachment_sys_id: shot.sys_id }) // base64 + content_type
レコードのすべての添付ファイルをまとめてダウンロードする呼び出しはありません。リストを自分でループ処理し、処理前に size_bytes を確認してください。ツールの結果にはファイル全体が base64 で含まれます。
テーブルで添付ファイルが制限されている場合
添付ファイルへのアクセスは、親レコードの ACL では決まりません。sys_attachment を通じて解決され、独立した 3 つの条件によって制限されます。
glide.attachment.role— 添付に必要なロールを指定するインスタンスプロパティです。- テーブルレベルの添付ファイル制御。 テーブルでは、名前付きロールを持つユーザーのみが添付または読み取りできるように設定できます。通常は、親テーブルを条件とする
sys_attachment上のsys_security_aclレコードで制御します。 - 親レコード自体の読み取り ACL。 インシデントの読み取り権限を失うと、添付ファイルにもアクセスできなくなります。
この問題の現れ方は誤解を招くため、把握しておくことが重要です。sys_attachment はほかのテーブルと同じなので、読み取り権限のない呼び出し元にはエラーではなく空のリストが返されます。UI では添付ファイルが表示されているレコードに対して snow_get_attachments が [] を返す場合、レコードが空なのではなく、権限に問題があるということです。単一の添付ファイルの読み取りに失敗した場合は、アクセスできない sys_id の存在をプラットフォームが確認しないため、アクセス拒否ではなく未検出として表示されることがよくあります。そのため、同じレコードの一覧取得に成功した直後に「添付ファイルが見つかりません」と表示されても、ファイルが削除されたのではなく、2 回の読み取りで権限に違いがあります。ファイルがなくなったと判断する前に、sys_attachment を対象に snow_acl_explain で確認してください。
snow_delete_attachment は、sys_attachment のレコードと sys_attachment_doc のチャンクを完全に削除します。ごみ箱はなく、更新セットにも記録されません。これは設定ではなくデータです。
MCP ツールの統合
利用可能なツール
| ツール | 目的 |
|---|---|
snow_query_table | 添付ファイルとテンプレートをクエリする |
snow_execute_script | ドキュメントスクリプトをテストする |
snow_artifact_manage | アーティファクトを検索する(action: "find"、type は必須) |
snow_get_attachments | レコードに添付されているファイルを一覧表示する |
snow_file_download | 1 つの添付ファイルのバイトデータを取得する |
snow_attach_file | ローカルファイルを添付する(マルチパート) |
snow_upload_attachment | Base64 コンテンツを添付する |
snow_file_upload | Base64 コンテンツを添付する(別のパラメーター名を使用) |
snow_delete_attachment | 添付ファイルを完全に削除する |
ワークフローの例
// 1. Get record attachments
await snow_query_table({
table: "sys_attachment",
query: "table_name=incident^table_sys_id=inc_sys_id",
fields: "file_name,content_type,size_bytes,sys_created_on",
})
// 2. Generate document
await snow_execute_script({
script: `
var html = generateDocumentFromTemplate('Incident Summary', 'inc_sys_id');
gs.info('Generated: ' + (html ? 'success' : 'failed'));
`,
})
// 3. Find document templates
await snow_query_table({
table: "sys_report_template",
query: "table=incident",
fields: "name,description,table",
})
ベストプラクティス
- ファイルの種類 - 許可する種類を制限します
- サイズ制限 - 最大ファイルサイズを設定します
- ウイルススキャン - アップロードをスキャンします
- アクセス制御 - 親レコードから継承します
- バージョン管理 - 変更を追跡します
- クリーンアップ - 孤立したファイルを削除します
- テンプレート - 一貫性を保つために使用します
- ES5 のみ - 最新の JavaScript 構文は使用しません