@kynver-app/mcp-agent-os 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,6 +30,10 @@ Schemas are mirrored in `lib/mcp/tool-manifests/kynver-mcp-agent-os.json` in
30
30
  the Kynver repo (used as the static fallback when stdio spawn isn't available
31
31
  — e.g. on Vercel).
32
32
 
33
+ Memory write/update tools accept `sourceRefs` for structured provenance. Use it
34
+ to attach repo paths, commits, PRs, map files, sessions, tools, or URLs to
35
+ important memories instead of burying those pointers in freeform text.
36
+
33
37
  ## Env
34
38
 
35
39
  | Var | Purpose |
package/dist/server.d.ts CHANGED
@@ -2,12 +2,9 @@
2
2
  * Kynver Agentic OS MCP server: slug-keyed agent identity, goals, projects,
3
3
  * sessions, contacts, and long-term memory.
4
4
  *
5
- * Each tool proxies to `/api/agent-os/{slug}/*` on Kynver those routes are
6
- * admin-only. The `slug` argument is optional on every tool; if omitted, it
7
- * falls back to `KYNVER_AGENT_OS_SLUG` env, then to `"ghost"`. This is the
8
- * twelve-tool `agent_os_*` family — previously colocated with `kynver-mcp-analyst`,
9
- * split into its own package so each AgentOS deployment can mount this server
10
- * independently from the analyst surface.
5
+ * Each tool proxies to `/api/agent-os/{slug}/*` on Kynver. The optional `slug`
6
+ * argument resolves in order: explicit arg `KYNVER_AGENT_OS_SLUG` cached
7
+ * `GET /api/agent-os` `{ primarySlug }` list first item → legacy `"ghost"`.
11
8
  */
12
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
10
  export declare function createAgentOsServer(): McpServer;
package/dist/server.js CHANGED
@@ -2,49 +2,88 @@
2
2
  * Kynver Agentic OS MCP server: slug-keyed agent identity, goals, projects,
3
3
  * sessions, contacts, and long-term memory.
4
4
  *
5
- * Each tool proxies to `/api/agent-os/{slug}/*` on Kynver those routes are
6
- * admin-only. The `slug` argument is optional on every tool; if omitted, it
7
- * falls back to `KYNVER_AGENT_OS_SLUG` env, then to `"ghost"`. This is the
8
- * twelve-tool `agent_os_*` family — previously colocated with `kynver-mcp-analyst`,
9
- * split into its own package so each AgentOS deployment can mount this server
10
- * independently from the analyst surface.
5
+ * Each tool proxies to `/api/agent-os/{slug}/*` on Kynver. The optional `slug`
6
+ * argument resolves in order: explicit arg `KYNVER_AGENT_OS_SLUG` cached
7
+ * `GET /api/agent-os` `{ primarySlug }` list first item → legacy `"ghost"`.
11
8
  */
12
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
10
  import { z } from "zod";
14
11
  import { get, post, patch } from "./lib/kynver-client.js";
15
- function toolResult(text) {
16
- return { content: [{ type: "text", text }] };
17
- }
18
12
  function jsonResult(data) {
19
- return toolResult(JSON.stringify(data, null, 2));
13
+ return {
14
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
15
+ structuredContent: data,
16
+ };
20
17
  }
