@llamaventures/cli 2.7.1 → 2.8.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 +18 -0
- package/bin/llama-mcp.mjs +4 -368
- package/lib/build-manifest.json +2 -2
- package/lib/client.mjs +1 -7
- package/lib/mcp-api-tools.mjs +414 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ this project adheres to [Semantic Versioning](https://semver.org).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.8.0] - 2026-11-12
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Export the API-backed MCP tool catalog from `lib/mcp-api-tools.mjs`.
|
|
14
|
+
`registerApiTools(server, { request, formatError, version, only })` registers
|
|
15
|
+
the Deal, Deal Memory, bootstrap, Page schema, skills, preference and Wiki
|
|
16
|
+
tools against an injected request function, so a remote MCP host can serve
|
|
17
|
+
the same catalog as the stdio server. The module also exports
|
|
18
|
+
`writeDealCommandSchema`. Tools that need local credentials or files stay in
|
|
19
|
+
`llama-mcp`. No tool name, schema or behavior changed for stdio users.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `write_deal` and `wiki_save` descriptions each name the other as the
|
|
24
|
+
alternative destination: a file or HTML about one company is a Deal
|
|
25
|
+
Artifact; knowledge not tied to one company belongs in the Wiki.
|
|
26
|
+
|
|
9
27
|
## [2.7.1] - 2026-11-10
|
|
10
28
|
|
|
11
29
|
### Added
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { buildArtifactReadPath, buildWikiReadPath } from "../lib/content-read.mjs";
|
|
4
3
|
import { createRequire } from "node:module";
|
|
5
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
6
|
import { z } from "zod";
|
|
7
|
+
import { registerApiTools, structuredBootstrapManifest, textResult } from "../lib/mcp-api-tools.mjs";
|
|
8
8
|
import {
|
|
9
9
|
formatErrorForDisplay,
|
|
10
10
|
getAuthHeaders,
|
|
11
11
|
readBriefing,
|
|
12
12
|
request,
|
|
13
13
|
setClientRuntime,
|
|
14
|
-
structuredBootstrapManifest,
|
|
15
14
|
} from "../lib/client.mjs";
|
|
16
15
|
import {
|
|
17
16
|
clearExternalSession,
|
|
@@ -20,18 +19,7 @@ import {
|
|
|
20
19
|
startExternalSession,
|
|
21
20
|
uploadExternalFile,
|
|
22
21
|
} from "../lib/external.mjs";
|
|
23
|
-
import {
|
|
24
|
-
buildDealReadPath,
|
|
25
|
-
buildDealSearchPath,
|
|
26
|
-
verifyDealSearchResult,
|
|
27
|
-
informationReadFilter,
|
|
28
|
-
verifyInformationReadResult,
|
|
29
|
-
compactDealWriteResult,
|
|
30
|
-
prepareDealCommand,
|
|
31
|
-
} from "../lib/deal-actions.mjs";
|
|
32
22
|
import { submitFeedback, readFeedback, resolveFeedback } from "../lib/feedback.mjs";
|
|
33
|
-
import { buildDealMemoryPath } from "../lib/deal-memory-actions.mjs";
|
|
34
|
-
import { buildPageSchemaPath, MAX_PAGE_SCHEMA_FIELDS } from "../lib/page-schema.mjs";
|
|
35
23
|
|
|
36
24
|
const requireFromHere = createRequire(import.meta.url);
|
|
37
25
|
const { version: PKG_VERSION } = requireFromHere("../package.json");
|
|
@@ -39,13 +27,6 @@ setClientRuntime({ client: "mcp" });
|
|
|
39
27
|
|
|
40
28
|
const server = new McpServer({ name: "llama-mcp", version: PKG_VERSION });
|
|
41
29
|
|
|
42
|
-
function textResult(text, isError = false) {
|
|
43
|
-
return {
|
|
44
|
-
content: [{ type: "text", text }],
|
|
45
|
-
...(isError ? { isError: true } : {}),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
30
|
async function callApi(method, path, body, transform = (value) => value) {
|
|
50
31
|
try {
|
|
51
32
|
const result = transform(await request(method, path, body));
|
|
@@ -86,175 +67,9 @@ server.registerTool("feedback_resolve", {
|
|
|
86
67
|
catch (error) { return textResult(formatErrorForDisplay(error), true); }
|
|
87
68
|
});
|
|
88
69
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
originatingChatRecordId: z.string().uuid().optional(),
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const writeDealCommandSchema = z.discriminatedUnion("operation", [
|
|
96
|
-
z.object({
|
|
97
|
-
operation: z.literal("input.submit"),
|
|
98
|
-
dealId: z.string().uuid(),
|
|
99
|
-
format: z.string().min(1).max(120).optional(),
|
|
100
|
-
content: z.any(),
|
|
101
|
-
source: z.record(z.string(), z.any()).optional(),
|
|
102
|
-
origin: originSchema,
|
|
103
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
104
|
-
}).strict(),
|
|
105
|
-
z.object({
|
|
106
|
-
operation: z.literal("information.put"),
|
|
107
|
-
dealId: z.string().uuid(),
|
|
108
|
-
informationId: z.string().uuid().optional(),
|
|
109
|
-
type: z.string().min(1).max(120),
|
|
110
|
-
labels: z.array(z.string()).optional(),
|
|
111
|
-
subject: z.record(z.string(), z.any()).optional(),
|
|
112
|
-
value: z.any(),
|
|
113
|
-
expectedVersion: z.number().int().positive().optional(),
|
|
114
|
-
origin: originSchema,
|
|
115
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
116
|
-
}).strict(),
|
|
117
|
-
z.object({
|
|
118
|
-
operation: z.literal("page.patch"),
|
|
119
|
-
dealId: z.string().uuid(),
|
|
120
|
-
patch: z.record(z.string(), z.any()),
|
|
121
|
-
expectedRevision: z.number().int().nonnegative().optional(),
|
|
122
|
-
origin: originSchema,
|
|
123
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
124
|
-
}).strict(),
|
|
125
|
-
z.object({
|
|
126
|
-
operation: z.literal("artifact.put"),
|
|
127
|
-
dealId: z.string().uuid(),
|
|
128
|
-
artifactId: z.string().uuid().optional().describe("Reuse the existing ID to append a complete file version, including on rename. Omit for a new document without a title collision, or explicitly use a fresh UUID for a distinct same-name source."),
|
|
129
|
-
kind: z.string().min(1).max(120),
|
|
130
|
-
title: z.string().min(1).max(500),
|
|
131
|
-
mimeType: z.string().min(1).max(240),
|
|
132
|
-
contentBase64: z.string().min(1).optional(),
|
|
133
|
-
storageKey: z.string().min(1).optional(),
|
|
134
|
-
storageUrl: z.string().url().optional(),
|
|
135
|
-
byteSize: z.number().int().nonnegative().optional(),
|
|
136
|
-
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
137
|
-
metadata: z.record(z.string(), z.any()).optional(),
|
|
138
|
-
origin: originSchema,
|
|
139
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
140
|
-
}).strict(),
|
|
141
|
-
]);
|
|
142
|
-
|
|
143
|
-
// Deal is deliberately a closed four-tool surface. Do not add split resource
|
|
144
|
-
// tools here: one more tool is one more branch every agent must reason about.
|
|
145
|
-
server.registerTool(
|
|
146
|
-
"search_deals",
|
|
147
|
-
{
|
|
148
|
-
description: "Search current Deal metadata by company name/key, description, sector/subsector or founder name in either stored language. Use short keywords; matching is a case-insensitive literal substring, not semantic search. Combine q with sector/status/state/limit, or use sector alone for category discovery. status filters by exact pipeline status (e.g. [\"Invested\"]); to list every invested Deal use status [\"Invested\"] with limit 200 and order by investmentDate/markedInvestedAt, instead of guessing from names. Sector filters before limit and requires a supporting Core server. Exact names rank first. Inspect searchMatch fields and search.has_more; truncated results are not exhaustive. On zero matches, try broader or alternative wording before concluding no Deal exists. Read candidates to verify specific categories and claims. Always search before creating a company.",
|
|
149
|
-
inputSchema: {
|
|
150
|
-
q: z.string().optional(),
|
|
151
|
-
sector: z.string().trim().min(1).max(200).optional(),
|
|
152
|
-
status: z.array(z.string().min(1).max(60)).min(1).max(10).optional(),
|
|
153
|
-
state: z.enum(["active", "archived", "trashed"]).optional(),
|
|
154
|
-
limit: z.number().int().min(1).max(2000).optional(),
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
// @core-api-operation GET /api/occam/deals
|
|
158
|
-
async ({ q, sector, status, state, limit } = {}) => callApi(
|
|
159
|
-
"GET", buildDealSearchPath(q, { sector, status, state, limit }), undefined,
|
|
160
|
-
(result) => verifyDealSearchResult(result, { sector, status }),
|
|
161
|
-
),
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
server.registerTool(
|
|
165
|
-
"read_deal",
|
|
166
|
-
{
|
|
167
|
-
description: "Read one Deal. Live Page is current human-visible state; Information is memory and never updates Page automatically. Read Information selectively with informationIds (full values, up to 50), informationTypes, and/or informationQuery (literal text; returns match.snippets, and values over 8,000 characters as { omitted, chars }: then read that id). Prefer these over detail=memory, which returns up to ~2 MB. Use artifactId to read original source text with page/paragraph anchors. Continue with nextOffset and source.sha256. Page field semantics come from agent_bootstrap, not every Deal read.",
|
|
168
|
-
inputSchema: {
|
|
169
|
-
dealId: z.string().min(1),
|
|
170
|
-
detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
|
|
171
|
-
artifactId: z.string().uuid().optional(),
|
|
172
|
-
version: z.number().int().min(1).optional(),
|
|
173
|
-
offset: z.number().int().min(0).optional(),
|
|
174
|
-
limit: z.number().int().min(1).max(50000).optional(),
|
|
175
|
-
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
176
|
-
informationIds: z.array(z.string().uuid()).min(1).max(50).optional(),
|
|
177
|
-
informationTypes: z.array(z.string().min(1).max(120)).min(1).max(20).optional(),
|
|
178
|
-
informationQuery: z.string().min(2).max(200).optional(),
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
async ({ dealId, detail, artifactId, informationIds, informationTypes, informationQuery, ...options }) => {
|
|
182
|
-
const filter = informationReadFilter({ ids: informationIds, types: informationTypes, q: informationQuery });
|
|
183
|
-
if (artifactId) {
|
|
184
|
-
if (detail) throw new Error("Use artifactId or detail, not both");
|
|
185
|
-
if (filter) throw new Error("Use artifactId or information filters, not both");
|
|
186
|
-
// @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
|
|
187
|
-
return callApi("GET", buildArtifactReadPath(dealId, artifactId, options));
|
|
188
|
-
}
|
|
189
|
-
if (Object.values(options).some(value => value !== undefined)) throw new Error("Content options require artifactId");
|
|
190
|
-
// @core-api-operation GET /api/occam/deals/{dealId}
|
|
191
|
-
return callApi("GET", buildDealReadPath(dealId, detail || "overview", filter), undefined,
|
|
192
|
-
(result) => verifyInformationReadResult(result, filter));
|
|
193
|
-
},
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
server.registerTool(
|
|
197
|
-
"create_deal",
|
|
198
|
-
{
|
|
199
|
-
description: "Create one Deal intent. Core owns Drive, initial resources, idempotency, and Events.",
|
|
200
|
-
inputSchema: {
|
|
201
|
-
companyName: z.string().min(1).max(240),
|
|
202
|
-
companyKey: z.string().min(1).max(240).optional(),
|
|
203
|
-
page: z.record(z.string(), z.any()).optional(),
|
|
204
|
-
information: z.array(z.record(z.string(), z.any())).optional(),
|
|
205
|
-
origin: originSchema,
|
|
206
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
async (input) => callApi("POST", "/api/occam/deals/commands", prepareDealCommand("create", input)),
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
server.registerTool(
|
|
213
|
-
"write_deal",
|
|
214
|
-
{
|
|
215
|
-
description: "The only Deal mutation tool. Pass one command: input.submit, information.put, page.patch, or artifact.put. Before artifact.put, read Deal files: reuse artifactId for a revision; a same-kind/title collision without an ID returns OCCAM_CONFLICT. Titles are candidates, not identity. Select the intended existing ID from the request/content, or explicitly generate a fresh UUID for a distinct same-name source. Send complete revised bytes, preserve applicable metadata, and verify the ID/version. Core hosts text/html in Command; do not mirror HTML to Drive or require a Drive link. Non-HTML source files, including original transcripts and recordings, go to Drive through artifact.put. Keep source-file bodies out of Input, Information, Page and metadata; save extracted facts and summaries with source references and preserve the user request. page.patch does not edit file bytes. All Page content fields are Agent-writable; author controls attribution, not permission. Before page.patch, use get_live_deal_page_schema for the exact fields being changed and write bilingual Page prose. page.patch is JSON Merge Patch: arrays replace whole arrays, so read-modify-write and preserve sibling slots. A Llama user's own judgment is human_subjective_view.people or human_subjective_view.business and must carry value.speaker, value.rawText quoted verbatim from origin.originalUserUtterance, and value.summary (the agent's restatement); it is rejected otherwise.",
|
|
216
|
-
// MCP SDK 1.30 only publishes object schemas at the tool root. Nesting one
|
|
217
|
-
// command preserves the real discriminated union instead of flattening four
|
|
218
|
-
// incompatible operations into false affordances.
|
|
219
|
-
inputSchema: { command: writeDealCommandSchema },
|
|
220
|
-
},
|
|
221
|
-
async ({ command }) => callApi(
|
|
222
|
-
"POST",
|
|
223
|
-
"/api/occam/deals/commands",
|
|
224
|
-
prepareDealCommand("write", command),
|
|
225
|
-
(result) => compactDealWriteResult(command, result),
|
|
226
|
-
),
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
// Deal Memory is a separate sidecar domain. These do not add an Occam Deal
|
|
230
|
-
// business resource or widen the deliberately closed four-tool Deal surface.
|
|
231
|
-
server.registerTool(
|
|
232
|
-
"get_deal_memory",
|
|
233
|
-
{
|
|
234
|
-
description: "Read the complete canonical Markdown Deal Story and its opaque concurrency version. A returned placeholder is an existing Story whose version is required for a write; only 404 means no Story exists.",
|
|
235
|
-
inputSchema: { dealId: z.string().uuid() },
|
|
236
|
-
},
|
|
237
|
-
// @core-api-operation GET /api/deal-memory/{dealId}/story
|
|
238
|
-
async ({ dealId }) => callApi("GET", buildDealMemoryPath(dealId)),
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
server.registerTool(
|
|
242
|
-
"update_deal_memory",
|
|
243
|
-
{
|
|
244
|
-
description: "Replace the complete Markdown Deal Story with one coherent current understanding; never append an update log, restate Live Deal Page/Deal Information fields, or write when understanding would not materially improve. Frontmatter must contain matching deal_id plus stable uuid and created timestamps; updated must strictly advance. Pass the version from get_deal_memory for every returned Story, including a placeholder; only 404 permits creation without expected_version.",
|
|
245
|
-
inputSchema: {
|
|
246
|
-
dealId: z.string().uuid(),
|
|
247
|
-
markdown: z.string().min(1).max(1_048_576),
|
|
248
|
-
expected_version: z.string().min(1).optional(),
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
// @core-api-operation PUT /api/deal-memory/{dealId}/story
|
|
252
|
-
async ({ dealId, markdown, expected_version }) => callApi(
|
|
253
|
-
"PUT",
|
|
254
|
-
buildDealMemoryPath(dealId),
|
|
255
|
-
{ markdown, ...(expected_version ? { expected_version } : {}) },
|
|
256
|
-
),
|
|
257
|
-
);
|
|
70
|
+
// Deal, Deal Memory, bootstrap, Page schema, skills, preferences and Wiki tools
|
|
71
|
+
// are shared with remote MCP hosts; see lib/mcp-api-tools.mjs.
|
|
72
|
+
registerApiTools(server, { request, formatError: formatErrorForDisplay, version: PKG_VERSION });
|
|
258
73
|
|
|
259
74
|
server.registerTool(
|
|
260
75
|
"auth_status",
|
|
@@ -274,185 +89,6 @@ server.registerTool(
|
|
|
274
89
|
},
|
|
275
90
|
);
|
|
276
91
|
|
|
277
|
-
server.registerTool(
|
|
278
|
-
"agent_bootstrap",
|
|
279
|
-
{
|
|
280
|
-
description: "Mandatory before Llama work: fetch the authenticated two-part private Brain (Investment Framework V3 + Llama Command operating skill), a compact Live Deal Page schema index, runtime contract including Wiki publishing guidance, and visible Llama OS skills. Read exact Page fields separately with get_live_deal_page_schema.",
|
|
281
|
-
inputSchema: { limit: z.number().int().min(1).max(100).optional() },
|
|
282
|
-
},
|
|
283
|
-
async ({ limit } = {}) => {
|
|
284
|
-
const params = new URLSearchParams({ clientVersion: PKG_VERSION });
|
|
285
|
-
params.set("page_schema", "progressive");
|
|
286
|
-
if (limit) params.set("limit", String(limit));
|
|
287
|
-
try {
|
|
288
|
-
const result = await request("GET", `/api/agent/manifest?${params}`);
|
|
289
|
-
return textResult(JSON.stringify(structuredBootstrapManifest(result), null, 2));
|
|
290
|
-
} catch (error) {
|
|
291
|
-
return textResult(formatErrorForDisplay(error), true);
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
server.registerTool(
|
|
297
|
-
"get_live_deal_page_schema",
|
|
298
|
-
{
|
|
299
|
-
description: "Read the Live Deal Page schema progressively. Call with no selector for the compact index; before page.patch, request only exact fields or one section. This is read-only context, not a fifth Deal action.",
|
|
300
|
-
inputSchema: {
|
|
301
|
-
fields: z.array(z.string().min(1)).min(1).max(MAX_PAGE_SCHEMA_FIELDS).optional(),
|
|
302
|
-
section: z.string().min(1).optional(),
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
async ({ fields, section } = {}) => {
|
|
306
|
-
try {
|
|
307
|
-
// @core-api-operation GET /api/agent/page-schema
|
|
308
|
-
return await callApi("GET", buildPageSchemaPath({ fields, section }));
|
|
309
|
-
} catch (error) {
|
|
310
|
-
return textResult(formatErrorForDisplay(error), true);
|
|
311
|
-
}
|
|
312
|
-
},
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
server.registerTool(
|
|
316
|
-
"skills_search",
|
|
317
|
-
{
|
|
318
|
-
description: "Search authenticated runtime skills; read only the relevant result.",
|
|
319
|
-
inputSchema: { q: z.string().min(1), limit: z.number().int().min(1).max(100).optional() },
|
|
320
|
-
},
|
|
321
|
-
async ({ q, limit }) => {
|
|
322
|
-
const params = new URLSearchParams({ q });
|
|
323
|
-
if (limit) params.set("limit", String(limit));
|
|
324
|
-
return callApi("GET", `/api/agent/skills?${params}`);
|
|
325
|
-
},
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
server.registerTool(
|
|
329
|
-
"skills_read",
|
|
330
|
-
{
|
|
331
|
-
description: "Read one authenticated runtime skill by slug.",
|
|
332
|
-
inputSchema: { slug: z.string().min(1) },
|
|
333
|
-
},
|
|
334
|
-
async ({ slug }) => callApi("GET", `/api/agent/skills/${encodeURIComponent(slug)}`),
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
server.registerTool(
|
|
338
|
-
"pref_list",
|
|
339
|
-
{
|
|
340
|
-
description: "List standing agent preferences.",
|
|
341
|
-
inputSchema: { status: z.enum(["active", "proposed", "retired", "all"]).optional() },
|
|
342
|
-
},
|
|
343
|
-
async ({ status } = {}) => {
|
|
344
|
-
const params = new URLSearchParams();
|
|
345
|
-
if (status) params.set("status", status);
|
|
346
|
-
return callApi("GET", `/api/agent/preferences${params.size ? `?${params}` : ""}`);
|
|
347
|
-
},
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
server.registerTool(
|
|
351
|
-
"pref_add",
|
|
352
|
-
{
|
|
353
|
-
description: "Save a durable user or team preference; procedures belong in skills.",
|
|
354
|
-
inputSchema: {
|
|
355
|
-
key: z.string().min(1),
|
|
356
|
-
content: z.string().min(1).max(280),
|
|
357
|
-
scope: z.enum(["user", "team"]).optional(),
|
|
358
|
-
evidence: z.string().optional(),
|
|
359
|
-
},
|
|
360
|
-
},
|
|
361
|
-
async ({ key, content, scope, evidence }) =>
|
|
362
|
-
callApi("POST", "/api/agent/preferences", { key, content, scope, evidence }),
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
server.registerTool(
|
|
366
|
-
"pref_set_status",
|
|
367
|
-
{
|
|
368
|
-
description: "Activate or retire a standing preference.",
|
|
369
|
-
inputSchema: { id: z.number().int().positive(), status: z.enum(["active", "retired"]) },
|
|
370
|
-
},
|
|
371
|
-
async ({ id, status }) => callApi("PATCH", `/api/agent/preferences/${id}`, { status }),
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
server.registerTool(
|
|
375
|
-
"object_inspect",
|
|
376
|
-
{
|
|
377
|
-
description: "Explain a Command URL or object before guessing that it is broken.",
|
|
378
|
-
inputSchema: {
|
|
379
|
-
q: z.string().optional(),
|
|
380
|
-
type: z.string().optional(),
|
|
381
|
-
id: z.string().optional(),
|
|
382
|
-
lang: z.enum(["en", "zh"]).optional(),
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
async ({ q, type, id, lang } = {}) => {
|
|
386
|
-
const params = new URLSearchParams();
|
|
387
|
-
if (q) params.set("q", q);
|
|
388
|
-
if (type) params.set("type", type);
|
|
389
|
-
if (id) params.set("id", id);
|
|
390
|
-
if (lang) params.set("lang", lang);
|
|
391
|
-
return callApi("GET", `/api/agent/explain?${params}`);
|
|
392
|
-
},
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
server.registerTool(
|
|
396
|
-
"wiki_search",
|
|
397
|
-
{
|
|
398
|
-
description: "Search institutional knowledge in the internal Wiki.",
|
|
399
|
-
inputSchema: { q: z.string().min(1) },
|
|
400
|
-
},
|
|
401
|
-
async ({ q }) => callApi("GET", `/api/wiki/search?q=${encodeURIComponent(q)}`),
|
|
402
|
-
);
|
|
403
|
-
|
|
404
|
-
server.registerTool(
|
|
405
|
-
"wiki_read",
|
|
406
|
-
{
|
|
407
|
-
description: "Read one Wiki article. format=text extracts its original upload or page body. attachment follows an ID listed in attachments. Continuations require offset and sha256.",
|
|
408
|
-
inputSchema: {
|
|
409
|
-
slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional(),
|
|
410
|
-
format: z.literal("text").optional(), attachment: z.string().optional(),
|
|
411
|
-
offset: z.number().int().min(0).optional(), limit: z.number().int().min(1).max(50000).optional(),
|
|
412
|
-
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
413
|
-
},
|
|
414
|
-
},
|
|
415
|
-
// @core-api-operation GET /api/wiki/{slug}
|
|
416
|
-
async ({ slug, ...options }) => callApi("GET", buildWikiReadPath(slug, options)),
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
server.registerTool(
|
|
420
|
-
"wiki_save",
|
|
421
|
-
{
|
|
422
|
-
description: "Publish institutional knowledge when authorized. Follow agent_bootstrap contract.wiki_publishing. New pages default to complete self-contained HTML with embedded rendering assets and full report content/interactions; set content_type=html. Honor explicit Markdown requests. Read the same slug/lang before edits and preserve its format unless conversion is authorized. Verify the receipt, canonical page and interactions before reporting completion.",
|
|
423
|
-
inputSchema: {
|
|
424
|
-
slug: z.string().min(1),
|
|
425
|
-
title: z.string().min(1),
|
|
426
|
-
content: z.string().describe("Complete page source. For HTML, include the full document and embedded rendering dependencies."),
|
|
427
|
-
sources: z.array(z.string()).min(1),
|
|
428
|
-
content_type: z.enum(["markdown", "html"]).optional().describe("Set html for HTML publications. Use markdown for an explicit Markdown request or an existing Markdown article."),
|
|
429
|
-
lang: z.enum(["en", "zh"]).optional().describe("Article language; Core defaults to en when omitted."),
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
async ({ slug, title, content, sources, content_type, lang }) =>
|
|
433
|
-
callApi("POST", "/api/wiki/save", { slug, title, content, sources, content_type, lang }),
|
|
434
|
-
);
|
|
435
|
-
|
|
436
|
-
server.registerTool(
|
|
437
|
-
"wiki_delete",
|
|
438
|
-
{
|
|
439
|
-
description: "Soft-delete a Wiki article.",
|
|
440
|
-
inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
|
|
441
|
-
},
|
|
442
|
-
async ({ slug, lang }) =>
|
|
443
|
-
callApi("DELETE", `/api/wiki/${encodeURIComponent(slug)}?lang=${lang === "zh" ? "zh" : "en"}`),
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
server.registerTool(
|
|
447
|
-
"wiki_restore",
|
|
448
|
-
{
|
|
449
|
-
description: "Restore a soft-deleted Wiki article.",
|
|
450
|
-
inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
|
|
451
|
-
},
|
|
452
|
-
async ({ slug, lang }) =>
|
|
453
|
-
callApi("POST", `/api/wiki/${encodeURIComponent(slug)}/restore?lang=${lang === "zh" ? "zh" : "en"}`),
|
|
454
|
-
);
|
|
455
|
-
|
|
456
92
|
server.registerTool(
|
|
457
93
|
"pitch_start",
|
|
458
94
|
{
|
package/lib/build-manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "llama.cli-build.v1",
|
|
3
3
|
"packageName": "@llamaventures/cli",
|
|
4
|
-
"packageVersion": "2.
|
|
5
|
-
"sourceSha": "
|
|
4
|
+
"packageVersion": "2.8.0",
|
|
5
|
+
"sourceSha": "ec9daefda51090ca3331a22db1db45b3c56c3452",
|
|
6
6
|
"sourceKind": "github",
|
|
7
7
|
"sourceDirty": false,
|
|
8
8
|
"coreApiContract": {
|
package/lib/client.mjs
CHANGED
|
@@ -594,13 +594,7 @@ export function formatErrorForDisplay(error) {
|
|
|
594
594
|
return `Error${code}: ${message}`;
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
export
|
|
598
|
-
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
599
|
-
return manifest;
|
|
600
|
-
}
|
|
601
|
-
const { briefing: _renderedDuplicate, ...structured } = manifest;
|
|
602
|
-
return structured;
|
|
603
|
-
}
|
|
597
|
+
export { structuredBootstrapManifest } from "./mcp-api-tools.mjs";
|
|
604
598
|
|
|
605
599
|
async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
606
600
|
const authHeaders = await getAuthHeaders();
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
// The API-backed Llama tool catalog, independent of transport and of how the
|
|
2
|
+
// caller authenticates. The stdio server (bin/llama-mcp.mjs) passes the local
|
|
3
|
+
// HTTP client; a remote MCP endpoint hosted by Llama Command passes its own
|
|
4
|
+
// in-process request function. Nothing here may touch local credentials,
|
|
5
|
+
// local files, or process state: tools that need those stay in bin/.
|
|
6
|
+
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { buildArtifactReadPath, buildWikiReadPath } from "./content-read.mjs";
|
|
9
|
+
import {
|
|
10
|
+
buildDealReadPath,
|
|
11
|
+
buildDealSearchPath,
|
|
12
|
+
verifyDealSearchResult,
|
|
13
|
+
informationReadFilter,
|
|
14
|
+
verifyInformationReadResult,
|
|
15
|
+
compactDealWriteResult,
|
|
16
|
+
prepareDealCommand,
|
|
17
|
+
} from "./deal-actions.mjs";
|
|
18
|
+
import { buildDealMemoryPath } from "./deal-memory-actions.mjs";
|
|
19
|
+
import { buildPageSchemaPath, MAX_PAGE_SCHEMA_FIELDS } from "./page-schema.mjs";
|
|
20
|
+
|
|
21
|
+
export function textResult(text, isError = false) {
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text }],
|
|
24
|
+
...(isError ? { isError: true } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The manifest also carries a rendered duplicate of itself; tools return the structured half. */
|
|
29
|
+
export function structuredBootstrapManifest(manifest) {
|
|
30
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
31
|
+
return manifest;
|
|
32
|
+
}
|
|
33
|
+
const { briefing: _renderedDuplicate, ...structured } = manifest;
|
|
34
|
+
return structured;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const originSchema = z.object({
|
|
38
|
+
kind: z.enum(["user", "agent", "system"]),
|
|
39
|
+
originalUserUtterance: z.string().min(1).optional(),
|
|
40
|
+
originatingChatRecordId: z.string().uuid().optional(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const writeDealCommandSchema = z.discriminatedUnion("operation", [
|
|
44
|
+
z.object({
|
|
45
|
+
operation: z.literal("input.submit"),
|
|
46
|
+
dealId: z.string().uuid(),
|
|
47
|
+
format: z.string().min(1).max(120).optional(),
|
|
48
|
+
content: z.any(),
|
|
49
|
+
source: z.record(z.string(), z.any()).optional(),
|
|
50
|
+
origin: originSchema,
|
|
51
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
52
|
+
}).strict(),
|
|
53
|
+
z.object({
|
|
54
|
+
operation: z.literal("information.put"),
|
|
55
|
+
dealId: z.string().uuid(),
|
|
56
|
+
informationId: z.string().uuid().optional(),
|
|
57
|
+
type: z.string().min(1).max(120),
|
|
58
|
+
labels: z.array(z.string()).optional(),
|
|
59
|
+
subject: z.record(z.string(), z.any()).optional(),
|
|
60
|
+
value: z.any(),
|
|
61
|
+
expectedVersion: z.number().int().positive().optional(),
|
|
62
|
+
origin: originSchema,
|
|
63
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
64
|
+
}).strict(),
|
|
65
|
+
z.object({
|
|
66
|
+
operation: z.literal("page.patch"),
|
|
67
|
+
dealId: z.string().uuid(),
|
|
68
|
+
patch: z.record(z.string(), z.any()),
|
|
69
|
+
expectedRevision: z.number().int().nonnegative().optional(),
|
|
70
|
+
origin: originSchema,
|
|
71
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
72
|
+
}).strict(),
|
|
73
|
+
z.object({
|
|
74
|
+
operation: z.literal("artifact.put"),
|
|
75
|
+
dealId: z.string().uuid(),
|
|
76
|
+
artifactId: z.string().uuid().optional().describe("Reuse the existing ID to append a complete file version, including on rename. Omit for a new document without a title collision, or explicitly use a fresh UUID for a distinct same-name source."),
|
|
77
|
+
kind: z.string().min(1).max(120),
|
|
78
|
+
title: z.string().min(1).max(500),
|
|
79
|
+
mimeType: z.string().min(1).max(240),
|
|
80
|
+
contentBase64: z.string().min(1).optional(),
|
|
81
|
+
storageKey: z.string().min(1).optional(),
|
|
82
|
+
storageUrl: z.string().url().optional(),
|
|
83
|
+
byteSize: z.number().int().nonnegative().optional(),
|
|
84
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
85
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
86
|
+
origin: originSchema,
|
|
87
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
88
|
+
}).strict(),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Register the API-backed tools on an MCP server.
|
|
93
|
+
*
|
|
94
|
+
* @param server an McpServer
|
|
95
|
+
* @param deps.request async (method, path, body) => parsed JSON; throws on failure
|
|
96
|
+
* @param deps.formatError (error) => string shown to the agent
|
|
97
|
+
* @param deps.version client version reported to bootstrap
|
|
98
|
+
* @param deps.only optional list of tool names; others are not registered
|
|
99
|
+
*/
|
|
100
|
+
export function registerApiTools(server, { request, formatError, version, only } = {}) {
|
|
101
|
+
if (typeof request !== "function") throw new Error("registerApiTools requires a request function");
|
|
102
|
+
const describeError = formatError ?? ((error) => `Error: ${error?.message ?? String(error)}`);
|
|
103
|
+
const formatErrorForDisplay = describeError;
|
|
104
|
+
const allowed = only ? new Set(only) : null;
|
|
105
|
+
const registerTool = (name, ...rest) => {
|
|
106
|
+
if (allowed && !allowed.has(name)) return;
|
|
107
|
+
server.registerTool(name, ...rest);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
async function callApi(method, path, body, transform = (value) => value) {
|
|
111
|
+
try {
|
|
112
|
+
const result = transform(await request(method, path, body));
|
|
113
|
+
return textResult(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return textResult(describeError(error), true);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
// Deal is deliberately a closed four-tool surface. Do not add split resource
|
|
121
|
+
// tools here: one more tool is one more branch every agent must reason about.
|
|
122
|
+
registerTool(
|
|
123
|
+
"search_deals",
|
|
124
|
+
{
|
|
125
|
+
description: "Search current Deal metadata by company name/key, description, sector/subsector or founder name in either stored language. Use short keywords; matching is a case-insensitive literal substring, not semantic search. Combine q with sector/status/state/limit, or use sector alone for category discovery. status filters by exact pipeline status (e.g. [\"Invested\"]); to list every invested Deal use status [\"Invested\"] with limit 200 and order by investmentDate/markedInvestedAt, instead of guessing from names. Sector filters before limit and requires a supporting Core server. Exact names rank first. Inspect searchMatch fields and search.has_more; truncated results are not exhaustive. On zero matches, try broader or alternative wording before concluding no Deal exists. Read candidates to verify specific categories and claims. Always search before creating a company.",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
q: z.string().optional(),
|
|
128
|
+
sector: z.string().trim().min(1).max(200).optional(),
|
|
129
|
+
status: z.array(z.string().min(1).max(60)).min(1).max(10).optional(),
|
|
130
|
+
state: z.enum(["active", "archived", "trashed"]).optional(),
|
|
131
|
+
limit: z.number().int().min(1).max(2000).optional(),
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
// @core-api-operation GET /api/occam/deals
|
|
135
|
+
async ({ q, sector, status, state, limit } = {}) => callApi(
|
|
136
|
+
"GET", buildDealSearchPath(q, { sector, status, state, limit }), undefined,
|
|
137
|
+
(result) => verifyDealSearchResult(result, { sector, status }),
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
registerTool(
|
|
142
|
+
"read_deal",
|
|
143
|
+
{
|
|
144
|
+
description: "Read one Deal. Live Page is current human-visible state; Information is memory and never updates Page automatically. Read Information selectively with informationIds (full values, up to 50), informationTypes, and/or informationQuery (literal text; returns match.snippets, and values over 8,000 characters as { omitted, chars }: then read that id). Prefer these over detail=memory, which returns up to ~2 MB. Use artifactId to read original source text with page/paragraph anchors. Continue with nextOffset and source.sha256. Page field semantics come from agent_bootstrap, not every Deal read.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
dealId: z.string().min(1),
|
|
147
|
+
detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
|
|
148
|
+
artifactId: z.string().uuid().optional(),
|
|
149
|
+
version: z.number().int().min(1).optional(),
|
|
150
|
+
offset: z.number().int().min(0).optional(),
|
|
151
|
+
limit: z.number().int().min(1).max(50000).optional(),
|
|
152
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
153
|
+
informationIds: z.array(z.string().uuid()).min(1).max(50).optional(),
|
|
154
|
+
informationTypes: z.array(z.string().min(1).max(120)).min(1).max(20).optional(),
|
|
155
|
+
informationQuery: z.string().min(2).max(200).optional(),
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
async ({ dealId, detail, artifactId, informationIds, informationTypes, informationQuery, ...options }) => {
|
|
159
|
+
const filter = informationReadFilter({ ids: informationIds, types: informationTypes, q: informationQuery });
|
|
160
|
+
if (artifactId) {
|
|
161
|
+
if (detail) throw new Error("Use artifactId or detail, not both");
|
|
162
|
+
if (filter) throw new Error("Use artifactId or information filters, not both");
|
|
163
|
+
// @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
|
|
164
|
+
return callApi("GET", buildArtifactReadPath(dealId, artifactId, options));
|
|
165
|
+
}
|
|
166
|
+
if (Object.values(options).some(value => value !== undefined)) throw new Error("Content options require artifactId");
|
|
167
|
+
// @core-api-operation GET /api/occam/deals/{dealId}
|
|
168
|
+
return callApi("GET", buildDealReadPath(dealId, detail || "overview", filter), undefined,
|
|
169
|
+
(result) => verifyInformationReadResult(result, filter));
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
registerTool(
|
|
174
|
+
"create_deal",
|
|
175
|
+
{
|
|
176
|
+
description: "Create one Deal intent. Core owns Drive, initial resources, idempotency, and Events.",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
companyName: z.string().min(1).max(240),
|
|
179
|
+
companyKey: z.string().min(1).max(240).optional(),
|
|
180
|
+
page: z.record(z.string(), z.any()).optional(),
|
|
181
|
+
information: z.array(z.record(z.string(), z.any())).optional(),
|
|
182
|
+
origin: originSchema,
|
|
183
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
async (input) => callApi("POST", "/api/occam/deals/commands", prepareDealCommand("create", input)),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
registerTool(
|
|
190
|
+
"write_deal",
|
|
191
|
+
{
|
|
192
|
+
description: "The only Deal mutation tool. Pass one command: input.submit, information.put, page.patch, or artifact.put. A file or HTML about one company belongs here as artifact.put; standalone knowledge not tied to one company belongs in wiki_save. Before artifact.put, read Deal files: reuse artifactId for a revision; a same-kind/title collision without an ID returns OCCAM_CONFLICT. Titles are candidates, not identity. Select the intended existing ID from the request/content, or explicitly generate a fresh UUID for a distinct same-name source. Send complete revised bytes, preserve applicable metadata, and verify the ID/version. Core hosts text/html in Command; do not mirror HTML to Drive or require a Drive link. Non-HTML source files, including original transcripts and recordings, go to Drive through artifact.put. Keep source-file bodies out of Input, Information, Page and metadata; save extracted facts and summaries with source references and preserve the user request. page.patch does not edit file bytes. All Page content fields are Agent-writable; author controls attribution, not permission. Before page.patch, use get_live_deal_page_schema for the exact fields being changed and write bilingual Page prose. page.patch is JSON Merge Patch: arrays replace whole arrays, so read-modify-write and preserve sibling slots. A Llama user's own judgment is human_subjective_view.people or human_subjective_view.business and must carry value.speaker, value.rawText quoted verbatim from origin.originalUserUtterance, and value.summary (the agent's restatement); it is rejected otherwise.",
|
|
193
|
+
// MCP SDK 1.30 only publishes object schemas at the tool root. Nesting one
|
|
194
|
+
// command preserves the real discriminated union instead of flattening four
|
|
195
|
+
// incompatible operations into false affordances.
|
|
196
|
+
inputSchema: { command: writeDealCommandSchema },
|
|
197
|
+
},
|
|
198
|
+
async ({ command }) => callApi(
|
|
199
|
+
"POST",
|
|
200
|
+
"/api/occam/deals/commands",
|
|
201
|
+
prepareDealCommand("write", command),
|
|
202
|
+
(result) => compactDealWriteResult(command, result),
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Deal Memory is a separate sidecar domain. These do not add an Occam Deal
|
|
207
|
+
// business resource or widen the deliberately closed four-tool Deal surface.
|
|
208
|
+
registerTool(
|
|
209
|
+
"get_deal_memory",
|
|
210
|
+
{
|
|
211
|
+
description: "Read the complete canonical Markdown Deal Story and its opaque concurrency version. A returned placeholder is an existing Story whose version is required for a write; only 404 means no Story exists.",
|
|
212
|
+
inputSchema: { dealId: z.string().uuid() },
|
|
213
|
+
},
|
|
214
|
+
// @core-api-operation GET /api/deal-memory/{dealId}/story
|
|
215
|
+
async ({ dealId }) => callApi("GET", buildDealMemoryPath(dealId)),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
registerTool(
|
|
219
|
+
"update_deal_memory",
|
|
220
|
+
{
|
|
221
|
+
description: "Replace the complete Markdown Deal Story with one coherent current understanding; never append an update log, restate Live Deal Page/Deal Information fields, or write when understanding would not materially improve. Frontmatter must contain matching deal_id plus stable uuid and created timestamps; updated must strictly advance. Pass the version from get_deal_memory for every returned Story, including a placeholder; only 404 permits creation without expected_version.",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
dealId: z.string().uuid(),
|
|
224
|
+
markdown: z.string().min(1).max(1_048_576),
|
|
225
|
+
expected_version: z.string().min(1).optional(),
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
// @core-api-operation PUT /api/deal-memory/{dealId}/story
|
|
229
|
+
async ({ dealId, markdown, expected_version }) => callApi(
|
|
230
|
+
"PUT",
|
|
231
|
+
buildDealMemoryPath(dealId),
|
|
232
|
+
{ markdown, ...(expected_version ? { expected_version } : {}) },
|
|
233
|
+
),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
registerTool(
|
|
237
|
+
"agent_bootstrap",
|
|
238
|
+
{
|
|
239
|
+
description: "Mandatory before Llama work: fetch the authenticated two-part private Brain (Investment Framework V3 + Llama Command operating skill), a compact Live Deal Page schema index, runtime contract including Wiki publishing guidance, and visible Llama OS skills. Read exact Page fields separately with get_live_deal_page_schema.",
|
|
240
|
+
inputSchema: { limit: z.number().int().min(1).max(100).optional() },
|
|
241
|
+
},
|
|
242
|
+
async ({ limit } = {}) => {
|
|
243
|
+
const params = new URLSearchParams({ clientVersion: version });
|
|
244
|
+
params.set("page_schema", "progressive");
|
|
245
|
+
if (limit) params.set("limit", String(limit));
|
|
246
|
+
try {
|
|
247
|
+
const result = await request("GET", `/api/agent/manifest?${params}`);
|
|
248
|
+
return textResult(JSON.stringify(structuredBootstrapManifest(result), null, 2));
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return textResult(formatErrorForDisplay(error), true);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
registerTool(
|
|
256
|
+
"get_live_deal_page_schema",
|
|
257
|
+
{
|
|
258
|
+
description: "Read the Live Deal Page schema progressively. Call with no selector for the compact index; before page.patch, request only exact fields or one section. This is read-only context, not a fifth Deal action.",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
fields: z.array(z.string().min(1)).min(1).max(MAX_PAGE_SCHEMA_FIELDS).optional(),
|
|
261
|
+
section: z.string().min(1).optional(),
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
async ({ fields, section } = {}) => {
|
|
265
|
+
try {
|
|
266
|
+
// @core-api-operation GET /api/agent/page-schema
|
|
267
|
+
return await callApi("GET", buildPageSchemaPath({ fields, section }));
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return textResult(formatErrorForDisplay(error), true);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
registerTool(
|
|
275
|
+
"skills_search",
|
|
276
|
+
{
|
|
277
|
+
description: "Search authenticated runtime skills; read only the relevant result.",
|
|
278
|
+
inputSchema: { q: z.string().min(1), limit: z.number().int().min(1).max(100).optional() },
|
|
279
|
+
},
|
|
280
|
+
async ({ q, limit }) => {
|
|
281
|
+
const params = new URLSearchParams({ q });
|
|
282
|
+
if (limit) params.set("limit", String(limit));
|
|
283
|
+
return callApi("GET", `/api/agent/skills?${params}`);
|
|
284
|
+
},
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
registerTool(
|
|
288
|
+
"skills_read",
|
|
289
|
+
{
|
|
290
|
+
description: "Read one authenticated runtime skill by slug.",
|
|
291
|
+
inputSchema: { slug: z.string().min(1) },
|
|
292
|
+
},
|
|
293
|
+
async ({ slug }) => callApi("GET", `/api/agent/skills/${encodeURIComponent(slug)}`),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
registerTool(
|
|
297
|
+
"pref_list",
|
|
298
|
+
{
|
|
299
|
+
description: "List standing agent preferences.",
|
|
300
|
+
inputSchema: { status: z.enum(["active", "proposed", "retired", "all"]).optional() },
|
|
301
|
+
},
|
|
302
|
+
async ({ status } = {}) => {
|
|
303
|
+
const params = new URLSearchParams();
|
|
304
|
+
if (status) params.set("status", status);
|
|
305
|
+
return callApi("GET", `/api/agent/preferences${params.size ? `?${params}` : ""}`);
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
registerTool(
|
|
310
|
+
"pref_add",
|
|
311
|
+
{
|
|
312
|
+
description: "Save a durable user or team preference; procedures belong in skills.",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
key: z.string().min(1),
|
|
315
|
+
content: z.string().min(1).max(280),
|
|
316
|
+
scope: z.enum(["user", "team"]).optional(),
|
|
317
|
+
evidence: z.string().optional(),
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
async ({ key, content, scope, evidence }) =>
|
|
321
|
+
callApi("POST", "/api/agent/preferences", { key, content, scope, evidence }),
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
registerTool(
|
|
325
|
+
"pref_set_status",
|
|
326
|
+
{
|
|
327
|
+
description: "Activate or retire a standing preference.",
|
|
328
|
+
inputSchema: { id: z.number().int().positive(), status: z.enum(["active", "retired"]) },
|
|
329
|
+
},
|
|
330
|
+
async ({ id, status }) => callApi("PATCH", `/api/agent/preferences/${id}`, { status }),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
registerTool(
|
|
334
|
+
"object_inspect",
|
|
335
|
+
{
|
|
336
|
+
description: "Explain a Command URL or object before guessing that it is broken.",
|
|
337
|
+
inputSchema: {
|
|
338
|
+
q: z.string().optional(),
|
|
339
|
+
type: z.string().optional(),
|
|
340
|
+
id: z.string().optional(),
|
|
341
|
+
lang: z.enum(["en", "zh"]).optional(),
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
async ({ q, type, id, lang } = {}) => {
|
|
345
|
+
const params = new URLSearchParams();
|
|
346
|
+
if (q) params.set("q", q);
|
|
347
|
+
if (type) params.set("type", type);
|
|
348
|
+
if (id) params.set("id", id);
|
|
349
|
+
if (lang) params.set("lang", lang);
|
|
350
|
+
return callApi("GET", `/api/agent/explain?${params}`);
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
registerTool(
|
|
355
|
+
"wiki_search",
|
|
356
|
+
{
|
|
357
|
+
description: "Search institutional knowledge in the internal Wiki.",
|
|
358
|
+
inputSchema: { q: z.string().min(1) },
|
|
359
|
+
},
|
|
360
|
+
async ({ q }) => callApi("GET", `/api/wiki/search?q=${encodeURIComponent(q)}`),
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
registerTool(
|
|
364
|
+
"wiki_read",
|
|
365
|
+
{
|
|
366
|
+
description: "Read one Wiki article. format=text extracts its original upload or page body. attachment follows an ID listed in attachments. Continuations require offset and sha256.",
|
|
367
|
+
inputSchema: {
|
|
368
|
+
slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional(),
|
|
369
|
+
format: z.literal("text").optional(), attachment: z.string().optional(),
|
|
370
|
+
offset: z.number().int().min(0).optional(), limit: z.number().int().min(1).max(50000).optional(),
|
|
371
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
// @core-api-operation GET /api/wiki/{slug}
|
|
375
|
+
async ({ slug, ...options }) => callApi("GET", buildWikiReadPath(slug, options)),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
registerTool(
|
|
379
|
+
"wiki_save",
|
|
380
|
+
{
|
|
381
|
+
description: "Publish institutional knowledge when authorized. For knowledge not tied to one company; a file or HTML about one company is a Deal Artifact (write_deal artifact.put), unless the user names the Wiki. Follow agent_bootstrap contract.wiki_publishing. New pages default to complete self-contained HTML with embedded rendering assets and full report content/interactions; set content_type=html. Honor explicit Markdown requests. Read the same slug/lang before edits and preserve its format unless conversion is authorized. Verify the receipt, canonical page and interactions before reporting completion.",
|
|
382
|
+
inputSchema: {
|
|
383
|
+
slug: z.string().min(1),
|
|
384
|
+
title: z.string().min(1),
|
|
385
|
+
content: z.string().describe("Complete page source. For HTML, include the full document and embedded rendering dependencies."),
|
|
386
|
+
sources: z.array(z.string()).min(1),
|
|
387
|
+
content_type: z.enum(["markdown", "html"]).optional().describe("Set html for HTML publications. Use markdown for an explicit Markdown request or an existing Markdown article."),
|
|
388
|
+
lang: z.enum(["en", "zh"]).optional().describe("Article language; Core defaults to en when omitted."),
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
async ({ slug, title, content, sources, content_type, lang }) =>
|
|
392
|
+
callApi("POST", "/api/wiki/save", { slug, title, content, sources, content_type, lang }),
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
registerTool(
|
|
396
|
+
"wiki_delete",
|
|
397
|
+
{
|
|
398
|
+
description: "Soft-delete a Wiki article.",
|
|
399
|
+
inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
|
|
400
|
+
},
|
|
401
|
+
async ({ slug, lang }) =>
|
|
402
|
+
callApi("DELETE", `/api/wiki/${encodeURIComponent(slug)}?lang=${lang === "zh" ? "zh" : "en"}`),
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
registerTool(
|
|
406
|
+
"wiki_restore",
|
|
407
|
+
{
|
|
408
|
+
description: "Restore a soft-deleted Wiki article.",
|
|
409
|
+
inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
|
|
410
|
+
},
|
|
411
|
+
async ({ slug, lang }) =>
|
|
412
|
+
callApi("POST", `/api/wiki/${encodeURIComponent(slug)}/restore?lang=${lang === "zh" ? "zh" : "en"}`),
|
|
413
|
+
);
|
|
414
|
+
}
|