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

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/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,71 @@
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.1" }, { 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
+ 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) => {
59
+ const s = await resolveToolSlug(args.slug);
60
+ return jsonResult(await get(`/agent-os/${s}/stats`));
61
+ });
41
62
  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
63
  channel: z.string().describe("Runtime channel: 'webchat' | 'telegram' | 'discord' | …"),
43
64
  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'."),
65
+ slug: slugField,
45
66
  }, async (args) => {
46
67
  const { slug, ...body } = args;
47
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/sessions`, body));
68
+ const s = await resolveToolSlug(slug);
69
+ return jsonResult(await post(`/agent-os/${s}/sessions`, body));
48
70
  });
49
71
  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
72
  sessionId: z.string(),
@@ -53,17 +75,18 @@ export function createAgentOsServer() {
53
75
  decisionsLog: z.unknown().optional(),
54
76
  goalIds: z.array(z.string()).optional(),
55
77
  projectIds: z.array(z.string()).optional(),
56
- slug: z.string().optional(),
78
+ slug: slugField,
57
79
  }, async (args) => {
58
80
  const { slug, sessionId, ...body } = args;
59
- return jsonResult(await patch(`/agent-os/${osSlug(slug)}/sessions/${sessionId}`, body));
81
+ const s = await resolveToolSlug(slug);
82
+ return jsonResult(await patch(`/agent-os/${s}/sessions/${sessionId}`, body));
60
83
  });
61
84
  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
85
  summary: z.string().describe("2-4 sentence summary of what was worked on."),
63
86
  topicsWorked: z.array(z.string()).optional().describe("Topics covered this session."),
64
87
  keyDecisions: z.array(z.string()).optional().describe("Important decisions made."),
65
88
  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'."),
89
+ slug: slugField,
67
90
  }, async (args) => {
68
91
  const lines = [args.summary];
69
92
  if (args.topicsWorked?.length)
@@ -73,7 +96,8 @@ export function createAgentOsServer() {
73
96
  args.keyDecisions.forEach((d) => lines.push(`- ${d}`));
74
97
  }
75
98
  const entry = lines.join("\n");
76
- return jsonResult(await post(`/agent-os/${osSlug(args.slug)}/daily-log`, {
99
+ const s = await resolveToolSlug(args.slug);
100
+ return jsonResult(await post(`/agent-os/${s}/daily-log`, {
77
101
  entry,
78
102
  ...(args.date ? { date: args.date } : {}),
79
103
  }));
@@ -84,7 +108,7 @@ export function createAgentOsServer() {
84
108
  .optional()
85
109
  .describe("Filter by status. Omit to get all goals."),
86
110
  projectId: z.string().optional().describe("Filter by project DB ID."),
87
- slug: z.string().optional(),
111
+ slug: slugField,
88
112
  }, async (args) => {
89
113
  const params = new URLSearchParams();
90
114
  if (args.status)
@@ -92,7 +116,8 @@ export function createAgentOsServer() {
92
116
  if (args.projectId)
93
117
  params.set("projectId", args.projectId);
94
118
  const qs = params.toString();
95
- return jsonResult(await get(`/agent-os/${osSlug(args.slug)}/goals${qs ? `?${qs}` : ""}`));
119
+ const s = await resolveToolSlug(args.slug);
120
+ return jsonResult(await get(`/agent-os/${s}/goals${qs ? `?${qs}` : ""}`));
96
121
  });
97
122
  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
123
  title: z.string().optional().describe("Goal title. Required when creating."),
@@ -103,29 +128,31 @@ export function createAgentOsServer() {
103
128
  analystHypothesisId: z.string().optional().describe("Optional analyst hypothesis to link (create-only)."),
104
129
  outcome: z.string().optional().describe("Outcome — include when closing a goal."),
105
130
  goalId: z.string().optional().describe("ID of existing goal to update. Omit to create."),
106
- slug: z.string().optional(),
131
+ slug: slugField,
107
132
  }, async (args) => {
108
133
  const { goalId, slug, ...body } = args;
134
+ const s = await resolveToolSlug(slug);
109
135
  if (goalId) {
110
- return jsonResult(await patch(`/agent-os/${osSlug(slug)}/goals/${goalId}`, body));
136
+ return jsonResult(await patch(`/agent-os/${s}/goals/${goalId}`, body));
111
137
  }
112
138
  if (!body.title) {
113
139
  return jsonResult({ error: "title is required when creating a goal" });
114
140
  }
115
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/goals`, body));
141
+ return jsonResult(await post(`/agent-os/${s}/goals`, body));
116
142
  });
