@littlebigbrain/mcp 0.14.0 → 0.15.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 +1 -1
- package/dist/files-tool.d.ts +9 -0
- package/dist/files-tool.js +70 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -158,7 +158,7 @@ its original identifiers; inspect it with SPARQL when choosing query predicates.
|
|
|
158
158
|
| `lbb_workflows` | List the starter workflows this server offers and the workflows created from them on a graph (triggered workflows), read one with its status, and preview a new one. Read the workflows with triggers (target type, shards, routing, window, trigger) and what each watch has not sent yet, with origins. |
|
|
159
159
|
| `lbb_workflows_manage` | Create a workflow from a starter workflow on a class (a developer workflow with its workflow type, owned outputs and batch), ask it to run now, or pause and resume a developer workflow. Define a workflow with triggers of your worker (shards, routing field, window) and run it now. |
|
|
160
160
|
| `lbb_workflows_delete` | Delete a triggered workflow, or a workflow with triggers by its name. What it wrote stays. |
|
|
161
|
-
| `lbb_files` | List the files of a graph, and read the parsed pages of one: each line with its id, its box on the page and its text. |
|
|
161
|
+
| `lbb_files` | List the files of a graph, all or by parse status or a part of the name, and read the parsed pages of one: each line with its id, its box on the page and its text. |
|
|
162
162
|
| `lbb_commit` | Write or retract JSON facts, or record search feedback. |
|
|
163
163
|
| `lbb_configure` | Define record types and relationships, list and apply ontology starters (CRM, documents, work), publish validation rules, or keep the graph's notes and worked examples for questions in plain words (`get_rewrite_profile`, `set_rewrite_profile`). |
|
|
164
164
|
| `lbb_evals` | Label query results and check whether later queries return the expected answers. Read the model checks of a graph, and agree with or correct a check that the user confirmed. |
|
package/dist/files-tool.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { LbbClient, Schemas } from "@littlebigbrain/client";
|
|
3
|
+
/** Files one `list` call returns: one page, newest upload first. */
|
|
4
|
+
export declare const FILES_PER_LIST = 50;
|
|
5
|
+
/**
|
|
6
|
+
* Files `pages` reads to find a name: one page of the list filtered by the
|
|
7
|
+
* name, the most one page holds.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FILES_PER_NAME_LOOKUP = 1000;
|
|
10
|
+
/** The parse statuses `list` filters by; `waiting` is a file without one. */
|
|
11
|
+
export declare const FILE_PARSE_STATUSES: readonly ["parsed", "waiting", "unsupported", "failed"];
|
|
3
12
|
/** Lines one `pages` call returns by default, and at most. */
|
|
4
13
|
export declare const FILE_LINES_DEFAULT = 200;
|
|
5
14
|
export declare const FILE_LINES_MAX = 1000;
|
package/dist/files-tool.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { READ_ONLY, detailSchema, graphScope } from "./tool-contracts.js";
|
|
3
3
|
import { enrichError, errorResult, run, scoped, toolResult, } from "./tool-runtime.js";
|
|
4
|
+
/** Files one `list` call returns: one page, newest upload first. */
|
|
5
|
+
export const FILES_PER_LIST = 50;
|
|
6
|
+
/**
|
|
7
|
+
* Files `pages` reads to find a name: one page of the list filtered by the
|
|
8
|
+
* name, the most one page holds.
|
|
9
|
+
*/
|
|
10
|
+
export const FILES_PER_NAME_LOOKUP = 1_000;
|
|
11
|
+
/** The parse statuses `list` filters by; `waiting` is a file without one. */
|
|
12
|
+
export const FILE_PARSE_STATUSES = [
|
|
13
|
+
"parsed",
|
|
14
|
+
"waiting",
|
|
15
|
+
"unsupported",
|
|
16
|
+
"failed",
|
|
17
|
+
];
|
|
4
18
|
/** Lines one `pages` call returns by default, and at most. */
|
|
5
19
|
export const FILE_LINES_DEFAULT = 200;
|
|
6
20
|
export const FILE_LINES_MAX = 1_000;
|
|
@@ -62,7 +76,17 @@ export function pageLines(pages, window) {
|
|
|
62
76
|
return answer;
|
|
63
77
|
}
|
|
64
78
|
const SHA256 = /^[0-9a-fA-F]{64}$/;
|
|
65
|
-
/** The
|
|
79
|
+
/** The files of `files` named `name` exactly (both composed, NFC). */
|
|
80
|
+
function namedExactly(files, name) {
|
|
81
|
+
const wanted = name.normalize("NFC");
|
|
82
|
+
return files.filter((file) => file.name.normalize("NFC") === wanted);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The SHA-256 of the file a call names, by `sha256` or by its exact name.
|
|
86
|
+
* A name is looked up in the list filtered by it, which finds a file of any
|
|
87
|
+
* age; that list reads the published generation, so a file uploaded since
|
|
88
|
+
* is looked up in the newest files of the plain list.
|
|
89
|
+
*/
|
|
66
90
|
async function fileSha(target, args) {
|
|
67
91
|
if (args.sha256) {
|
|
68
92
|
if (!SHA256.test(args.sha256.trim()))
|
|
@@ -71,8 +95,16 @@ async function fileSha(target, args) {
|
|
|
71
95
|
}
|
|
72
96
|
if (!args.name)
|
|
73
97
|
throw new Error("pages needs sha256 or name");
|
|
74
|
-
const
|
|
75
|
-
|
|
98
|
+
const filtered = await target.files.list({
|
|
99
|
+
name: args.name,
|
|
100
|
+
limit: FILES_PER_NAME_LOOKUP,
|
|
101
|
+
});
|
|
102
|
+
let files = filtered.files;
|
|
103
|
+
let named = namedExactly(files, args.name);
|
|
104
|
+
if (named.length === 0) {
|
|
105
|
+
({ files } = await target.files.list({ limit: FILES_PER_LIST }));
|
|
106
|
+
named = namedExactly(files, args.name);
|
|
107
|
+
}
|
|
76
108
|
if (named.length === 1)
|
|
77
109
|
return { sha256: named[0].sha256, name: named[0].name };
|
|
78
110
|
if (named.length > 1)
|
|
@@ -82,9 +114,17 @@ async function fileSha(target, args) {
|
|
|
82
114
|
}
|
|
83
115
|
export function registerFilesTool(server, client) {
|
|
84
116
|
server.registerTool("lbb_files", {
|
|
85
|
-
description: "Files of the graph, read only. A file is a document the graph holds (its bytes and a Document entity); the documents.parse workflow (lbb_workflows) reads the text layer of each PDF into pages with every line and its box. list shows
|
|
117
|
+
description: "Files of the graph, read only. A file is a document the graph holds (its bytes and a Document entity); the documents.parse workflow (lbb_workflows) reads the text layer of each PDF into pages with every line and its box. list shows 50 files a call, newest upload first, with their sha256, name, size, Document IRI and parse (status parsed, unsupported or failed, page_count); when more follow, next names the after of the next call (with the same filters). parse_status keeps the files of one parse status; waiting is a file not parsed yet. name keeps the files whose name holds a text, in any case. A filtered list reads the Documents of the published generation and names its commit (as_of_commit_seq). A file uploaded, renamed or parsed a moment ago can be missing from it until publication. pages reads parsed pages of one file (sha256, or name for an exact file name): from is the first 0-based page index (the page of a region and of a DocEntry or DocMention), to the page after the last one (default from+1). Each line comes as `<line id> [x0,y0,x1,y1] <text>`: the id is p<page number>-l<line number>, both from 1, and the box is normalized to the page (0 to 1, origin top left). contains keeps the lines that hold a text; max_lines caps the lines (default 200). When lines were left out, next names the from and line_offset of the next call. A file not parsed yet answers file_not_parsed.",
|
|
86
118
|
inputSchema: {
|
|
87
119
|
action: z.enum(["list", "pages"]),
|
|
120
|
+
after: z
|
|
121
|
+
.string()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe("list: the next 50 files, from a previous call's next (pass the same parse_status and name)."),
|
|
124
|
+
parse_status: z
|
|
125
|
+
.enum(FILE_PARSE_STATUSES)
|
|
126
|
+
.optional()
|
|
127
|
+
.describe("list: only the files of this parse status; waiting is a file not parsed yet."),
|
|
88
128
|
sha256: z
|
|
89
129
|
.string()
|
|
90
130
|
.optional()
|
|
@@ -92,7 +132,7 @@ export function registerFilesTool(server, client) {
|
|
|
92
132
|
name: z
|
|
93
133
|
.string()
|
|
94
134
|
.optional()
|
|
95
|
-
.describe("pages: the file's exact name, when you do not have its sha256."),
|
|
135
|
+
.describe("list: only the files whose name holds this text, in any case. pages: the file's exact name, when you do not have its sha256."),
|
|
96
136
|
from: z
|
|
97
137
|
.number()
|
|
98
138
|
.int()
|
|
@@ -129,10 +169,32 @@ export function registerFilesTool(server, client) {
|
|
|
129
169
|
}, async (args) => {
|
|
130
170
|
const target = scoped(client, args.graph);
|
|
131
171
|
if (args.action === "list") {
|
|
172
|
+
// The server binds a cursor to its filters: the next call repeats them.
|
|
173
|
+
const filters = {
|
|
174
|
+
...(args.parse_status ? { parse_status: args.parse_status } : {}),
|
|
175
|
+
...(args.name ? { name: args.name } : {}),
|
|
176
|
+
};
|
|
177
|
+
let next;
|
|
132
178
|
return run(client, "lbb_files.list", args.detail, async () => {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
179
|
+
const page = await target.files.list({
|
|
180
|
+
limit: FILES_PER_LIST,
|
|
181
|
+
...(args.after ? { after: args.after } : {}),
|
|
182
|
+
...(args.parse_status ? { parseStatus: args.parse_status } : {}),
|
|
183
|
+
...(args.name ? { name: args.name } : {}),
|
|
184
|
+
});
|
|
185
|
+
next = page.next ?? undefined;
|
|
186
|
+
return {
|
|
187
|
+
files: page.files,
|
|
188
|
+
...(page.as_of_commit_seq != null
|
|
189
|
+
? { as_of_commit_seq: page.as_of_commit_seq }
|
|
190
|
+
: {}),
|
|
191
|
+
};
|
|
192
|
+
}, () => next
|
|
193
|
+
? {
|
|
194
|
+
truncated: true,
|
|
195
|
+
next: { action: "list", ...filters, after: next },
|
|
196
|
+
}
|
|
197
|
+
: {});
|
|
136
198
|
}
|
|
137
199
|
try {
|
|
138
200
|
const file = await fileSha(target, args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@littlebigbrain/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "MCP server for little big brain: search, graph queries, and data import",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"test:coverage": "npm run clean:test && tsc -p tsconfig.test.json && node --test --experimental-test-coverage --test-coverage-include=test-dist/*.js --test-coverage-exclude=test-dist/*.test.js --test-coverage-exclude=test-dist/stdio.js --test-coverage-exclude=test-dist/test-support.js --test-coverage-lines=90 --test-coverage-branches=70 --test-coverage-functions=85 \"test-dist/**/*.test.js\""
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@littlebigbrain/client": "^0.
|
|
55
|
+
"@littlebigbrain/client": "^0.28.0",
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1",
|
|
57
57
|
"zod": "^3"
|
|
58
58
|
},
|