@theronap/cortex-mcp 0.9.131 → 0.9.132
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/lib/server.mjs +68 -0
- package/package.json +1 -1
package/lib/server.mjs
CHANGED
|
@@ -2167,6 +2167,74 @@ function renderNudge(payload) {
|
|
|
2167
2167
|
},
|
|
2168
2168
|
)
|
|
2169
2169
|
|
|
2170
|
+
server.registerTool(
|
|
2171
|
+
'propose_containers',
|
|
2172
|
+
{
|
|
2173
|
+
title: 'Which event could this record belong to?',
|
|
2174
|
+
description: "Given a record, list the events (class sessions, meetings) it could have COME FROM — candidates only, nothing is written. Candidates are generated by time overlap and filtered by whether the container's type admits that kind of record; a CLOSED container still qualifies, because closing seals a candidate set but does not stop admitting (a transcript synced days later still belongs to the meeting it came from). ⚠ OVERLAP IS NOT AN ANSWER: a coding session that overlapped a meeting may be evidence you SKIPPED it. Ranked by how TIGHT the container is, ascending — a point in a 50-minute class is specific evidence, the same point in an all-day event is nearly none. Choose, then call contain_record.",
|
|
2175
|
+
inputSchema: {
|
|
2176
|
+
record_id: z.string().describe('the record to place'),
|
|
2177
|
+
limit: z.number().optional().describe('max candidates (default 10)'),
|
|
2178
|
+
},
|
|
2179
|
+
},
|
|
2180
|
+
async ({ record_id, limit }) => {
|
|
2181
|
+
const qs = new URLSearchParams(limit ? { limit: String(limit) } : {})
|
|
2182
|
+
let res
|
|
2183
|
+
try {
|
|
2184
|
+
res = await fetchCortex(`${BASE}/api/records/${encodeURIComponent(record_id)}/containers?${qs}`,
|
|
2185
|
+
{ headers: { Authorization: `Bearer ${TOKEN}` } })
|
|
2186
|
+
} catch (e) {
|
|
2187
|
+
return toolError(`Could not reach Agnoclast to find containers for ${record_id}: ${e.message}`)
|
|
2188
|
+
}
|
|
2189
|
+
const body = await res.json().catch(() => ({}))
|
|
2190
|
+
if (!res.ok || !body?.ok) return toolError(`Could not propose containers: ${body?.error ?? res.status}`)
|
|
2191
|
+
if (!body.proposals?.length) {
|
|
2192
|
+
return { content: [{ type: 'text', text: `No container candidates for ${record_id}. Either nothing overlaps its time, or no overlapping event's type admits this kind of record.` }] }
|
|
2193
|
+
}
|
|
2194
|
+
const lines = [`Container candidates for ${record_id} (tightest first):`]
|
|
2195
|
+
for (const p of body.proposals) {
|
|
2196
|
+
lines.push(`- ${p.title ?? '(untitled)'} · ${p.containerType} · ${p.containerMinutes}min · starts ${p.startsAt}`
|
|
2197
|
+
+ `\n id: ${p.containerRecordId}${p.late ? '\n LATE — this arrived after the container closed; still a valid home' : ''}`)
|
|
2198
|
+
}
|
|
2199
|
+
lines.push('', 'Nothing has been written. Use contain_record with the id you choose.')
|
|
2200
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] }
|
|
2201
|
+
},
|
|
2202
|
+
)
|
|
2203
|
+
|
|
2204
|
+
server.registerTool(
|
|
2205
|
+
'contain_record',
|
|
2206
|
+
{
|
|
2207
|
+
title: 'Place a record inside an event',
|
|
2208
|
+
description: "Put a record INSIDE an event — the transcript that came out of a meeting, the session that filled a work block. This is a DECISION, not an inference, and it is recorded as one (designated_by: human). ⚠ It only accepts a container that propose_containers actually offered for this record: naming an arbitrary id would skip the checks that keep containment inside one brain and inside the container type's admission set. `kind` defaults to 'produced' (it came OUT of that event); use 'context' for something that was merely going on at the time. Placing a record in an event is different from attaching it to a PAGE: pages are what a record is ABOUT, containers are where it CAME FROM.",
|
|
2209
|
+
inputSchema: {
|
|
2210
|
+
record_id: z.string().describe('the record to place'),
|
|
2211
|
+
container_record_id: z.string().describe('the event to place it in — an id from propose_containers or find_sessions'),
|
|
2212
|
+
kind: z.enum(['produced', 'context']).optional().describe("'produced' (default) = it came out of that event; 'context' = it was merely happening at the same time"),
|
|
2213
|
+
},
|
|
2214
|
+
},
|
|
2215
|
+
async ({ record_id, container_record_id, kind }) => {
|
|
2216
|
+
let res
|
|
2217
|
+
try {
|
|
2218
|
+
res = await fetchCortex(`${BASE}/api/records/${encodeURIComponent(record_id)}/containers`, {
|
|
2219
|
+
method: 'POST',
|
|
2220
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
2221
|
+
body: JSON.stringify({ containerRecordId: container_record_id, kind }),
|
|
2222
|
+
})
|
|
2223
|
+
} catch (e) {
|
|
2224
|
+
return toolError(`Could not reach Agnoclast to place ${record_id}: ${e.message}`)
|
|
2225
|
+
}
|
|
2226
|
+
const body = await res.json().catch(() => ({}))
|
|
2227
|
+
if (!res.ok || !body?.ok) {
|
|
2228
|
+
return toolError(`Could not place ${record_id}: ${body?.error ?? res.status}${body?.detail ? ` — ${body.detail}` : ''}`)
|
|
2229
|
+
}
|
|
2230
|
+
// `contained: false` means it was ALREADY in that container — a no-op, not a failure. Saying
|
|
2231
|
+
// "placed" either way would hide that a second call did nothing.
|
|
2232
|
+
return { content: [{ type: 'text', text: body.contained
|
|
2233
|
+
? `Placed ${record_id} inside ${container_record_id} as '${kind ?? 'produced'}'.`
|
|
2234
|
+
: `${record_id} was already inside ${container_record_id} — nothing changed.` }] }
|
|
2235
|
+
},
|
|
2236
|
+
)
|
|
2237
|
+
|
|
2170
2238
|
server.registerTool(
|
|
2171
2239
|
'find_sessions',
|
|
2172
2240
|
{
|
package/package.json
CHANGED