Get your API key
Create a key on the API keys page - the secret is shown only once,
so copy it right away.
Set API keys
Create the project directory and a Put the API keys inside
.env file:mkdir vespper-quickstart && cd vespper-quickstart
touch .env
.env. This guide uses GPT 5.5 to edit the document,
but you can use another model provider:.env
VESPPER_API_KEY="sk_live_YOUR_KEY"
OPENAI_API_KEY="sk-..."
Download the sample document
The examples edit a local Word file. Download it into
vespper-quickstart.Download sample.docxIntegrate
Every example uses the Vespper SDK to open and close its document session.
Auto-patching also injects MCP metadata and retains the latest DOCX for you.
- Framework
- Native
Your framework runs the agent loop. Open one document session, wire in the read_document, search_document, and edit_document tools, then close the session when the run finishes.
- Mastra
- OpenAI Agents SDK
- Vercel AI SDK
1. Install - add the packages for this stack:2. Add the code - move your 3. Run - this writes
Requirement: Mastra requires Node.js 22.13.0 or newer.
npm init -y && npm pkg set type=module && npm install @mastra/core@latest @mastra/mcp@latest vespper zod dotenv && npm install --save-dev typescript @types/node && npx tsc --init --types node
pnpm init && pnpm pkg set type=module && pnpm add @mastra/core@latest @mastra/mcp@latest vespper zod dotenv && pnpm add --save-dev typescript @types/node && pnpm exec tsc --init --types node
bun init -y && bun add @mastra/core@latest @mastra/mcp@latest vespper zod dotenv && bun add --dev @types/node
sample.docx into the folder, create main.ts, and paste:import "dotenv/config";
import { writeFileSync } from "node:fs";
import { Agent } from "@mastra/core/agent";
import { MCPClient } from "@mastra/mcp";
import Vespper from "vespper";
const client = new Vespper();
const sessionId = await client.openSession("./sample.docx");
const mcp = new MCPClient({
id: "vespper-quickstart",
servers: {
vespperDocx: {
url: new URL("https://mcp.vespper.com/mcp"),
requestInit: {
headers: { Authorization: client.authorizationHeader },
},
},
},
});
await client.patchMCPTools({
mcp,
sessionId,
author: "Vespper Agent",
});
const agent = new Agent({
id: "example-agent",
name: "Example Agent",
instructions:
"You edit a Word document that is already loaded. Call read_document to load its HTML (or search_document to find text in a large document), then edit_document to apply the change as tracked edits.",
model: "openai/gpt-5.5",
tools: await mcp.listTools(),
});
await agent.generate("Add the word hello to the end of the document");
const finalDocument = client.getSessionDocument(sessionId);
writeFileSync("sample-redlined.docx", finalDocument);
await mcp.disconnect();
await client.closeSession(sessionId);
import "dotenv/config";
import { readFileSync, writeFileSync } from "node:fs";
import { Agent } from "@mastra/core/agent";
import { createTool } from "@mastra/core/tools";
import { MCPClient } from "@mastra/mcp";
import Vespper from "vespper";
import { z } from "zod";
const client = new Vespper();
const document = readFileSync("sample.docx");
let docx_b64 = document.toString("base64");
const sessionId = await client.openSession(document);
const sessionMeta = { "com.vespper/session-id": sessionId };
const mcp = new MCPClient({
id: "vespper-quickstart",
servers: {
vespperDocx: {
url: new URL("https://mcp.vespper.com/mcp"),
requestInit: {
headers: { Authorization: client.authorizationHeader },
},
},
},
});
const tools: any = await mcp.listTools();
const read_document = createTool({
id: "read_document",
description: tools.vespperDocx_read_document.description,
inputSchema: z.object({
start_page: z.number().int().default(1),
end_page: z.number().int().nullable().default(null),
}),
execute: async ({ start_page, end_page }) =>
tools.vespperDocx_read_document.execute(
{ start_page, end_page },
{ _meta: sessionMeta },
),
});
const search_document = createTool({
id: "search_document",
description: tools.vespperDocx_search_document.description,
inputSchema: z.object({
pattern: z.string(),
start_at: z.number().optional(),
}),
execute: async ({ pattern, start_at }) =>
tools.vespperDocx_search_document.execute(
{ pattern, start_at },
{ _meta: sessionMeta },
),
});
const edit_document = createTool({
id: "edit_document",
description: tools.vespperDocx_edit_document.description,
inputSchema: z.object({
edits: z.array(z.object({ old: z.string(), new: z.string() })),
}),
execute: async ({ edits }) => {
const out = await tools.vespperDocx_edit_document.execute(
{ edits },
{
_meta: {
...sessionMeta,
"com.vespper/author": "Vespper Agent",
},
},
);
const { base64, ...modelResult } = out;
docx_b64 = base64;
return modelResult;
},
});
const agent = new Agent({
id: "example-agent",
name: "Example Agent",
instructions:
"You edit a Word document that is already loaded. Call read_document to load its HTML (or search_document to find text in a large document), then edit_document to apply the change as tracked edits.",
model: "openai/gpt-5.5",
tools: { read_document, search_document, edit_document },
});
await agent.generate("Add the word hello to the end of the document");
writeFileSync("sample-redlined.docx", Buffer.from(docx_b64, "base64"));
await mcp.disconnect();
await client.closeSession(sessionId);
Use the tool descriptions returned by the Vespper MCP. Don’t replace them with
your own; they carry the editing rules the model needs to do well.
sample-redlined.docx with the edit as a tracked change:npx tsx main.ts
pnpm dlx tsx main.ts
bun main.ts
1. Install - add the packages for this stack:2. Add the code - move your 3. Run - this writes
Requirement: Python 3.10 or newer.
python3 -m venv .venv && source .venv/bin/activate
pip install openai-agents "mcp<2" vespper python-dotenv httpx
uv init && uv add openai-agents "mcp<2" vespper python-dotenv httpx
poetry init -n && poetry add openai-agents "mcp<2" vespper python-dotenv httpx
sample.docx into the folder, create main.py, and paste:import asyncio
from pathlib import Path
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp
from dotenv import load_dotenv
from vespper import Vespper
load_dotenv()
async def main():
client = Vespper()
session_id = await client.open_session("./sample.docx")
mcp = MCPServerStreamableHttp(
name="Vespper DOCX",
params={
"url": "https://mcp.vespper.com/mcp",
"headers": {"Authorization": client.authorization_header},
},
cache_tools_list=True,
use_structured_content=True,
)
async with mcp:
await client.patch_mcp_tools(
mcp=mcp,
session_id=session_id,
author="Vespper Agent",
)
await Runner.run(
Agent(
name="Example Agent",
instructions="You edit a Word document that is already loaded. Call read_document to load its HTML (or search_document to find text in a large document), then edit_document to apply the change as tracked edits.",
mcp_servers=[mcp],
model="gpt-5.5",
),
"Add the word hello to the end of the document",
)
final_document = client.get_session_document(session_id)
Path("sample-redlined.docx").write_bytes(final_document)
await client.close_session(session_id)
await client.close()
asyncio.run(main())
import asyncio
import base64
from pathlib import Path
from agents import Agent, Runner, function_tool
from dotenv import load_dotenv
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from vespper import Vespper
load_dotenv()
async def main():
client = Vespper()
document = Path("sample.docx").read_bytes()
docx_b64 = base64.b64encode(document).decode()
session_id = await client.open_session(document)
session_meta = {"com.vespper/session-id": session_id}
async with streamablehttp_client(
"https://mcp.vespper.com/mcp",
headers={"Authorization": client.authorization_header},
) as (read, write, _):
async with ClientSession(read, write) as mcp:
await mcp.initialize()
descriptions = {
tool.name: tool.description
for tool in (await mcp.list_tools()).tools
}
@function_tool(description_override=descriptions["read_document"])
async def read_document(
start_page: int = 1, end_page: int | None = None
):
return (
await mcp.call_tool(
"read_document",
{"start_page": start_page, "end_page": end_page},
meta=session_meta,
)
).structuredContent
@function_tool(description_override=descriptions["search_document"])
async def search_document(pattern: str, start_at: int = 1):
return (
await mcp.call_tool(
"search_document",
{"pattern": pattern, "start_at": start_at},
meta=session_meta,
)
).structuredContent
@function_tool(
description_override=descriptions["edit_document"],
strict_mode=False,
)
async def edit_document(edits: list[dict[str, str]]):
nonlocal docx_b64
out = (
await mcp.call_tool(
"edit_document",
{"edits": edits},
meta={
**session_meta,
"com.vespper/author": "Vespper Agent",
},
)
).structuredContent or {}
docx_b64 = out["base64"]
return {
key: value
for key, value in out.items()
if key != "base64"
}
await Runner.run(
Agent(
name="Example Agent",
instructions="You edit a Word document that is already loaded. Call read_document to load its HTML (or search_document to find text in a large document), then edit_document to apply the change as tracked edits.",
tools=[read_document, search_document, edit_document],
model="gpt-5.5",
),
"Add the word hello to the end of the document",
)
Path("sample-redlined.docx").write_bytes(base64.b64decode(docx_b64))
await client.close_session(session_id)
await client.close()
asyncio.run(main())
Use the tool descriptions returned by the Vespper MCP. Don’t replace them with
your own; they carry the editing rules the model needs to do well.
sample-redlined.docx with the edit as a tracked change:python main.py
uv run main.py
poetry run python main.py
1. Install - add the packages for this stack:2. Add the code - move your 3. Run - this writes
Requirement: Node.js 22 or newer.
npm init -y && npm pkg set type=module && npm install ai @ai-sdk/openai @ai-sdk/mcp @modelcontextprotocol/sdk vespper dotenv && npm install --save-dev typescript @types/node && npx tsc --init --types node
pnpm init && pnpm pkg set type=module && pnpm add ai @ai-sdk/openai @ai-sdk/mcp @modelcontextprotocol/sdk vespper dotenv && pnpm add --save-dev typescript @types/node && pnpm exec tsc --init --types node
bun init -y && bun add ai @ai-sdk/openai @ai-sdk/mcp @modelcontextprotocol/sdk vespper dotenv && bun add --dev @types/node
sample.docx into the folder, create main.ts, and paste:import "dotenv/config";
import { writeFileSync } from "node:fs";
import { createMCPClient } from "@ai-sdk/mcp";
import { openai } from "@ai-sdk/openai";
import { generateText, stepCountIs } from "ai";
import Vespper from "vespper";
const client = new Vespper();
const sessionId = await client.openSession("./sample.docx");
const mcp = await createMCPClient({
transport: {
type: "http",
url: "https://mcp.vespper.com/mcp",
headers: { Authorization: client.authorizationHeader },
},
});
await client.patchMCPTools({
mcp,
sessionId,
author: "Vespper Agent",
});
await generateText({
model: openai("gpt-5.5"),
system:
"You edit a Word document that is already loaded. Call read_document to load its HTML (or search_document to find text in a large document), then edit_document to apply the change as tracked edits.",
tools: await mcp.tools(),
stopWhen: stepCountIs(5),
prompt: "Add the word hello to the end of the document",
});
const finalDocument = client.getSessionDocument(sessionId);
writeFileSync("sample-redlined.docx", finalDocument);
await mcp.close();
await client.closeSession(sessionId);
import "dotenv/config";
import { readFileSync, writeFileSync } from "node:fs";
import { createMCPClient } from "@ai-sdk/mcp";
import { openai } from "@ai-sdk/openai";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { generateText, stepCountIs } from "ai";
import Vespper from "vespper";
const client = new Vespper();
const document = readFileSync("sample.docx");
const doc = { b64: document.toString("base64") };
const sessionId = await client.openSession(document);
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.vespper.com/mcp"),
{
requestInit: {
headers: { Authorization: client.authorizationHeader },
},
fetch: async (url, init) => {
if (init?.method === "POST" && typeof init.body === "string") {
const message = JSON.parse(init.body);
if (message.method === "tools/call") {
message.params._meta = {
...message.params._meta,
"com.vespper/session-id": sessionId,
...(message.params.name === "edit_document"
? { "com.vespper/author": "Vespper Agent" }
: {}),
};
init = { ...init, body: JSON.stringify(message) };
}
}
return fetch(url, init);
},
},
);
const mcp = await createMCPClient({ transport });
const tools = await mcp.tools();
const mcpEdit = tools.edit_document;
tools.edit_document = {
...mcpEdit,
execute: async (args, opts) => {
const out = await mcpEdit.execute(args, opts);
const structuredContent = out?.structuredContent;
if (!structuredContent?.base64) return out;
const { base64, ...modelResult } = structuredContent;
doc.b64 = base64;
return { ...out, structuredContent: modelResult };
},
toModelOutput: ({ output }) => ({
type: "json",
value: output.structuredContent ?? output,
}),
};
await generateText({
model: openai("gpt-5.5"),
system:
"You edit a Word document that is already loaded. Call read_document to load its HTML (or search_document to find text in a large document), then edit_document to apply the change as tracked edits.",
tools,
stopWhen: stepCountIs(5),
prompt: "Add the word hello to the end of the document",
});
writeFileSync("sample-redlined.docx", Buffer.from(doc.b64, "base64"));
await mcp.close();
await client.closeSession(sessionId);
sample-redlined.docx with the edit as a tracked change:npx tsx main.ts
pnpm dlx tsx main.ts
bun main.ts
No framework - you own the agent loop. Open one document session, pass its ID with each MCP tool call, and close it after the OpenAI tool loop finishes.
- Python
- TypeScript
1. Install - add the packages for this stack:2. Add the code - move your 3. Run - this writes
Requirement: Python 3.10 or newer.
python3 -m venv .venv && source .venv/bin/activate
pip install "mcp<2" openai vespper python-dotenv httpx
uv init && uv add "mcp<2" openai vespper python-dotenv httpx
poetry init -n && poetry add "mcp<2" openai vespper python-dotenv httpx
sample.docx into the folder, create main.py, and paste:import asyncio
import json
from pathlib import Path
from dotenv import load_dotenv
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from openai import OpenAI
from vespper import Vespper
load_dotenv()
async def main():
vespper = Vespper()
openai = OpenAI()
session_id = await vespper.open_session("./sample.docx")
async with streamablehttp_client(
"https://mcp.vespper.com/mcp",
headers={"Authorization": vespper.authorization_header},
) as (read, write, _):
async with ClientSession(read, write) as mcp:
await mcp.initialize()
await vespper.patch_mcp_tools(
mcp=mcp,
session_id=session_id,
author="Vespper Agent",
)
listed = await mcp.list_tools()
tools = [
{
"type": "function",
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema,
}
for tool in listed.tools
]
instructions = (
"You edit a Word document that is already loaded. Call read_document "
"to load its HTML (or search_document to find text in a large document), "
"then edit_document to apply the change as tracked edits."
)
conversation = [
{
"role": "user",
"content": "Add the word hello to the end of the document",
}
]
while True:
response = openai.responses.create(
model="gpt-5.5",
instructions=instructions,
tools=tools,
input=conversation,
)
conversation += response.output
tool_calls = [
item
for item in response.output
if item.type == "function_call"
]
if not tool_calls:
break
for call in tool_calls:
result = await mcp.call_tool(
call.name,
json.loads(call.arguments),
)
data = result.structuredContent or {}
conversation.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": (
data.get("message")
if call.name == "edit_document"
else json.dumps(data)
),
}
)
final_document = vespper.get_session_document(session_id)
Path("sample-redlined.docx").write_bytes(final_document)
await vespper.close_session(session_id)
await vespper.close()
asyncio.run(main())
import asyncio
import base64
import json
from pathlib import Path
from dotenv import load_dotenv
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from openai import OpenAI
from vespper import Vespper
load_dotenv()
async def main():
vespper = Vespper()
openai = OpenAI()
document = Path("sample.docx").read_bytes()
docx_b64 = base64.b64encode(document).decode()
session_id = await vespper.open_session(document)
session_meta = {"com.vespper/session-id": session_id}
async with streamablehttp_client(
"https://mcp.vespper.com/mcp",
headers={"Authorization": vespper.authorization_header},
) as (read, write, _):
async with ClientSession(read, write) as mcp:
await mcp.initialize()
listed = await mcp.list_tools()
tools = [
{
"type": "function",
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema,
}
for tool in listed.tools
]
instructions = (
"You edit a Word document that is already loaded. Call read_document "
"to load its HTML (or search_document to find text in a large document), "
"then edit_document to apply the change as tracked edits."
)
conversation = [
{
"role": "user",
"content": "Add the word hello to the end of the document",
}
]
while True:
response = openai.responses.create(
model="gpt-5.5",
instructions=instructions,
tools=tools,
input=conversation,
)
conversation += response.output
tool_calls = [
item
for item in response.output
if item.type == "function_call"
]
if not tool_calls:
break
for call in tool_calls:
meta = session_meta
if call.name == "edit_document":
meta = {
**session_meta,
"com.vespper/author": "Vespper Agent",
}
result = await mcp.call_tool(
call.name,
json.loads(call.arguments),
meta=meta,
)
data = result.structuredContent or {}
if call.name == "edit_document" and "base64" in data:
docx_b64 = data["base64"]
conversation.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": (
data.get("message")
if call.name == "edit_document"
else json.dumps(data)
),
}
)
Path("sample-redlined.docx").write_bytes(base64.b64decode(docx_b64))
await vespper.close_session(session_id)
await vespper.close()
asyncio.run(main())
Use the tool descriptions returned by the Vespper MCP. Don’t replace them with
your own; they carry the editing rules the model needs to do well.
sample-redlined.docx with the edit as a tracked change:python main.py
uv run main.py
poetry run python main.py
1. Install - add the packages for this stack:2. Add the code - move your 3. Run - this writes
Requirement: Node.js 22 or newer.
npm init -y && npm pkg set type=module && npm install @modelcontextprotocol/sdk openai vespper dotenv && npm install --save-dev typescript @types/node && npx tsc --init --types node
pnpm init && pnpm pkg set type=module && pnpm add @modelcontextprotocol/sdk openai vespper dotenv && pnpm add --save-dev typescript @types/node && pnpm exec tsc --init --types node
bun init -y && bun add @modelcontextprotocol/sdk openai vespper dotenv && bun add --dev @types/node
sample.docx into the folder, create main.ts, and paste:import "dotenv/config";
import { writeFileSync } from "node:fs";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import OpenAI from "openai";
import Vespper from "vespper";
const vespper = new Vespper();
const openai = new OpenAI();
const sessionId = await vespper.openSession("sample.docx");
const mcp = new Client({ name: "native-loop-example", version: "0.1.0" });
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.vespper.com/mcp"),
{
requestInit: {
headers: { Authorization: vespper.authorizationHeader },
},
},
);
await mcp.connect(transport);
await vespper.patchMCPTools({
mcp,
sessionId,
author: "Vespper Agent",
});
const listed = await mcp.listTools();
const tools = listed.tools.map((tool) => ({
type: "function" as const,
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
}));
const instructions =
"You edit a Word document that is already loaded. Call read_document to load its HTML (or search_document to find text in a large document), then edit_document to apply the change as tracked edits.";
const conversation: any[] = [
{ role: "user", content: "Add the word hello to the end of the document" },
];
while (true) {
const response = await openai.responses.create({
model: "gpt-5.5",
instructions,
tools,
input: conversation,
});
conversation.push(...response.output);
const toolCalls = response.output.filter(
(item) => item.type === "function_call",
);
if (toolCalls.length === 0) break;
for (const call of toolCalls) {
const result = await mcp.callTool({
name: call.name,
arguments: JSON.parse(call.arguments),
});
const data = result.structuredContent as Record<string, unknown>;
conversation.push({
type: "function_call_output",
call_id: call.call_id,
output:
call.name === "edit_document" && typeof data.message === "string"
? data.message
: JSON.stringify(data),
});
}
}
const finalDocument = vespper.getSessionDocument(sessionId);
writeFileSync("sample-redlined.docx", finalDocument);
await mcp.close();
await vespper.closeSession(sessionId);
import "dotenv/config";
import { readFileSync, writeFileSync } from "node:fs";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import OpenAI from "openai";
import Vespper from "vespper";
const vespper = new Vespper();
const openai = new OpenAI();
const document = readFileSync("sample.docx");
let docx_b64 = document.toString("base64");
const sessionId = await vespper.openSession(document);
const sessionMeta = { "com.vespper/session-id": sessionId };
const mcp = new Client({ name: "native-loop-example", version: "0.1.0" });
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.vespper.com/mcp"),
{
requestInit: {
headers: { Authorization: vespper.authorizationHeader },
},
},
);
await mcp.connect(transport);
const listed = await mcp.listTools();
const tools = listed.tools.map((tool) => ({
type: "function" as const,
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
}));
const instructions =
"You edit a Word document that is already loaded. Call read_document to load its HTML (or search_document to find text in a large document), then edit_document to apply the change as tracked edits.";
const conversation: any[] = [
{ role: "user", content: "Add the word hello to the end of the document" },
];
while (true) {
const response = await openai.responses.create({
model: "gpt-5.5",
instructions,
tools,
input: conversation,
});
conversation.push(...response.output);
const toolCalls = response.output.filter(
(item) => item.type === "function_call",
);
if (toolCalls.length === 0) break;
for (const call of toolCalls) {
const meta =
call.name === "edit_document"
? {
...sessionMeta,
"com.vespper/author": "Vespper Agent",
}
: sessionMeta;
const result = await mcp.callTool({
name: call.name,
arguments: JSON.parse(call.arguments),
_meta: meta,
});
const data = result.structuredContent as {
base64?: string;
message?: string;
};
if (call.name === "edit_document" && data.base64) {
docx_b64 = data.base64;
}
conversation.push({
type: "function_call_output",
call_id: call.call_id,
output:
call.name === "edit_document" && data.message
? data.message
: JSON.stringify(data),
});
}
}
writeFileSync("sample-redlined.docx", Buffer.from(docx_b64, "base64"));
await mcp.close();
await vespper.closeSession(sessionId);
Use the tool descriptions returned by the Vespper MCP. Don’t replace them with
your own; they carry the editing rules the model needs to do well.
sample-redlined.docx with the edit as a tracked change:npx tsx main.ts
pnpm dlx tsx main.ts
bun main.ts

