@hydradb/mcp 1.2.2 → 1.4.0
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/CHANGELOG.md +50 -0
- package/README.md +48 -2
- package/dist/adapters.d.ts +3 -3
- package/dist/adapters.js.map +1 -1
- package/dist/descriptions.d.ts +24 -0
- package/dist/descriptions.js +63 -3
- package/dist/descriptions.js.map +1 -1
- package/dist/http-config.d.ts +31 -1
- package/dist/http-config.js +29 -29
- package/dist/http-config.js.map +1 -1
- package/dist/http.js +82 -5
- package/dist/http.js.map +1 -1
- package/dist/hydra/client.d.ts +224 -5
- package/dist/hydra/client.js +166 -9
- package/dist/hydra/client.js.map +1 -1
- package/dist/hydra/graph.d.ts +1 -5
- package/dist/hydra/graph.js +5 -92
- package/dist/hydra/graph.js.map +1 -1
- package/dist/hydra/index.d.ts +4 -1
- package/dist/hydra/index.js +2 -0
- package/dist/hydra/index.js.map +1 -1
- package/dist/hydra/transport.d.ts +37 -0
- package/dist/hydra/transport.js +114 -0
- package/dist/hydra/transport.js.map +1 -0
- package/dist/oauth.d.ts +109 -0
- package/dist/oauth.js +228 -0
- package/dist/oauth.js.map +1 -0
- package/dist/server.d.ts +9 -1
- package/dist/server.js +265 -7
- package/dist/server.js.map +1 -1
- package/dist/tool-names.d.ts +10 -1
- package/dist/tool-names.js +17 -0
- package/dist/tool-names.js.map +1 -1
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -8,7 +8,7 @@ import { resolveConfig, resolveGraphConfig } from "./config.js";
|
|
|
8
8
|
import { renderRecalledContext } from "./context.js";
|
|
9
9
|
import { COLLECTION_PATTERN, MAX_BODY_BYTES, renderRows } from "./cypher.js";
|
|
10
10
|
import { SERVER_INSTRUCTIONS, TOOL_DESCRIPTIONS } from "./descriptions.js";
|
|
11
|
-
import { HydraDB } from "./hydra/index.js";
|
|
11
|
+
import { assertCollectionAllowed, assertDatabaseAllowed, HydraDB } from "./hydra/index.js";
|
|
12
12
|
import { logger } from "./logger.js";
|
|
13
13
|
import { ALIAS_REPLACEMENTS, DEPRECATED_TOOL_NAMES, TOOL_NAMES } from "./tool-names.js";
|
|
14
14
|
// Host-owned default: silently attached to ingest so Hydra DB extracts the kind
|
|
@@ -245,7 +245,7 @@ export function createHydraDBServer(hydraOverride,
|
|
|
245
245
|
* Graph scope/gating override, for tests and embedders. Without it the graph
|
|
246
246
|
* config is read from the environment exactly as the rest of the config is.
|
|
247
247
|
*/
|
|
248
|
-
graphOverride) {
|
|
248
|
+
graphOverride, options = {}) {
|
|
249
249
|
const server = new McpServer({
|
|
250
250
|
name: "hydradb-mcp",
|
|
251
251
|
version: SERVER_VERSION,
|
|
@@ -290,17 +290,25 @@ graphOverride) {
|
|
|
290
290
|
operator: args.operator,
|
|
291
291
|
ids: args.source_ids,
|
|
292
292
|
metadataFilters: args.metadata_filters,
|
|
293
|
+
acl: args.acl,
|
|
293
294
|
numRelatedChunks: args.num_related_chunks,
|
|
294
295
|
graphContext: args.graph_context ?? true,
|
|
296
|
+
queryApps: args.query_apps,
|
|
295
297
|
database: args.database,
|
|
296
298
|
collection: args.collection,
|
|
299
|
+
collections: args.collections,
|
|
297
300
|
// Host-owned default (CONTRACT §2 rule 5), but only where it means
|
|
298
301
|
// something: alpha balances dense against sparse retrieval in HYBRID
|
|
299
302
|
// mode, and an `operator` switches the query to text retrieval (see
|
|
300
303
|
// the wrapper), where there are no two lanes to weigh. Injecting it
|
|
301
304
|
// there would send a hybrid-only knob on a request that is not hybrid.
|
|
302
305
|
alpha: args.operator != null ? undefined : 0.8,
|
|
303
|
-
|
|
306
|
+
// Host-owned default (CONTRACT §2 rule 5), and 0 is also what the API
|
|
307
|
+
// applies when the field is omitted — so the default ranking is
|
|
308
|
+
// unchanged. It stops being a constant here because "what is the
|
|
309
|
+
// current state of X" is a different question from "what matches X",
|
|
310
|
+
// and only a caller that can raise this can ask the first one.
|
|
311
|
+
recencyBias: args.recency_bias ?? 0,
|
|
304
312
|
}, { signal });
|
|
305
313
|
// The renderer reads the SDK payload directly; there is no longer a
|
|
306
314
|
// snake_case mirror to convert into.
|
|
@@ -522,6 +530,7 @@ graphOverride) {
|
|
|
522
530
|
ids: args.source_ids,
|
|
523
531
|
page: args.page,
|
|
524
532
|
pageSize: args.page_size,
|
|
533
|
+
acl: args.acl,
|
|
525
534
|
database: args.database,
|
|
526
535
|
collection: args.collection,
|
|
527
536
|
}, { signal });
|
|
@@ -572,6 +581,7 @@ graphOverride) {
|
|
|
572
581
|
ids: args.source_ids,
|
|
573
582
|
page: args.page,
|
|
574
583
|
pageSize: args.page_size,
|
|
584
|
+
acl: args.acl,
|
|
575
585
|
database: args.database,
|
|
576
586
|
collection: args.collection,
|
|
577
587
|
}, { signal });
|
|
@@ -713,16 +723,120 @@ graphOverride) {
|
|
|
713
723
|
offset: a.offset,
|
|
714
724
|
limit: a.limit,
|
|
715
725
|
expiry_seconds: a.expiry_seconds,
|
|
726
|
+
acl: a.acl,
|
|
716
727
|
database: a.database,
|
|
717
728
|
collection: a.collection,
|
|
718
729
|
};
|
|
719
730
|
}
|
|
731
|
+
async function runSubgraph(args, signal) {
|
|
732
|
+
// Blank is rejected; anything else is forwarded byte for byte. The
|
|
733
|
+
// server treats an item id as opaque (ingest stores a caller's
|
|
734
|
+
// source_id verbatim), so trimming here could ask about a different
|
|
735
|
+
// item. Same rule as the CLI's `hydradb subgraph`.
|
|
736
|
+
const id = args.id ?? "";
|
|
737
|
+
if (id.trim() === "") {
|
|
738
|
+
throw new Error(`${TOOL_NAMES.SUBGRAPH} requires \`id\` — the value shown as [id: …] in ` +
|
|
739
|
+
`${TOOL_NAMES.QUERY} results or in [brackets] in ${TOOL_NAMES.LIST} output.`);
|
|
740
|
+
}
|
|
741
|
+
logger.debug(`${TOOL_NAMES.SUBGRAPH}: ${id}`);
|
|
742
|
+
const res = await hydra.context.subgraph({
|
|
743
|
+
id,
|
|
744
|
+
kind: args.kind,
|
|
745
|
+
depth: args.depth,
|
|
746
|
+
maxSources: args.max_sources,
|
|
747
|
+
acl: args.acl,
|
|
748
|
+
database: args.database,
|
|
749
|
+
collection: args.collection,
|
|
750
|
+
}, { signal });
|
|
751
|
+
// Soft failure, like inspect: the server's own message, flagged.
|
|
752
|
+
if (!res.success) {
|
|
753
|
+
return errorResult(`Could not read the subgraph of ${id}: ${res.message || "unknown error"}`);
|
|
754
|
+
}
|
|
755
|
+
const members = res.sources ?? [];
|
|
756
|
+
if (members.length === 0) {
|
|
757
|
+
return structuredResult(`No item with id ${id} was found in this collection, so there is no subgraph to show. ` +
|
|
758
|
+
`Ids come from ${TOOL_NAMES.QUERY} or ${TOOL_NAMES.LIST}; check the collection as well as the id.`,
|
|
759
|
+
// Same keys as a populated result: a client reading
|
|
760
|
+
// structuredContent should not have to branch on which shape it got.
|
|
761
|
+
{
|
|
762
|
+
seed_id: id,
|
|
763
|
+
member_count: 0,
|
|
764
|
+
max_depth_reached: 0,
|
|
765
|
+
truncated: false,
|
|
766
|
+
relations: [],
|
|
767
|
+
structural_link_count: 0,
|
|
768
|
+
structural_truncated: false,
|
|
769
|
+
members: [],
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
const hops = res.max_depth_reached ?? 0;
|
|
773
|
+
// Sorted once, then used for both the prose and structuredContent: a
|
|
774
|
+
// machine client that renders the members must not get a different
|
|
775
|
+
// order from the one a reader sees.
|
|
776
|
+
const ordered = [...members].sort((a, b) => a.depth - b.depth);
|
|
777
|
+
const lines = [
|
|
778
|
+
members.length === 1 && !res.is_truncated
|
|
779
|
+
? `${id} stands alone: nothing in the graph links to it yet.`
|
|
780
|
+
: `${members.length} item${members.length === 1 ? "" : "s"} connected to ${id} through ${hops} hop${hops === 1 ? "" : "s"}` +
|
|
781
|
+
(res.is_truncated ? ` (clipped at max_sources; the subgraph continues)` : "") +
|
|
782
|
+
":",
|
|
783
|
+
"",
|
|
784
|
+
];
|
|
785
|
+
// discovered_relation is the MECHANISM (same_thread, parent, child, or a
|
|
786
|
+
// relates_to type); discovered_via is the member this one was reached
|
|
787
|
+
// FROM — another member's id, so the list is also a tree. The parent id
|
|
788
|
+
// is shortened in the prose because it appears in full on its own line
|
|
789
|
+
// and in structuredContent; the relation is what a reader scans for.
|
|
790
|
+
const shortId = (id) => (id.length > 14 ? `${id.slice(0, 12)}…` : id);
|
|
791
|
+
for (const m of ordered) {
|
|
792
|
+
const title = m.title?.trim() || m.app_external_id || "(untitled)";
|
|
793
|
+
const reached = m.depth === 0
|
|
794
|
+
? "the item you started from"
|
|
795
|
+
: `${m.discovered_relation || "linked"}${m.discovered_via ? ` from ${shortId(m.discovered_via)}` : ""}`;
|
|
796
|
+
const kind = [m.app_provider, m.app_kind].filter(Boolean).join(" ");
|
|
797
|
+
lines.push(`- [id: ${m.source_id}] ${title}${kind ? ` (${kind})` : ""} — depth ${m.depth}, ${reached}`);
|
|
798
|
+
}
|
|
799
|
+
lines.push("", `${(res.relations ?? []).length} relation(s) among them; ` +
|
|
800
|
+
`${(res.auxiliary_relations ?? []).length} structural link(s) around them` +
|
|
801
|
+
(res.auxiliary_truncated ? " (structural links clipped)" : "") +
|
|
802
|
+
`. Pass any [id: …] to ${TOOL_NAMES.INSPECT} for its full content.`);
|
|
803
|
+
// The edges make it a graph rather than a list, so structuredContent
|
|
804
|
+
// carries them too, compacted to what a client can act on: both
|
|
805
|
+
// endpoints are Source nodes, so entity_id is the member's id. The
|
|
806
|
+
// structural links are about entities and comments, not members, so
|
|
807
|
+
// they stay a count plus their own clipped flag, exactly as the prose
|
|
808
|
+
// reports them.
|
|
809
|
+
const edges = (res.relations ?? []).map((r) => ({
|
|
810
|
+
from: r.source?.entity_id ?? null,
|
|
811
|
+
to: r.target?.entity_id ?? null,
|
|
812
|
+
type: r.relations?.[0]?.canonical_predicate ?? null,
|
|
813
|
+
}));
|
|
814
|
+
return structuredResult(lines.join("\n"), {
|
|
815
|
+
seed_id: res.seed_source_id || id,
|
|
816
|
+
member_count: members.length,
|
|
817
|
+
max_depth_reached: hops,
|
|
818
|
+
truncated: Boolean(res.is_truncated),
|
|
819
|
+
relations: edges,
|
|
820
|
+
structural_link_count: (res.auxiliary_relations ?? []).length,
|
|
821
|
+
structural_truncated: Boolean(res.auxiliary_truncated),
|
|
822
|
+
members: ordered.map((m) => ({
|
|
823
|
+
id: m.source_id,
|
|
824
|
+
title: m.title ?? null,
|
|
825
|
+
depth: m.depth,
|
|
826
|
+
discovered_via: m.discovered_via ?? null,
|
|
827
|
+
discovered_relation: m.discovered_relation ?? null,
|
|
828
|
+
app_provider: m.app_provider ?? null,
|
|
829
|
+
app_kind: m.app_kind ?? null,
|
|
830
|
+
})),
|
|
831
|
+
});
|
|
832
|
+
}
|
|
720
833
|
async function runInspect(args, signal) {
|
|
721
834
|
logger.debug(`${TOOL_NAMES.INSPECT}: ${args.source_id}`);
|
|
722
835
|
const res = await hydra.context.inspect({
|
|
723
836
|
id: args.source_id,
|
|
724
837
|
mode: args.mode ?? "content",
|
|
725
838
|
expirySeconds: args.expiry_seconds,
|
|
839
|
+
acl: args.acl,
|
|
726
840
|
database: args.database,
|
|
727
841
|
collection: args.collection,
|
|
728
842
|
}, { signal });
|
|
@@ -898,6 +1012,34 @@ graphOverride) {
|
|
|
898
1012
|
}
|
|
899
1013
|
return deleteReport(kind, args.ids, res, removed, removedCount);
|
|
900
1014
|
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Which databases this connection can address, and which is the default.
|
|
1017
|
+
*
|
|
1018
|
+
* A confined connection answers from its allowed list without a network
|
|
1019
|
+
* call: the list IS the answer, and asking the API would only show
|
|
1020
|
+
* databases the connection is not permitted to use.
|
|
1021
|
+
*/
|
|
1022
|
+
async function runDatabases() {
|
|
1023
|
+
const defaultDatabase = hydra.database;
|
|
1024
|
+
let databases;
|
|
1025
|
+
let confined = false;
|
|
1026
|
+
if (hydra.allowedDatabases) {
|
|
1027
|
+
databases = [...hydra.allowedDatabases];
|
|
1028
|
+
confined = true;
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
const listed = await hydra.databases.list();
|
|
1032
|
+
databases = (listed.databases ?? listed.tenantIds ?? []).filter(Boolean);
|
|
1033
|
+
}
|
|
1034
|
+
if (!databases.includes(defaultDatabase))
|
|
1035
|
+
databases.unshift(defaultDatabase);
|
|
1036
|
+
const lines = databases.map((d) => ` - ${d}${d === defaultDatabase ? " (default for this connection)" : ""}`);
|
|
1037
|
+
const note = confined
|
|
1038
|
+
? "\nThis connection is confined to the database(s) above; any other name is refused. " +
|
|
1039
|
+
"The user chose this when approving the connection."
|
|
1040
|
+
: "\nPass `database` on any tool to work in another one.";
|
|
1041
|
+
return structuredResult(`${databases.length} database(s):\n${lines.join("\n")}${note}`, { databases, default: defaultDatabase, confined });
|
|
1042
|
+
}
|
|
901
1043
|
// --- BYOG graph handlers ---
|
|
902
1044
|
/**
|
|
903
1045
|
* The scope a graph call runs against.
|
|
@@ -907,9 +1049,33 @@ graphOverride) {
|
|
|
907
1049
|
* validated here because the server's rule is a documented charset and
|
|
908
1050
|
* rejecting locally names it, where the remote failure is a bare 400.
|
|
909
1051
|
*/
|
|
1052
|
+
/**
|
|
1053
|
+
* The graph database a call runs against, checked against the connection's
|
|
1054
|
+
* allowed set. The graph client does not go through the context client's
|
|
1055
|
+
* scope(), so the same rule is applied here for every graph tool.
|
|
1056
|
+
*/
|
|
1057
|
+
function graphDatabase(override) {
|
|
1058
|
+
const database = override?.trim() || graphConfig.database;
|
|
1059
|
+
if (database && database !== hydra.database) {
|
|
1060
|
+
assertDatabaseAllowed(database, hydra.allowedDatabases);
|
|
1061
|
+
}
|
|
1062
|
+
return database;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* The graph collection a call runs against, checked the same way the
|
|
1066
|
+
* database is. `drop_collection` makes an unchecked override destructive,
|
|
1067
|
+
* so confinement covers both axes or it covers nothing.
|
|
1068
|
+
*/
|
|
1069
|
+
function graphCollection(override) {
|
|
1070
|
+
const collection = override?.trim() || graphConfig.collection;
|
|
1071
|
+
if (collection && collection !== hydra.collection) {
|
|
1072
|
+
assertCollectionAllowed(collection, hydra.allowedCollections);
|
|
1073
|
+
}
|
|
1074
|
+
return collection;
|
|
1075
|
+
}
|
|
910
1076
|
function graphScope(args) {
|
|
911
|
-
const database = args.database
|
|
912
|
-
const collection = args.collection
|
|
1077
|
+
const database = graphDatabase(args.database);
|
|
1078
|
+
const collection = graphCollection(args.collection);
|
|
913
1079
|
if (!database) {
|
|
914
1080
|
throw new Error("No graph database configured. Set HYDRADB_GRAPH_DATABASE (or HYDRADB_DATABASE), " +
|
|
915
1081
|
"or pass `database` on this call.");
|
|
@@ -990,7 +1156,7 @@ graphOverride) {
|
|
|
990
1156
|
});
|
|
991
1157
|
}
|
|
992
1158
|
async function runGraphCollections(args, signal) {
|
|
993
|
-
const database = args.database
|
|
1159
|
+
const database = graphDatabase(args.database);
|
|
994
1160
|
if (!database) {
|
|
995
1161
|
throw new Error("No graph database configured. Set HYDRADB_GRAPH_DATABASE (or HYDRADB_DATABASE), " +
|
|
996
1162
|
"or pass `database` on this call.");
|
|
@@ -1005,7 +1171,7 @@ graphOverride) {
|
|
|
1005
1171
|
collections.map((name) => ` - ${name}`).join("\n"), { database, collections, count: collections.length });
|
|
1006
1172
|
}
|
|
1007
1173
|
async function runGraphAdmin(args, signal) {
|
|
1008
|
-
const database = args.database
|
|
1174
|
+
const database = graphDatabase(args.database);
|
|
1009
1175
|
if (!database) {
|
|
1010
1176
|
throw new Error("No graph database configured. Set HYDRADB_GRAPH_DATABASE (or HYDRADB_DATABASE), " +
|
|
1011
1177
|
"or pass `database` on this call.");
|
|
@@ -1022,6 +1188,13 @@ graphOverride) {
|
|
|
1022
1188
|
throw new Error(`${TOOL_NAMES.GRAPH_ADMIN} action "drop_collection" requires \`collection\` — ` +
|
|
1023
1189
|
"the name of the graph to drop. Nothing was deleted.");
|
|
1024
1190
|
}
|
|
1191
|
+
// This action takes its collection directly rather than through
|
|
1192
|
+
// graphCollection(), because there is no default to fall back to, so
|
|
1193
|
+
// the confinement check has to be stated here as well. It is the one
|
|
1194
|
+
// place an unchecked collection would be irreversible.
|
|
1195
|
+
if (collection !== hydra.collection) {
|
|
1196
|
+
assertCollectionAllowed(collection, hydra.allowedCollections);
|
|
1197
|
+
}
|
|
1025
1198
|
await hydra.graph.dropCollection({ database, collection }, { signal });
|
|
1026
1199
|
// The endpoint is idempotent and does not report whether anything was
|
|
1027
1200
|
// there, so this states what was requested rather than claiming a
|
|
@@ -1031,6 +1204,21 @@ graphOverride) {
|
|
|
1031
1204
|
"not exist.", { action: args.action, database, collection, dropped: true });
|
|
1032
1205
|
}
|
|
1033
1206
|
if (args.action === "drop_database") {
|
|
1207
|
+
// This deletes EVERY graph collection in the database, so on a
|
|
1208
|
+
// collection-confined connection it cannot be performed within the
|
|
1209
|
+
// confinement: even an allowed database holds collections the user
|
|
1210
|
+
// never approved. The per-collection checks elsewhere cannot catch
|
|
1211
|
+
// this one, because the call names no collection at all. Refuse the
|
|
1212
|
+
// action outright rather than let the broadest destructive operation
|
|
1213
|
+
// be the way around the narrowest grant.
|
|
1214
|
+
if (hydra.allowedCollections) {
|
|
1215
|
+
throw new Error(`This connection is confined to collection ${hydra.allowedCollections
|
|
1216
|
+
.map((c) => `"${c}"`)
|
|
1217
|
+
.join(", ")}, and "drop_database" removes every collection in ` +
|
|
1218
|
+
`"${database}", including ones it was not granted. Nothing was deleted. ` +
|
|
1219
|
+
'Use "drop_collection" for a collection this connection may use, or ' +
|
|
1220
|
+
"reconnect with wider access.");
|
|
1221
|
+
}
|
|
1034
1222
|
const res = await hydra.graph.dropDatabase(database, { signal });
|
|
1035
1223
|
const dropped = res.deleted_collections ?? [];
|
|
1036
1224
|
const listed = dropped.length > 0 ? ` Collections removed: ${dropped.join(", ")}.` : "";
|
|
@@ -1151,6 +1339,25 @@ graphOverride) {
|
|
|
1151
1339
|
.max(5)
|
|
1152
1340
|
.optional()
|
|
1153
1341
|
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.QUERY].params.num_related_chunks),
|
|
1342
|
+
recency_bias: z
|
|
1343
|
+
.number()
|
|
1344
|
+
.min(0)
|
|
1345
|
+
.max(1)
|
|
1346
|
+
.optional()
|
|
1347
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.QUERY].params.recency_bias),
|
|
1348
|
+
query_apps: z
|
|
1349
|
+
.boolean()
|
|
1350
|
+
.optional()
|
|
1351
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.QUERY].params.query_apps),
|
|
1352
|
+
collections: z
|
|
1353
|
+
.array(z.string().min(1))
|
|
1354
|
+
.min(1)
|
|
1355
|
+
.optional()
|
|
1356
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.QUERY].params.collections),
|
|
1357
|
+
acl: z
|
|
1358
|
+
.array(z.string())
|
|
1359
|
+
.optional()
|
|
1360
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.QUERY].params.acl),
|
|
1154
1361
|
...scopeSchema,
|
|
1155
1362
|
};
|
|
1156
1363
|
const storeSchema = {
|
|
@@ -1273,6 +1480,10 @@ graphOverride) {
|
|
|
1273
1480
|
.max(100)
|
|
1274
1481
|
.optional()
|
|
1275
1482
|
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.LIST].params.page_size),
|
|
1483
|
+
acl: z
|
|
1484
|
+
.array(z.string())
|
|
1485
|
+
.optional()
|
|
1486
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.LIST].params.acl),
|
|
1276
1487
|
...scopeSchema,
|
|
1277
1488
|
};
|
|
1278
1489
|
const listSourcesSchema = {
|
|
@@ -1280,9 +1491,43 @@ graphOverride) {
|
|
|
1280
1491
|
.array(z.string())
|
|
1281
1492
|
.optional()
|
|
1282
1493
|
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.LIST_SOURCES].params.source_ids),
|
|
1494
|
+
acl: z
|
|
1495
|
+
.array(z.string())
|
|
1496
|
+
.optional()
|
|
1497
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.LIST].params.acl),
|
|
1283
1498
|
...scopeSchema,
|
|
1284
1499
|
};
|
|
1285
1500
|
const listMemoriesSchema = {
|
|
1501
|
+
acl: z
|
|
1502
|
+
.array(z.string())
|
|
1503
|
+
.optional()
|
|
1504
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.LIST].params.acl),
|
|
1505
|
+
...scopeSchema,
|
|
1506
|
+
};
|
|
1507
|
+
const subgraphSchema = {
|
|
1508
|
+
id: z.string().min(1).describe(TOOL_DESCRIPTIONS[TOOL_NAMES.SUBGRAPH].params.id),
|
|
1509
|
+
kind: z
|
|
1510
|
+
.enum(["memory", "knowledge"])
|
|
1511
|
+
.optional()
|
|
1512
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.SUBGRAPH].params.kind),
|
|
1513
|
+
depth: z
|
|
1514
|
+
.number()
|
|
1515
|
+
.int()
|
|
1516
|
+
.min(1)
|
|
1517
|
+
.max(10)
|
|
1518
|
+
.optional()
|
|
1519
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.SUBGRAPH].params.depth),
|
|
1520
|
+
max_sources: z
|
|
1521
|
+
.number()
|
|
1522
|
+
.int()
|
|
1523
|
+
.min(1)
|
|
1524
|
+
.max(1000)
|
|
1525
|
+
.optional()
|
|
1526
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.SUBGRAPH].params.max_sources),
|
|
1527
|
+
acl: z
|
|
1528
|
+
.array(z.string())
|
|
1529
|
+
.optional()
|
|
1530
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.SUBGRAPH].params.acl),
|
|
1286
1531
|
...scopeSchema,
|
|
1287
1532
|
};
|
|
1288
1533
|
const inspectSchema = {
|
|
@@ -1321,6 +1566,10 @@ graphOverride) {
|
|
|
1321
1566
|
.min(1)
|
|
1322
1567
|
.optional()
|
|
1323
1568
|
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.INSPECT].params.expiry_seconds),
|
|
1569
|
+
acl: z
|
|
1570
|
+
.array(z.string())
|
|
1571
|
+
.optional()
|
|
1572
|
+
.describe(TOOL_DESCRIPTIONS[TOOL_NAMES.INSPECT].params.acl),
|
|
1324
1573
|
...scopeSchema,
|
|
1325
1574
|
};
|
|
1326
1575
|
const deleteSchema = {
|
|
@@ -1575,6 +1824,7 @@ graphOverride) {
|
|
|
1575
1824
|
source_ids: ids,
|
|
1576
1825
|
page: a.page,
|
|
1577
1826
|
page_size: a.page_size,
|
|
1827
|
+
acl: a.acl,
|
|
1578
1828
|
database: a.database,
|
|
1579
1829
|
collection: a.collection,
|
|
1580
1830
|
}, extra?.signal);
|
|
@@ -1583,13 +1833,21 @@ graphOverride) {
|
|
|
1583
1833
|
source_ids: ids,
|
|
1584
1834
|
page: a.page,
|
|
1585
1835
|
page_size: a.page_size,
|
|
1836
|
+
acl: a.acl,
|
|
1586
1837
|
database: a.database,
|
|
1587
1838
|
collection: a.collection,
|
|
1588
1839
|
}, extra?.signal);
|
|
1589
1840
|
}, readOnly, listOutputSchema);
|
|
1590
1841
|
register(TOOL_NAMES.INSPECT, inspectSchema, (args, extra) => runInspect(toInspectArgs(args), extra?.signal), readOnly);
|
|
1842
|
+
register(TOOL_NAMES.SUBGRAPH, subgraphSchema, (args, extra) => runSubgraph(args, extra?.signal), readOnly);
|
|
1591
1843
|
register(TOOL_NAMES.DELETE, deleteSchema, (args, extra) => runDelete(toDeleteArgs(args), extra?.signal), destructive, deleteOutputSchema);
|
|
1592
1844
|
register(TOOL_NAMES.STATUS, statusSchema, (args, extra) => runStatus(args, extra?.signal), readOnly);
|
|
1845
|
+
// Only an OAuth connection carries a user's database decision, and only
|
|
1846
|
+
// there does an agent need a way to see it. Registering this for API-key
|
|
1847
|
+
// connections too would change their tool list, which must stay identical.
|
|
1848
|
+
if (options.oauthTools) {
|
|
1849
|
+
register(TOOL_NAMES.DATABASES, {}, () => runDatabases(), readOnly);
|
|
1850
|
+
}
|
|
1593
1851
|
// --- BYOG graph tools (PRO-1681) ---
|
|
1594
1852
|
//
|
|
1595
1853
|
// A separate product surface from the memory/knowledge tools above: property
|