onenote-mcp-impl.js.bak•10.9 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/dist/cjs/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/dist/cjs/server/stdio.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/dist/cjs/types.js';
import OneNoteAutomation from './onenote-browser-automation.js';
import { promises as fs } from 'fs';
export class OneNoteMcpServer {
constructor() {
this.server = new Server(
{
name: 'onenote-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.automation = null;
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.cleanup();
process.exit(0);
});
}
async cleanup() {
if (this.automation) {
await this.automation.close();
this.automation = null;
}
await this.server.close();
}
setupToolHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'connect',
description: 'Connect to a OneNote notebook using a shared link',
inputSchema: {
type: 'object',
properties: {
notebookLink: {
type: 'string',
description: 'The shared link to the OneNote notebook'
}
},
required: ['notebookLink']
}
},
{
name: 'get_structure',
description: 'Get the hierarchical structure of the notebook',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'read_section',
description: 'Read all pages in a section',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'array',
items: { type: 'string' },
description: 'Path to the section (e.g., ["CMM", "Board Questions"])'
}
},
required: ['path']
}
},
{
name: 'read_page',
description: 'Read a specific page',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'array',
items: { type: 'string' },
description: 'Path to the page (e.g., ["CMM", "Board Questions", "Page 1"])'
}
},
required: ['path']
}
},
{
name: 'write_page',
description: 'Write content to a page',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'array',
items: { type: 'string' },
description: 'Path to the page'
},
content: {
type: 'string',
description: 'Content to write to the page'
}
},
required: ['path', 'content']
}
},
{
name: 'create_section',
description: 'Create a new section',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'array',
items: { type: 'string' },
description: 'Path where to create the section'
},
name: {
type: 'string',
description: 'Name of the new section'
}
},
required: ['path', 'name']
}
},
{
name: 'create_page',
description: 'Create a new page',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'array',
items: { type: 'string' },
description: 'Path where to create the page'
},
name: {
type: 'string',
description: 'Name of the new page'
},
content: {
type: 'string',
description: 'Initial content for the page'
}
},
required: ['path', 'name']
}
},
{
name: 'disconnect',
description: 'Disconnect from the notebook and close the browser',
inputSchema: {
type: 'object',
properties: {}
}
}
]
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'connect':
return await this.handleConnect(args);
case 'get_structure':
return await this.handleGetStructure();
case 'read_section':
return await this.handleReadSection(args);
case 'read_page':
return await this.handleReadPage(args);
case 'write_page':
return await this.handleWritePage(args);
case 'create_section':
return await this.handleCreateSection(args);
case 'create_page':
return await this.handleCreatePage(args);
case 'disconnect':
return await this.handleDisconnect();
default:
throw new Error(`Unknown tool: ${name}`);
}
});
}
async handleConnect({ notebookLink }) {
console.log('handleConnect: called'); // Add log at the start of handleConnect
if (this.automation) {
await this.automation.close();
console.log('handleConnect: closed existing automation'); // Log if existing automation is closed
}
this.automation = new OneNoteAutomation(notebookLink);
console.log('handleConnect: OneNoteAutomation instance created', !!this.automation); // Log after creating OneNoteAutomation instance
await this.automation.initialize();
console.log('handleConnect: automation initialized'); // Log after initialize call
await this.automation.navigateToNotebook();
console.log('handleConnect: navigateToNotebook call completed'); // Log after navigateToNotebook call
return {
content: [{
type: 'text',
text: 'Successfully connected to notebook'
}]
};
}
async handleGetStructure() {
if (!this.automation) {
throw new Error('Not connected to a notebook');
}
const structure = await this.automation.extractNotebookStructure();
return {
content: [{
type: 'text',
text: JSON.stringify(structure, null, 2)
}]
};
}
async handleReadSection({ path }) {
if (!this.automation) {
throw new Error('Not connected to a notebook');
}
try {
await this.automation.navigateToPath(path);
const content = await this.automation.readCurrentPage();
if (!content || content.title === 'Error' || content.content === 'Could not extract page content due to browser automation error.') {
return {
content: [{
type: 'text',
text: `Error reading section: ${path.join(' > ')}. Section might be empty or not load correctly.`
}],
isError: true
};
}
const response = [{
type: 'text',
text: JSON.stringify({
title: content.title,
content: content.content,
level: content.level
}, null, 2)
}];
// Add any captured images
if (content.images && content.images.length > 0) {
for (const image of content.images) {
const imageData = await fs.readFile(image.path, 'base64');
response.push({
type: 'image',
data: imageData,
mimeType: 'image/png',
description: `${image.type} from ${content.title}`
});
}
}
return { content: response };
} catch (error) {
return {
content: [{
type: 'text',
text: `Error reading section: ${path.join(' > ')}. ${error.message}`
}],
isError: true
};
}
}
async handleReadPage({ path }) {
if (!this.automation) {
throw new Error('Not connected to a notebook');
}
await this.automation.navigateToPath(path);
const content = await this.automation.readCurrentPage();
const response = [{
type: 'text',
text: JSON.stringify({
title: content.title,
content: content.content,
level: content.level
}, null, 2)
}];
// Add any captured images
if (content.images && content.images.length > 0) {
for (const image of content.images) {
const imageData = await fs.readFile(image.path, 'base64');
response.push({
type: 'image',
data: imageData,
mimeType: 'image/png',
description: `${image.type} from ${content.title}`
});
}
}
return { content: response };
}
async handleWritePage({ path, content }) {
if (!this.automation) {
throw new Error('Not connected to a notebook');
}
// Navigate to the page
await this.automation.navigateToPath(path);
// Write the content
await this.automation.writePage(content);
return {
content: [{
type: 'text',
text: `Successfully wrote content to page: ${path.join(' > ')}`
}]
};
}
async handleCreateSection({ path, name }) {
if (!this.automation) {
throw new Error('Not connected to a notebook');
}
await this.automation.createSection(path, name);
return {
content: [{
type: 'text',
text: `Successfully created section "${name}" at: ${path.join(' > ')}`
}]
};
}
async handleCreatePage({ path, name, content }) {
if (!this.automation) {
throw new Error('Not connected to a notebook');
}
await this.automation.createPage(path, name, content);
return {
content: [{
type: 'text',
text: `Successfully created page "${name}" at: ${path.join(' > ')}`
}]
};
}
async handleDisconnect() {
if (this.automation) {
await this.automation.close();
this.automation = null;
}
return {
content: [{
type: 'text',
text: 'Successfully disconnected'
}]
};
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('OneNote MCP server running on stdio');
}
}
let server = new OneNoteMcpServer();
server.run().catch(console.error);