blazar-source/dsh-r7-office ↗★ 1

dsh-r7-office

R7-Office文档处理插件及MCP服务 适合需要AI智能体对Office和PDF文档进行读写、编辑与转换的任务。

套件
dsh-r7-office
相容性
待驗證
版本
0.1.1
授權
MIT
最近更新
2026年10月1日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:blazar-source/dsh-r7-office

dsh-r7-office

CI License: MIT Node.js

R7-Office (Р7-Офис) document processing plugin and Model Context Protocol (MCP) server for DeepSeek Harness.

Lets an AI agent inspect, read, create, format, edit and convert DOCX, XLSX, PPTX and PDF documents — through standard OOXML manipulation plus the R7 converter you already have installed — without emulating a mouse or keyboard.

⚠️ Unofficial community project

This is an independent, community-maintained project. It is not affiliated with, endorsed by, sponsored by, or supported by АО «Р7» (R7-Office) or DeepSeek, and it is not an official R7-Office or DeepSeek product.

R7-Office and Р7-Офис are trademarks of their respective owners and are used here only to describe what this software interoperates with.

This repository contains no R7-Office code, binaries or other assets. It detects an R7-Office installation already present on the user's machine and drives it locally. You must install and license R7-Office yourself.


Русская документация: README.ru.md


What it does

  • Format-preserving editing — replacements and edits keep the original XML styles, fonts, colours and numbering hierarchies. Untouched parts of a document are preserved byte for byte.
  • All four formats — DOCX (headings, paragraphs, lists, tables, page breaks), XLSX (sheets, ranges, values, formulas), PPTX (slides, titles, text frames), PDF (conversion through the local R7 x2t engine).
  • Validation built in — r7_validate checks package integrity before and after a change.
  • Two ways to run — as a native DeepSeek Harness plugin, or as a standalone MCP server over stdio for any MCP client.
  • Live desktop bridge — optionally drives a document the user has open in R7-Office Desktop, over a loopback WebSocket.

Architecture

DeepSeek Harness ──► dsh-r7-office plugin ──┐
                                            ├──► DOCX / XLSX / PPTX / PDF
Any MCP client ────► MCP server (stdio) ────┤
                                            │
R7-Office Desktop ◄── desktop bridge ◄──────┘

Details in docs/architecture.md; design decisions in docs/decisions/.


Requirements

Node.js>= 20.0.0 to run the plugin and MCP server. >= 22.0.0 to run the full test suite (the bridge and CDP clients use the global WebSocket, which only exists from Node 22).
Runtime dependenciesnone — the plugin has zero runtime dependencies
R7-Office Desktopoptional. Needed for r7_convert (→ PDF/HTML), inspect fidelity on exotic documents, and the whole desktop bridge. Without it the pure-OOXML tools still work.
PlatformWindows, Linux and macOS. R7 auto-detection covers the standard install locations of all three; the live desktop bridge is verified on Windows only (see limitations).

Clean install from scratch

These steps assume an empty directory and a machine with Node.js 20+.

# 1. Get the code
git clone https://github.com/blazar-source/dsh-r7-office.git
cd dsh-r7-office

# 2. Install the optional dev dependencies (test-only: the MCP client SDK)
npm install

# 3. Verify the checkout — no R7-Office required for this step
npm test

npm test is the portable suite and needs nothing but Node: unit tests, OOXML round-trip regression tests, file end-to-end workflows, security-policy tests and an external MCP client smoke suite. Tests whose subject is the installed product — R7's own templates, the x2t converter, the desktop editor — skip themselves with a stated reason instead of failing, so a machine that has never seen R7-Office gets a green run and a visible skip count.

$ npm test
# tests 469
# pass 390
# fail 0
# skipped 79

The R7-dependent tests are only meaningful where R7-Office is installed, so they get their own command, which refuses to run without it rather than skipping everything and reporting success:

# Requires an installed R7-Office. Runs the whole suite with nothing skipped,
# then drives the live desktop editor over CDP and saves a document for real.
npm run test:r7

# Just the suite, or just the live editor, if you want them separately:
npm run test:r7 -- --suite
npm run test:r7 -- --live

To see what the portable suite does on a machine without R7 even though you have one installed, set R7_OFFICE_DISABLED=1 — detection then reports no installation, which is exactly the condition CI runs under.

Then verify the integration you actually intend to use:

# R7-Office file pipeline (author → edit → validate → PDF). Skips if R7 is absent.
npm run test:live

# DeepSeek Harness plugin activation. Boots a fresh Harness and asserts the
# 28 r7_* tools reached the tool registry.
npm run test:harness -- --profile web

As a DeepSeek Harness plugin

Install the package directory as a bundle:

# in the DeepSeek Harness UI: plugin_manager → install_bundle
# target: /absolute/path/to/dsh-r7-office

or from the CLI equivalent for your profile:

dsh plugin --profile 
 add /absolute/path/to/dsh-r7-office

