@promptev/context-engine 0.0.3 → 0.0.4

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/dist/mcp.d.cts CHANGED
@@ -1,2 +1,2 @@
1
1
  import 'node:http';
2
- export { i as createMcpApp } from './mcp-BKSmxayM.cjs';
2
+ export { i as createMcpApp } from './mcp-uirRbluA.cjs';
package/dist/mcp.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import 'node:http';
2
- export { i as createMcpApp } from './mcp-BKSmxayM.js';
2
+ export { i as createMcpApp } from './mcp-uirRbluA.js';
package/dist/mcp.js CHANGED
@@ -407,7 +407,7 @@ var KNOWLEDGE_ACTIONS = [
407
407
  "get_neighbors",
408
408
  "community_summary"
409
409
  ];
410
- var KNOWLEDGE_TOOL_DESCRIPTION = "The knowledge base \u2014 the ingested documents and data files (PDF, Word, Excel, CSV and the rest) \u2014 as ONE tool with actions. Nothing is searched for you: use it before answering anything that should come from those documents, and do not use it for general knowledge. Call action 'discover' FIRST when you do not already know what is there: it lists the documents, says which are spreadsheets, and says which actions this deployment can run. Then match the action to the task. 'search' finds passages by meaning or keywords \u2014 for questions answered by reading text. Looking up an identifier (an ID, code, SKU or invoice number) is the exception: search the BARE identifier alone, e.g. '2525', never the whole question. Those are CONTENT identifiers \u2014 written inside a document \u2014 and they belong in a query; a document_id is a system id (a uuid) that only discover, list or a search hit can give you, so never search a uuid as text and never hand an invoice number to get_doc. 'get_doc' reads one whole document by id, 'get_docs' reads several at once, and 'get_chunks' walks one long document in order a piece at a time when you need more of it than an excerpt; 'list' browses the documents without searching. 'map_reduce' asks the SAME question of every document in scope and answers once per document \u2014 for 'which contracts mention X', where search would return a handful of passages and miss the rest. 'query_meta' filters and aggregates documents by their structured fields (dates, amounts, categories) \u2014 usually the right action for a question about spreadsheet data. 'compute' runs code over the spreadsheets for any figure DERIVED from them \u2014 a total, average, count, ranking, margin or comparison across rows \u2014 AND for finding the exact row matching one id or value. In a large table, search cannot reliably locate an individual row; compute can. When the question is about how things are CONNECTED rather than what a document says \u2014 who works with whom, what belongs to what, what a change touches \u2014 use the graph actions: 'get_neighbors' for what is one step from one thing, 'traverse' for everything within a few steps of it, 'find_related' for connections of a kind across the corpus, and 'community_summary' for the themes the corpus groups into. They are available only where a graph was built; discover says so. Searching with mode='graph' ranks passages by those same connections instead of by wording alone, which finds a passage that never repeats your words. Rules that decide answers: search returns EXCERPTS, and rows of a spreadsheet are not arithmetic \u2014 never add up, average or rank rows yourself from what search returned, and never say a figure is not available before computing over the sheet that holds it. A listing that says has_more has MORE: send its next_cursor back as 'cursor' for the next page, and never conclude a document is absent from a first page that was truncated. Search again with different words before saying a document is missing. A result may carry a next_action (or next_page): it is the call to make next, already filled in \u2014 follow it rather than guessing the next step. Cite document names.";
410
+ var KNOWLEDGE_TOOL_DESCRIPTION = "The knowledge base \u2014 the ingested documents and data files (PDF, Word, Excel, CSV and the rest) \u2014 as ONE tool with actions. Nothing is searched for you: use it before answering anything that should come from those documents, and do not use it for general knowledge. Call action 'discover' FIRST when you do not already know what is there: it lists the documents and says what is INSIDE each one \u2014 a spreadsheet's sheet names, column headers and row counts; a document's section titles and last page; a JSON file's top-level keys \u2014 plus the extracted field names grouped by document type with each field's data type and how many documents carry it, a census of the whole corpus, and which actions this deployment can run. Use those names verbatim: they are what compute, query_meta and get_chunks match on, so one discover is enough and you never have to go looking for a column, a section or a field name. A long list is shortened there, with the rest reported as a count and, when it is worth the trip, the exact call that returns it in full \u2014 discover again with document_ids set to that one document, which answers whole. When it is not worth the trip the payload says so and says what to do instead: a sheet with hundreds of columns is one to compute over, never one to read back. Then match the action to the task. 'search' finds passages by meaning or keywords \u2014 for questions answered by reading text. Looking up an identifier (an ID, code, SKU or invoice number) is the exception: search the BARE identifier alone, e.g. '2525', never the whole question. Those are CONTENT identifiers \u2014 written inside a document \u2014 and they belong in a query; a document_id is a system id (a uuid) that only discover, list or a search hit can give you, so never search a uuid as text and never hand an invoice number to get_doc. 'get_doc' reads one whole document by id, 'get_docs' reads several at once, and 'get_chunks' walks one long document in order a piece at a time when you need more of it than an excerpt; 'list' browses the documents without searching. 'map_reduce' asks the SAME question of every document in scope and answers once per document \u2014 for 'which contracts mention X', where search would return a handful of passages and miss the rest. 'query_meta' filters and aggregates documents by their structured fields (dates, amounts, categories) \u2014 usually the right action for a question about spreadsheet data. 'compute' runs code over the spreadsheets for any figure DERIVED from them \u2014 a total, average, count, ranking, margin or comparison across rows \u2014 AND for finding the exact row matching one id or value. In a large table, search cannot reliably locate an individual row; compute can. When the question is about how things are CONNECTED rather than what a document says \u2014 who works with whom, what belongs to what, what a change touches \u2014 use the graph actions: 'get_neighbors' for what is one step from one thing, 'traverse' for everything within a few steps of it, 'find_related' for connections of a kind across the corpus, and 'community_summary' for the themes the corpus groups into. They are available only where a graph was built; discover says so. Searching with mode='graph' ranks passages by those same connections instead of by wording alone, which finds a passage that never repeats your words. Rules that decide answers: search returns EXCERPTS, and rows of a spreadsheet are not arithmetic \u2014 never add up, average or rank rows yourself from what search returned, and never say a figure is not available before computing over the sheet that holds it. A listing that says has_more has MORE: send its next_cursor back as 'cursor' for the next page, and never conclude a document is absent from a first page that was truncated. Search again with different words before saying a document is missing. A result may carry a next_action (or next_page): it is the call to make next, already filled in \u2014 follow it rather than guessing the next step. Cite document names.";
411
411
  var ACTION_PARAM_DESCRIPTION = "Match it to the task: reading questions -> search (a bare identifier for an ID or code); spreadsheet analysis, or the exact row for one id or value -> query_meta or compute; how things connect -> get_neighbors, traverse, find_related or community_summary; browse everything -> list; unsure what exists -> discover first.";
