@kynver-app/mcp-agent-os 0.3.32 → 0.3.33

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { resolveAgentContext } from "../server.js";
3
+ const OLD_CONTEXT = process.env.KYNVER_AGENT_CONTEXT;
4
+ const OLD_OS_CONTEXT = process.env.KYNVER_AGENT_OS_CONTEXT;
5
+ afterEach(() => {
6
+ if (OLD_CONTEXT === undefined)
7
+ delete process.env.KYNVER_AGENT_CONTEXT;
8
+ else
9
+ process.env.KYNVER_AGENT_CONTEXT = OLD_CONTEXT;
10
+ if (OLD_OS_CONTEXT === undefined)
11
+ delete process.env.KYNVER_AGENT_OS_CONTEXT;
12
+ else
13
+ process.env.KYNVER_AGENT_OS_CONTEXT = OLD_OS_CONTEXT;
14
+ });
15
+ describe("resolveAgentContext", () => {
16
+ it("prefers explicit agentContext over environment defaults", () => {
17
+ process.env.KYNVER_AGENT_CONTEXT = "forge";
18
+ process.env.KYNVER_AGENT_OS_CONTEXT = "ghost";
19
+ expect(resolveAgentContext(" hermes-forge ")).toBe("hermes-forge");
20
+ });
21
+ it("defaults to KYNVER_AGENT_CONTEXT when omitted", () => {
22
+ process.env.KYNVER_AGENT_CONTEXT = " forge ";
23
+ process.env.KYNVER_AGENT_OS_CONTEXT = "ghost";
24
+ expect(resolveAgentContext()).toBe("forge");
25
+ });
26
+ it("falls back to KYNVER_AGENT_OS_CONTEXT for compatibility", () => {
27
+ delete process.env.KYNVER_AGENT_CONTEXT;
28
+ process.env.KYNVER_AGENT_OS_CONTEXT = " hermes-forge ";
29
+ expect(resolveAgentContext()).toBe("hermes-forge");
30
+ });
31
+ it("omits agentContext when neither explicit nor environment context is set", () => {
32
+ delete process.env.KYNVER_AGENT_CONTEXT;
33
+ delete process.env.KYNVER_AGENT_OS_CONTEXT;
34
+ expect(resolveAgentContext(" ")).toBeUndefined();
35
+ });
36
+ });
@@ -0,0 +1,12 @@
1
+ type ToolResult = {
2
+ content: {
3
+ type: "text";
4
+ text: string;
5
+ }[];
6
+ structuredContent?: unknown;
7
+ };
8
+ /** Default MCP text result — pretty JSON for small payloads. */
9
+ export declare function jsonResult(data: unknown): ToolResult;
10
+ /** Truncation-safe MCP result — priority keys serialize first. */
11
+ export declare function jsonResultOrdered(data: Record<string, unknown>, priorityKeys: readonly string[]): ToolResult;
12
+ export {};
@@ -0,0 +1,30 @@
1
+ function orderedStringifyForMcp(value, priorityKeys) {
2
+ const ordered = {};
3
+ const seen = new Set();
4
+ for (const key of priorityKeys) {
5
+ if (key in value) {
6
+ ordered[key] = value[key];
7
+ seen.add(key);
8
+ }
9
+ }
10
+ for (const [key, val] of Object.entries(value)) {
11
+ if (!seen.has(key))
12
+ ordered[key] = val;
13
+ }
14
+ return JSON.stringify(ordered, null, 2);
15
+ }
16
+ /** Default MCP text result — pretty JSON for small payloads. */
17
+ export function jsonResult(data) {
18
+ return {
19
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
20
+ structuredContent: data,
21
+ };
22
+ }
23
+ /** Truncation-safe MCP result — priority keys serialize first. */
24
+ export function jsonResultOrdered(data, priorityKeys) {
25
+ const text = orderedStringifyForMcp(data, priorityKeys);
26
+ return {
27
+ content: [{ type: "text", text }],
28
+ structuredContent: data,
29
+ };
30
+ }
package/dist/server.d.ts CHANGED
@@ -7,4 +7,5 @@
7
7
  * `GET /api/agent-os` `{ primarySlug }` → list first item → legacy `"ghost"`.
8
8
  */
9
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ export declare function resolveAgentContext(explicitAgentContext?: string): string | undefined;
10
11
  export declare function createAgentOsServer(): McpServer;
package/dist/server.js CHANGED
@@ -36,6 +36,13 @@ function compact(source) {
36
36
  return out;
37
37
  }
38
38
  const MCP_LEGACY_SLUG_FALLBACK = "ghost";