Confirm it activated — a fresh Harness boot prints:

[r7-office] зарегистрировано инструментов: 27 (r7_inspect, r7_read, ...); desktop bridge port=7888, developerMode=false

Note. Add the row either through install_bundle or by hand in the profile's cordis.patch.yml — never both. Two entries with the same r7-office id make the row fail to activate.

The plugin also registers a short usage section into the agent system prompt, so the agent knows the tools and the intended inspect → read → edit → validate → convert order.

As a standalone MCP server

The MCP server speaks line-delimited JSON-RPC 2.0 on stdio.

{
  "mcpServers": {
    "r7-office": {
      "command": "node",
      "args": ["/absolute/path/to/dsh-r7-office/src/mcp/cli.js"]
    }
  }
}

Put that in your client's configuration file (for example claude_desktop_config.json or .cursor/mcp.json). Any stdio MCP client works; interoperability is verified in npm test against the official @modelcontextprotocol/client SDK.

You can also run it by hand:

echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node src/mcp/cli.js

Tools

ToolPurpose
r7_inspectDocument outline: headings, paragraphs, tables, sheets, slides, metadata
r7_readStructured text, Markdown view, or spreadsheet cell ranges
r7_createNew DOCX / XLSX / PPTX. Refuses to replace an existing file unless overwrite: true; every sheets[] entry and name is honoured
r7_editReplace, restyle or delete one paragraph by index
r7_replaceFind and replace text, preserving run formatting
r7_insertInsert paragraphs, headings, bullet items or page breaks
r7_tableCreate, inspect or update tables and cells, including merge, borders, shading and column widths
r7_docx_formattingRead the normalized formatting of every paragraph, run and table cell: style, font, size, colour, alignment, indents, spacing, line spacing, lists
r7_docx_sectionsRead and set page size, orientation, margins, columns, page breaks and section breaks
r7_docx_header_footerList, read, create or retitle headers and footers; page-number fields survive
r7_docx_imageInsert a PNG/JPEG/GIF at a size, keeping the aspect ratio
r7_docx_hyperlinkList, insert, retitle or remove hyperlinks and their relationships
r7_sheet_readRead values, formulas or the normalized formatting of a sheet or range (e.g. A1:D10)
r7_sheet_writeWrite cells or a 2-D matrix, addressed by sheet name or index; dates are stored as real date serials
r7_sheet_formatFormat a cell or range: font, background, borders, alignment, wrap, number format (integer, decimal, currency, percent, date, datetime, custom), merge, column width and row height
r7_sheet_addAdd a worksheet to an existing workbook; other sheets are untouched
r7_sheet_formulaInsert or update a formula
r7_slide_readRead a slide as a normalized structure: every object's id, type, geometry, text, font, fill, stroke, alignment and paragraphs
r7_slide_createCreate a deck, or append a slide built on one of the deck's own layouts, without altering the slides already there
r7_slide_formatRestyle or reposition one existing object in place: font, geometry, fill, border, alignment, lists, spacing, text
r7_slide_editDuplicate, move, reorder or delete slides
r7_slide_objectAdd or remove an object: shape, text box or PNG/JPEG image
r7_convertConvert via the R7 x2t engine (PDF, HTML, TXT, DOCX, XLSX, PPTX). PDF conversion uses the font list R7 generates, repairs a malformed ToUnicode count, and refuses to return a PDF with no extractable text instead of silently emitting a blank one. XLSX exports every worksheet by default (allSheets)
r7_validateCheck package integrity and XML health
r7_pdf_inspectReport whether a PDF really contains text or was produced without fonts: per-font embedded flag and ToUnicode/Cyrillic map counts, text glyphs, empty fill operators, and a text / outlined / mixed verdict. repair: true fixes a malformed ToUnicode count in place
r7_desktop_statusDesktop bridge connection and effective security mode
r7_desktop_selectionRead or replace the selection in the open editor
r7_desktop_execRun a safe editor command, or raw DocScript in developer mode

Presentations

A deck is built on the layouts it already has. Adding a slide registers the slide part, its relationship part, its [Content_Types].xml override, its entry and the presentation relationship; the layout, the master, the theme, the notes and every slide that already existed keep their original bytes. A placeholder written onto a slide stays a placeholder, so it keeps inheriting its geometry and typography from the master.

Reading resolves that inheritance for you: r7_slide_read reports a title slide's 60 pt layout title rather than the presentation's 18 pt default, and reports the position a placeholder inherits from the layout or the master instead of null.

import { PptxEngine } from 'dsh-r7-office/r7'

const pptx = new PptxEngine()

// 1. Read: geometry, text, font, fill, stroke, alignment, paragraphs.
const before = await pptx.readSlide('Deck.pptx', 1)
const title = before.slide.objects.find(o => o.placeholder?.type === 'title')
const body = before.slide.objects.find(o => o.placeholder?.type === 'body')
console.log(title.x, title.width, title.font.family, title.font.size)