117
143
  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
144
  status: z
119
145
  .enum(["active", "on_hold", "shipped", "archived"])
120
146
  .optional()
121
147
  .describe("Filter by project status. Omit to get all."),
122
- slug: z.string().optional(),
148
+ slug: slugField,
123
149
  }, async (args) => {
124
150
  const params = new URLSearchParams();
125
151
  if (args.status)
126
152
  params.set("status", args.status);
127
153
  const qs = params.toString();
128
- return jsonResult(await get(`/agent-os/${osSlug(args.slug)}/projects${qs ? `?${qs}` : ""}`));
154
+ const s = await resolveToolSlug(args.slug);
155
+ return jsonResult(await get(`/agent-os/${s}/projects${qs ? `?${qs}` : ""}`));
129
156
  });
130
157
  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
158
  projectId: z.string().describe("Project DB ID (from agent_os_get_projects)."),
@@ -137,20 +164,22 @@ export function createAgentOsServer() {
137
164
  blockers: z.array(z.string()).optional().describe("Current blockers."),
138
165
  repoUrl: z.string().optional(),
139
166
  deployUrl: z.string().optional(),
140
- slug: z.string().optional(),
167
+ slug: slugField,
141
168
  }, async (args) => {
142
169
  const { projectId, slug, ...body } = args;
143
- return jsonResult(await patch(`/agent-os/${osSlug(slug)}/projects/${projectId}`, body));
170
+ const s = await resolveToolSlug(slug);
171
+ return jsonResult(await patch(`/agent-os/${s}/projects/${projectId}`, body));
144
172
  });
145
173
  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
174
  query: z.string().describe("Natural language query to search memory."),
147
175
  k: z.number().optional().describe("Number of results (default 10, max 50)."),
148
- slug: z.string().optional(),
176
+ slug: slugField,
149
177
  }, async (args) => {
150
178
  const params = new URLSearchParams({ q: args.query });
151
179
  if (args.k)
152
180
  params.set("k", String(args.k));
153
- return jsonResult(await get(`/agent-os/${osSlug(args.slug)}/memory?${params}`));
181
+ const s = await resolveToolSlug(args.slug);
182
+ return jsonResult(await get(`/agent-os/${s}/memory?${params}`));
154
183
  });
155
184
  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
185
  content: z.string().describe("The memory content to store."),
@@ -161,7 +190,7 @@ export function createAgentOsServer() {
161
190
  .describe("Memory category. Mapped to sourceId: long_term/contact/tool_config -> 'agent:long-term', project -> 'agent:project'. Ignored if sourceId is set."),
162
191
  sourceId: z.string().optional().describe("Advanced: override the sourceId tag directly. If set, category is ignored."),
163
192
  metadata: z.record(z.string(), z.unknown()).optional(),
164
- slug: z.string().optional().describe("AgentOS slug. Defaults to KYNVER_AGENT_OS_SLUG, then 'ghost'."),
193
+ slug: slugField,
165
194
  }, async (args) => {
166
195
  const categoryToSourceId = {
167
196
  long_term: "agent:long-term",
@@ -177,10 +206,17 @@ export function createAgentOsServer() {
177
206
  body.sourceId = sourceId;
178
207
  if (args.metadata)
179
208
  body.metadata = args.metadata;
180
- return jsonResult(await post(`/agent-os/${osSlug(args.slug)}/memory`, body));
209
+ const s = await resolveToolSlug(args.slug);
210
+ return jsonResult(await post(`/agent-os/${s}/memory`, body));
211
+ });
212
+ 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) => {
213
+ const s = await resolveToolSlug(args.slug);
214
+ return jsonResult(await get(`/agent-os/${s}/contacts`));
215
+ });
216
+ 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) => {
217
+ const s = await resolveToolSlug(args.slug);
218
+ return jsonResult(await post(`/agent-os/${s}/consolidate`, {}));
181
219
  });
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
220
  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
