@edda-business/mcp 0.60.0 → 0.62.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/package.json +1 -1
- package/src/server.js +181 -141
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -13,21 +13,23 @@
|
|
|
13
13
|
* Tools, by group (v0.56 — consolidated, Cerebras-style):
|
|
14
14
|
* RETRIEVE search (unified, scope=all|company|personal|notes) · get_context ·
|
|
15
15
|
* get_account · who_knows · query · attention · verify
|
|
16
|
-
* WRITE save_note ·
|
|
17
|
-
*
|
|
16
|
+
* WRITE save_note · save_personal_file · update_personal_file · add_company_wiki_page ·
|
|
17
|
+
* add_company_wiki_pages (bulk) · create_company_wiki_folder
|
|
18
18
|
* FIX merge_contacts (action=merge|split)
|
|
19
19
|
* RUN list_integrations · connect_integration
|
|
20
20
|
*
|
|
21
21
|
* Deprecated aliases kept for back-compat (forward to the above, removed after a window):
|
|
22
22
|
* search_company_knowledge · search_my_vault · search_notes → search
|
|
23
|
-
* propose_vault_file →
|
|
23
|
+
* save_to_vault/propose_vault_file → save_personal_file · update_vault_file → update_personal_file
|
|
24
|
+
* propose_company_file → add_company_wiki_page · propose_company_files → add_company_wiki_pages
|
|
25
|
+
* create_folder → create_company_wiki_folder · unmerge_contacts → merge_contacts(action:split)
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
27
29
|
import { z } from "zod";
|
|
28
30
|
import { get, post, del } from "./client.js";
|
|
29
31
|
|
|
30
|
-
export const SERVER_VERSION = "0.
|
|
32
|
+
export const SERVER_VERSION = "0.62.0";
|
|
31
33
|
|
|
32
34
|
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
33
35
|
|
|
@@ -78,7 +80,7 @@ export function createServer() {
|
|
|
78
80
|
description:
|
|
79
81
|
"Edda — the company knowledge layer for AI agents. The agent reads engineered, " +
|
|
80
82
|
"epistemics-tagged context instead of raw rows. Call get_context before preparing for a " +
|
|
81
|
-
"meeting or a decision about a person;
|
|
83
|
+
"meeting or a decision about a person; search for how the company works; " +
|
|
82
84
|
"save_note to keep a brief or transcript on a contact.",
|
|
83
85
|
icons: [
|
|
84
86
|
{ src: "https://opennous.cloud/newlogoP.png", mimeType: "image/png", sizes: ["64x64"] },
|
|
@@ -585,7 +587,7 @@ export function createServer() {
|
|
|
585
587
|
// same direct personal-vault save (the member owns their vault, so no inbox step).
|
|
586
588
|
server.tool(
|
|
587
589
|
"propose_vault_file",
|
|
588
|
-
"DEPRECATED — use `
|
|
590
|
+
"DEPRECATED — use `save_personal_file`. Saves a markdown file into the member's own private knowledge.",
|
|
589
591
|
{
|
|
590
592
|
folder: z.string().describe("The vault folder (inbox | projects | decisions | companies | people | resources | archive)."),
|
|
591
593
|
name: z.string().describe("The file name, ending in .md."),
|
|
@@ -693,48 +695,49 @@ export function createServer() {
|
|
|
693
695
|
|
|
694
696
|
// ===========================================================================
|
|
695
697
|
// TOOL: propose_company_file — POST /v2/company/pages
|
|
696
|
-
// The WRITE side of the company
|
|
698
|
+
// The WRITE side of the company wiki (search is the read side).
|
|
697
699
|
// Writes an in-app company page directly (no GitHub). Members with a write role
|
|
698
700
|
// publish it live; viewers land it in the company inbox for an admin to approve.
|
|
699
701
|
// ===========================================================================
|
|
702
|
+
const ADD_WIKI_PAGE_SCHEMA = {
|
|
703
|
+
name: z.string().describe("The page name, e.g. 'Refund Policy' or 'Q3 Planning — 2026-08-22'. A '.md' suffix is added if missing."),
|
|
704
|
+
content: z.string().describe("The full markdown content of the page."),
|
|
705
|
+
visibility: z.enum(["owner", "department", "company"]).optional().describe("Who can see the page: 'department' (the team it's filed in — default), 'company' (everyone), or 'owner' (just you)."),
|
|
706
|
+
folder: z.string().optional().describe("Folder to file under, by NAME or path. Top-level folders are fixed — a bare name becomes a project under Projects (e.g. 'Agency' → Projects/Agency); 'Top-Level/Sub' nests under a fixed folder. Created if missing (write role). Use this OR folder_id."),
|
|
707
|
+
subfolder: z.string().optional().describe("Optional subfolder within `folder`, e.g. 'Specs'."),
|
|
708
|
+
folder_id: z.string().optional().describe("Id of an existing company folder to file under (alternative to `folder`). Omit both to use the default folder."),
|
|
709
|
+
};
|
|
710
|
+
const runAddCompanyWikiPage = async ({ name, content, visibility, folder, subfolder, folder_id }) => {
|
|
711
|
+
try {
|
|
712
|
+
const r = await post("/v2/company/pages", { name, content, visibility, folder, subfolder, folder_id });
|
|
713
|
+
const live = r?.page?.status === "live";
|
|
714
|
+
const where = live ? `published live to the company wiki — searchable now` : `sent to the company inbox for an admin to approve`;
|
|
715
|
+
return { content: [{ type: "text", text: `"${r?.page?.name ?? name}" ${where}.` }] };
|
|
716
|
+
} catch (e) {
|
|
717
|
+
const msg = String(e?.message || e);
|
|
718
|
+
if (msg.includes("forbidden") || msg.includes("(403)")) return { content: [{ type: "text", text: `Not saved — you don't have access to write to this company wiki.` }] };
|
|
719
|
+
if (msg.includes("bad_folder")) return { content: [{ type: "text", text: `Not saved — that folder_id isn't a company folder in this workspace. Omit it to use the default folder.` }] };
|
|
720
|
+
throw e;
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
server.tool(
|
|
724
|
+
"add_company_wiki_page",
|
|
725
|
+
"Add a markdown page to the COMPANY WIKI — the shared, declared knowledge (policies, playbooks, " +
|
|
726
|
+
"decisions, SOPs, how-we-work) every member's agent can read via search. A member with a write role " +
|
|
727
|
+
"publishes it live; a viewer's page lands in the company inbox for an admin to approve. File it with " +
|
|
728
|
+
"`folder` — a name or path like 'Projects/Data Centre' (created if missing) — plus optional `subfolder`. " +
|
|
729
|
+
"Set `visibility`: 'department' (the team it's filed in — default), 'company' (everyone), or 'owner' " +
|
|
730
|
+
"(just you); a page in a project inherits the project's Shared-with setting for OTHER departments. To add " +
|
|
731
|
+
"many at once use add_company_wiki_pages. NOT the member's private files (use save_personal_file), NOT a contact note (save_note).",
|
|
732
|
+
ADD_WIKI_PAGE_SCHEMA,
|
|
733
|
+
runAddCompanyWikiPage,
|
|
734
|
+
);
|
|
735
|
+
// DEPRECATED alias → add_company_wiki_page. Kept so existing agents keep working.
|
|
700
736
|
server.tool(
|
|
701
737
|
"propose_company_file",
|
|
702
|
-
"
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
"it live immediately; a viewer's page lands in the company inbox for an admin to approve. File " +
|
|
706
|
-
"it with `folder` — a name or path like 'Projects/Data Centre' (created if missing) — plus an " +
|
|
707
|
-
"optional `subfolder`. Set `visibility` to scope who sees it: 'department' (the team it's filed " +
|
|
708
|
-
"in — the default), 'company' (everyone), or 'owner' (just you). A page in a project inherits the " +
|
|
709
|
-
"project's Shared-with setting for OTHER departments. To add many pages at once, use " +
|
|
710
|
-
"propose_company_files. NOT the member's private vault (use save_to_vault), NOT a contact note (save_note).",
|
|
711
|
-
{
|
|
712
|
-
name: z.string().describe("The page name, e.g. 'Refund Policy' or 'Q3 Planning — 2026-08-22'. A '.md' suffix is added if missing."),
|
|
713
|
-
content: z.string().describe("The full markdown content of the page."),
|
|
714
|
-
visibility: z.enum(["owner", "department", "company"]).optional().describe("Who can see the page: 'department' (the team it's filed in — default), 'company' (everyone), or 'owner' (just you)."),
|
|
715
|
-
folder: z.string().optional().describe("Folder to file under, by NAME or path, e.g. 'Projects/Data Centre'. Created if it doesn't exist (needs a write role). Use this OR folder_id."),
|
|
716
|
-
subfolder: z.string().optional().describe("Optional subfolder within `folder`, e.g. 'Specs'."),
|
|
717
|
-
folder_id: z.string().optional().describe("Id of an existing company folder to file under (alternative to `folder`). Omit both to use the default 'wiki' folder."),
|
|
718
|
-
},
|
|
719
|
-
async ({ name, content, visibility, folder, subfolder, folder_id }) => {
|
|
720
|
-
try {
|
|
721
|
-
const r = await post("/v2/company/pages", { name, content, visibility, folder, subfolder, folder_id });
|
|
722
|
-
const live = r?.page?.status === "live";
|
|
723
|
-
const where = live
|
|
724
|
-
? `published live to the company vault — searchable now`
|
|
725
|
-
: `sent to the company inbox for an admin to approve`;
|
|
726
|
-
return { content: [{ type: "text", text: `"${r?.page?.name ?? name}" ${where}.` }] };
|
|
727
|
-
} catch (e) {
|
|
728
|
-
const msg = String(e?.message || e);
|
|
729
|
-
if (msg.includes("forbidden") || msg.includes("(403)")) {
|
|
730
|
-
return { content: [{ type: "text", text: `Not saved — you don't have access to write to this company vault.` }] };
|
|
731
|
-
}
|
|
732
|
-
if (msg.includes("bad_folder")) {
|
|
733
|
-
return { content: [{ type: "text", text: `Not saved — that folder_id isn't a company folder in this workspace. Omit it to use the default 'wiki' folder.` }] };
|
|
734
|
-
}
|
|
735
|
-
throw e;
|
|
736
|
-
}
|
|
737
|
-
},
|
|
738
|
+
"DEPRECATED — use `add_company_wiki_page`. Adds a markdown page to the shared company wiki.",
|
|
739
|
+
ADD_WIKI_PAGE_SCHEMA,
|
|
740
|
+
runAddCompanyWikiPage,
|
|
738
741
|
);
|
|
739
742
|
|
|
740
743
|
|
|
@@ -742,63 +745,83 @@ export function createServer() {
|
|
|
742
745
|
// TOOL: create_folder — POST /v2/company/folders { path }
|
|
743
746
|
// Create a folder / nested path in the shared company tree so pages can be filed into it.
|
|
744
747
|
// ===========================================================================
|
|
748
|
+
const CREATE_WIKI_FOLDER_SCHEMA = { path: z.string().describe("Folder name or path. A bare name → a project under Projects; 'Top-Level/Sub' nests under a fixed top-level folder.") };
|
|
749
|
+
const runCreateCompanyWikiFolder = async ({ path }) => {
|
|
750
|
+
try {
|
|
751
|
+
const r = await post("/v2/company/folders", { path });
|
|
752
|
+
const made = r?.created?.length ? ` (created ${r.created.join(" / ")})` : " (already existed)";
|
|
753
|
+
return { content: [{ type: "text", text: `Folder "${r?.folder?.name ?? path}" ready${made}. folder_id: ${r?.folder?.id ?? "?"}` }] };
|
|
754
|
+
} catch (e) {
|
|
755
|
+
const msg = String(e?.message || e);
|
|
756
|
+
if (msg.includes("read_only") || msg.includes("(403)")) return { content: [{ type: "text", text: "You don't have a write role in this company wiki, so you can't create folders here." }] };
|
|
757
|
+
if (msg.includes("bad_path")) return { content: [{ type: "text", text: "Couldn't create that path — give a folder name or 'Parent/Child' path." }] };
|
|
758
|
+
throw e;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
server.tool(
|
|
762
|
+
"create_company_wiki_folder",
|
|
763
|
+
"Create a folder in the COMPANY WIKI tree so you can file pages into it. The TOP-LEVEL folders are FIXED " +
|
|
764
|
+
"(Company · Projects · Decisions · Companies · People · Raw Documents · Archive) — you cannot add new ones. " +
|
|
765
|
+
"A bare name (or a path that doesn't start with one of those) becomes a PROJECT under Projects: 'Agency' → " +
|
|
766
|
+
"Projects/Agency. To nest under a specific top-level folder, start the path with it, e.g. 'Company/Policies' " +
|
|
767
|
+
"or 'Projects/Data Centre/Specs'. A project gets a 'Shared with' control the Ambassador uses to grant " +
|
|
768
|
+
"departments. A write role is required; the folder appears immediately. Then add pages with " +
|
|
769
|
+
"add_company_wiki_page / add_company_wiki_pages using the same path. Returns the folder id.",
|
|
770
|
+
CREATE_WIKI_FOLDER_SCHEMA,
|
|
771
|
+
runCreateCompanyWikiFolder,
|
|
772
|
+
);
|
|
773
|
+
// DEPRECATED alias → create_company_wiki_folder.
|
|
745
774
|
server.tool(
|
|
746
775
|
"create_folder",
|
|
747
|
-
"
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
"to grant departments). A write role is required and the folder appears immediately. Then add pages with " +
|
|
751
|
-
"propose_company_file / propose_company_files using the same `folder` path. Returns the folder id.",
|
|
752
|
-
{ path: z.string().describe("Folder name or path, e.g. 'Projects/Data Centre' or 'Playbooks'.") },
|
|
753
|
-
async ({ path }) => {
|
|
754
|
-
try {
|
|
755
|
-
const r = await post("/v2/company/folders", { path });
|
|
756
|
-
const made = r?.created?.length ? ` (created ${r.created.join(" / ")})` : " (already existed)";
|
|
757
|
-
return { content: [{ type: "text", text: `Folder "${r?.folder?.name ?? path}" ready${made}. folder_id: ${r?.folder?.id ?? "?"}` }] };
|
|
758
|
-
} catch (e) {
|
|
759
|
-
const msg = String(e?.message || e);
|
|
760
|
-
if (msg.includes("read_only") || msg.includes("(403)")) return { content: [{ type: "text", text: "You don't have a write role in this company vault, so you can't create folders here." }] };
|
|
761
|
-
if (msg.includes("bad_path")) return { content: [{ type: "text", text: "Couldn't create that path — give a folder name or 'Parent/Child' path." }] };
|
|
762
|
-
throw e;
|
|
763
|
-
}
|
|
764
|
-
},
|
|
776
|
+
"DEPRECATED — use `create_company_wiki_folder`. Creates a folder in the shared company wiki tree.",
|
|
777
|
+
CREATE_WIKI_FOLDER_SCHEMA,
|
|
778
|
+
runCreateCompanyWikiFolder,
|
|
765
779
|
);
|
|
766
780
|
|
|
767
781
|
// ===========================================================================
|
|
768
782
|
// TOOL: propose_company_files — POST /v2/company/pages/batch
|
|
769
783
|
// Bulk-add many pages into ONE company folder (resolved/created once).
|
|
770
784
|
// ===========================================================================
|
|
785
|
+
const ADD_WIKI_PAGES_SCHEMA = {
|
|
786
|
+
folder: z.string().optional().describe("Folder to file all pages under. Top-level is fixed — a bare name becomes a project under Projects (e.g. 'Agency' → Projects/Agency); 'Top-Level/Sub' nests under a fixed folder. Created if missing."),
|
|
787
|
+
subfolder: z.string().optional().describe("Optional subfolder within `folder`."),
|
|
788
|
+
folder_id: z.string().optional().describe("Id of an existing folder (alternative to `folder`)."),
|
|
789
|
+
visibility: z.enum(["owner", "department", "company"]).optional().describe("Default visibility for the pages (a page can override its own). Default 'department'."),
|
|
790
|
+
pages: z.array(z.object({
|
|
791
|
+
name: z.string().describe("Page name."),
|
|
792
|
+
content: z.string().optional().describe("Markdown content."),
|
|
793
|
+
visibility: z.enum(["owner", "department", "company"]).optional().describe("Optional per-page visibility."),
|
|
794
|
+
})).describe("The pages to add: each { name, content, visibility? }."),
|
|
795
|
+
};
|
|
796
|
+
const runAddCompanyWikiPages = async ({ folder, subfolder, folder_id, visibility, pages }) => {
|
|
797
|
+
try {
|
|
798
|
+
const r = await post("/v2/company/pages/batch", { folder, subfolder, folder_id, visibility, pages });
|
|
799
|
+
const where = r?.status === "live" ? "published live — searchable now" : "sent to the company inbox for an admin to approve";
|
|
800
|
+
return { content: [{ type: "text", text: `${r?.count ?? 0} page(s) ${where} in "${r?.folder ?? folder ?? "wiki"}".` }] };
|
|
801
|
+
} catch (e) {
|
|
802
|
+
const msg = String(e?.message || e);
|
|
803
|
+
if (msg.includes("forbidden") || msg.includes("(403)")) return { content: [{ type: "text", text: "You don't have access to write to this company wiki." }] };
|
|
804
|
+
if (msg.includes("bad_folder")) return { content: [{ type: "text", text: "That folder path couldn't be resolved or created — check the name, or pass folder_id." }] };
|
|
805
|
+
if (msg.includes("too_many")) return { content: [{ type: "text", text: "Too many pages — max 50 per call. Split into batches." }] };
|
|
806
|
+
if (msg.includes("pages_required")) return { content: [{ type: "text", text: "Give at least one page ({ name, content })." }] };
|
|
807
|
+
throw e;
|
|
808
|
+
}
|
|
809
|
+
};
|
|
771
810
|
server.tool(
|
|
772
|
-
"
|
|
773
|
-
"Add MANY markdown pages to the
|
|
774
|
-
"
|
|
775
|
-
"
|
|
811
|
+
"add_company_wiki_pages",
|
|
812
|
+
"Add MANY markdown pages to the COMPANY WIKI in ONE call — all into the SAME folder (resolved or created " +
|
|
813
|
+
"once). Use it to bulk-file: 'put these 10 docs in Data Centre'. Same rules as add_company_wiki_page: a " +
|
|
814
|
+
"write role publishes them live; a viewer sends the whole batch to the company inbox. Name the target with " +
|
|
776
815
|
"`folder` (a path, created if missing) + optional `subfolder`, or an existing `folder_id`. Max 50 pages.",
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
})).describe("The pages to add: each { name, content, visibility? }."),
|
|
787
|
-
},
|
|
788
|
-
async ({ folder, subfolder, folder_id, visibility, pages }) => {
|
|
789
|
-
try {
|
|
790
|
-
const r = await post("/v2/company/pages/batch", { folder, subfolder, folder_id, visibility, pages });
|
|
791
|
-
const where = r?.status === "live" ? "published live — searchable now" : "sent to the company inbox for an admin to approve";
|
|
792
|
-
return { content: [{ type: "text", text: `${r?.count ?? 0} page(s) ${where} in "${r?.folder ?? folder ?? "wiki"}".` }] };
|
|
793
|
-
} catch (e) {
|
|
794
|
-
const msg = String(e?.message || e);
|
|
795
|
-
if (msg.includes("forbidden") || msg.includes("(403)")) return { content: [{ type: "text", text: "You don't have access to write to this company vault." }] };
|
|
796
|
-
if (msg.includes("bad_folder")) return { content: [{ type: "text", text: "That folder path couldn't be resolved or created — check the name, or pass folder_id." }] };
|
|
797
|
-
if (msg.includes("too_many")) return { content: [{ type: "text", text: "Too many pages — max 50 per call. Split into batches." }] };
|
|
798
|
-
if (msg.includes("pages_required")) return { content: [{ type: "text", text: "Give at least one page ({ name, content })." }] };
|
|
799
|
-
throw e;
|
|
800
|
-
}
|
|
801
|
-
},
|
|
816
|
+
ADD_WIKI_PAGES_SCHEMA,
|
|
817
|
+
runAddCompanyWikiPages,
|
|
818
|
+
);
|
|
819
|
+
// DEPRECATED alias → add_company_wiki_pages.
|
|
820
|
+
server.tool(
|
|
821
|
+
"propose_company_files",
|
|
822
|
+
"DEPRECATED — use `add_company_wiki_pages`. Bulk-adds pages to the shared company wiki.",
|
|
823
|
+
ADD_WIKI_PAGES_SCHEMA,
|
|
824
|
+
runAddCompanyWikiPages,
|
|
802
825
|
);
|
|
803
826
|
|
|
804
827
|
// ===========================================================================
|
|
@@ -845,35 +868,43 @@ export function createServer() {
|
|
|
845
868
|
// TOOL: save_to_vault — POST /v2/personal/files
|
|
846
869
|
// Save a file into the MEMBER'S OWN private vault folder. Extracts a PDF/DOCX to text.
|
|
847
870
|
// ===========================================================================
|
|
871
|
+
const SAVE_PERSONAL_SCHEMA = {
|
|
872
|
+
folder: z.string().describe("The personal folder: inbox | projects | decisions | companies | people | raw documents | archive."),
|
|
873
|
+
name: z.string().describe("The file name, e.g. 'Q3 Plan.md' or 'vendor-contract.pdf'."),
|
|
874
|
+
content: z.string().optional().describe("The text/markdown content, when you already have it as text."),
|
|
875
|
+
file: z.string().optional().describe("A PDF or DOCX as base64 (a data: URL is fine). It's extracted to text automatically. Provide this OR content."),
|
|
876
|
+
mime: z.string().optional().describe("The file's MIME type, e.g. 'application/pdf' or the docx type — helps pick the right extractor."),
|
|
877
|
+
subfolder: z.string().optional().describe("Optional subfolder within the folder."),
|
|
878
|
+
};
|
|
879
|
+
const runSavePersonalFile = async ({ folder, name, content, file, mime, subfolder }) => {
|
|
880
|
+
try {
|
|
881
|
+
const r = await post("/v2/personal/files", { folder, name, content, file_base64: file, mime, subfolder });
|
|
882
|
+
return { content: [{ type: "text", text: `"${name}" is waiting in your Inbox to approve — once you do, it files into ${r?.folder || folder} and becomes searchable.` }] };
|
|
883
|
+
} catch (e) {
|
|
884
|
+
const msg = String(e?.message || e);
|
|
885
|
+
if (msg.includes("no_text_extracted")) return { content: [{ type: "text", text: `Couldn't read any text from that file — a scanned/image-only PDF has no extractable text. Text, markdown, or a text-based PDF/DOCX works.` }] };
|
|
886
|
+
if (msg.includes("content_or_file_required")) return { content: [{ type: "text", text: `Nothing to save — pass either text content or a base64 file.` }] };
|
|
887
|
+
throw e;
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
server.tool(
|
|
891
|
+
"save_personal_file",
|
|
892
|
+
"Save a file into the MEMBER'S OWN personal knowledge (their private files — 'My Knowledge'). It lands in " +
|
|
893
|
+
"their INBOX for review with the target folder you name remembered (inbox, projects, decisions, companies, " +
|
|
894
|
+
"people, raw documents, archive); the member approves it in the app, which files it and makes it searchable. " +
|
|
895
|
+
"Nothing enters their knowledge without their approval. Use it when the member asks you to keep or file " +
|
|
896
|
+
"something — 'save this to raw documents', 'put this PDF in projects'. Pass markdown/text as `content`, OR a " +
|
|
897
|
+
"PDF/DOCX as base64 in `file` (auto-extracted). PRIVATE to the member. NOT the company wiki (use " +
|
|
898
|
+
"add_company_wiki_page), NOT a note on a contact (use save_note).",
|
|
899
|
+
SAVE_PERSONAL_SCHEMA,
|
|
900
|
+
runSavePersonalFile,
|
|
901
|
+
);
|
|
902
|
+
// DEPRECATED alias → save_personal_file.
|
|
848
903
|
server.tool(
|
|
849
904
|
"save_to_vault",
|
|
850
|
-
"
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
"and makes it searchable. Nothing enters their knowledge without their approval. Use it when the " +
|
|
854
|
-
"member asks you to keep or file something — 'save this to resources', 'put this PDF in projects'. " +
|
|
855
|
-
"Pass markdown/text as `content`, OR a PDF/DOCX as base64 in `file` (extracted to text automatically). " +
|
|
856
|
-
"PRIVATE to the member. NOT the shared company vault (use propose_company_file), NOT a note on a " +
|
|
857
|
-
"contact (use save_note).",
|
|
858
|
-
{
|
|
859
|
-
folder: z.string().describe("The vault folder: inbox | projects | decisions | companies | people | resources | archive | thoughts."),
|
|
860
|
-
name: z.string().describe("The file name, e.g. 'Q3 Plan.md' or 'vendor-contract.pdf'."),
|
|
861
|
-
content: z.string().optional().describe("The text/markdown content, when you already have it as text."),
|
|
862
|
-
file: z.string().optional().describe("A PDF or DOCX as base64 (a data: URL is fine). It's extracted to text automatically. Provide this OR content."),
|
|
863
|
-
mime: z.string().optional().describe("The file's MIME type, e.g. 'application/pdf' or the docx type — helps pick the right extractor."),
|
|
864
|
-
subfolder: z.string().optional().describe("Optional subfolder within the folder."),
|
|
865
|
-
},
|
|
866
|
-
async ({ folder, name, content, file, mime, subfolder }) => {
|
|
867
|
-
try {
|
|
868
|
-
const r = await post("/v2/personal/files", { folder, name, content, file_base64: file, mime, subfolder });
|
|
869
|
-
return { content: [{ type: "text", text: `"${name}" is waiting in your Inbox to approve — once you do, it files into ${r?.folder || folder} and becomes searchable.` }] };
|
|
870
|
-
} catch (e) {
|
|
871
|
-
const msg = String(e?.message || e);
|
|
872
|
-
if (msg.includes("no_text_extracted")) return { content: [{ type: "text", text: `Couldn't read any text from that file — a scanned/image-only PDF has no extractable text. Text, markdown, or a text-based PDF/DOCX works.` }] };
|
|
873
|
-
if (msg.includes("content_or_file_required")) return { content: [{ type: "text", text: `Nothing to save — pass either text content or a base64 file.` }] };
|
|
874
|
-
throw e;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
905
|
+
"DEPRECATED — use `save_personal_file`. Saves a file into the member's own private knowledge.",
|
|
906
|
+
SAVE_PERSONAL_SCHEMA,
|
|
907
|
+
runSavePersonalFile,
|
|
877
908
|
);
|
|
878
909
|
|
|
879
910
|
// ===========================================================================
|
|
@@ -882,32 +913,41 @@ export function createServer() {
|
|
|
882
913
|
// then replaces/appends + re-embeds. save_to_vault is for NEW files (→ Inbox);
|
|
883
914
|
// this edits one that's already live.
|
|
884
915
|
// ===========================================================================
|
|
916
|
+
const UPDATE_PERSONAL_SCHEMA = {
|
|
917
|
+
file_id: z.string().optional().describe("The file's id (from a search/save result). Provide this OR path."),
|
|
918
|
+
path: z.string().optional().describe("The file's path, e.g. 'raw documents/Edda Architecture.md'. Provide this OR file_id."),
|
|
919
|
+
content: z.string().describe("The new markdown content. Replaces the file's content unless append is true."),
|
|
920
|
+
append: z.boolean().optional().describe("If true, append this to the end of the file instead of replacing it."),
|
|
921
|
+
};
|
|
922
|
+
const runUpdatePersonalFile = async ({ file_id, path, content, append }) => {
|
|
923
|
+
try {
|
|
924
|
+
const r = await post("/v2/personal/files/update", { file_id, path, content, append });
|
|
925
|
+
return { content: [{ type: "text", text: r?.note || "Updated the file." }] };
|
|
926
|
+
} catch (e) {
|
|
927
|
+
const msg = String(e?.message || e);
|
|
928
|
+
if (msg.includes("not_found")) return { content: [{ type: "text", text: "Couldn't find that file — check the file_id or path (folder/name.md)." }] };
|
|
929
|
+
if (msg.includes("file_id_or_path_required")) return { content: [{ type: "text", text: "Tell me which file to edit — pass file_id or path." }] };
|
|
930
|
+
if (msg.includes("content_required")) return { content: [{ type: "text", text: "Give me the new content to write." }] };
|
|
931
|
+
throw e;
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
server.tool(
|
|
935
|
+
"update_personal_file",
|
|
936
|
+
"Edit an EXISTING file in the member's own personal knowledge — revise it or append to it. Identify the file " +
|
|
937
|
+
"by `file_id` (from a search or save result) or by `path` ('folder/[subfolder/]name.md'). The previous " +
|
|
938
|
+
"content is saved to version history FIRST (never lost), then your new content replaces it — or is added to " +
|
|
939
|
+
"the end with append:true — and the file is re-embedded; the member sees a fresh 'updated' time. Use this to " +
|
|
940
|
+
"keep a living document current: a rolling brief, a spec, a running log. For a NEW file use save_personal_file " +
|
|
941
|
+
"(which lands in the Inbox for approval); this edits one that already exists and is live.",
|
|
942
|
+
UPDATE_PERSONAL_SCHEMA,
|
|
943
|
+
runUpdatePersonalFile,
|
|
944
|
+
);
|
|
945
|
+
// DEPRECATED alias → update_personal_file.
|
|
885
946
|
server.tool(
|
|
886
947
|
"update_vault_file",
|
|
887
|
-
"
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
"to the end with append:true — and the file is re-embedded; the member sees a fresh 'updated' time. Use " +
|
|
891
|
-
"this to keep a living document current: a rolling brief, a spec, a running log. For a NEW file use " +
|
|
892
|
-
"save_to_vault (which lands in the Inbox for approval); this edits one that already exists and is live.",
|
|
893
|
-
{
|
|
894
|
-
file_id: z.string().optional().describe("The file's id (from a search/save result). Provide this OR path."),
|
|
895
|
-
path: z.string().optional().describe("The file's vault path, e.g. 'resources/Edda Architecture.md'. Provide this OR file_id."),
|
|
896
|
-
content: z.string().describe("The new markdown content. Replaces the file's content unless append is true."),
|
|
897
|
-
append: z.boolean().optional().describe("If true, append this to the end of the file instead of replacing it."),
|
|
898
|
-
},
|
|
899
|
-
async ({ file_id, path, content, append }) => {
|
|
900
|
-
try {
|
|
901
|
-
const r = await post("/v2/personal/files/update", { file_id, path, content, append });
|
|
902
|
-
return { content: [{ type: "text", text: r?.note || "Updated the file." }] };
|
|
903
|
-
} catch (e) {
|
|
904
|
-
const msg = String(e?.message || e);
|
|
905
|
-
if (msg.includes("not_found")) return { content: [{ type: "text", text: "Couldn't find that file — check the file_id or path (folder/name.md)." }] };
|
|
906
|
-
if (msg.includes("file_id_or_path_required")) return { content: [{ type: "text", text: "Tell me which file to edit — pass file_id or path." }] };
|
|
907
|
-
if (msg.includes("content_required")) return { content: [{ type: "text", text: "Give me the new content to write." }] };
|
|
908
|
-
throw e;
|
|
909
|
-
}
|
|
910
|
-
}
|
|
948
|
+
"DEPRECATED — use `update_personal_file`. Edits/append an existing file in the member's own knowledge.",
|
|
949
|
+
UPDATE_PERSONAL_SCHEMA,
|
|
950
|
+
runUpdatePersonalFile,
|
|
911
951
|
);
|
|
912
952
|
|
|
913
953
|
server.tool(
|