feat: port MCP retrieval and write hardening

- align os MCP/runtime/docs with ra-h contract
- add safe direct node lookup and context-optional flows
- gate edge and context writes behind confirmation
This commit is contained in:
“BeeRad”
2026-04-14 20:57:07 +10:00
parent d825e7a783
commit 929423cb21
35 changed files with 1172 additions and 534 deletions
+11 -4
View File
@@ -100,17 +100,24 @@ export async function PUT(
updates.metadata = mergeNodeMetadata(existingNode.metadata, body.metadata);
}
if (Object.prototype.hasOwnProperty.call(body, 'context_name')) {
const hasContextName = typeof body.context_name === 'string' && body.context_name.trim().length > 0;
const wantsClearContext = body.clear_context === true;
delete updates.context_name;
delete updates.clear_context;
if (hasContextName && wantsClearContext) {
return NextResponse.json({
success: false,
error: 'context_name is only supported on node creation. Use context_id for updates.'
error: 'context_name cannot be combined with clear_context: true.'
}, { status: 400 });
}
if (Object.prototype.hasOwnProperty.call(body, 'context_id')) {
if (hasContextName || Object.prototype.hasOwnProperty.call(body, 'context_id') || wantsClearContext) {
try {
const resolvedContextId = await contextService.resolveContextId({
context_id: body.context_id,
context_id: wantsClearContext ? null : body.context_id,
context_name: hasContextName ? body.context_name : undefined,
});
updates.context_id = resolvedContextId;
} catch (error) {
+43
View File
@@ -0,0 +1,43 @@
import { NextRequest, NextResponse } from 'next/server';
import { directNodeLookup } from '@/services/retrieval/directNodeLookup';
export const runtime = 'nodejs';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const query = typeof body.query === 'string' ? body.query : '';
if (!query.trim()) {
return NextResponse.json({
success: false,
error: 'Missing required field: query',
}, { status: 400 });
}
const result = await directNodeLookup({
search: query,
limit: typeof body.limit === 'number' ? body.limit : undefined,
context_name: typeof body.context_name === 'string' ? body.context_name : undefined,
contextId: typeof body.contextId === 'number' ? body.contextId : undefined,
createdAfter: typeof body.createdAfter === 'string' ? body.createdAfter : undefined,
createdBefore: typeof body.createdBefore === 'string' ? body.createdBefore : undefined,
eventAfter: typeof body.eventAfter === 'string' ? body.eventAfter : undefined,
eventBefore: typeof body.eventBefore === 'string' ? body.eventBefore : undefined,
});
return NextResponse.json({
success: true,
data: {
count: result.count,
nodes: result.nodes,
filters_applied: result.filtersApplied,
},
});
} catch (error) {
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Failed to run direct node lookup',
}, { status: 500 });
}
}
-6
View File
@@ -3,7 +3,6 @@ import { contextService, nodeService } from '@/services/database';
import { Node, NodeFilters } from '@/types/database';
import { autoEmbedQueue } from '@/services/embedding/autoEmbedQueue';
import { generateDescription } from '@/services/database/descriptionService';
import { scheduleAutoEdgeCreation } from '@/services/agents/autoEdge';
import { coerceDescriptionForStorage } from '@/services/database/quality';
import { normalizeNodeLink } from '@/utils/nodeLink';
import { buildCanonicalNodeMetadata } from '@/services/nodes/metadata';
@@ -181,11 +180,6 @@ export async function POST(request: NextRequest) {
autoEmbedQueue.enqueue(node.id, { reason: 'node_created' });
}
// Schedule auto-edge creation (fire-and-forget, non-blocking)
if (node.id) {
scheduleAutoEdgeCreation(node.id);
}
return NextResponse.json({
success: true,
data: node,