@dinoxx/dinox-cli 1.0.22 → 1.1.1
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/README.md +33 -10
- package/dist/auth/userInfo.d.ts +2 -0
- package/dist/auth/userInfo.js +55 -14
- package/dist/cli.js +36 -2
- package/dist/commands/auth/index.d.ts +35 -0
- package/dist/commands/auth/index.js +382 -83
- package/dist/commands/boxes/index.js +2 -6
- package/dist/commands/boxes/model.d.ts +3 -0
- package/dist/commands/boxes/model.js +16 -1
- package/dist/commands/boxes/operations.js +18 -8
- package/dist/commands/boxes/repo.js +13 -11
- package/dist/commands/boxes/store.js +1 -1
- package/dist/commands/config/index.js +7 -5
- package/dist/commands/descriptor/register.js +9 -0
- package/dist/commands/doctor.d.ts +123 -0
- package/dist/commands/doctor.js +539 -0
- package/dist/commands/graph/index.d.ts +1 -1
- package/dist/commands/graph/index.js +35 -21
- package/dist/commands/graph/repo.js +92 -17
- package/dist/commands/meta/index.js +4 -4
- package/dist/commands/meta/repo.js +4 -3
- package/dist/commands/notes/actions.d.ts +1 -1
- package/dist/commands/notes/actions.js +121 -78
- package/dist/commands/notes/bulk.d.ts +1 -0
- package/dist/commands/notes/bulk.js +23 -17
- package/dist/commands/notes/contentEngine.js +69 -11
- package/dist/commands/notes/contentPatch.d.ts +4 -1
- package/dist/commands/notes/contentPatch.js +105 -41
- package/dist/commands/notes/contentPatchCommand.js +28 -11
- package/dist/commands/notes/descriptors.js +50 -20
- package/dist/commands/notes/dryRun.d.ts +1 -0
- package/dist/commands/notes/dryRun.js +4 -7
- package/dist/commands/notes/export.d.ts +6 -0
- package/dist/commands/notes/export.js +26 -6
- package/dist/commands/notes/metadataMutation.d.ts +1 -0
- package/dist/commands/notes/metadataMutation.js +14 -3
- package/dist/commands/notes/repo.d.ts +46 -4
- package/dist/commands/notes/repo.js +113 -28
- package/dist/commands/notes/runtime.d.ts +2 -1
- package/dist/commands/notes/runtime.js +17 -1
- package/dist/commands/notes/searchSql.js +13 -6
- package/dist/commands/notes/writeReceipt.d.ts +20 -0
- package/dist/commands/notes/writeReceipt.js +82 -0
- package/dist/commands/prompt/index.js +3 -3
- package/dist/commands/prompt/repo.d.ts +4 -1
- package/dist/commands/prompt/repo.js +17 -7
- package/dist/commands/runtime.d.ts +14 -2
- package/dist/commands/runtime.js +89 -19
- package/dist/commands/schema/renderSkills.js +5 -2
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/storage/index.js +8 -2
- package/dist/commands/storage/repo.d.ts +4 -0
- package/dist/commands/storage/repo.js +356 -92
- package/dist/commands/sync.d.ts +23 -1
- package/dist/commands/sync.js +105 -60
- package/dist/commands/tags/content.js +9 -0
- package/dist/commands/tags/index.js +2 -6
- package/dist/commands/tags/operations.js +15 -10
- package/dist/commands/tags/repo.d.ts +2 -0
- package/dist/commands/tags/repo.js +18 -5
- package/dist/commands/tags/store.js +1 -1
- package/dist/commands/todo/contentModel.d.ts +4 -3
- package/dist/commands/todo/contentModel.js +78 -28
- package/dist/commands/todo/dryRun.d.ts +1 -0
- package/dist/commands/todo/dryRun.js +17 -13
- package/dist/commands/todo/index.d.ts +6 -0
- package/dist/commands/todo/index.js +89 -123
- package/dist/commands/todo/repo.d.ts +36 -1
- package/dist/commands/todo/repo.js +146 -37
- package/dist/commands/todo/taskIdentity.d.ts +2 -0
- package/dist/commands/todo/taskIdentity.js +64 -0
- package/dist/commands/todo/taskModel.js +27 -17
- package/dist/commands/update.d.ts +13 -1
- package/dist/commands/update.js +135 -23
- package/dist/commands/writeDryRun.d.ts +3 -2
- package/dist/commands/writeDryRun.js +4 -3
- package/dist/commands/writeDurability.d.ts +5 -0
- package/dist/commands/writeDurability.js +22 -0
- package/dist/config/paths.js +6 -1
- package/dist/config/resolve.d.ts +2 -0
- package/dist/config/resolve.js +23 -9
- package/dist/config/store.d.ts +3 -0
- package/dist/config/store.js +61 -14
- package/dist/config/types.d.ts +4 -0
- package/dist/daemon/client.d.ts +26 -1
- package/dist/daemon/client.js +236 -12
- package/dist/daemon/ensure.js +74 -12
- package/dist/daemon/index.d.ts +42 -0
- package/dist/daemon/index.js +202 -52
- package/dist/daemon/lifecycle.d.ts +44 -0
- package/dist/daemon/lifecycle.js +118 -0
- package/dist/daemon/pid.d.ts +19 -6
- package/dist/daemon/pid.js +147 -13
- package/dist/daemon/route.d.ts +14 -0
- package/dist/daemon/route.js +40 -0
- package/dist/daemon/server.d.ts +11 -0
- package/dist/daemon/server.js +201 -50
- package/dist/daemon/types.d.ts +29 -1
- package/dist/daemon/types.js +1 -0
- package/dist/dinox.js +25 -1
- package/dist/powersync/connector.js +31 -16
- package/dist/powersync/exclusiveRuntime.d.ts +20 -0
- package/dist/powersync/exclusiveRuntime.js +48 -0
- package/dist/powersync/runtime.d.ts +25 -4
- package/dist/powersync/runtime.js +255 -35
- package/dist/powersync/syncGate.d.ts +4 -1
- package/dist/powersync/syncGate.js +11 -2
- package/dist/powersync/syncPass.d.ts +2 -0
- package/dist/powersync/syncPass.js +15 -4
- package/dist/powersync/tokenIndex.d.ts +13 -0
- package/dist/powersync/tokenIndex.js +216 -37
- package/dist/powersync/uploader.js +107 -52
- package/dist/utils/abort.js +0 -4
- package/dist/utils/asyncRelease.d.ts +2 -0
- package/dist/utils/asyncRelease.js +25 -0
- package/dist/utils/bidlinkCanonicalization.js +3 -0
- package/dist/utils/dryRun.d.ts +1 -1
- package/dist/utils/errors.d.ts +15 -1
- package/dist/utils/errors.js +223 -5
- package/dist/utils/output.d.ts +1 -0
- package/dist/utils/output.js +51 -15
- package/dist/utils/redact.js +1 -1
- package/dist/utils/sqliteLease.d.ts +8 -0
- package/dist/utils/sqliteLease.js +59 -0
- package/dist/utils/syncTimeout.d.ts +7 -1
- package/dist/utils/syncTimeout.js +8 -7
- package/dist/utils/terminalSanitization.js +6 -2
- package/dist/utils/tiptapMarkdown.js +49 -3
- package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
- package/dist/utils/tiptapMarkdownSyntax.js +249 -0
- package/dist/utils/tokenize.js +24 -2
- package/dist/utils/updateNotice.js +60 -12
- package/package.json +14 -10
- package/dist/commands/mcp/auth.d.ts +0 -4
- package/dist/commands/mcp/auth.js +0 -75
- package/dist/commands/mcp/index.d.ts +0 -2
- package/dist/commands/mcp/index.js +0 -86
- package/dist/commands/mcp/noteContentTools.d.ts +0 -222
- package/dist/commands/mcp/noteContentTools.js +0 -390
- package/dist/commands/mcp/server.d.ts +0 -34
- package/dist/commands/mcp/server.js +0 -424
- package/dist/commands/mcp/tools.d.ts +0 -2
- package/dist/commands/mcp/tools.js +0 -32
- package/dist/commands/mcp/types.d.ts +0 -39
- package/dist/commands/mcp/types.js +0 -1
- package/dist/utils/noteContentEngine.d.ts +0 -333
- package/dist/utils/noteContentEngine.js +0 -740
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
|
-
import { DEFAULT_MCP_PORT } from './types.js';
|
|
3
|
-
import { mcpTools } from './tools.js';
|
|
4
|
-
import { isAuthorizedMCPRequest } from './auth.js';
|
|
5
|
-
import { NOTE_CONTENT_EDIT_TOOL_NAME, NOTE_CONTENT_READ_TOOL_NAME, NoteContentMcpService, buildNoteContentToolError, } from './noteContentTools.js';
|
|
6
|
-
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
7
|
-
import { loadConfig } from '../../config/store.js';
|
|
8
|
-
import { resolveConfig } from '../../config/resolve.js';
|
|
9
|
-
import { connectPowerSync } from '../../powersync/runtime.js';
|
|
10
|
-
import { canonicalizeBidlinksInMarkdown } from '../../utils/bidlinkCanonicalization.js';
|
|
11
|
-
import { parseCreatedAtRange } from '../notes/searchTime.js';
|
|
12
|
-
import { parseNoteSearchFields, projectNoteSearchFields } from '../notes/searchOutput.js';
|
|
13
|
-
import { createNote, getNoteDetails, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForSearch, searchNotes, validateTagsForCreate, } from '../notes/repo.js';
|
|
14
|
-
import { getBacklinks, getGraphStats, getOutlinks, getRelatedNotes } from '../graph/repo.js';
|
|
15
|
-
import { searchTodoTasks } from '../todo/repo.js';
|
|
16
|
-
import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
|
|
17
|
-
import { generateId } from '../../utils/id.js';
|
|
18
|
-
import { printHumanReadableLine } from '../../utils/output.js';
|
|
19
|
-
import { getPackageVersion } from '../../utils/version.js';
|
|
20
|
-
const MAX_MCP_REQUEST_BYTES = 1 * 1024 * 1024;
|
|
21
|
-
const DEFAULT_SEARCH_LIMIT = 50;
|
|
22
|
-
const MAX_SEARCH_LIMIT = 500;
|
|
23
|
-
function normalizeLimit(value) {
|
|
24
|
-
if (value === undefined || value === null) {
|
|
25
|
-
return DEFAULT_SEARCH_LIMIT;
|
|
26
|
-
}
|
|
27
|
-
const raw = typeof value === 'number'
|
|
28
|
-
? value
|
|
29
|
-
: typeof value === 'string'
|
|
30
|
-
? Number(value)
|
|
31
|
-
: NaN;
|
|
32
|
-
if (!Number.isFinite(raw)) {
|
|
33
|
-
return DEFAULT_SEARCH_LIMIT;
|
|
34
|
-
}
|
|
35
|
-
const int = Math.trunc(raw);
|
|
36
|
-
if (int <= 0) {
|
|
37
|
-
return DEFAULT_SEARCH_LIMIT;
|
|
38
|
-
}
|
|
39
|
-
return Math.min(int, MAX_SEARCH_LIMIT);
|
|
40
|
-
}
|
|
41
|
-
function normalizeOffset(value) {
|
|
42
|
-
if (value === undefined || value === null) {
|
|
43
|
-
return 0;
|
|
44
|
-
}
|
|
45
|
-
const raw = typeof value === 'number'
|
|
46
|
-
? value
|
|
47
|
-
: typeof value === 'string'
|
|
48
|
-
? Number(value)
|
|
49
|
-
: NaN;
|
|
50
|
-
if (!Number.isFinite(raw)) {
|
|
51
|
-
return 0;
|
|
52
|
-
}
|
|
53
|
-
const int = Math.trunc(raw);
|
|
54
|
-
if (int < 0) {
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
return int;
|
|
58
|
-
}
|
|
59
|
-
function normalizeStringArray(value) {
|
|
60
|
-
if (!Array.isArray(value)) {
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
63
|
-
return value.map((item) => (typeof item === 'string' ? item.trim() : '')).filter(Boolean);
|
|
64
|
-
}
|
|
65
|
-
function parseBoxFilter(value) {
|
|
66
|
-
if (Array.isArray(value)) {
|
|
67
|
-
return { names: normalizeStringArray(value), matchEmpty: false };
|
|
68
|
-
}
|
|
69
|
-
if (typeof value !== 'string') {
|
|
70
|
-
return { names: [], matchEmpty: false };
|
|
71
|
-
}
|
|
72
|
-
const trimmed = value.trim();
|
|
73
|
-
if (!trimmed) {
|
|
74
|
-
return { names: [], matchEmpty: false };
|
|
75
|
-
}
|
|
76
|
-
if (trimmed === '[]') {
|
|
77
|
-
return { names: [], matchEmpty: true };
|
|
78
|
-
}
|
|
79
|
-
const names = trimmed
|
|
80
|
-
.split(/[,\n]/)
|
|
81
|
-
.map((name) => name.trim())
|
|
82
|
-
.filter(Boolean);
|
|
83
|
-
return { names, matchEmpty: false };
|
|
84
|
-
}
|
|
85
|
-
function isJsonRpcId(value) {
|
|
86
|
-
return value === null || typeof value === 'string' || typeof value === 'number';
|
|
87
|
-
}
|
|
88
|
-
function parseMCPRequest(value) {
|
|
89
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
90
|
-
throw new Error('Invalid JSON-RPC request');
|
|
91
|
-
}
|
|
92
|
-
const record = value;
|
|
93
|
-
if (record.jsonrpc !== '2.0') {
|
|
94
|
-
throw new Error('Invalid JSON-RPC request: jsonrpc must be "2.0"');
|
|
95
|
-
}
|
|
96
|
-
if (typeof record.method !== 'string' || !record.method.trim()) {
|
|
97
|
-
throw new Error('Invalid JSON-RPC request: method is required');
|
|
98
|
-
}
|
|
99
|
-
if (record.id !== undefined && !isJsonRpcId(record.id)) {
|
|
100
|
-
throw new Error('Invalid JSON-RPC request: id must be a string, number, or null');
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
jsonrpc: '2.0',
|
|
104
|
-
id: record.id,
|
|
105
|
-
method: record.method.trim(),
|
|
106
|
-
params: record.params,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
function hasRequestId(req) {
|
|
110
|
-
return req.id !== undefined;
|
|
111
|
-
}
|
|
112
|
-
function buildResponse(req, payload) {
|
|
113
|
-
return {
|
|
114
|
-
jsonrpc: '2.0',
|
|
115
|
-
id: req.id ?? null,
|
|
116
|
-
...payload,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
async function readJsonBody(req) {
|
|
120
|
-
return new Promise((resolve, reject) => {
|
|
121
|
-
const chunks = [];
|
|
122
|
-
let totalBytes = 0;
|
|
123
|
-
req.on('data', (chunk) => {
|
|
124
|
-
totalBytes += chunk.length;
|
|
125
|
-
if (totalBytes > MAX_MCP_REQUEST_BYTES) {
|
|
126
|
-
req.destroy(new Error('Request body too large'));
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
chunks.push(chunk);
|
|
130
|
-
});
|
|
131
|
-
req.on('end', () => {
|
|
132
|
-
try {
|
|
133
|
-
const body = Buffer.concat(chunks).toString('utf-8');
|
|
134
|
-
resolve(JSON.parse(body));
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
reject(error);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
req.on('error', reject);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
export class MCPServer {
|
|
144
|
-
db = null;
|
|
145
|
-
noteContent = new NoteContentMcpService(() => this.requireDb());
|
|
146
|
-
async initialize() {
|
|
147
|
-
const config = resolveConfig(await loadConfig());
|
|
148
|
-
const { db } = await connectPowerSync({ config, offline: false, timeoutMs: config.sync.timeoutMs });
|
|
149
|
-
this.db = db;
|
|
150
|
-
}
|
|
151
|
-
async handleRequest(req) {
|
|
152
|
-
if (req.method === 'notifications/initialized') {
|
|
153
|
-
return hasRequestId(req) ? buildResponse(req, { result: {} }) : null;
|
|
154
|
-
}
|
|
155
|
-
const shouldRespond = hasRequestId(req);
|
|
156
|
-
try {
|
|
157
|
-
switch (req.method) {
|
|
158
|
-
case 'initialize':
|
|
159
|
-
if (!shouldRespond) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
return this.handleInitialize(req);
|
|
163
|
-
case 'tools/list':
|
|
164
|
-
if (!shouldRespond) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
return this.handleToolsList(req);
|
|
168
|
-
case 'tools/call':
|
|
169
|
-
if (!shouldRespond) {
|
|
170
|
-
await this.handleToolsCall(req);
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
return await this.handleToolsCall(req);
|
|
174
|
-
default:
|
|
175
|
-
if (!shouldRespond) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
return buildResponse(req, {
|
|
179
|
-
error: { code: -32601, message: 'Method not found' },
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
if (!shouldRespond) {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
return buildResponse(req, {
|
|
188
|
-
error: { code: -32603, message: error.message || 'Internal error' },
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
handleInitialize(req) {
|
|
193
|
-
const result = {
|
|
194
|
-
protocolVersion: '2024-11-05',
|
|
195
|
-
capabilities: {
|
|
196
|
-
tools: {},
|
|
197
|
-
},
|
|
198
|
-
serverInfo: {
|
|
199
|
-
name: 'dinox-mcp-server',
|
|
200
|
-
version: getPackageVersion(),
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
return buildResponse(req, { result });
|
|
204
|
-
}
|
|
205
|
-
handleToolsList(req) {
|
|
206
|
-
return buildResponse(req, { result: { tools: mcpTools } });
|
|
207
|
-
}
|
|
208
|
-
async handleToolsCall(req) {
|
|
209
|
-
const db = this.requireDb();
|
|
210
|
-
const name = req.params?.name;
|
|
211
|
-
const args = req.params?.arguments ?? {};
|
|
212
|
-
if (typeof name !== 'string') {
|
|
213
|
-
throw new Error('Invalid tools/call: missing tool name');
|
|
214
|
-
}
|
|
215
|
-
switch (name) {
|
|
216
|
-
case 'dinox_search_notes': {
|
|
217
|
-
const keyword = typeof args.query === 'string' ? args.query : '';
|
|
218
|
-
const tagsExpression = typeof args.tags === 'string' ? args.tags.trim() : '';
|
|
219
|
-
const rawBoxes = args.boxes ?? args.box;
|
|
220
|
-
const { names: boxNames, matchEmpty: matchEmptyBoxes } = parseBoxFilter(rawBoxes);
|
|
221
|
-
const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, boxNames);
|
|
222
|
-
const daysInput = typeof args.days === 'number' ? String(args.days) : args.days;
|
|
223
|
-
const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
|
|
224
|
-
from: args.from,
|
|
225
|
-
to: args.to,
|
|
226
|
-
days: daysInput,
|
|
227
|
-
});
|
|
228
|
-
const limit = normalizeLimit(args.limit);
|
|
229
|
-
const offset = normalizeOffset(args.offset);
|
|
230
|
-
const fields = parseNoteSearchFields(args.fields, 'dinox_search_notes.fields');
|
|
231
|
-
const rows = await searchNotes(db, keyword, {
|
|
232
|
-
includeDeleted: false,
|
|
233
|
-
tagsExpression: tagsExpression || undefined,
|
|
234
|
-
createdAtFrom,
|
|
235
|
-
createdAtTo,
|
|
236
|
-
zettelBoxIds,
|
|
237
|
-
matchEmptyZettelBoxes: matchEmptyBoxes,
|
|
238
|
-
});
|
|
239
|
-
const totalCount = rows.length;
|
|
240
|
-
const paged = rows.slice(offset, offset + limit);
|
|
241
|
-
const result = projectNoteSearchFields(paged, fields);
|
|
242
|
-
const text = JSON.stringify({
|
|
243
|
-
meta: {
|
|
244
|
-
total_count: totalCount,
|
|
245
|
-
returned_count: result.length,
|
|
246
|
-
limit,
|
|
247
|
-
offset,
|
|
248
|
-
has_more: offset + result.length < totalCount,
|
|
249
|
-
fields: fields ?? null,
|
|
250
|
-
},
|
|
251
|
-
notes: result,
|
|
252
|
-
});
|
|
253
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
254
|
-
}
|
|
255
|
-
case 'dinox_get_note': {
|
|
256
|
-
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
257
|
-
throw new Error('dinox_get_note: id is required');
|
|
258
|
-
}
|
|
259
|
-
const notes = await getNoteDetails(db, [args.id.trim()]);
|
|
260
|
-
const text = JSON.stringify(notes[0] ?? null);
|
|
261
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
262
|
-
}
|
|
263
|
-
case 'dinox_create_note': {
|
|
264
|
-
const title = typeof args.title === 'string' ? args.title.trim() : '';
|
|
265
|
-
const contentMd = typeof args.content === 'string' ? args.content : '';
|
|
266
|
-
if (!title) {
|
|
267
|
-
throw new Error('dinox_create_note: title is required');
|
|
268
|
-
}
|
|
269
|
-
if (!contentMd) {
|
|
270
|
-
throw new Error('dinox_create_note: content is required');
|
|
271
|
-
}
|
|
272
|
-
const rawConfig = await loadConfig();
|
|
273
|
-
const config = resolveConfig(rawConfig);
|
|
274
|
-
const identity = await resolveAuthIdentity(rawConfig, config);
|
|
275
|
-
const canonicalContentMd = await canonicalizeBidlinksInMarkdown(db, contentMd, identity.userId);
|
|
276
|
-
const { contentJson, contentText } = convertMarkdownToNoteContent(canonicalContentMd);
|
|
277
|
-
const tags = await validateTagsForCreate(db, normalizeStringArray(args.tags));
|
|
278
|
-
const zettelBoxIds = await resolveZettelBoxIdsForCreate(db, normalizeStringArray(args.boxes));
|
|
279
|
-
const id = generateId();
|
|
280
|
-
await createNote(db, {
|
|
281
|
-
id,
|
|
282
|
-
title,
|
|
283
|
-
noteType: 'crawl',
|
|
284
|
-
contentMd: canonicalContentMd,
|
|
285
|
-
contentText,
|
|
286
|
-
contentJson,
|
|
287
|
-
tags,
|
|
288
|
-
zettelBoxIds,
|
|
289
|
-
userId: identity.userId,
|
|
290
|
-
});
|
|
291
|
-
const text = JSON.stringify({ id });
|
|
292
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
293
|
-
}
|
|
294
|
-
case 'dinox_graph_backlinks': {
|
|
295
|
-
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
296
|
-
throw new Error('dinox_graph_backlinks: id is required');
|
|
297
|
-
}
|
|
298
|
-
const result = await getBacklinks(db, args.id.trim());
|
|
299
|
-
const text = JSON.stringify(result);
|
|
300
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
301
|
-
}
|
|
302
|
-
case 'dinox_graph_outlinks': {
|
|
303
|
-
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
304
|
-
throw new Error('dinox_graph_outlinks: id is required');
|
|
305
|
-
}
|
|
306
|
-
const result = await getOutlinks(db, args.id.trim());
|
|
307
|
-
const text = JSON.stringify(result);
|
|
308
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
309
|
-
}
|
|
310
|
-
case 'dinox_graph_related': {
|
|
311
|
-
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
312
|
-
throw new Error('dinox_graph_related: id is required');
|
|
313
|
-
}
|
|
314
|
-
const depthRaw = typeof args.depth === 'number' && Number.isFinite(args.depth)
|
|
315
|
-
? Math.trunc(args.depth)
|
|
316
|
-
: 2;
|
|
317
|
-
const depth = Math.min(Math.max(depthRaw, 1), 5);
|
|
318
|
-
const result = await getRelatedNotes(db, args.id.trim(), depth);
|
|
319
|
-
const text = JSON.stringify(result);
|
|
320
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
321
|
-
}
|
|
322
|
-
case 'dinox_graph_stats': {
|
|
323
|
-
const result = await getGraphStats(db);
|
|
324
|
-
const text = JSON.stringify(result);
|
|
325
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
326
|
-
}
|
|
327
|
-
case 'dinox_search_todos': {
|
|
328
|
-
const query = typeof args.query === 'string' ? args.query : '';
|
|
329
|
-
const status = typeof args.status === 'string' && ['all', 'completed', 'uncompleted'].includes(args.status)
|
|
330
|
-
? args.status
|
|
331
|
-
: 'all';
|
|
332
|
-
const limit = normalizeLimit(args.limit);
|
|
333
|
-
const result = await searchTodoTasks(db, query, {
|
|
334
|
-
includeDeleted: false,
|
|
335
|
-
taskStatus: status || 'all',
|
|
336
|
-
taskTagFilter: normalizeStringArray(args.tags),
|
|
337
|
-
limit,
|
|
338
|
-
scanLimit: 200,
|
|
339
|
-
});
|
|
340
|
-
const text = JSON.stringify(result);
|
|
341
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
342
|
-
}
|
|
343
|
-
case NOTE_CONTENT_READ_TOOL_NAME: {
|
|
344
|
-
const runtime = await this.getRuntime();
|
|
345
|
-
const result = await this.noteContent.read(args, runtime).catch((error) => buildNoteContentToolError(error));
|
|
346
|
-
const text = JSON.stringify(result);
|
|
347
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
348
|
-
}
|
|
349
|
-
case NOTE_CONTENT_EDIT_TOOL_NAME: {
|
|
350
|
-
const runtime = await this.getRuntime();
|
|
351
|
-
const operations = Array.isArray(args.operations) ? args.operations : [];
|
|
352
|
-
const result = await this.noteContent.edit({
|
|
353
|
-
readToken: args.readToken,
|
|
354
|
-
operations,
|
|
355
|
-
}, runtime).catch((error) => buildNoteContentToolError(error, operations));
|
|
356
|
-
const text = JSON.stringify(result);
|
|
357
|
-
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
358
|
-
}
|
|
359
|
-
default:
|
|
360
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
requireDb() {
|
|
364
|
-
if (!this.db) {
|
|
365
|
-
throw new Error('Database not initialized');
|
|
366
|
-
}
|
|
367
|
-
return this.db;
|
|
368
|
-
}
|
|
369
|
-
async getRuntime() {
|
|
370
|
-
const rawConfig = await loadConfig();
|
|
371
|
-
const config = resolveConfig(rawConfig);
|
|
372
|
-
const identity = await resolveAuthIdentity(rawConfig, config);
|
|
373
|
-
return { userId: identity.userId };
|
|
374
|
-
}
|
|
375
|
-
async close() {
|
|
376
|
-
if (this.db) {
|
|
377
|
-
await this.db.close().catch(() => undefined);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
export async function startMCPServer(port = DEFAULT_MCP_PORT, host = '127.0.0.1', authToken) {
|
|
382
|
-
const mcpServer = new MCPServer();
|
|
383
|
-
await mcpServer.initialize();
|
|
384
|
-
const server = createServer(async (req, res) => {
|
|
385
|
-
if (req.method === 'POST') {
|
|
386
|
-
if (!isAuthorizedMCPRequest(req.headers, authToken)) {
|
|
387
|
-
res.writeHead(401, {
|
|
388
|
-
'Content-Type': 'application/json',
|
|
389
|
-
'WWW-Authenticate': 'Bearer realm="dinox-mcp"',
|
|
390
|
-
});
|
|
391
|
-
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
try {
|
|
395
|
-
const body = await readJsonBody(req);
|
|
396
|
-
const mcpReq = parseMCPRequest(body);
|
|
397
|
-
const mcpRes = await mcpServer.handleRequest(mcpReq);
|
|
398
|
-
if (!mcpRes) {
|
|
399
|
-
res.writeHead(204);
|
|
400
|
-
res.end();
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
404
|
-
res.end(JSON.stringify(mcpRes));
|
|
405
|
-
}
|
|
406
|
-
catch (error) {
|
|
407
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
408
|
-
const statusCode = message.includes('too large') ? 413 : 400;
|
|
409
|
-
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
410
|
-
res.end(JSON.stringify({ error: message }));
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
res.writeHead(405);
|
|
415
|
-
res.end();
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
server.headersTimeout = 5_000;
|
|
419
|
-
server.requestTimeout = 10_000;
|
|
420
|
-
server.listen(port, host, () => {
|
|
421
|
-
printHumanReadableLine(`MCP server listening on http://${host}:${port}`);
|
|
422
|
-
});
|
|
423
|
-
return { server, mcpServer };
|
|
424
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { listMcpCommandSchemas } from '../schema/spec.js';
|
|
2
|
-
import { noteContentMcpTools } from './noteContentTools.js';
|
|
3
|
-
function toMcpProperty(property) {
|
|
4
|
-
if (property.type === 'string[]') {
|
|
5
|
-
return {
|
|
6
|
-
type: 'array',
|
|
7
|
-
items: { type: 'string' },
|
|
8
|
-
...(property.description ? { description: property.description } : {}),
|
|
9
|
-
...(property.enum ? { enum: property.enum } : {}),
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
return {
|
|
13
|
-
type: property.type,
|
|
14
|
-
...(property.description ? { description: property.description } : {}),
|
|
15
|
-
...(property.enum ? { enum: property.enum } : {}),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const schemaBackedMcpTools = listMcpCommandSchemas().map((schema) => {
|
|
19
|
-
const mcp = schema.mcp;
|
|
20
|
-
const properties = Object.fromEntries(mcp.properties.map((property) => [property.name, toMcpProperty(property)]));
|
|
21
|
-
const required = mcp.properties.filter((property) => property.required).map((property) => property.name);
|
|
22
|
-
return {
|
|
23
|
-
name: mcp.toolName,
|
|
24
|
-
description: mcp.description,
|
|
25
|
-
inputSchema: {
|
|
26
|
-
type: 'object',
|
|
27
|
-
properties,
|
|
28
|
-
...(required.length > 0 ? { required } : {}),
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
export const mcpTools = [...schemaBackedMcpTools, ...noteContentMcpTools];
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export type MCPTool = {
|
|
2
|
-
name: string;
|
|
3
|
-
description: string;
|
|
4
|
-
inputSchema: {
|
|
5
|
-
type: 'object';
|
|
6
|
-
properties: Record<string, {
|
|
7
|
-
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
8
|
-
description?: string;
|
|
9
|
-
enum?: string[];
|
|
10
|
-
items?: {
|
|
11
|
-
type: 'string' | 'object';
|
|
12
|
-
};
|
|
13
|
-
properties?: Record<string, {
|
|
14
|
-
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
15
|
-
description?: string;
|
|
16
|
-
}>;
|
|
17
|
-
}>;
|
|
18
|
-
required?: string[];
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
export type MCPResource = {
|
|
22
|
-
uri: string;
|
|
23
|
-
name: string;
|
|
24
|
-
description?: string;
|
|
25
|
-
mimeType?: string;
|
|
26
|
-
};
|
|
27
|
-
export type MCPServerInfo = {
|
|
28
|
-
name: string;
|
|
29
|
-
version: string;
|
|
30
|
-
};
|
|
31
|
-
export type MCPInitializeResult = {
|
|
32
|
-
protocolVersion: string;
|
|
33
|
-
capabilities: {
|
|
34
|
-
tools?: Record<string, never>;
|
|
35
|
-
resources?: Record<string, never>;
|
|
36
|
-
};
|
|
37
|
-
serverInfo: MCPServerInfo;
|
|
38
|
-
};
|
|
39
|
-
export declare const DEFAULT_MCP_PORT = 45137;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_MCP_PORT = 45137;
|