18
+ const MCP_LEGACY_SLUG_FALLBACK = "ghost";
21
19
  export function createAgentOsServer() {
22
- const server = new McpServer({ name: "kynver-mcp-agent-os", version: "0.1.0" }, { capabilities: { tools: {} } });
23
- // The deprecated `server.tool(name, desc, zodShape, handler)` overloads
24
- // generate a heavy generic type graph that OOMs tsc when many tools share
25
- // one file. We use `registerTool` with an `as any` cast — same pattern the
26
- // estimator/contents packages already use — so the build stays fast.
20
+ const server = new McpServer({ name: "kynver-mcp-agent-os", version: "0.2.2" }, { capabilities: { tools: {} } });
27
21
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
22
  function register(name, description, inputSchema, cb) {
29
23
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
24
  server.registerTool(name, { description, inputSchema }, cb);
31
25
  }
32
- // ─── Agentic OS Tools ──────────────────────────────────────────────
33
- // Proxy to /api/agent-os/{slug}/* (admin-only on Kynver). Each tool takes
34
- // an optional `slug` (the AgentOS slug — Ghost, Aria, etc.); if omitted it
35
- // falls back to KYNVER_AGENT_OS_SLUG, then "ghost". The Ghost-scoped
36
- // `agent_os_*` aliases and the legacy `agentOS_*` set were unified into
37
- // this single snake_case family.
38
- const defaultAgentOsSlug = () => process.env.KYNVER_AGENT_OS_SLUG?.trim() || "ghost";
39
- const osSlug = (s) => (s && s.trim()) || defaultAgentOsSlug();
40
- register("agent_os_get_context", "Get the agent's full Agentic-OS state in one call: identity, open goals, active projects with current focus/next-actions/blockers, recent sessions, contacts, and long-term memory stats. Use at session start instead of reading SOUL.md / USER.md / MEMORY.md.", { slug: z.string().optional().describe("AgentOS slug. Defaults to KYNVER_AGENT_OS_SLUG, then 'ghost'.") }, async (args) => jsonResult(await get(`/agent-os/${osSlug(args.slug)}/stats`)));
26
+ let cachedResolvedPrimarySlug = null;
27
+ async function resolveToolSlug(explicitSlug) {
28
+ const trimmed = explicitSlug?.trim();
29
+ if (trimmed)
30
+ return trimmed;
31
+ const fromEnv = process.env.KYNVER_AGENT_OS_SLUG?.trim();
32
+ if (fromEnv)
33
+ return fromEnv;
34
+ if (cachedResolvedPrimarySlug)
35
+ return cachedResolvedPrimarySlug;
36
+ try {
37
+ const body = await get("/agent-os");
38
+ const primary = typeof body.primarySlug === "string" ? body.primarySlug.trim() : "";
39
+ if (primary) {
40
+ cachedResolvedPrimarySlug = primary;
41
+ return primary;
42
+ }
43
+ const first = Array.isArray(body.items) ? body.items[0]?.slug : undefined;
44
+ if (typeof first === "string") {
45
+ const t = first.trim();
46
+ if (t) {
47
+ cachedResolvedPrimarySlug = t;
48
+ return t;
49
+ }
50
+ }
51
+ }
52
+ catch {
53
+ // fall through
54
+ }
55
+ return MCP_LEGACY_SLUG_FALLBACK;
56
+ }
57
+ const slugField = z.string().optional().describe("AgentOS slug within your account. Omit to use env or server-discovered primary (GET /api/agent-os). Legacy: `ghost`.");
58
+ const sourceRefSchema = z
59
+ .object({
60
+ type: z
61
+ .enum(["repo", "commit", "branch", "pr", "map", "session", "tool", "url", "manual", "unknown"])
62
+ .describe("What kind of source backs this memory."),
63
+ label: z.string().optional(),
64
+ repo: z.string().optional(),
65
+ path: z.string().optional(),
66
+ commit: z.string().optional(),
67
+ branch: z.string().optional(),
68
+ pr: z.union([z.number(), z.string()]).optional(),
69
+ url: z.string().optional(),
70
+ sessionId: z.string().optional(),
71
+ toolName: z.string().optional(),
72
+ note: z.string().optional(),
73
+ })
74
+ .passthrough();
75
+ register("agent_os_get_context", "Get the agent's full Agentic-OS state in one call: identity, open goals, active projects with current focus/next-actions/blockers, recent sessions, contacts, and long-term memory stats. Use at session start instead of reading SOUL.md / USER.md / MEMORY.md.", { slug: slugField }, async (args) => {
76
+ const s = await resolveToolSlug(args.slug);
77
+ return jsonResult(await get(`/agent-os/${s}/stats`));
78
+ });
41
79
  register("agent_os_open_session", "Call this at the START of every session. Records channel, model, and opens a session record for continuity. Ghost calls this immediately after agent_os_get_context on startup.", {
42
80
  channel: z.string().describe("Runtime channel: 'webchat' | 'telegram' | 'discord' | …"),
43
81
  model: z.string().optional().describe("Model used for the session (default 'claude-sonnet-4-6')."),
44
- slug: z.string().optional().describe("AgentOS slug. Defaults to KYNVER_AGENT_OS_SLUG, then 'ghost'."),
82
+ slug: slugField,
45
83
  }, async (args) => {
46
84
  const { slug, ...body } = args;
47
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/sessions`, body));
85
+ const s = await resolveToolSlug(slug);
86
+ return jsonResult(await post(`/agent-os/${s}/sessions`, body));
48
87
  });
49
88
  register("agent_os_close_session", "Call this at the END of every session with a summary of what was accomplished and key decisions made. This is how Ghost maintains cross-session continuity — without it, the next session has no record of what happened.", {
50
89
  sessionId: z.string(),
@@ -53,17 +92,18 @@ export function createAgentOsServer() {
53
92
  decisionsLog: z.unknown().optional(),
54
93
  goalIds: z.array(z.string()).optional(),
55
94
  projectIds: z.array(z.string()).optional(),
56
- slug: z.string().optional(),
95
+ slug: slugField,
57
96
  }, async (args) => {
58
97
  const { slug, sessionId, ...body } = args;
59
- return jsonResult(await patch(`/agent-os/${osSlug(slug)}/sessions/${sessionId}`, body));
98
+ const s = await resolveToolSlug(slug);
99
+ return jsonResult(await patch(`/agent-os/${s}/sessions/${sessionId}`, body));
60
100
  });
61
101
  register("agent_os_log_session", "Append a structured session-log entry to the agent's daily log. Call at session end or mid-session checkpoints. Replaces writing to memory/YYYY-MM-DD.md. Entries accumulate across sessions; consolidation distils them into long-term memory.", {
62
102
  summary: z.string().describe("2-4 sentence summary of what was worked on."),
63
103
  topicsWorked: z.array(z.string()).optional().describe("Topics covered this session."),
64
104
  keyDecisions: z.array(z.string()).optional().describe("Important decisions made."),
65
105
  date: z.string().optional().describe("Date to log against (YYYY-MM-DD, defaults to today UTC)."),
66
- slug: z.string().optional().describe("AgentOS slug. Defaults to KYNVER_AGENT_OS_SLUG, then 'ghost'."),
106
+ slug: slugField,
67
107
  }, async (args) => {
68
108
  const lines = [args.summary];
69
109
  if (args.topicsWorked?.length)
@@ -73,7 +113,8 @@ export function createAgentOsServer() {
73
113
  args.keyDecisions.forEach((d) => lines.push(`- ${d}`));
74
114
  }
75
115
  const entry = lines.join("\n");
76
- return jsonResult(await post(`/agent-os/${osSlug(args.slug)}/daily-log`, {
116
+ const s = await resolveToolSlug(args.slug);
117
+ return jsonResult(await post(`/agent-os/${s}/daily-log`, {
77
118
  entry,
78
119
  ...(args.date ? { date: args.date } : {}),
79
120
  }));
@@ -84,7 +125,7 @@ export function createAgentOsServer() {
84
125
  .optional()
85
126
  .describe("Filter by status. Omit to get all goals."),
86
127
  projectId: z.string().optional().describe("Filter by project DB ID."),
87
- slug: z.string().optional(),
128
+ slug: slugField,
88
129
  }, async (args) => {
89
130
  const params = new URLSearchParams();
90
131
  if (args.status)
@@ -92,7 +133,8 @@ export function createAgentOsServer() {
92
133
  if (args.projectId)
93
134
  params.set("projectId", args.projectId);
94
135
  const qs = params.toString();
95
- return jsonResult(await get(`/agent-os/${osSlug(args.slug)}/goals${qs ? `?${qs}` : ""}`));
136
+ const s = await resolveToolSlug(args.slug);
137
+ return jsonResult(await get(`/agent-os/${s}/goals${qs ? `?${qs}` : ""}`));
96
138
  });
97
139
  register("agent_os_update_goal", "Create a new goal or update an existing one. Pass goalId to update; omit to create (title is then required). Set status=complete with an outcome to close a goal (stamps closedAt). projectId is the project DB ID from agent_os_get_projects.", {
98
140
  title: z.string().optional().describe("Goal title. Required when creating."),
@@ -103,29 +145,31 @@ export function createAgentOsServer() {
103
145
  analystHypothesisId: z.string().optional().describe("Optional analyst hypothesis to link (create-only)."),
104
146
  outcome: z.string().optional().describe("Outcome — include when closing a goal."),
105
147
  goalId: z.string().optional().describe("ID of existing goal to update. Omit to create."),
106
- slug: z.string().optional(),
148
+ slug: slugField,
107
149
  }, async (args) => {
108
150
  const { goalId, slug, ...body } = args;
151
+ const s = await resolveToolSlug(slug);
109
152
  if (goalId) {
110
- return jsonResult(await patch(`/agent-os/${osSlug(slug)}/goals/${goalId}`, body));
153
+ return jsonResult(await patch(`/agent-os/${s}/goals/${goalId}`, body));
111
154
  }
112
155
  if (!body.title) {
113
156
  return jsonResult({ error: "title is required when creating a goal" });
114
157
  }
115
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/goals`, body));
158
+ return jsonResult(await post(`/agent-os/${s}/goals`, body));
116
159
  });
