@live-context/mcp 0.4.0 → 0.7.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 CHANGED
@@ -40,7 +40,15 @@ For Claude Desktop, Cursor, or any other MCP client, add this to your `mcpServer
40
40
 
41
41
  ## Tools
42
42
 
43
- `get_context`, `list_spaces`, `list_collections`, `list_notebooks`, `list_context`, `write_context`, `health_check`. See the [Live Context docs](https://live-context.com/docs) for usage.
43
+ **Read** — `get_context`, `list_spaces`, `list_collections`, `list_notebooks`, `list_context`
44
+
45
+ **Write** — `write_context` (knowledge → Spaces or Notebooks). Honors `write_mode='review_required'` Spaces: writes to those queue as proposals for admin approval. Pass `trigger_message` (min 10 chars) explaining why the write should be applied when targeting such a Space.
46
+
47
+ **Propose** *(v0.4.0+)* — `propose_space`, `propose_project`, `list_my_proposals`. Use these when the user asks for a new container. Both `propose_*` tools require a `trigger_message` (min 10 chars). Auto-approves when the caller is a team admin; otherwise queues for review at `live-context.com/dashboard/proposals`.
48
+
49
+ **Health** — `health_check`
50
+
51
+ See the [Live Context docs](https://live-context.com/docs) for full schemas and examples.
44
52
 
45
53
  ## Get a key
46
54
 
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ async function callMcpApi(tool, args, method = 'POST') {
91
91
  // ---------------------------------------------------------------------------
92
92
  const server = new McpServer({
93
93
  name: 'live-context-mcp',
94
- version: '0.3.0', // bumped for Wave 5 hosted-proxy rewrite
94
+ version: '0.7.1', // 0.7.0 added body_tiptap; 0.7.1 adds width_mode (fixed | wide)
95
95
  });
96
96
  server.registerTool('get_context', {
97
97
  description: 'Retrieve relevant context entries. Returns L0 summaries (title, type, snippet) by default. Pass drawer_id to get full page body.',
@@ -204,6 +204,19 @@ Use for time-bound work (launches, fundraises, redesigns). For ongoing knowledge
204
204
  trigger_message: z.string().describe('Why this Project should exist (min 10 chars). Required.'),
205
205
  },
206
206
  }, (args) => callMcpApi('propose-project', args));
207
+ server.registerTool('propose_collection', {
208
+ description: `Propose a new Collection inside an existing Space — a grouping layer for related drawers (e.g. "Tokens" inside "design-system"). When the caller is a team admin of the Space's owner team, the proposal auto-approves and the Collection is created immediately. Otherwise it queues for admin review.
209
+
210
+ trigger_message is REQUIRED — explain WHY this Collection should exist (min 10 chars). Cite the drawers or topic that motivated it.
211
+
212
+ Use when several drawers in a Space cluster around a sub-topic that deserves its own grouping. For new top-level containers, use propose_space instead.`,
213
+ inputSchema: {
214
+ space_id: z.string().describe('Parent Space ID. Find via list_spaces.'),
215
+ name: z.string().describe('Collection name (e.g. "Tokens", "Migrations")'),
216
+ description: z.string().optional().describe('Short description of what goes in this Collection.'),
217
+ trigger_message: z.string().describe('Why this Collection should exist (min 10 chars). Required.'),
218
+ },
219
+ }, (args) => callMcpApi('propose-collection', args));
207
220
  server.registerTool('list_my_proposals', {
208
221
  description: 'List your proposals (Spaces + Projects you proposed) plus any proposals you can see based on team_visible setting. Filter by status.',
209
222
  inputSchema: {
@@ -213,6 +226,91 @@ server.registerTool('list_my_proposals', {
213
226
  .describe('Filter by proposal status.'),
214
227
  },
215
228
  }, (args) => callMcpApi('list-my-proposals', args));
229
+ server.registerTool('write_page', {
230
+ description: `Compose a Page — a human-readable, evidence-backed narrative document — into a Space or Notebook. Pages are how research, decisions, and synthesis get forwarded to other humans; drawers stay atomic for retrieval, Pages are the readable artifact.
231
+
232
+ When to use:
233
+ - The user asks to "write up", "summarise", "synthesise", "hand off", or "share with someone" a body of team knowledge.
234
+ - An empty Page draft exists (created from the dashboard) — fill it by passing page_id.
235
+
236
+ Body format — pick ONE:
237
+
238
+ 1) body_tiptap (preferred — full formatting). A Tiptap document JSON tree. Top shape:
239
+ { "type": "doc", "content": [...nodes] }
240
+
241
+ Block node types you can emit:
242
+ - paragraph: { "type": "paragraph", "content": [...inline] }
243
+ - heading: { "type": "heading", "attrs": { "level": 1|2|3|4 }, "content": [...inline] }
244
+ - bulletList / orderedList: { "type": "bulletList"|"orderedList", "content": [{ "type": "listItem", "content": [...blocks] }] }
245
+ - taskList: { "type": "taskList", "content": [{ "type": "taskItem", "attrs": { "checked": false }, "content": [...blocks] }] }
246
+ - blockquote: { "type": "blockquote", "content": [...blocks] }
247
+ - codeBlock: { "type": "codeBlock", "attrs": { "language": "ts" }, "content": [{ "type": "text", "text": "..." }] }
248
+ - horizontalRule: { "type": "horizontalRule" }
249
+ - table: { "type": "table", "attrs": { "width": "inline"|"wide"|"full" }, "content": [{ "type": "tableRow", "content": [{ "type": "tableHeader"|"tableCell", "content": [...blocks] }] }] }
250
+ - details (collapsible / Confluence Expand): { "type": "details", "content": [{ "type": "detailsSummary", "content": [...inline] }, { "type": "detailsContent", "content": [...blocks] }] }
251
+
252
+ Inline node types:
253
+ - text: { "type": "text", "text": "string", "marks": [...] }
254
+ - hardBreak: { "type": "hardBreak" }
255
+ - drawerCitation (clickable evidence chip — use these to ground claims!): { "type": "drawerCitation", "attrs": { "drawerId": "<uuid>", "drawerTitle": "<title>", "drawerType": "decision|constraint|..." } }
256
+ - mention (person): { "type": "mention", "attrs": { "id": "<user_uuid>", "label": "Display Name" } }
257
+
258
+ Marks (applied to text nodes via the "marks" array):
259
+ - bold, italic, underline, strike, code
260
+ - highlight: { "type": "highlight" }
261
+ - subscript, superscript
262
+ - link: { "type": "link", "attrs": { "href": "https://..." } }
263
+ - textAlign: paragraphs and headings accept "attrs": { "textAlign": "left"|"center"|"right" }
264
+
265
+ Use drawerCitation inline anywhere you would cite. The server auto-extracts cited drawer_ids from your doc and snapshots each drawer's body so the Page stays interpretable even if drawers later change. You do NOT need to also pass "citations" — extraction is automatic.
266
+
267
+ 2) body_markdown (legacy — limited). Markdown string. Supports headings, paragraphs, lists, blockquote, code blocks, bold/italic/strike, links, inline code, horizontal rule. Does NOT support tables, details, mentions, highlight, alignment, underline. Use \`[[drawer:<uuid>|<title>]]\` inline for citations. Prefer body_tiptap.
268
+
269
+ Always retrieve evidence with get_context first; never compose from training data when team knowledge exists.`,
270
+ inputSchema: {
271
+ title: z.string().describe('Page title — short, descriptive, sentence case.'),
272
+ body_tiptap: z
273
+ .object({
274
+ type: z.literal('doc'),
275
+ content: z.array(z.any()).optional(),
276
+ })
277
+ .passthrough()
278
+ .optional()
279
+ .describe('Preferred. Tiptap document JSON — see tool description for full node + mark schema.'),
280
+ body_markdown: z
281
+ .string()
282
+ .optional()
283
+ .describe('Legacy fallback. Markdown subset (no tables, details, mentions). Prefer body_tiptap.'),
284
+ scope: z.enum(['team', 'personal']).optional().describe("'team' (default — writes to a Space) or 'personal' (writes to a Notebook; requires user token)."),
285
+ space: z.string().optional().describe('Target Space name (required for team scope).'),
286
+ notebook: z.string().optional().describe('Target Notebook name (required for personal scope).'),
287
+ page_id: z.string().optional().describe('If filling an existing empty draft, the draft id. Else omit to create new.'),
288
+ citations: z
289
+ .array(z.object({ drawer_id: z.string() }))
290
+ .optional()
291
+ .describe('Optional. If body_tiptap is passed, citations auto-extract from drawerCitation nodes. Pass explicitly only to attribute drawers that don\'t appear inline.'),
292
+ width_mode: z
293
+ .enum(['fixed', 'wide'])
294
+ .optional()
295
+ .describe("Optional. 'fixed' (default, ~768px reading width) or 'wide' (~1024px). Use 'wide' for pages dominated by tables, wide code blocks, or side-by-side comparisons. Matches Confluence's per-page Full-width toggle."),
296
+ },
297
+ }, (args) => callMcpApi('write-page', args));
298
+ server.registerTool('list_pages', {
299
+ description: 'List Pages in the team or in a specific Space/Notebook. Returns title + container + updated_at. Use get_page for full body and citations.',
300
+ inputSchema: {
301
+ scope: z.enum(['team', 'personal', 'all']).optional().describe("Default 'all'. 'team' = Space pages; 'personal' = Notebook pages."),
302
+ space: z.string().optional().describe('Filter by Space name.'),
303
+ notebook: z.string().optional().describe('Filter by Notebook name (requires user token).'),
304
+ limit: z.number().int().positive().max(100).optional().describe('Page size (default 20, max 100).'),
305
+ offset: z.number().int().min(0).optional().describe('Pagination offset (default 0).'),
306
+ },
307
+ }, (args) => callMcpApi('list-pages', args));
308
+ server.registerTool('get_page', {
309
+ description: 'Fetch a Page by id. Returns title, body_json (Tiptap doc), container, and citations enriched with current drawer title/type. Use this before re-composing so you can show what already exists.',
310
+ inputSchema: {
311
+ page_id: z.string().describe('Page id (from list_pages or write_page result).'),
312
+ },
313
+ }, (args) => callMcpApi('get-page', args));
216
314
  server.registerTool('health_check', {
217
315
  description: 'Returns server health status and resolved team identity',
218
316
  }, () => callMcpApi('health-check', undefined, 'GET'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-context/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.7.1",
4
4
  "private": false,
5
5
  "description": "Live Context MCP — stdio proxy to live-context.com hosted API. Customer-side has no Supabase dependency.",
6
6
  "type": "module",