221
  name: z.string().describe("Project name (required)."),
186
222
  description: z.string().optional(),
@@ -190,10 +226,11 @@ export function createAgentOsServer() {
190
226
  blockers: z.array(z.string()).optional(),
191
227
  repoUrl: z.string().optional(),
192
228
  deployUrl: z.string().optional(),
193
- slug: z.string().optional(),
229
+ slug: slugField,
194
230
  }, async (args) => {
195
231
  const { slug, ...body } = args;
196
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/projects`, body));
232
+ const s = await resolveToolSlug(slug);
233
+ return jsonResult(await post(`/agent-os/${s}/projects`, body));
197
234
  });
198
235
  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
236
  name: z.string().describe("Contact name (required)."),
@@ -201,10 +238,11 @@ export function createAgentOsServer() {
201
238
  context: z.string().optional().describe("Background on who they are and what they do."),
202
239
  preferences: z.record(z.string(), z.unknown()).optional().describe("Communication preferences, timezone, style notes, etc."),
203
240
  notes: z.string().optional(),
204
- slug: z.string().optional(),
241
+ slug: slugField,
205
242
  }, async (args) => {
206
243
  const { slug, ...body } = args;
207
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/contacts`, body));
244
+ const s = await resolveToolSlug(slug);
245
+ return jsonResult(await post(`/agent-os/${s}/contacts`, body));
208
246
  });
209
247
  register("agent_os_update_contact", "Update an existing contact by their DB id. Use agent_os_get_contacts to get the id first.", {
210
248
  contactId: z.string().describe("DB id of the contact to update (from agent_os_get_contacts)."),
@@ -213,17 +251,18 @@ export function createAgentOsServer() {
213
251
  context: z.string().optional(),
214
252
  preferences: z.record(z.string(), z.unknown()).optional(),
215
253
  notes: z.string().optional(),
216
- slug: z.string().optional(),
254
+ slug: slugField,
217
255
  }, async (args) => {
218
256
  const { contactId, slug, ...body } = args;
219
- return jsonResult(await patch(`/agent-os/${osSlug(slug)}/contacts/${contactId}`, body));
257
+ const s = await resolveToolSlug(slug);
258
+ return jsonResult(await patch(`/agent-os/${s}/contacts/${contactId}`, body));
220
259
  });
221
260
  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
261
  key: z.string().describe("The slug/key of the memory to update (from the original agent_os_write_memory call)."),
223
262
  content: z.string().describe("New content to replace the existing memory with."),
224
263
  sourceId: z.string().optional().describe("Override the sourceId tag if needed."),
225
264
  metadata: z.record(z.string(), z.unknown()).optional(),
226
- slug: z.string().optional().describe("AgentOS slug. Defaults to KYNVER_AGENT_OS_SLUG, then ghost."),
265
+ slug: slugField,
227
266
  }, async (args) => {
228
267
  const { key, slug, content, sourceId, metadata } = args;
229
268
  const body = { content, slug: key };
@@ -231,7 +270,8 @@ export function createAgentOsServer() {
231
270
  body.sourceId = sourceId;
232
271
  if (metadata)
233
272
  body.metadata = metadata;
234
- return jsonResult(await post(`/agent-os/${osSlug(slug)}/memory`, body));
273
+ const s = await resolveToolSlug(slug);
274
+ return jsonResult(await post(`/agent-os/${s}/memory`, body));
235
275
  });
236
276
  return server;
237
277
  }
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.1",
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": {