roam-research-mcp 0.12.3
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 +21 -0
- package/README.md +273 -0
- package/build/index.js +826 -0
- package/build/markdown-utils.js +204 -0
- package/build/test-addMarkdownText.js +87 -0
- package/build/test-queries.js +116 -0
- package/package.json +46 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if text has a traditional markdown table
|
|
3
|
+
*/
|
|
4
|
+
function hasMarkdownTable(text) {
|
|
5
|
+
return /^\|([^|]+\|)+\s*$\n\|(\s*:?-+:?\s*\|)+\s*$\n(\|([^|]+\|)+\s*$\n*)+$/.test(text);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Converts a markdown table to Roam format
|
|
9
|
+
*/
|
|
10
|
+
function convertTableToRoamFormat(text) {
|
|
11
|
+
const lines = text.split('\n')
|
|
12
|
+
.map(line => line.trim())
|
|
13
|
+
.filter(line => line.length > 0);
|
|
14
|
+
const tableRegex = /^\|([^|]+\|)+\s*$\n\|(\s*:?-+:?\s*\|)+\s*$\n(\|([^|]+\|)+\s*$\n*)+/m;
|
|
15
|
+
if (!tableRegex.test(text)) {
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
const rows = lines
|
|
19
|
+
.filter((_, index) => index !== 1)
|
|
20
|
+
.map(line => line.trim()
|
|
21
|
+
.replace(/^\||\|$/g, '')
|
|
22
|
+
.split('|')
|
|
23
|
+
.map(cell => cell.trim()));
|
|
24
|
+
let roamTable = '{{table}}\n';
|
|
25
|
+
// First row becomes column headers
|
|
26
|
+
const headers = rows[0];
|
|
27
|
+
for (let i = 0; i < headers.length; i++) {
|
|
28
|
+
roamTable += `${' '.repeat(i + 1)}- ${headers[i]}\n`;
|
|
29
|
+
}
|
|
30
|
+
// Remaining rows become nested under each column
|
|
31
|
+
for (let rowIndex = 1; rowIndex < rows.length; rowIndex++) {
|
|
32
|
+
const row = rows[rowIndex];
|
|
33
|
+
for (let colIndex = 0; colIndex < row.length; colIndex++) {
|
|
34
|
+
roamTable += `${' '.repeat(colIndex + 1)}- ${row[colIndex]}\n`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return roamTable.trim();
|
|
38
|
+
}
|
|
39
|
+
function convertAllTables(text) {
|
|
40
|
+
return text.replaceAll(/(^\|([^|]+\|)+\s*$\n\|(\s*:?-+:?\s*\|)+\s*$\n(\|([^|]+\|)+\s*$\n*)+)/gm, (match) => {
|
|
41
|
+
return '\n' + convertTableToRoamFormat(match) + '\n';
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function convertToRoamMarkdown(text) {
|
|
45
|
+
// First handle double asterisks/underscores (bold)
|
|
46
|
+
text = text.replace(/\*\*(.+?)\*\*/g, '**$1**'); // Preserve double asterisks
|
|
47
|
+
// Then handle single asterisks/underscores (italic)
|
|
48
|
+
text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '__$1__'); // Single asterisk to double underscore
|
|
49
|
+
text = text.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, '__$1__'); // Single underscore to double underscore
|
|
50
|
+
// Handle highlights
|
|
51
|
+
text = text.replace(/==(.+?)==/g, '^^$1^^');
|
|
52
|
+
// Convert tables
|
|
53
|
+
text = convertAllTables(text);
|
|
54
|
+
return text;
|
|
55
|
+
}
|
|
56
|
+
function parseMarkdown(markdown) {
|
|
57
|
+
const lines = markdown.split('\n');
|
|
58
|
+
const rootNodes = [];
|
|
59
|
+
const stack = [];
|
|
60
|
+
for (let i = 0; i < lines.length; i++) {
|
|
61
|
+
const line = lines[i];
|
|
62
|
+
const trimmedLine = line.trimEnd();
|
|
63
|
+
// Skip truly empty lines (no spaces)
|
|
64
|
+
if (trimmedLine === '') {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Calculate indentation level (2 spaces = 1 level)
|
|
68
|
+
const indentation = line.match(/^\s*/)?.[0].length ?? 0;
|
|
69
|
+
let level = Math.floor(indentation / 2);
|
|
70
|
+
// Extract content after bullet point or heading
|
|
71
|
+
let content = trimmedLine;
|
|
72
|
+
if (trimmedLine.startsWith('#') || trimmedLine.includes('{{table}}')) {
|
|
73
|
+
// Remove bullet point if it precedes a table marker
|
|
74
|
+
content = trimmedLine.replace(/^\s*[-*+]\s+/, '');
|
|
75
|
+
level = 0;
|
|
76
|
+
// Reset stack but keep heading/table as parent
|
|
77
|
+
stack.length = 1; // Keep only the heading/table
|
|
78
|
+
}
|
|
79
|
+
else if (stack[0]?.content.startsWith('#') || stack[0]?.content.includes('{{table}}')) {
|
|
80
|
+
// If previous node was a heading or table marker, increase level by 1
|
|
81
|
+
level = Math.max(level, 1);
|
|
82
|
+
// Remove bullet point
|
|
83
|
+
content = trimmedLine.replace(/^\s*[-*+]\s+/, '');
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Remove bullet point
|
|
87
|
+
content = trimmedLine.replace(/^\s*[-*+]\s+/, '');
|
|
88
|
+
}
|
|
89
|
+
// Create new node
|
|
90
|
+
const node = {
|
|
91
|
+
content,
|
|
92
|
+
level,
|
|
93
|
+
children: []
|
|
94
|
+
};
|
|
95
|
+
// Find the appropriate parent for this node based on level
|
|
96
|
+
if (level === 0) {
|
|
97
|
+
rootNodes.push(node);
|
|
98
|
+
stack[0] = node;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Pop stack until we find the parent level
|
|
102
|
+
while (stack.length > level) {
|
|
103
|
+
stack.pop();
|
|
104
|
+
}
|
|
105
|
+
// Add as child to parent
|
|
106
|
+
if (stack[level - 1]) {
|
|
107
|
+
stack[level - 1].children.push(node);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// If no parent found, treat as root node
|
|
111
|
+
rootNodes.push(node);
|
|
112
|
+
}
|
|
113
|
+
stack[level] = node;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return rootNodes;
|
|
117
|
+
}
|
|
118
|
+
function parseTableRows(lines) {
|
|
119
|
+
const tableNodes = [];
|
|
120
|
+
let currentLevel = -1;
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
const trimmedLine = line.trimEnd();
|
|
123
|
+
if (!trimmedLine)
|
|
124
|
+
continue;
|
|
125
|
+
// Calculate indentation level
|
|
126
|
+
const indentation = line.match(/^\s*/)?.[0].length ?? 0;
|
|
127
|
+
const level = Math.floor(indentation / 2);
|
|
128
|
+
// Extract content after bullet point
|
|
129
|
+
const content = trimmedLine.replace(/^\s*[-*+]\s*/, '');
|
|
130
|
+
// Create node for this cell
|
|
131
|
+
const node = {
|
|
132
|
+
content,
|
|
133
|
+
level,
|
|
134
|
+
children: []
|
|
135
|
+
};
|
|
136
|
+
// Track the first level we see to maintain relative nesting
|
|
137
|
+
if (currentLevel === -1) {
|
|
138
|
+
currentLevel = level;
|
|
139
|
+
}
|
|
140
|
+
// Add node to appropriate parent based on level
|
|
141
|
+
if (level === currentLevel) {
|
|
142
|
+
tableNodes.push(node);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Find parent by walking back through nodes
|
|
146
|
+
let parent = tableNodes[tableNodes.length - 1];
|
|
147
|
+
while (parent && parent.level < level - 1) {
|
|
148
|
+
parent = parent.children[parent.children.length - 1];
|
|
149
|
+
}
|
|
150
|
+
if (parent) {
|
|
151
|
+
parent.children.push(node);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return tableNodes;
|
|
156
|
+
}
|
|
157
|
+
function generateBlockUid() {
|
|
158
|
+
// Generate a random string of 9 characters (Roam's format)
|
|
159
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
|
|
160
|
+
let uid = '';
|
|
161
|
+
for (let i = 0; i < 9; i++) {
|
|
162
|
+
uid += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
163
|
+
}
|
|
164
|
+
return uid;
|
|
165
|
+
}
|
|
166
|
+
function convertNodesToBlocks(nodes) {
|
|
167
|
+
return nodes.map(node => ({
|
|
168
|
+
uid: generateBlockUid(),
|
|
169
|
+
content: node.content,
|
|
170
|
+
children: convertNodesToBlocks(node.children)
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
function convertToRoamActions(nodes, parentUid, order = 'last') {
|
|
174
|
+
// First convert nodes to blocks with UIDs
|
|
175
|
+
const blocks = convertNodesToBlocks(nodes);
|
|
176
|
+
const actions = [];
|
|
177
|
+
// Helper function to recursively create actions
|
|
178
|
+
function createBlockActions(blocks, parentUid, order) {
|
|
179
|
+
for (const block of blocks) {
|
|
180
|
+
// Create the current block
|
|
181
|
+
const action = {
|
|
182
|
+
action: 'create-block',
|
|
183
|
+
location: {
|
|
184
|
+
'parent-uid': parentUid,
|
|
185
|
+
order
|
|
186
|
+
},
|
|
187
|
+
block: {
|
|
188
|
+
uid: block.uid,
|
|
189
|
+
string: block.content
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
actions.push(action);
|
|
193
|
+
// Create child blocks if any
|
|
194
|
+
if (block.children.length > 0) {
|
|
195
|
+
createBlockActions(block.children, block.uid, 'last');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Create all block actions
|
|
200
|
+
createBlockActions(blocks, parentUid, order);
|
|
201
|
+
return actions;
|
|
202
|
+
}
|
|
203
|
+
// Export public functions and types
|
|
204
|
+
export { parseMarkdown, convertToRoamActions, hasMarkdownTable, convertAllTables, convertToRoamMarkdown };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { initializeGraph, createPage, batchActions, q } from '@roam-research/roam-api-sdk';
|
|
2
|
+
import { parseMarkdown, convertToRoamActions } from './markdown-utils.js';
|
|
3
|
+
import * as dotenv from 'dotenv';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
// Load environment variables
|
|
7
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
8
|
+
const projectRoot = dirname(dirname(scriptPath));
|
|
9
|
+
const envPath = join(projectRoot, '.env');
|
|
10
|
+
dotenv.config({ path: envPath });
|
|
11
|
+
const API_TOKEN = process.env.ROAM_API_TOKEN;
|
|
12
|
+
const GRAPH_NAME = process.env.ROAM_GRAPH_NAME;
|
|
13
|
+
if (!API_TOKEN || !GRAPH_NAME) {
|
|
14
|
+
throw new Error('Missing required environment variables: ROAM_API_TOKEN and/or ROAM_GRAPH_NAME');
|
|
15
|
+
}
|
|
16
|
+
async function testAddMarkdownText() {
|
|
17
|
+
try {
|
|
18
|
+
// Initialize graph
|
|
19
|
+
console.log('Initializing graph...');
|
|
20
|
+
const graph = initializeGraph({
|
|
21
|
+
token: API_TOKEN,
|
|
22
|
+
graph: GRAPH_NAME,
|
|
23
|
+
});
|
|
24
|
+
// Test markdown content
|
|
25
|
+
const testPageTitle = `Test Markdown Import ${new Date().toISOString()}`;
|
|
26
|
+
console.log(`Using test page title: ${testPageTitle}`);
|
|
27
|
+
const markdownContent = `
|
|
28
|
+
| Month | Savings |
|
|
29
|
+
| -------- | ------- |
|
|
30
|
+
| January | $250 |
|
|
31
|
+
| February | $80 |
|
|
32
|
+
| March | $420 |
|
|
33
|
+
|
|
34
|
+
# Main Topic
|
|
35
|
+
- First point
|
|
36
|
+
- Nested point A
|
|
37
|
+
- Deep nested point
|
|
38
|
+
- Nested point B
|
|
39
|
+
- Second point
|
|
40
|
+
1. Numbered subpoint
|
|
41
|
+
2. Another numbered point
|
|
42
|
+
- Mixed list type
|
|
43
|
+
- Third point
|
|
44
|
+
- With some **bold** text
|
|
45
|
+
- And *italic* text
|
|
46
|
+
- And a [[Page Reference]]
|
|
47
|
+
- And a #[[Page Tag]]
|
|
48
|
+
`;
|
|
49
|
+
// First create the page
|
|
50
|
+
console.log('\nCreating page...');
|
|
51
|
+
const success = await createPage(graph, {
|
|
52
|
+
action: 'create-page',
|
|
53
|
+
page: {
|
|
54
|
+
title: testPageTitle
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
if (!success) {
|
|
58
|
+
throw new Error('Failed to create test page');
|
|
59
|
+
}
|
|
60
|
+
// Get the page UID
|
|
61
|
+
const findQuery = `[:find ?uid :in $ ?title :where [?e :node/title ?title] [?e :block/uid ?uid]]`;
|
|
62
|
+
const findResults = await q(graph, findQuery, [testPageTitle]);
|
|
63
|
+
if (!findResults || findResults.length === 0) {
|
|
64
|
+
throw new Error('Could not find created page');
|
|
65
|
+
}
|
|
66
|
+
const pageUid = findResults[0][0];
|
|
67
|
+
console.log('Page UID:', pageUid);
|
|
68
|
+
// Import markdown
|
|
69
|
+
console.log('\nImporting markdown...');
|
|
70
|
+
const nodes = parseMarkdown(markdownContent);
|
|
71
|
+
console.log('Parsed nodes:', JSON.stringify(nodes, null, 2));
|
|
72
|
+
// Convert and add markdown content
|
|
73
|
+
const actions = convertToRoamActions(nodes, pageUid, 'last');
|
|
74
|
+
const result = await batchActions(graph, {
|
|
75
|
+
action: 'batch-actions',
|
|
76
|
+
actions
|
|
77
|
+
});
|
|
78
|
+
console.log('\nImport result:', JSON.stringify(result, null, 2));
|
|
79
|
+
console.log('\nTest completed successfully!');
|
|
80
|
+
console.log(`Check your Roam graph for the page titled: ${testPageTitle}`);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error('Error:', error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Run the test
|
|
87
|
+
testAddMarkdownText();
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { initializeGraph, q } from '@roam-research/roam-api-sdk';
|
|
3
|
+
import * as dotenv from 'dotenv';
|
|
4
|
+
// Load environment variables
|
|
5
|
+
dotenv.config();
|
|
6
|
+
const API_TOKEN = process.env.ROAM_API_TOKEN;
|
|
7
|
+
const GRAPH_NAME = process.env.ROAM_GRAPH_NAME;
|
|
8
|
+
if (!API_TOKEN || !GRAPH_NAME) {
|
|
9
|
+
throw new Error('Missing required environment variables: ROAM_API_TOKEN and ROAM_GRAPH_NAME');
|
|
10
|
+
}
|
|
11
|
+
async function main() {
|
|
12
|
+
const graph = initializeGraph({
|
|
13
|
+
token: API_TOKEN,
|
|
14
|
+
graph: GRAPH_NAME,
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
// First verify we can find the page
|
|
18
|
+
console.log('Finding page...');
|
|
19
|
+
const searchQuery = `[:find ?uid .
|
|
20
|
+
:where [?e :node/title "December 18th, 2024"]
|
|
21
|
+
[?e :block/uid ?uid]]`;
|
|
22
|
+
const uid = await q(graph, searchQuery, []);
|
|
23
|
+
console.log('Page UID:', uid);
|
|
24
|
+
if (!uid) {
|
|
25
|
+
throw new Error('Page not found');
|
|
26
|
+
}
|
|
27
|
+
// Get all blocks under this page with their order
|
|
28
|
+
console.log('\nGetting blocks...');
|
|
29
|
+
const blocksQuery = `[:find ?block-uid ?block-str ?order ?parent-uid
|
|
30
|
+
:where [?p :block/uid "${uid}"]
|
|
31
|
+
[?b :block/page ?p]
|
|
32
|
+
[?b :block/uid ?block-uid]
|
|
33
|
+
[?b :block/string ?block-str]
|
|
34
|
+
[?b :block/order ?order]
|
|
35
|
+
[?b :block/parents ?parent]
|
|
36
|
+
[?parent :block/uid ?parent-uid]]`;
|
|
37
|
+
const blocks = await q(graph, blocksQuery, []);
|
|
38
|
+
console.log('Found', blocks.length, 'blocks');
|
|
39
|
+
// Create a map of all blocks
|
|
40
|
+
const blockMap = new Map();
|
|
41
|
+
blocks.forEach(([uid, string, order]) => {
|
|
42
|
+
if (!blockMap.has(uid)) {
|
|
43
|
+
blockMap.set(uid, {
|
|
44
|
+
uid,
|
|
45
|
+
string,
|
|
46
|
+
order: order,
|
|
47
|
+
children: []
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
console.log('Created block map with', blockMap.size, 'entries');
|
|
52
|
+
// Build parent-child relationships
|
|
53
|
+
let relationshipsBuilt = 0;
|
|
54
|
+
blocks.forEach(([childUid, _, __, parentUid]) => {
|
|
55
|
+
const child = blockMap.get(childUid);
|
|
56
|
+
const parent = blockMap.get(parentUid);
|
|
57
|
+
if (child && parent && !parent.children.includes(child)) {
|
|
58
|
+
parent.children.push(child);
|
|
59
|
+
relationshipsBuilt++;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
console.log('Built', relationshipsBuilt, 'parent-child relationships');
|
|
63
|
+
// Get top-level blocks (those directly under the page)
|
|
64
|
+
console.log('\nGetting top-level blocks...');
|
|
65
|
+
const topQuery = `[:find ?block-uid ?block-str ?order
|
|
66
|
+
:where [?p :block/uid "${uid}"]
|
|
67
|
+
[?b :block/page ?p]
|
|
68
|
+
[?b :block/uid ?block-uid]
|
|
69
|
+
[?b :block/string ?block-str]
|
|
70
|
+
[?b :block/order ?order]
|
|
71
|
+
(not-join [?b]
|
|
72
|
+
[?b :block/parents ?parent]
|
|
73
|
+
[?parent :block/page ?p])]`;
|
|
74
|
+
const topBlocks = await q(graph, topQuery, []);
|
|
75
|
+
console.log('Found', topBlocks.length, 'top-level blocks');
|
|
76
|
+
// Create root blocks
|
|
77
|
+
const rootBlocks = topBlocks
|
|
78
|
+
.map(([uid, string, order]) => ({
|
|
79
|
+
uid,
|
|
80
|
+
string,
|
|
81
|
+
order: order,
|
|
82
|
+
children: blockMap.get(uid)?.children || []
|
|
83
|
+
}))
|
|
84
|
+
.sort((a, b) => a.order - b.order);
|
|
85
|
+
// Log block hierarchy
|
|
86
|
+
console.log('\nBlock hierarchy:');
|
|
87
|
+
const logHierarchy = (blocks, level = 0) => {
|
|
88
|
+
blocks.forEach(block => {
|
|
89
|
+
console.log(' '.repeat(level) + '- ' + block.string.substring(0, 50) + '...');
|
|
90
|
+
if (block.children.length > 0) {
|
|
91
|
+
logHierarchy(block.children.sort((a, b) => a.order - b.order), level + 1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
logHierarchy(rootBlocks);
|
|
96
|
+
// Convert to markdown
|
|
97
|
+
console.log('\nConverting to markdown...');
|
|
98
|
+
const toMarkdown = (blocks, level = 0) => {
|
|
99
|
+
return blocks.map(block => {
|
|
100
|
+
const indent = ' '.repeat(level);
|
|
101
|
+
let md = `${indent}- ${block.string}\n`;
|
|
102
|
+
if (block.children.length > 0) {
|
|
103
|
+
md += toMarkdown(block.children.sort((a, b) => a.order - b.order), level + 1);
|
|
104
|
+
}
|
|
105
|
+
return md;
|
|
106
|
+
}).join('');
|
|
107
|
+
};
|
|
108
|
+
const markdown = toMarkdown(rootBlocks);
|
|
109
|
+
console.log('\nMarkdown output:');
|
|
110
|
+
console.log(markdown);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error('Error:', error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "roam-research-mcp",
|
|
3
|
+
"version": "0.12.3",
|
|
4
|
+
"description": "A Model Context Protocol (MCP) server for Roam Research API integration",
|
|
5
|
+
"private": false,
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/2b3pro/roam-research-mcp.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mcp",
|
|
12
|
+
"roam-research",
|
|
13
|
+
"api",
|
|
14
|
+
"claude",
|
|
15
|
+
"model-context-protocol"
|
|
16
|
+
],
|
|
17
|
+
"author": "Ian Shen / 2B3 PRODUCTIONS LLC",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/2b3pro/roam-research-mcp/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/2b3pro/roam-research-mcp#readme",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"roam-research": "./build/index.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"build"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc && chmod 755 build/index.js",
|
|
32
|
+
"prepare": "npm run build",
|
|
33
|
+
"watch": "tsc --watch",
|
|
34
|
+
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "0.6.0",
|
|
38
|
+
"@roam-research/roam-api-sdk": "^0.10.0",
|
|
39
|
+
"dotenv": "^16.4.7"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.11.24",
|
|
43
|
+
"ts-node": "^10.9.2",
|
|
44
|
+
"typescript": "^5.3.3"
|
|
45
|
+
}
|
|
46
|
+
}
|