117
160
  register("agent_os_get_projects", "Get all projects the agent is tracking with current focus, next actions, blockers, and status. Replaces reading project sections of MEMORY.md.", {
118
161
  status: z
119
162
  .enum(["active", "on_hold", "shipped", "archived"])
120
163
  .optional()
121
164
  .describe("Filter by project status. Omit to get all."),
122
- slug: z.string().optional(),
165
+ slug: slugField,
123
166
  }, async (args) => {
124
167
  const params = new URLSearchParams();
125
168
  if (args.status)
126
169
  params.set("status", args.status);
127
170
  const qs = params.toString();
128
- return jsonResult(await get(`/agent-os/${osSlug(args.slug)}/projects${qs ? `?${qs}` : ""}`));
171
+ const s = await resolveToolSlug(args.slug);
172
+ return jsonResult(await get(`/agent-os/${s}/projects${qs ? `?${qs}` : ""}`));
129
173
  });
130
174
  register("agent_os_update_project", "Update a project's current focus, next-actions queue, blockers list, status, or repo/deploy URLs. The projectId is the DB ID from agent_os_get_projects.", {
131
175
  projectId: z.string().describe("Project DB ID (from agent_os_get_projects)."),
@@ -137,20 +181,22 @@ export function createAgentOsServer() {
137
181
  blockers: z.array(z.string()).optional().describe("Current blockers."),
138
182
  repoUrl: z.string().optional(),
139
183
  deployUrl: z.string().optional(),
140
- slug: z.string().optional(),
184
+ slug: slugField,
141
185
  }, async (args) => {
142
186
  const { projectId, slug, ...body } = args;
143
- return jsonResult(await patch(`/agent-os/${osSlug(slug)}/projects/${projectId}`, body));
187
+ const s = await resolveToolSlug(slug);
188
+ return jsonResult(await patch(`/agent-os/${s}/projects/${projectId}`, body));
144
189
  });
145
190
  register("agent_os_search_memory", "Semantic + keyword hybrid search over the agent's long-term MARM memory (ownerType=agent). Use to recall specific decisions, project details, or lessons without loading full context. Replaces grep/reading MEMORY.md. Results include rrfScore — a Reciprocal Rank Fusion score where ~0.033 is a near-perfect match. Higher is better; do not treat low numbers as poor results.", {
146
191
  query: z.string().describe("Natural language query to search memory."),
147
192
  k: z.number().optional().describe("Number of results (default 10, max 50)."),
148
- slug: z.string().optional(),
193
+ slug: slugField,
149
194
  }, async (args) => {
150
195
  const params = new URLSearchParams({ q: args.query });
151
196
  if (args.k)
152
197
  params.set("k", String(args.k));
153
- return jsonResult(await get(`/agent-os/${osSlug(args.slug)}/memory?${params}`));
198
+ const s = await resolveToolSlug(args.slug);
199
+ return jsonResult(await get(`/agent-os/${s}/memory?${params}`));
154
200
  });
155
201
  register("agent_os_write_memory", "Write a durable memory entry to the agent's MARM. Use to persist decisions, lessons, or key facts across sessions. Replaces appending to MEMORY.md. Either pass a category enum (mapped to sourceId) or override sourceId directly.", {
156
202
  content: z.string().describe("The memory content to store."),
@@ -161,7 +207,8 @@ export function createAgentOsServer() {
161
207
  .describe("Memory category. Mapped to sourceId: long_term/contact/tool_config -> 'agent:long-term', project -> 'agent:project'. Ignored if sourceId is set."),
162
208
  sourceId: z.string().optional().describe("Advanced: override the sourceId tag directly. If set, category is ignored."),
163
209
  metadata: z.record(z.string(), z.unknown()).optional(),
164
- slug: z.string().optional().describe("AgentOS slug. Defaults to KYNVER_AGENT_OS_SLUG, then 'ghost'."),
210
+ sourceRefs: z.array(sourceRefSchema).optional().describe("Structured provenance references for this memory, such as repo/path/commit/PR/map/session."),
211
+ slug: slugField,
165
212
  }, async (args) => {
166
213
  const categoryToSourceId = {
167
214
  long_term: "agent:long-term",
@@ -177,10 +224,19 @@ export function createAgentOsServer() {
177
224
  body.sourceId = sourceId;
178
225
  if (args.metadata)
179
226
  body.metadata = args.metadata;
180
- return jsonResult(await post(`/agent-os/${osSlug(args.slug)}/memory`, body));
227
+ if (args.sourceRefs)
228
+ body.sourceRefs = args.sourceRefs;
229
+ const s = await resolveToolSlug(args.slug);
230
+ return jsonResult(await post(`/agent-os/${s}/memory`, body));
231
+ });
232
+ register("agent_os_get_contacts", "Get all people the agent knows: their relationship, context, preferences, and notes. Replaces reading USER.md and contact sections of MEMORY.md.", { slug: slugField }, async (args) => {
233
+ const s = await resolveToolSlug(args.slug);
234
+ return jsonResult(await get(`/agent-os/${s}/contacts`));
235
+ });
236
+ register("agent_os_consolidate_memory", "Trigger a memory consolidation pass: LLM reviews recent daily logs and distils key decisions, lessons, and facts into long-term MARM memory. Use during heartbeat maintenance instead of manually rewriting MEMORY.md. Rate-limited to 2 runs per agent per day.", { slug: slugField }, async (args) => {
237
+ const s = await resolveToolSlug(args.slug);
238
+ return jsonResult(await post(`/agent-os/${s}/consolidate`, {}));
181
239
  });
182
- register("agent_os_get_contacts", "Get all people the agent knows: their relationship, context, preferences, and notes. Replaces reading USER.md and contact sections of MEMORY.md.", { slug: z.string().optional() }, async (args) => jsonResult(await get(`/agent-os/${osSlug(args.slug)}/contacts`)));
183
- register("agent_os_consolidate_memory", "Trigger a memory consolidation pass: LLM reviews recent daily logs and distils key decisions, lessons, and facts into long-term MARM memory. Use during heartbeat maintenance instead of manually rewriting MEMORY.md. Rate-limited to 2 runs per agent per day.", { slug: z.string().optional() }, async (args) => jsonResult(await post(`/agent-os/${osSlug(args.slug)}/consolidate`, {})));
184
240
  register("agent_os_create_project", "Create a new project in the agent OS. Use this to add a newly started project to tracking. Returns the created project with its DB id.", {
185
241
  name: z.string().describe("Project name (required)."),
186
242
  description: z.string().optional(),
@@ -190,10 +246,11 @@ export function createAgentOsServer() {
190
246
  blockers: z.array(z.string()).optional(),
191
247
  repoUrl: z.string().optional(),
192
248
  deployUrl: z.string().optional(),
193
- slug: z.string().optional(),
249
+ slug: slugField,
194
250
  }, async (args) => {
195
251
  const { slug, ...body } = args;
196
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/projects`, body));
252
+ const s = await resolveToolSlug(slug);
253
+ return jsonResult(await post(`/agent-os/${s}/projects`, body));
197
254
  });
198
255
  register("agent_os_create_contact", "Add a new contact (or upsert by name) to the agent OS. Use when you meet someone new or want to record info about a person.", {
199
256
  name: z.string().describe("Contact name (required)."),
@@ -201,10 +258,11 @@ export function createAgentOsServer() {
201
258
  context: z.string().optional().describe("Background on who they are and what they do."),
202
259
  preferences: z.record(z.string(), z.unknown()).optional().describe("Communication preferences, timezone, style notes, etc."),
203
260
  notes: z.string().optional(),
204
- slug: z.string().optional(),
261
+ slug: slugField,
205
262
  }, async (args) => {
206
263
  const { slug, ...body } = args;
207
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/contacts`, body));
264
+ const s = await resolveToolSlug(slug);
265
+ return jsonResult(await post(`/agent-os/${s}/contacts`, body));
208
266
  });
209
267
  register("agent_os_update_contact", "Update an existing contact by their DB id. Use agent_os_get_contacts to get the id first.", {
210
268
  contactId: z.string().describe("DB id of the contact to update (from agent_os_get_contacts)."),
@@ -213,25 +271,30 @@ export function createAgentOsServer() {
213
271
  context: z.string().optional(),
214
272
  preferences: z.record(z.string(), z.unknown()).optional(),
215
273
  notes: z.string().optional(),
216
- slug: z.string().optional(),
274
+ slug: slugField,
217
275
  }, async (args) => {
218
276
  const { contactId, slug, ...body } = args;
219
- return jsonResult(await patch(`/agent-os/${osSlug(slug)}/contacts/${contactId}`, body));
277
+ const s = await resolveToolSlug(slug);
278
+ return jsonResult(await patch(`/agent-os/${s}/contacts/${contactId}`, body));
220
279
  });
221
280
  register("agent_os_update_memory", "Update an existing memory entry by its key/slug. Use to correct or replace a previously written memory. The key must match the slug used when the memory was written — if key was omitted on write, the auto-generated slug (note-<timestamp>) is returned in the write response.", {
222
281
  key: z.string().describe("The slug/key of the memory to update (from the original agent_os_write_memory call)."),
223
282
  content: z.string().describe("New content to replace the existing memory with."),
224
283
  sourceId: z.string().optional().describe("Override the sourceId tag if needed."),
225
284
  metadata: z.record(z.string(), z.unknown()).optional(),
226
- slug: z.string().optional().describe("AgentOS slug. Defaults to KYNVER_AGENT_OS_SLUG, then ghost."),
285
+ sourceRefs: z.array(sourceRefSchema).optional().describe("Structured provenance references for this replacement memory."),
286
+ slug: slugField,
227
287
  }, async (args) => {
228
- const { key, slug, content, sourceId, metadata } = args;
288
+ const { key, slug, content, sourceId, metadata, sourceRefs } = args;
229
289
  const body = { content, slug: key };
230
290
  if (sourceId)
231
291
  body.sourceId = sourceId;
232
292
  if (metadata)
233
293
  body.metadata = metadata;
234
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/memory`, body));
294
+ if (sourceRefs)
295
+ body.sourceRefs = sourceRefs;
296
+ const s = await resolveToolSlug(slug);
297
+ return jsonResult(await post(`/agent-os/${s}/memory`, body));
235
298
  });
236
299
  return server;
237
300
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kynver-app/mcp-agent-os",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Kynver Agentic OS MCP server — slug-keyed agent identity, goals, projects, sessions, and long-term memory",
5
5
  "type": "module",
6
6
  "bin": {