412
412
  var ACTION_HELP = {
413
413
  search: "passages by meaning or keywords; an ID, code or number as the BARE identifier",
@@ -430,7 +430,7 @@ var OFF = {
430
430
  graph: "the graph actions are off: this deployment has no graph configured, so nothing has been linked up. Use search, get_doc or list instead."
431
431
  };
432
432
  var GRAPH_ACTIONS = ["traverse", "find_related", "get_neighbors", "community_summary"];
433
- var DOCUMENT_ID_ACTIONS = ["search", "compute", "get_docs", "map_reduce"];
433
+ var DOCUMENT_ID_ACTIONS = ["search", "compute", "get_docs", "map_reduce", "discover", "list"];
434
434
  var DEFAULT_GET_DOCS_CHARS = 2e5;
435
435
  var INPUT_PROPERTIES = {
436
436
  action: { type: "string", enum: [...KNOWLEDGE_ACTIONS], description: ACTION_PARAM_DESCRIPTION },
@@ -450,7 +450,7 @@ var INPUT_PROPERTIES = {
450
450
  document_ids: {
451
451
  type: "array",
452
452
  items: { type: "string" },
453
- description: "Narrows to these documents, using ids from discover, list or a search hit. It INTERSECTS with source_ids, so a document outside the sources you named returns nothing. (for search, compute)"
453
+ description: "Narrows to these documents, using ids from discover, list or a search hit. It INTERSECTS with source_ids, so a document outside the sources you named returns nothing. On discover it also asks for those documents' structure IN FULL, past the shortening a whole-page discover applies. (for search, compute, get_docs, map_reduce, discover, list)"
454
454
  },
455
455
  entity: {
456
456
  type: "string",
@@ -566,11 +566,16 @@ function parseCursor(cursor) {
566
566
  }
567
567
  return parsed;
568
568
  }
569
- async function listPage(engine, sourceIds, principals, action, limit, cursor, ceiling, redaction) {
569
+ async function listPage(engine, sourceIds, principals, action, limit, cursor, documentIds, ceiling, redaction) {
570
570
  const page = await engine.listDocuments({
571
571
  // `!= null`, NOT truthiness: an EMPTY array means "nothing is in scope"
572
572
  // and collapsing it to null would list the whole corpus.
573
573
  sourceIds: sourceIds != null ? [...new Set(sourceIds)] : null,
574
+ // Already intersected with the host's ceiling by `narrowToCeiling`, and
575
+ // filtered in SQL rather than after the page is built — three named
576
+ // documents sitting on page four must come back as themselves, not as an
577
+ // empty page.
578
+ documentIds: documentIds != null ? [...new Set(documentIds)] : null,
574
579
  principals,
575
580
  cursor,
576
581
  limit: Math.max(1, Math.min(limit, MAX_LIST_LIMIT)),
@@ -580,7 +585,9 @@ async function listPage(engine, sourceIds, principals, action, limit, cursor, ce
580
585
  id: String(raw.id),
581
586
  name: raw.name,
582
587
  source_id: raw.sourceId ?? raw.source_id,
583
- kind: documentKind(raw)
588
+ kind: documentKind(raw),
589
+ document_type: raw.documentType ?? raw.document_type ?? null,
590
+ mode: raw.mode ?? null
584
591
  }));
585
592
  if (ceiling.documentIds != null) {
586
593
  const allowed = new Set(ceiling.documentIds);
@@ -633,30 +640,62 @@ async function callKnowledgeTool(engine, args) {
633
640
  action,
634
641
  args.limit ?? 50,
635
642
  parseCursor(args.cursor),
643
+ documentIds,
636
644
  ceiling,
637
645
  args.redaction
638
646
  );
639
647
  if (action === "list") return { success: true, ...page };
640
- const spreadsheets = page.documents.filter((d) => d.kind === "spreadsheet").map((d) => d.name);
648
+ const structures = await engine.documentStructure({
649
+ documentIds: page.documents.map((d) => d.id),
650
+ sourceIds,
651
+ principals,
652
+ // The cap is for the call that did NOT name its documents. A caller
653
+ // that asked about specific documents asked for all of them, and the
654
+ // truncated payload tells it to make exactly this call — so answering
655
+ // it truncated again would be a loop.
656
+ bounded: !requestedDocumentIds?.length,
657
+ redaction: args.redaction
658
+ });
659
+ for (const doc of page.documents) doc.structure = structures[doc.id] ?? {};
660
+ const hasSpreadsheet = page.documents.some((d) => d.kind === "spreadsheet");
661
+ const fieldsByType = await engine.fieldSummary({
662
+ sourceIds,
663
+ documentIds,
664
+ principals,
665
+ redaction: args.redaction
666
+ });
667
+ const census = await engine.documentTypes({
668
+ sourceIds,
669
+ documentIds,
670
+ principals,
671
+ redaction: args.redaction
672
+ });
641
673
  const forAFact = { action: "search", query: "<bare identifier or key words>" };
642
674
  const nextAction = {};
643
- if (spreadsheets.length && available.compute) {
675
+ if (hasSpreadsheet && available.compute) {
644
676
  nextAction["for a figure from a spreadsheet"] = {
645
677
  action: "compute",
646
- query: "<what to compute, columns as named>"
678
+ query: "<what to compute, columns as named above>"
647
679
  };
648
680
  }
649
681
  nextAction["for a clause or a fact"] = forAFact;
682
+ if (fieldsByType.length && available.query_meta) {
683
+ nextAction["for documents by a field value"] = {
684
+ action: "query_meta",
685
+ query: "<a question naming a field from fields_by_type>"
686
+ };
687
+ }
650
688
  if (available.get_neighbors) {
651
689
  nextAction["for how things connect"] = {
652
690
  action: "get_neighbors",
653
691
  entity: "<a name that appears in the documents>"
654
692
  };
655
693
  }
656
- return {
694
+ const discovered = {
657
695
  success: true,
658
696
  ...page,
659
- spreadsheets,
697
+ document_types: census,
698
+ fields_by_type: fieldsByType,
660
699
  available_actions: Object.fromEntries(
661
700
  Object.keys(ACTION_HELP).map((name) => [
662
701
  name,
@@ -665,6 +704,7 @@ async function callKnowledgeTool(engine, args) {
665
704
  ),
666
705
  next_action: nextAction
667
706
  };
707
+ return discovered;
668
708
  }
669
709
  if (action === "search") {
670
710
  if (!text) return { success: false, error: "search needs a query" };