39
+ export function resolveAgentContext(explicitAgentContext) {
40
+ const explicit = explicitAgentContext?.trim();
41
+ if (explicit)
42
+ return explicit;
43
+ const fromEnv = process.env.KYNVER_AGENT_CONTEXT?.trim() || process.env.KYNVER_AGENT_OS_CONTEXT?.trim() || "";
44
+ return fromEnv || undefined;
45
+ }
39
46
  export function createAgentOsServer() {
40
47
  const server = new McpServer({ name: "kynver-mcp-agent-os", version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
41
48
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -79,6 +86,14 @@ export function createAgentOsServer() {
79
86
  .enum(["operator", "customer"])
80
87
  .optional()
81
88
  .describe("Response shape for memory reads. Owners/env admins may pass operator for full diagnostics or customer to preview grantee-safe JSON. Grantees always receive customer-safe fields regardless of this flag.");
89
+ const contextProjectionField = z
90
+ .enum(["brief", "full"])
91
+ .optional()
92
+ .describe("Response shape. Default brief returns compact identity/current-work context with follow-up hints. full returns the legacy broad stats payload and can be large/truncation-prone.");
93
+ const agentContextField = z
94
+ .string()
95
+ .optional()
96
+ .describe("Active agent/runtime/persona context within the shared AgentOS workspace, e.g. ghost, forge, or hermes-forge. Do not use slug for this; slug selects the workspace.");
82
97
  const sourceRefSchema = z
83
98
  .object({
84
99
  type: z
@@ -130,9 +145,15 @@ export function createAgentOsServer() {
130
145
  sourceRefs: z.array(sourceRefSchema).optional(),
131
146
  })
132
147
  .passthrough();
133
- 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) => {
148
+ register("agent_os_get_context", "Get compact startup context: identity/Soul-equivalent, key preferences, current-work pointers, memory stats, and follow-up instructions. Default brief stays small; projection=full returns the legacy broad stats payload. Pass agentContext for persona-specific context inside a shared workspace.", { slug: slugField, projection: contextProjectionField, agentContext: agentContextField }, async (args) => {
134
149
  const s = await resolveToolSlug(args.slug);
135
- return jsonResult(await get(`/agent-os/${s}/stats`));
150
+ const params = new URLSearchParams();
151
+ params.set("projection", args.projection === "full" ? "full" : "brief");
152
+ const agentContext = resolveAgentContext(args.agentContext);
153
+ if (agentContext) {
154
+ params.set("agentContext", agentContext);
155
+ }
156
+ return jsonResult(await get(`/agent-os/${s}/stats?${params}`));
136
157
  });
137
158
  register("agent_os_context_envelope", "Build a compact context envelope for one anchor (plan | task | goal | project | session). Returns the resolved anchor, its goal + plan (with current version body) + most-relevant task + recent session + top related memories with deduplicated source refs. Use this instead of reading giant docs when picking up an in-flight piece of work — one round trip, scoped to your workspace.", {
138
159
  anchorType: z.enum(["plan", "task", "goal", "project", "session"]).describe("Kind of anchor to load context around."),
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * agent_os_command_center_dashboard_contract_get proxies to
8
8
  * `.../command-center/dashboard-contract` — slim verification payload (counts,
9
- * packageVersionHealth, runtime capacity, activePlanRollups) without graph/hierarchy.
9
+ * packageVersionHealth, runtime capacity, activePlanRollups, blockerSlo) without graph/hierarchy.
10
10
  */
11
11
  type ToolResult = {
12
12
  content: {
@@ -6,10 +6,38 @@
6
6
  *
7
7
  * agent_os_command_center_dashboard_contract_get proxies to
8
8
  * `.../command-center/dashboard-contract` — slim verification payload (counts,
9
- * packageVersionHealth, runtime capacity, activePlanRollups) without graph/hierarchy.
9
+ * packageVersionHealth, runtime capacity, activePlanRollups, blockerSlo) without graph/hierarchy.
10
10
  */
11
11
  import { z } from "zod";
12
12
  import { get } from "../lib/kynver-client.js";
13
+ import { jsonResultOrdered } from "../lib/mcp-json-result.js";
14
+ const COMMAND_CENTER_MCP_PRIORITY_KEYS = [
15
+ "agentReadable",
16
+ "resultContract",
17
+ "agentOsId",
18
+ "rollupVersion",
19
+ "generatedAt",
20
+ "headsUp",
21
+ "runningTasks",
22
+ "nextActions",
23
+ "followUps",
24
+ "countSnapshot",
25
+ "readiness",
26
+ "attention",
27
+ "runtime",
28
+ "packageVersionHealth",
29
+ "dailyLandedPrs",
30
+ "usefulProgress",
31
+ "usefulProgressLedger",
32
+ "reviewLanes",
33
+ "activePlanRollups",
34
+ "tasksPreview",
35
+ "unparentedTasksPreview",
36
+ "harnessRunsPreview",
37
+ "freshness",
38
+ "sourceFreshness",
39
+ "dashboard",
40
+ ];
13
41
  async function resolveAgentOsId(slug) {
14
42
  const row = await get(`/agent-os/${encodeURIComponent(slug)}`);
15
43
  const id = typeof row.id === "string" ? row.id.trim() : "";
@@ -25,9 +53,10 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
25
53
  "prReadyNotifications (journal pr_ready events), dailyLandedPrs (journal merge counts:",
26
54
  "today/yesterday, rolling 24h/48h, pace + agentReadable summary), runtime capacity, and",
27
55
  "packageVersionHealth (managed npm alignment for @kynver-app/runtime).",
28
- "Includes `countSnapshot` (canonical header-chip / saved-view / review-lane counts — same",
29
- "contract the dashboard badges use). For a compact runtime verification payload without",
30
- "operationalGraph/hierarchy, use agent_os_command_center_dashboard_contract_get.",
56
+ "Default projection=compact returns a tiny navigable brief (<12k UTF-8): heads-up counts,",
57
+ "capped running tasks, top next actions/blockers, and follow-up tool hints. Deep detail",
58
+ "requires agent_os_task_get, agent_os_plan_get, agent_os_tasks_list, or graph tools.",
59
+ "Pass projection=full only for admin/debug (expensive, truncation-prone browser rollup).",
31
60
  "Same aggregate as the browser Command Center. Omit slug to use env/primary workspace;",
32
61
  "pass agentOsId when already known from a prior identity fetch.",
33
62
  ].join(" "), {
@@ -51,6 +80,10 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
51
80
  .number()
52
81
  .optional()
53
82
  .describe("Max harness runs in the rollup (server cap 200)."),
83
+ projection: z
84
+ .enum(["compact", "full"])
85
+ .optional()
86
+ .describe("Response shape. Default compact — tiny brief flight-board (<12k). Use full only for admin/debug."),
54
87
  }, async (args) => {
55
88
  const slug = await resolveToolSlug(args.slug);
56
89
  const agentOsId = typeof args.agentOsId === "string" && args.agentOsId.trim()
@@ -65,14 +98,19 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
65
98
  if (typeof args.harnessLimit === "number" && Number.isFinite(args.harnessLimit)) {
66
99
  qs.set("harnessLimit", String(args.harnessLimit));
67
100
  }
101
+ const projection = args.projection === "full" ? "full" : "compact";
102
+ qs.set("projection", projection);
68
103
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
69
- return jsonResult(await get(`/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center${suffix}`));
104
+ const payload = await get(`/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center${suffix}`);
105
+ if (projection === "compact") {
106
+ return jsonResultOrdered(payload, COMMAND_CENTER_MCP_PRIORITY_KEYS);
107
+ }
108
+ return jsonResult(payload);
70
109
  });
71
110
  register("agent_os_command_center_dashboard_contract_get", [
72
- "Fetch the slim Command Center dashboard contract for runtime verification:",
73
- "countSnapshot, readiness, packageVersionHealth, runtime capacity, activePlanRollups,",
74
- "and review-lane totals — same sources as the browser dashboard badges, without",
75
- "operationalGraph or hierarchy payloads (avoids MCP truncation on large boards).",
111
+ "Fetch the Command Center dashboard MCP brief (default projection=mcp): same tiny flight-board",
112
+ "as agent_os_command_center_get compact — heads-up counts, running tasks, next actions, follow-ups.",
113
+ "Pass projection=slim only when you need the full dashboard verification DTO (large on big boards).",
76
114
  ].join(" "), {
77
115
  slug: z
78
116
  .string()
@@ -90,6 +128,10 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
90
128
  .number()
91
129
  .optional()
92
130
  .describe("Max tasks to load when building counts (server cap 10000)."),
131
+ projection: z
132
+ .enum(["mcp", "slim"])
133
+ .optional()
134
+ .describe("mcp (default) — tiny brief. slim — full dashboard verification DTO (countSnapshot, activePlanRollups, …)."),
93
135
  }, async (args) => {
94
136
  const slug = await resolveToolSlug(args.slug);
95
137
  const agentOsId = typeof args.agentOsId === "string" && args.agentOsId.trim()
@@ -101,7 +143,13 @@ export function registerCommandCenterTools({ register, resolveToolSlug, jsonResu
101
143
  if (typeof args.limit === "number" && Number.isFinite(args.limit)) {
102
144
  qs.set("limit", String(args.limit));
103
145
  }
146
+ const projection = args.projection === "slim" ? "slim" : "mcp";
147
+ qs.set("projection", projection === "slim" ? "slim" : "mcp");
104
148
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
105
- return jsonResult(await get(`/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center/dashboard-contract${suffix}`));
149
+ const payload = await get(`/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center/dashboard-contract${suffix}`);
150
+ if (projection === "mcp") {
151
+ return jsonResultOrdered(payload, COMMAND_CENTER_MCP_PRIORITY_KEYS);
152
+ }
153
+ return jsonResult(payload);
106
154
  });
107
155
  }
@@ -27,17 +27,15 @@ const taskStatus = z
27
27
  "ready→running; the dispatcher/executor lease owns that.");
28
28
  const taskPriority = z
29
29
  .enum(["low", "normal", "high", "critical"])
30
- .describe("Priority — default 'normal'. Create coding/implementation tasks as 'normal' (routine/cleanup 'low'); " +
31
- "do NOT inherit the parent plan's priority — a task on a critical plan is still 'normal'. A task only " +
32
- "becomes 'critical' once it is actively BLOCKING progress (and the system already marks blocked tasks " +
33
- "critical automatically). Reserve manual 'high'/'critical' only when Will explicitly asked or there is a " +
34
- "live production/security incident — pass priorityEscalationReason quoting him or the incident. The server " +
35
- "downgrades unsolicited high/critical to normal. If everything is critical, nothing is — keep most tasks at normal/low.");
30
+ .describe("Priority is a worker-capacity queue signal, not importance. Default 'normal' (cleanup 'low'). " +
31
+ "Do NOT inherit plan/goal priority. Use 'high' only when harness workers are saturated and ready work waits for slots. " +
32
+ "Use 'critical' only for live production/security incidents — pass priorityEscalationReason or cite in title/description. " +
33
+ "When slots are free, omit priority and dispatch at normal. The API downgrades importance-style high/critical.");
36
34
  const priorityEscalationReason = z
37
35
  .string()
38
36
  .optional()
39
- .describe("Required when setting priority to high/critical: quote Will's words or name the live incident. " +
40
- "Without this (and without explicit escalation in title/description), the API coerces to normal.");
37
+ .describe("Required for critical: quote the requesting user or name the live production/security incident. " +
38
+ "High does not use this field — high requires worker-capacity contention at create time.");
41
39
  const taskExecutor = z.enum(["inline", "harness", "acp", "manual"]);
42
40
  const taskEventType = z.enum([
43
41
  "created",
@@ -72,8 +70,10 @@ export function registerTaskTools({ register, resolveToolSlug, jsonResult, compa
72
70
  .string()
73
71
  .optional()
74
72
  .describe("AgentOS slug. Omit to use env or the server-discovered primary.");
75
- register("agent_os_task_create", "Create an AgentTask on the board. New tasks start `ready` (dispatchable now) unless you defer them: pass scheduledFor for a timed start (→ `scheduled`) or dependsOnTaskIds to gate on upstream work (→ `waiting`); both auto-promote to `ready` when their gate clears. Idempotent within the AgentOS: pass idempotencyKey (or requestId) from the foreground turn so a Telegram retry or Ghost restart returns the existing task instead of spawning a duplicate. Response includes `created` (false = an existing task was returned).", {
76
- title: z.string().describe("Short task title (required)."),
73
+ register("agent_os_task_create", "Create an AgentTask on the board. New tasks start `ready` (dispatchable now) unless you defer them: pass scheduledFor for a timed start (→ `scheduled`) or dependsOnTaskIds to gate on upstream work (→ `waiting`); both auto-promote to `ready` when their gate clears. Idempotent within the AgentOS: pass idempotencyKey (or requestId) from the foreground turn so a Telegram retry or Ghost restart returns the existing task instead of spawning a duplicate. Response includes `created` (false = an existing task was returned). Do NOT create closeout-only bookkeeping tasks whose purpose is merely to close/reconcile/clean up another task — complete/update/block/cancel the existing task (or spawn a child only for genuine unresolved implementation work).", {
74
+ title: z
75
+ .string()
76
+ .describe("Short task title (required). Never a closeout-only sweep — mutate/close the existing task instead."),
77
77
  description: z.string().optional(),
78
78
  priority: taskPriority.optional(),
79
79
  priorityEscalationReason,
@@ -126,6 +126,7 @@ export function registerTaskTools({ register, resolveToolSlug, jsonResult, compa
126
126
  description: z.string().optional(),
127
127
  status: taskStatus.optional(),
128
128
  priority: taskPriority.optional(),
129
+ priorityEscalationReason,
129
130
  executor: taskExecutor.optional(),
130
131
  executorRef: z.string().optional(),
131
132
  parentTaskId: z.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kynver-app/mcp-agent-os",
3
- "version": "0.3.32",
3
+ "version": "0.3.33",
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": {