// 2. Add a slide on one of the deck's own layouts.
const added = await pptx.addSlide('Deck.pptx', {
  layoutType: 'obj',
  title: 'Ключевые выводы',
  paragraphs: [{ text: 'Выручка +18%', bullet: true }]
})

// 3. Restyle one object. Everything not named keeps its original bytes.
await pptx.formatObject('Deck.pptx', {
  slideIndex: added.slideIndex,
  objectId: title.id,
  font: { family: 'Georgia', size: 32, bold: true, color: '#1F6FEB' },
  alignment: 'center'
})

// 4. Add an object: fill, border, text, font, geometry — all in one call.
await pptx.addShape('Deck.pptx', {
  slideIndex: added.slideIndex,
  shape: 'rounded-rectangle',          // rectangle, ellipse, line, arrow, star, …
  x: '2cm', y: '10cm', width: '9.4cm', height: '8.2cm',
  fill: '#1F6FEB', fillTransparency: 0.1,
  line: '#0B3D91', lineWidth: 2,       // lineWidth is in points
  text: 'KPI 98%',
  font: { family: 'Arial', size: 24, bold: true, color: '#FFFFFF' },
  alignment: 'center', verticalAnchor: 'middle'
})

// 5. Insert and replace a picture.
const image = await pptx.addImage('Deck.pptx', {
  slideIndex: added.slideIndex, imagePath: 'chart.png', x: '4cm', y: '4cm', width: '24cm'
})
await pptx.formatObject('Deck.pptx', {
  slideIndex: added.slideIndex, objectId: image.objectId, imagePath: 'chart-v2.png'
})

// 6. Structure, and a PDF.
await pptx.duplicateSlide('Deck.pptx', 1)
await pptx.moveSlide('Deck.pptx', 2, 0)
await pptx.deleteSlide('Deck.pptx', 4)
console.log((await pptx.validate('Deck.pptx')).valid)
await pptx.toPdf('Deck.pptx', 'Deck.pdf')

Run the full acceptance deck:

node examples/pptx-acceptance.js            # writes into os.tmpdir()/r7-acceptance
node examples/pptx-acceptance.js ./out      # or wherever you like

Measurements. A bare number is EMU (the unit r7_slide_read returns), so a value can be read, adjusted and written back unchanged. "2cm", "1in", "30px" and "24pt" also work. lineWidth is the exception: a number below 100 is read as points, because "border: 1.5" means 1.5 pt to everyone who is not holding a DrawingML specification.

Colours. #RRGGBB, #AARRGGBB and transparency as a separate 0..1 option. transparency on a font and fillTransparency on a fill are deliberately distinct, so a half-transparent caption colour cannot make the shape under it see-through.

Shapes. rectangle, rounded-rectangle, ellipse, circle, line, arrow (and arrow-left/up/down/left-right), triangle, diamond, pentagon, hexagon, octagon, star, chevron, plus, cloud, heart, cylinder, cube, donut, pie, parallelogram, trapezoid — or any DrawingML preset name. pptx.shapeCatalog() lists them all.


Typical agent flow

"Take Report.docx, update section 3, keep the formatting, add a summary table and save a PDF."

r7_inspect → r7_read → r7_replace (keeps formatting) → r7_table
           → r7_validate → r7_convert

Run it yourself:

node examples/report-scenario.js

Library use:

import { DocxEngine, R7Adapter } from 'dsh-r7-office/r7'

const docx = new DocxEngine()
const adapter = new R7Adapter()

await docx.replaceText('Report.docx', 'draft text', 'approved text', {
  outputPath: 'Report_v2.docx'   // the original is never overwritten by default
})

await docx.table('Report_v2.docx', {
  action: 'create',
  rows: [['Objective', 'Timeline', 'Owner'], ['Deploy R7', 'Q2', 'IT']]
})

console.log((await docx.validate('Report_v2.docx')).valid)

await adapter.convert('Report_v2.docx', 'Report_v2.pdf')

Security

r7_desktop_exec can execute code inside the user's open editor. Raw DocScript is therefore disabled by default; only a fixed allowlist of safe argument-driven commands runs in production. Enable raw execution only deliberately:

- id: r7-office
  name: 'dsh-r7-office'
  config:
    developerMode: true        # or export DSH_R7_DEVELOPER_MODE=1

r7_desktop_status always reports the effective mode. The desktop bridge binds to 127.0.0.1 only.

See SECURITY.md for the full threat model and how to report a vulnerability.


Known limitations

  • Presentations: building layouts or masters is out of scope. Slides are created on the layouts the deck already contains; a deck that ships none gets the generic title/body pair.
  • Presentations: SmartArt and charts are preserved, never authored. The reader reports them (type: "chart", type: "graphicFrame") and every part behind them stays byte-identical through any edit, but the engine cannot create one. A hand-written SmartArt frame is just a dgm:relIds reference, and R7's own renderer dereferences the diagram parts behind it.
  • **Presentatio