@developer.k/ms-office-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +265 -0
- package/build/index.js +63 -0
- package/build/office-utils.js +36 -0
- package/build/tools/excel.js +230 -0
- package/build/tools/ppt.js +521 -0
- package/build/tools/word.js +206 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# MS Office MCP
|
|
2
|
+
|
|
3
|
+
`@developer.k/ms-office-mcp` is an MCP server for working with Microsoft Office documents from an MCP client.
|
|
4
|
+
|
|
5
|
+
It supports two workflows:
|
|
6
|
+
|
|
7
|
+
- File-based operations for `.xlsx`, `.docx`, and `.pptx` files.
|
|
8
|
+
- Active desktop Office automation for documents already open in Excel, Word, or PowerPoint on Windows.
|
|
9
|
+
|
|
10
|
+
The server communicates over stdio and is designed to be launched by MCP clients such as Claude Desktop, Cursor, or other MCP-compatible tools.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node.js 20 or later.
|
|
15
|
+
- Microsoft Office desktop apps for active-document tools.
|
|
16
|
+
- Windows PowerShell and Office COM automation for tools that read or edit currently open Office documents.
|
|
17
|
+
|
|
18
|
+
File-based tools can run anywhere Node.js can access the target files. Active Excel, Word, and PowerPoint tools require a Windows desktop session with the corresponding Office app already running.
|
|
19
|
+
|
|
20
|
+
## Usage with MCP Clients
|
|
21
|
+
|
|
22
|
+
### Using npx
|
|
23
|
+
|
|
24
|
+
Add this server to your MCP client configuration:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"ms-office-mcp": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "-p", "@developer.k/ms-office-mcp", "ms-office-mcp"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
No environment variables are required.
|
|
38
|
+
|
|
39
|
+
### Local Development
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install
|
|
43
|
+
npm run build
|
|
44
|
+
npm start
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For TypeScript development:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Tools
|
|
54
|
+
|
|
55
|
+
### File-Based Tools
|
|
56
|
+
|
|
57
|
+
| Tool | Office app | Purpose | Key inputs |
|
|
58
|
+
| --- | --- | --- | --- |
|
|
59
|
+
| `list_sheets` | Excel | List worksheet names in an `.xlsx` file. | `path` |
|
|
60
|
+
| `read_excel` | Excel | Read rows from a sheet as JSON objects. | `path`, optional `sheetName` |
|
|
61
|
+
| `write_excel` | Excel | Create or overwrite a sheet from object rows. | `path`, `sheetName`, `data` |
|
|
62
|
+
| `read_word` | Word | Extract raw text from a `.docx` file. | `path` |
|
|
63
|
+
| `write_word` | Word | Create a `.docx` file with optional title and text content. | `path`, optional `title`, `content` |
|
|
64
|
+
| `read_pptx` | PowerPoint | Extract text from a `.pptx` file. | `path` |
|
|
65
|
+
| `write_pptx` | PowerPoint | Create a `.pptx` file from slide title/content pairs. | `path`, `slides` |
|
|
66
|
+
|
|
67
|
+
## Active Office Tools
|
|
68
|
+
|
|
69
|
+
Active Office tools connect to an already-running Office desktop application. They operate on the open document but do not automatically save it.
|
|
70
|
+
|
|
71
|
+
Use the `filename` argument when multiple documents are open. If omitted, the active document is used.
|
|
72
|
+
|
|
73
|
+
| Tool | Office app | Purpose | Key inputs |
|
|
74
|
+
| --- | --- | --- | --- |
|
|
75
|
+
| `list_active_excel` | Excel | List open Excel workbooks. | none |
|
|
76
|
+
| `get_active_excel` | Excel | Read the active sheet from an open workbook. | optional `filename` |
|
|
77
|
+
| `write_active_excel` | Excel | Write object rows into the active sheet of an open workbook. | optional `filename`, `data` |
|
|
78
|
+
| `list_active_word` | Word | List open Word documents. | none |
|
|
79
|
+
| `get_active_word` | Word | Read text from an open Word document. | optional `filename` |
|
|
80
|
+
| `write_active_word` | Word | Edit an open Word document. | optional `filename`, `operation`, `text`, optional `findText`, optional `replaceAllMatches` |
|
|
81
|
+
| `list_active_pptx` | PowerPoint | List open PowerPoint presentations. | none |
|
|
82
|
+
| `get_active_pptx` | PowerPoint | Read slide text from an open presentation. | optional `filename` |
|
|
83
|
+
| `write_active_pptx` | PowerPoint | Edit slide-level text in an open presentation. | optional `filename`, `operation`, optional `slideIndex`, optional `title`, `text`, optional `findText`, optional `replaceAllMatches` |
|
|
84
|
+
| `inspect_active_pptx` | PowerPoint | Inspect slides and shapes, including IDs, names, types, positions, sizes, text, and basic colors. | optional `filename`, optional `slideIndex` |
|
|
85
|
+
| `edit_active_pptx_object` | PowerPoint | Edit a specific shape without selecting it in the UI. | optional `filename`, `slideIndex`, `shapeId` or `shapeName`, `operation` |
|
|
86
|
+
|
|
87
|
+
### Supported Operations
|
|
88
|
+
|
|
89
|
+
| Tool | Operations |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `write_active_word` | `append`, `replace_text`, `replace_all` |
|
|
92
|
+
| `write_active_pptx` | `add_slide`, `replace_text`, `replace_slide_text` |
|
|
93
|
+
| `edit_active_pptx_object` | `set_text`, `set_position`, `set_size`, `set_fill_color`, `set_line_color`, `delete` |
|
|
94
|
+
|
|
95
|
+
### Common Input Examples
|
|
96
|
+
|
|
97
|
+
Read a specific Excel sheet:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"path": "C:\\Documents\\report.xlsx",
|
|
102
|
+
"sheetName": "Sheet1"
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Replace text in an open Word document:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"filename": "Document1.docx",
|
|
111
|
+
"operation": "replace_text",
|
|
112
|
+
"findText": "old phrase",
|
|
113
|
+
"text": "new phrase",
|
|
114
|
+
"replaceAllMatches": true
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Edit a PowerPoint shape:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"filename": "Presentation1.pptx",
|
|
123
|
+
"slideIndex": 2,
|
|
124
|
+
"shapeId": 5,
|
|
125
|
+
"operation": "set_fill_color",
|
|
126
|
+
"color": "#FFAA00"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Example Workflow
|
|
131
|
+
|
|
132
|
+
1. Ask the MCP client to inspect slide 3 with `inspect_active_pptx`.
|
|
133
|
+
2. Pick the returned shape ID for the object you want to modify.
|
|
134
|
+
3. Call `edit_active_pptx_object` with that shape ID.
|
|
135
|
+
4. Review the open PowerPoint window.
|
|
136
|
+
5. Save manually if the result is correct.
|
|
137
|
+
|
|
138
|
+
## Prompt Examples
|
|
139
|
+
|
|
140
|
+
These are examples of prompts you can type into an MCP client after this server is configured.
|
|
141
|
+
|
|
142
|
+
### Excel
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
Read the Excel file at C:\Documents\sales.xlsx and summarize the first sheet.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
List all sheets in C:\Documents\sales.xlsx, then read the sheet named "Q2".
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
Create an Excel file at C:\Documents\tasks.xlsx with columns Task, Owner, Status, and DueDate. Add five sample rows.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
I have an Excel workbook open. Read the active sheet and tell me which rows have missing Status values.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
Write these rows into the active Excel workbook: Task=Review proposal, Owner=Kim, Status=Done; Task=Send deck, Owner=Lee, Status=In progress.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Word
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
Read C:\Documents\proposal.docx and give me a concise summary with action items.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
Create a Word document at C:\Documents\meeting-notes.docx titled "Meeting Notes" with sections for Attendees, Decisions, and Next Steps.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
I have a Word document open. Replace every occurrence of "FY25" with "FY26", but do not save the file.
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
Append this paragraph to the open Word document: "Please review the updated timeline before Friday."
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
Read the currently active Word document and rewrite the executive summary in a more concise tone.
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### PowerPoint
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
Read C:\Documents\quarterly-review.pptx and summarize each slide in one sentence.
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
Create a PowerPoint file at C:\Documents\project-update.pptx with slides for Overview, Timeline, Risks, and Next Steps.
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
I have a PowerPoint deck open. Replace "Q1" with "Q2" on slide 2 only, without saving the file.
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
Inspect slide 3 of the open PowerPoint deck and show me the shapes with their IDs, names, text, position, and size.
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
On slide 3 of the open PowerPoint deck, find the shape that contains "Revenue" and change its fill color to #FFAA00.
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```text
|
|
209
|
+
Add a new slide to the open PowerPoint deck titled "Next Steps" with three bullets: finalize scope, assign owners, confirm timeline.
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
On slide 5, move the shape named "Title 1" to the top-left corner and make it wider.
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Cross-Document Tasks
|
|
217
|
+
|
|
218
|
+
```text
|
|
219
|
+
Read the active Excel workbook, summarize the key numbers, and add a new slide to the open PowerPoint deck with the summary.
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```text
|
|
223
|
+
Read C:\Documents\notes.docx and create a PowerPoint deck at C:\Documents\summary.pptx with one slide per major section.
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
Inspect the open PowerPoint deck, find slides that mention "risk", and append a risk summary paragraph to the open Word document.
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Docker
|
|
231
|
+
|
|
232
|
+
The Docker image can be used for file-based operations where the target files are mounted into the container.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
docker compose up --build
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Active desktop Office automation is not available inside the provided Linux container because it depends on Windows Office COM automation.
|
|
239
|
+
|
|
240
|
+
## Publishing
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
npm run build
|
|
244
|
+
npm pack --dry-run
|
|
245
|
+
npm publish --access public
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The package includes only:
|
|
249
|
+
|
|
250
|
+
- `build`
|
|
251
|
+
- `README.md`
|
|
252
|
+
- `LICENSE`
|
|
253
|
+
- `package.json`
|
|
254
|
+
|
|
255
|
+
## Limitations
|
|
256
|
+
|
|
257
|
+
- Active-document tools are Windows-only.
|
|
258
|
+
- Active-document tools require the corresponding Office app to be running.
|
|
259
|
+
- Active-document tools modify the open document but do not save automatically.
|
|
260
|
+
- `.doc` and `.ppt` legacy formats are not the primary target; use `.docx` and `.pptx`.
|
|
261
|
+
- PowerPoint object editing currently targets shapes by slide and shape identifier/name.
|
|
262
|
+
|
|
263
|
+
## Security Notes
|
|
264
|
+
|
|
265
|
+
This package does not require API keys or credentials. It can read and modify files or active Office documents that the server process can access, so configure it only in MCP clients and environments you trust.
|
package/build/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { excelTools } from "./tools/excel.js";
|
|
7
|
+
import { pptTools } from "./tools/ppt.js";
|
|
8
|
+
import { wordTools } from "./tools/word.js";
|
|
9
|
+
class OfficeMcpServer {
|
|
10
|
+
server;
|
|
11
|
+
tools;
|
|
12
|
+
handlers;
|
|
13
|
+
constructor() {
|
|
14
|
+
this.server = new Server({
|
|
15
|
+
name: "ms-office-mcp",
|
|
16
|
+
version: "1.0.0",
|
|
17
|
+
}, {
|
|
18
|
+
capabilities: {
|
|
19
|
+
tools: {},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
const modules = [excelTools, wordTools, pptTools];
|
|
23
|
+
this.tools = modules.flatMap((module) => module.tools);
|
|
24
|
+
this.handlers = this.mergeHandlers(modules);
|
|
25
|
+
this.setupTools();
|
|
26
|
+
}
|
|
27
|
+
mergeHandlers(modules) {
|
|
28
|
+
return modules.reduce((handlers, module) => {
|
|
29
|
+
for (const [name, handler] of Object.entries(module.handlers)) {
|
|
30
|
+
handlers[name] = handler;
|
|
31
|
+
}
|
|
32
|
+
return handlers;
|
|
33
|
+
}, {});
|
|
34
|
+
}
|
|
35
|
+
setupTools() {
|
|
36
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
37
|
+
tools: this.tools,
|
|
38
|
+
}));
|
|
39
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
40
|
+
const { name, arguments: args } = request.params;
|
|
41
|
+
try {
|
|
42
|
+
const handler = this.handlers[name];
|
|
43
|
+
if (!handler) {
|
|
44
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
45
|
+
}
|
|
46
|
+
return await handler(args);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof z.ZodError) {
|
|
50
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments: ${error.issues.map((e) => e.message).join(", ")}`);
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async run() {
|
|
57
|
+
const transport = new StdioServerTransport();
|
|
58
|
+
await this.server.connect(transport);
|
|
59
|
+
console.error("MS Office MCP server running on stdio");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const server = new OfficeMcpServer();
|
|
63
|
+
server.run().catch(console.error);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
export function textContent(text) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: "text", text }],
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function toBase64(value) {
|
|
9
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
10
|
+
}
|
|
11
|
+
export function runPowerShell(script) {
|
|
12
|
+
try {
|
|
13
|
+
const fullScript = `
|
|
14
|
+
$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
15
|
+
try {
|
|
16
|
+
${script}
|
|
17
|
+
} catch {
|
|
18
|
+
Write-Error $_.Exception.Message
|
|
19
|
+
exit 1
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
const buffer = Buffer.from(fullScript, "utf16le");
|
|
23
|
+
const base64 = buffer.toString("base64");
|
|
24
|
+
return execSync(`powershell -NoProfile -NonInteractive -EncodedCommand ${base64}`, {
|
|
25
|
+
encoding: "utf8",
|
|
26
|
+
windowsHide: true,
|
|
27
|
+
}).trim();
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const errorMessage = error.stderr || error.message;
|
|
31
|
+
if (errorMessage.includes("0x800401E3")) {
|
|
32
|
+
throw new McpError(ErrorCode.InternalError, "Application is not running or no active document found.");
|
|
33
|
+
}
|
|
34
|
+
throw new McpError(ErrorCode.InternalError, `PowerShell error: ${errorMessage}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { runPowerShell, textContent } from "../office-utils.js";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const XLSX = require("xlsx");
|
|
9
|
+
const ListSheetsSchema = z.object({
|
|
10
|
+
path: z.string().describe("Path to the Excel file"),
|
|
11
|
+
});
|
|
12
|
+
const ReadExcelSchema = z.object({
|
|
13
|
+
path: z.string().describe("Path to the Excel file"),
|
|
14
|
+
sheetName: z.string().optional().describe("Name of the sheet to read. Defaults to the first sheet."),
|
|
15
|
+
});
|
|
16
|
+
const WriteExcelSchema = z.object({
|
|
17
|
+
path: z.string().describe("Path to the Excel file"),
|
|
18
|
+
sheetName: z.string().describe("Name of the sheet to write to"),
|
|
19
|
+
data: z.array(z.any()).describe("Array of objects representing the data to write"),
|
|
20
|
+
});
|
|
21
|
+
const GetActiveOfficeSchema = z.object({
|
|
22
|
+
filename: z.string().optional().describe("Name of the specific file to get data from. If omitted, the active document is used."),
|
|
23
|
+
});
|
|
24
|
+
const WriteActiveExcelSchema = z.object({
|
|
25
|
+
filename: z.string().optional().describe("Name of the workbook. If omitted, the active workbook is used."),
|
|
26
|
+
data: z.array(z.any()).describe("Array of objects representing the data to write"),
|
|
27
|
+
});
|
|
28
|
+
export const excelTools = {
|
|
29
|
+
tools: [
|
|
30
|
+
{
|
|
31
|
+
name: "list_sheets",
|
|
32
|
+
description: "List all sheet names in an Excel file",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
path: { type: "string", description: "Path to the Excel file" },
|
|
37
|
+
},
|
|
38
|
+
required: ["path"],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "read_excel",
|
|
43
|
+
description: "Read data from an Excel sheet",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
path: { type: "string", description: "Path to the Excel file" },
|
|
48
|
+
sheetName: { type: "string", description: "Sheet name (optional)" },
|
|
49
|
+
},
|
|
50
|
+
required: ["path"],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "write_excel",
|
|
55
|
+
description: "Write data to an Excel sheet (creates new or overwrites)",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
path: { type: "string", description: "Path to the Excel file" },
|
|
60
|
+
sheetName: { type: "string", description: "Sheet name" },
|
|
61
|
+
data: {
|
|
62
|
+
type: "array",
|
|
63
|
+
items: { type: "object" },
|
|
64
|
+
description: "Data as an array of objects",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
required: ["path", "sheetName", "data"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "list_active_excel",
|
|
72
|
+
description: "List all open Excel workbooks",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "get_active_excel",
|
|
80
|
+
description: "Get data from a currently open (active) Excel workbook without saving",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
filename: { type: "string", description: "Name of the workbook (e.g., 'Book1.xlsx'). If omitted, the active workbook is used." },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "write_active_excel",
|
|
90
|
+
description: "Write data to a currently open (active) Excel workbook without saving",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
filename: { type: "string", description: "Name of the workbook (e.g., 'Book1.xlsx'). If omitted, the active workbook is used." },
|
|
95
|
+
data: {
|
|
96
|
+
type: "array",
|
|
97
|
+
items: { type: "object" },
|
|
98
|
+
description: "Data as an array of objects",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
required: ["data"],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
handlers: {
|
|
106
|
+
async list_sheets(args) {
|
|
107
|
+
const { path: filePath } = ListSheetsSchema.parse(args);
|
|
108
|
+
const absolutePath = path.resolve(filePath);
|
|
109
|
+
if (!fs.existsSync(absolutePath)) {
|
|
110
|
+
throw new McpError(ErrorCode.InvalidParams, `File not found: ${filePath}`);
|
|
111
|
+
}
|
|
112
|
+
const workbook = XLSX.readFile(absolutePath);
|
|
113
|
+
return textContent(JSON.stringify(workbook.SheetNames, null, 2));
|
|
114
|
+
},
|
|
115
|
+
async read_excel(args) {
|
|
116
|
+
const { path: filePath, sheetName } = ReadExcelSchema.parse(args);
|
|
117
|
+
const absolutePath = path.resolve(filePath);
|
|
118
|
+
if (!fs.existsSync(absolutePath)) {
|
|
119
|
+
throw new McpError(ErrorCode.InvalidParams, `File not found: ${filePath}`);
|
|
120
|
+
}
|
|
121
|
+
const workbook = XLSX.readFile(absolutePath);
|
|
122
|
+
const targetSheet = sheetName || workbook.SheetNames[0];
|
|
123
|
+
if (!workbook.Sheets[targetSheet]) {
|
|
124
|
+
throw new McpError(ErrorCode.InvalidParams, `Sheet not found: ${targetSheet}`);
|
|
125
|
+
}
|
|
126
|
+
const data = XLSX.utils.sheet_to_json(workbook.Sheets[targetSheet]);
|
|
127
|
+
return textContent(JSON.stringify(data, null, 2));
|
|
128
|
+
},
|
|
129
|
+
async write_excel(args) {
|
|
130
|
+
const { path: filePath, sheetName, data } = WriteExcelSchema.parse(args);
|
|
131
|
+
const absolutePath = path.resolve(filePath);
|
|
132
|
+
let workbook;
|
|
133
|
+
if (fs.existsSync(absolutePath)) {
|
|
134
|
+
workbook = XLSX.readFile(absolutePath);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
workbook = XLSX.utils.book_new();
|
|
138
|
+
}
|
|
139
|
+
const worksheet = XLSX.utils.json_to_sheet(data);
|
|
140
|
+
if (workbook.Sheets[sheetName]) {
|
|
141
|
+
workbook.Sheets[sheetName] = worksheet;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
|
|
145
|
+
}
|
|
146
|
+
XLSX.writeFile(workbook, absolutePath);
|
|
147
|
+
return textContent(`Successfully wrote to ${filePath} (Sheet: ${sheetName})`);
|
|
148
|
+
},
|
|
149
|
+
async list_active_excel() {
|
|
150
|
+
const script = `
|
|
151
|
+
$excel = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
|
|
152
|
+
$names = @($excel.Workbooks | ForEach-Object { $_.Name })
|
|
153
|
+
ConvertTo-Json -InputObject @($names) -Compress
|
|
154
|
+
`;
|
|
155
|
+
return textContent(runPowerShell(script));
|
|
156
|
+
},
|
|
157
|
+
async get_active_excel(args) {
|
|
158
|
+
const { filename } = GetActiveOfficeSchema.parse(args);
|
|
159
|
+
const script = `
|
|
160
|
+
$excel = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
|
|
161
|
+
$target = "${filename || ""}"
|
|
162
|
+
$workbook = if ($target) {
|
|
163
|
+
$excel.Workbooks | Where-Object { $_.Name -eq $target -or $_.FullName -eq $target } | Select-Object -First 1
|
|
164
|
+
} else {
|
|
165
|
+
$excel.ActiveWorkbook
|
|
166
|
+
}
|
|
167
|
+
if ($null -eq $workbook) { throw "Workbook '$target' not found or no active workbook." }
|
|
168
|
+
$sheet = $workbook.ActiveSheet
|
|
169
|
+
$range = $sheet.UsedRange
|
|
170
|
+
$values = $range.Value2
|
|
171
|
+
if ($null -eq $values) { return "[]" }
|
|
172
|
+
if ($values.GetType().IsArray) {
|
|
173
|
+
$rows = $values.GetLength(0)
|
|
174
|
+
$cols = $values.GetLength(1)
|
|
175
|
+
$header = @()
|
|
176
|
+
for ($c = 1; $c -le $cols; $c++) { $header += $values[1, $c] }
|
|
177
|
+
$data = @()
|
|
178
|
+
for ($r = 2; $r -le $rows; $r++) {
|
|
179
|
+
$obj = @{}
|
|
180
|
+
for ($c = 1; $c -le $cols; $c++) {
|
|
181
|
+
$key = $header[$c-1]
|
|
182
|
+
if ($null -ne $key) { $obj[$key.ToString()] = $values[$r, $c] }
|
|
183
|
+
}
|
|
184
|
+
$data += $obj
|
|
185
|
+
}
|
|
186
|
+
ConvertTo-Json -InputObject @($data) -Compress
|
|
187
|
+
} else {
|
|
188
|
+
ConvertTo-Json -InputObject @($values) -Compress
|
|
189
|
+
}
|
|
190
|
+
`;
|
|
191
|
+
return textContent(runPowerShell(script));
|
|
192
|
+
},
|
|
193
|
+
async write_active_excel(args) {
|
|
194
|
+
const { filename, data } = WriteActiveExcelSchema.parse(args);
|
|
195
|
+
const jsonBase64 = Buffer.from(JSON.stringify(data)).toString("base64");
|
|
196
|
+
const script = `
|
|
197
|
+
$excel = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
|
|
198
|
+
$target = "${filename || ""}"
|
|
199
|
+
$workbook = if ($target) {
|
|
200
|
+
$excel.Workbooks | Where-Object { $_.Name -eq $target -or $_.FullName -eq $target } | Select-Object -First 1
|
|
201
|
+
} else {
|
|
202
|
+
$excel.ActiveWorkbook
|
|
203
|
+
}
|
|
204
|
+
if ($null -eq $workbook) { throw "Workbook '$target' not found or no active workbook." }
|
|
205
|
+
$sheet = $workbook.ActiveSheet
|
|
206
|
+
|
|
207
|
+
$json = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${jsonBase64}'))
|
|
208
|
+
$data = $json | ConvertFrom-Json
|
|
209
|
+
if ($data -is [PSCustomObject]) { $data = @($data) }
|
|
210
|
+
|
|
211
|
+
if ($data.Count -gt 0) {
|
|
212
|
+
$headers = $data[0].PSObject.Properties.Name
|
|
213
|
+
for ($c = 0; $c -lt $headers.Count; $c++) {
|
|
214
|
+
$sheet.Cells.Item(1, $c + 1).Value2 = $headers[$c]
|
|
215
|
+
}
|
|
216
|
+
for ($r = 0; $r -lt $data.Count; $r++) {
|
|
217
|
+
for ($c = 0; $c -lt $headers.Count; $c++) {
|
|
218
|
+
$val = $data[$r].$($headers[$c])
|
|
219
|
+
$sheet.Cells.Item($r + 2, $c + 1).Value2 = $val
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
"Successfully wrote $($data.Count) rows to $($workbook.Name)"
|
|
223
|
+
} else {
|
|
224
|
+
"No data to write"
|
|
225
|
+
}
|
|
226
|
+
`;
|
|
227
|
+
return textContent(runPowerShell(script));
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
};
|