@edda-business/mcp 0.64.0 → 0.65.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 +31 -31
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
* + embeds them. You only RETRIEVE them (they surface in search tagged ·distilled).
|
|
21
21
|
*
|
|
22
22
|
* TOOLS:
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* search RETRIEVE across BOTH natures (documents + distilled), kind filter
|
|
24
|
+
* read read one full wiki page (by path from a search result, or id)
|
|
25
|
+
* push PUSH one or many files up into the company folders (shared wiki)
|
|
26
|
+
* update edit an existing wiki page (replace or append)
|
|
27
|
+
* create organize the wiki tree
|
|
28
28
|
*
|
|
29
29
|
* Sources (connecting Slack/HubSpot/… so distillation happens) are wired in the Edda web
|
|
30
30
|
* app, not here — the agent only reads and writes knowledge.
|
|
@@ -34,7 +34,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
34
34
|
import { z } from "zod";
|
|
35
35
|
import { get, post, patch } from "./client.js";
|
|
36
36
|
|
|
37
|
-
export const SERVER_VERSION = "0.
|
|
37
|
+
export const SERVER_VERSION = "0.65.0";
|
|
38
38
|
|
|
39
39
|
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
40
40
|
|
|
@@ -63,7 +63,7 @@ async function resolvePathToId(path) {
|
|
|
63
63
|
const pages = list?.pages ?? [];
|
|
64
64
|
const base = norm(String(path).split("/").pop());
|
|
65
65
|
let cands = pages.filter((p) => norm(p.name) === base);
|
|
66
|
-
if (!cands.length) return { error: `No company page named "${String(path).split("/").pop()}". Use
|
|
66
|
+
if (!cands.length) return { error: `No company page named "${String(path).split("/").pop()}". Use search to find the exact path first.` };
|
|
67
67
|
if (cands.length > 1) {
|
|
68
68
|
// Prefer a candidate whose folder slug appears in the requested path.
|
|
69
69
|
const pref = cands.filter((p) => p.folder && norm(path).includes(norm(p.folder)));
|
|
@@ -87,7 +87,7 @@ export function createServer() {
|
|
|
87
87
|
"Edda — the company's shared knowledge for AI agents. Search the company wiki (declared " +
|
|
88
88
|
"policies, playbooks, decisions) AND distilled knowledge from connected sources (Slack, " +
|
|
89
89
|
"HubSpot, calls) in one call; read a full page; and contribute pages/folders back. Reach " +
|
|
90
|
-
"for
|
|
90
|
+
"for search before answering from generic knowledge. (Personal files live on the " +
|
|
91
91
|
"user's own disk; this is the shared company layer only.)",
|
|
92
92
|
icons: [
|
|
93
93
|
{ src: "https://opennous.cloud/newlogoP.png", mimeType: "image/png", sizes: ["64x64"] },
|
|
@@ -95,18 +95,18 @@ export function createServer() {
|
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
// ===========================================================================
|
|
98
|
-
// TOOL:
|
|
98
|
+
// TOOL: search — POST /v2/company/search
|
|
99
99
|
// The primary read surface. Hybrid retrieval (RRF + rerank) fused across the DECLARED wiki
|
|
100
100
|
// and the DERIVED distilled layer, ACL-filtered. `kind` narrows to one nature.
|
|
101
101
|
// ===========================================================================
|
|
102
102
|
server.tool(
|
|
103
|
-
"
|
|
103
|
+
"search",
|
|
104
104
|
"Search the COMPANY'S SHARED KNOWLEDGE in one call — both natures at once: the DECLARED wiki " +
|
|
105
105
|
"(policies, playbooks, decisions, SOPs, how-we-work) AND DERIVED knowledge distilled from " +
|
|
106
106
|
"connected sources (Slack, HubSpot, Stripe, calls). This is the default retrieval tool: reach " +
|
|
107
107
|
"for it before answering from generic knowledge about how the company works or what happened. " +
|
|
108
108
|
"Each hit is tagged [company] (a wiki page) or [company·distilled] (from activity), with a " +
|
|
109
|
-
"snippet; wiki hits include a `path:` you can pass to
|
|
109
|
+
"snippet; wiki hits include a `path:` you can pass to read for the full text. " +
|
|
110
110
|
"Narrow with `kind`: 'documents' (wiki only), 'distilled' (activity only), or 'all' (default).",
|
|
111
111
|
{
|
|
112
112
|
question: z.string().describe("Natural-language query."),
|
|
@@ -132,7 +132,7 @@ export function createServer() {
|
|
|
132
132
|
const tag = declared ? "[company]" : "[company·distilled]";
|
|
133
133
|
const from = d.source_member ? ` · from ${d.source_member}` : "";
|
|
134
134
|
lines.push(`${tag} ${String(d.name ?? "").replace(/\.md$/, "")}${from} (${pct(d.similarity)})`);
|
|
135
|
-
if (declared && d.ref) lines.push(` path: ${d.ref}`); // pass to
|
|
135
|
+
if (declared && d.ref) lines.push(` path: ${d.ref}`); // pass to read
|
|
136
136
|
else if (d.ref) lines.push(` source: ${d.ref}`); // distilled: origin, read-only
|
|
137
137
|
if (d.snippet) lines.push(` ${d.snippet}`);
|
|
138
138
|
lines.push("");
|
|
@@ -142,12 +142,12 @@ export function createServer() {
|
|
|
142
142
|
);
|
|
143
143
|
|
|
144
144
|
// ===========================================================================
|
|
145
|
-
// TOOL:
|
|
146
|
-
// Read one full wiki page. Take the `path:` from a
|
|
145
|
+
// TOOL: read — GET /v2/company/pages/:id
|
|
146
|
+
// Read one full wiki page. Take the `path:` from a search result, or an id.
|
|
147
147
|
// ===========================================================================
|
|
148
148
|
server.tool(
|
|
149
|
-
"
|
|
150
|
-
"Read the FULL text of one company wiki page —
|
|
149
|
+
"read",
|
|
150
|
+
"Read the FULL text of one company wiki page — search returns snippets; use this to " +
|
|
151
151
|
"get the whole document. Pass the `path:` shown under a [company] search hit (e.g. " +
|
|
152
152
|
"'Company/Policies/Refund Policy.md'), or an `id` if you already have one. Also returns the " +
|
|
153
153
|
"page's LINK GRAPH — outgoing [[links]] and backlinks — so you can TRAVERSE the wiki to " +
|
|
@@ -164,7 +164,7 @@ export function createServer() {
|
|
|
164
164
|
if (res.error) return text(res.error);
|
|
165
165
|
pageId = res.id;
|
|
166
166
|
}
|
|
167
|
-
if (!pageId) return text("Tell me which page to read — pass the `path` from a
|
|
167
|
+
if (!pageId) return text("Tell me which page to read — pass the `path` from a search result (its `path:` line), or an `id`.");
|
|
168
168
|
try {
|
|
169
169
|
const r = await get(`/v2/company/pages/${encodeURIComponent(pageId)}`);
|
|
170
170
|
const p = r?.page;
|
|
@@ -175,14 +175,14 @@ export function createServer() {
|
|
|
175
175
|
const outgoing = p.ingest?.outgoing ?? [];
|
|
176
176
|
const backlinks = p.ingest?.backlinks ?? [];
|
|
177
177
|
if (outgoing.length || backlinks.length) {
|
|
178
|
-
out.push("", "---", "Links (
|
|
178
|
+
out.push("", "---", "Links (read on any of these to traverse):");
|
|
179
179
|
if (outgoing.length) out.push(" → links to: " + outgoing.map((l) => `${l.target}${l.resolved ? "" : " (unresolved)"}`).join(", "));
|
|
180
180
|
if (backlinks.length) out.push(" ← linked from: " + backlinks.map((b) => b.source).join(", "));
|
|
181
181
|
}
|
|
182
182
|
return text(out.join("\n"));
|
|
183
183
|
} catch (e) {
|
|
184
184
|
const msg = String(e?.message || e);
|
|
185
|
-
if (msg.includes("not_found") || msg.includes("(404)")) return text("No page with that id — use
|
|
185
|
+
if (msg.includes("not_found") || msg.includes("(404)")) return text("No page with that id — use search to find the path, then read it.");
|
|
186
186
|
if (msg.includes("forbidden") || msg.includes("(403)")) return text("You don't have access to read that company page.");
|
|
187
187
|
throw e;
|
|
188
188
|
}
|
|
@@ -190,15 +190,15 @@ export function createServer() {
|
|
|
190
190
|
);
|
|
191
191
|
|
|
192
192
|
// ===========================================================================
|
|
193
|
-
// TOOL:
|
|
193
|
+
// TOOL: push — POST /v2/company/pages/batch
|
|
194
194
|
// PUSH one or many files UP into the shared company folders (the personal→company promotion).
|
|
195
195
|
// Hybrid write policy: a write role publishes live; a viewer sends the batch to the company
|
|
196
196
|
// inbox to approve.
|
|
197
197
|
// ===========================================================================
|
|
198
198
|
server.tool(
|
|
199
|
-
"
|
|
199
|
+
"push",
|
|
200
200
|
"PUSH a file (or several) UP into the COMPANY WIKI — promote knowledge from your own work into " +
|
|
201
|
-
"the shared company folders every member's agent can read via
|
|
201
|
+
"the shared company folders every member's agent can read via search. Typically you " +
|
|
202
202
|
"push a personal/working file up so the company has it. All pushed files go into the SAME " +
|
|
203
203
|
"folder (resolved or created once). Name the target with `folder` — a name or path like " +
|
|
204
204
|
"'Projects/Data Centre' (top-level folders are fixed; a bare name becomes a project under " +
|
|
@@ -233,16 +233,16 @@ export function createServer() {
|
|
|
233
233
|
);
|
|
234
234
|
|
|
235
235
|
// ===========================================================================
|
|
236
|
-
// TOOL:
|
|
236
|
+
// TOOL: update — PATCH /v2/company/pages/:id
|
|
237
237
|
// Edit an existing wiki page: replace its content, or append. Re-embeds on the server.
|
|
238
238
|
// ===========================================================================
|
|
239
239
|
server.tool(
|
|
240
|
-
"
|
|
240
|
+
"update",
|
|
241
241
|
"Edit an EXISTING company wiki page — revise it or append to it. Identify the page by `path` " +
|
|
242
|
-
"(the `path:` from a
|
|
242
|
+
"(the `path:` from a search result) or `id`. By default the new `content` REPLACES the " +
|
|
243
243
|
"page; set append:true to add to the end instead (kept for a rolling log or a running doc). " +
|
|
244
244
|
"You can also rename it with `name`. Re-embedded so search picks up the change. For a NEW page " +
|
|
245
|
-
"use
|
|
245
|
+
"use push. Distilled items (from sources) are read-only and can't be edited here.",
|
|
246
246
|
{
|
|
247
247
|
path: z.string().optional().describe("The page path from a search result's `path:` line. Provide this OR id."),
|
|
248
248
|
id: z.string().optional().describe("The page id (advanced). Provide this OR path."),
|
|
@@ -257,7 +257,7 @@ export function createServer() {
|
|
|
257
257
|
if (res.error) return text(res.error);
|
|
258
258
|
pageId = res.id;
|
|
259
259
|
}
|
|
260
|
-
if (!pageId) return text("Tell me which page to edit — pass the `path` from a
|
|
260
|
+
if (!pageId) return text("Tell me which page to edit — pass the `path` from a search result, or an `id`.");
|
|
261
261
|
try {
|
|
262
262
|
const body = {};
|
|
263
263
|
if (append) {
|
|
@@ -271,7 +271,7 @@ export function createServer() {
|
|
|
271
271
|
return text(`Updated "${name ? name.replace(/\.md$/, "") : (path ? String(path).split("/").pop().replace(/\.md$/, "") : pageId)}"${append ? " (appended)" : ""} — re-embedded, searchable now.`);
|
|
272
272
|
} catch (e) {
|
|
273
273
|
const msg = String(e?.message || e);
|
|
274
|
-
if (msg.includes("not_found") || msg.includes("(404)")) return text("No editable page with that id — distilled items are read-only. Use
|
|
274
|
+
if (msg.includes("not_found") || msg.includes("(404)")) return text("No editable page with that id — distilled items are read-only. Use search to find a wiki page.");
|
|
275
275
|
if (msg.includes("forbidden") || msg.includes("(403)")) return text("You don't have access to edit that company page.");
|
|
276
276
|
throw e;
|
|
277
277
|
}
|
|
@@ -279,17 +279,17 @@ export function createServer() {
|
|
|
279
279
|
);
|
|
280
280
|
|
|
281
281
|
// ===========================================================================
|
|
282
|
-
// TOOL:
|
|
282
|
+
// TOOL: create — POST /v2/company/folders
|
|
283
283
|
// Create a folder / nested path in the shared company tree so pages can be filed into it.
|
|
284
284
|
// ===========================================================================
|
|
285
285
|
server.tool(
|
|
286
|
-
"
|
|
286
|
+
"create",
|
|
287
287
|
"Create a folder in the COMPANY WIKI tree so you can file pages into it. The TOP-LEVEL folders " +
|
|
288
288
|
"are FIXED (Company · Projects · Decisions · Companies · People · Raw Documents · Archive) — you " +
|
|
289
289
|
"cannot add new ones. A bare name (or a path not starting with a fixed folder) becomes a PROJECT " +
|
|
290
290
|
"under Projects: 'Agency' → Projects/Agency. To nest under a specific top-level folder, start the " +
|
|
291
291
|
"path with it, e.g. 'Company/Policies' or 'Projects/Data Centre/Specs'. A write role is required; " +
|
|
292
|
-
"the folder appears immediately. Then push files into it with
|
|
292
|
+
"the folder appears immediately. Then push files into it with push using the same " +
|
|
293
293
|
"path. Returns the folder id.",
|
|
294
294
|
{ 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.") },
|
|
295
295
|
async ({ path }) => {
|