@kynver-app/mcp-agent-os 0.3.4 → 0.3.6

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
@@ -38,6 +38,19 @@ The full family:
38
38
  - `agent_os_update_contact`
39
39
  - `agent_os_consolidate_memory`
40
40
 
41
+ AgentTask board (Phase 2 — batch 1 CRUD + batch 1.5 steering/lease):
42
+
43
+ - `agent_os_task_create`
44
+ - `agent_os_task_get`
45
+ - `agent_os_task_list`
46
+ - `agent_os_task_update`
47
+ - `agent_os_task_log_event`
48
+ - `agent_os_task_close`
49
+ - `agent_os_task_steer`
50
+ - `agent_os_task_abort`
51
+ - `agent_os_task_claim`
52
+ - `agent_os_task_renew_lease`
53
+
41
54
  Schemas are mirrored in `lib/mcp/tool-manifests/kynver-mcp-agent-os.json` in
42
55
  the Kynver repo (used as the static fallback when stdio spawn isn't available
43
56
  — e.g. on Vercel).
@@ -50,6 +63,12 @@ state to important memories instead of burying those pointers in freeform text.
50
63
  Set `reviewStatus: "needs_review"` for important or uncertain memories that a
51
64
  human should be able to find and correct in the dashboard.
52
65
 
66
+ Active-state and correction tools expose the "what is true right now" layer:
67
+ `agent_os_record_state` / `agent_os_get_state` manage one current
68
+ `active_state` row per live PR, branch, or deployment, while
69
+ `agent_os_correct_memory` writes an auditable correction that supersedes stale
70
+ or wrong remembered claims instead of silently editing history.
71
+
53
72
  Skill tools expose the AgentOS skill registry and skill-tagged memory contract.
54
73
  Use `agent_os_get_skill` to fetch instructions on demand, `agent_os_create_skill`
55
74
  /`agent_os_update_skill` to manage user-authored skills, `agent_os_import_skills`
@@ -62,6 +81,17 @@ slug as `skillId`; user-authored skill memories use `AgentSkill.id`.
62
81
  summaries can then be synthesized from actual topics, actions, decisions, and
63
82
  follow-ups instead of generic lifecycle text.
64
83
 
84
+ AgentTask tools expose the durable task board. `agent_os_task_create` is
85
+ idempotent within an AgentOS — pass `idempotencyKey` (or `requestId`) from the
86
+ foreground turn so a retry or restart returns the existing task instead of
87
+ spawning a duplicate. `agent_os_task_log_event` accepts a stable `eventKey` so a
88
+ board-down executor can replay its local event spool without creating
89
+ duplicates; `artifact` events take an `artifactVisibility` tier (`internal` |
90
+ `telegram_safe` | `public_pr_safe`, default `internal`). `agent_os_task_claim`
91
+ and `agent_os_task_renew_lease` implement the lease model — an executor must
92
+ hold a live lease to own a task, and a claim is an atomic conditional update
93
+ that increments `attempt`.
94
+
65
95
  ## Env
66
96
 
67
97
  | Var | Purpose |
package/dist/server.js CHANGED
@@ -9,6 +9,7 @@
9
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
10
  import { z } from "zod";
11
11
  import { get, post, patch } from "./lib/kynver-client.js";
12
+ import { registerTaskTools } from "./tools/tasks.js";
12
13
  function jsonResult(data) {
13
14
  return {
14
15
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
@@ -25,7 +26,7 @@ function compact(source) {
25
26
  }
26
27
  const MCP_LEGACY_SLUG_FALLBACK = "ghost";
27
28
  export function createAgentOsServer() {
28
- const server = new McpServer({ name: "kynver-mcp-agent-os", version: "0.3.2" }, { capabilities: { tools: {} } });
29
+ const server = new McpServer({ name: "kynver-mcp-agent-os", version: "0.3.5" }, { capabilities: { tools: {} } });
29
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
31
  function register(name, description, inputSchema, cb) {
31
32
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -81,7 +82,7 @@ export function createAgentOsServer() {
81
82
  })
82
83
  .passthrough();
83
84
  const memoryTypeSchema = z
84
- .enum(["decision", "lesson", "fact", "preference", "project_update", "runbook", "source_map", "correction", "note"])
85
+ .enum(["decision", "lesson", "fact", "preference", "project_update", "runbook", "source_map", "correction", "active_state", "note"])
85
86
  .describe("What kind of durable memory this is. Use note only when a more specific type does not fit.");
86
87
  const confidenceSchema = z
87
88
  .enum(["low", "medium", "high"])
@@ -89,6 +90,9 @@ export function createAgentOsServer() {
89
90
  const reviewStatusSchema = z
90
91
  .enum(["none", "needs_review", "reviewed", "stale", "rejected"])
91
92
  .describe("Set needs_review for important, uncertain, stale-looking, or user-correctable memories.");
93
+ const activeStateEntityTypeSchema = z
94
+ .enum(["pr_watch", "branch", "deployment"])
95
+ .describe("Tracked entity kind. pr_watch: a live PR. branch: local-vs-remote branch state. deployment: a live release.");
92
96
  const skillCategorySchema = z.enum(["engineering", "productivity", "agent_ops", "domain", "personal", "misc"]);
93
97
  const skillStatusSchema = z.enum(["draft", "active", "deprecated", "archived"]);
94
98
  const skillDraftSchema = z.object({
@@ -243,14 +247,31 @@ export function createAgentOsServer() {
243
247
  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.", {
244
248
  query: z.string().describe("Natural language query to search memory."),
245
249
  k: z.number().optional().describe("Number of results (default 10, max 50)."),
250
+ ticker: z.string().optional().describe("Trading desk: filter hits to this ticker symbol."),
251
+ debatePersona: z.enum(["bull", "bear"]).optional().describe("Trading desk: filter to bull or bear advocate memories."),
246
252
  slug: slugField,
247
253
  }, async (args) => {
248
254
  const params = new URLSearchParams({ q: args.query });
249
255
  if (args.k)
250
256
  params.set("k", String(args.k));
257
+ if (args.ticker)
258
+ params.set("ticker", args.ticker);
259
+ if (args.debatePersona)
260
+ params.set("debatePersona", args.debatePersona);
251
261
  const s = await resolveToolSlug(args.slug);
252
262
  return jsonResult(await get(`/agent-os/${s}/memory?${params}`));
253
263
  });
264
+ register("agent_os_get_desk_debate_context", "Fetch trading-desk debate context for a ticker: latest canonical DeskAnalysisReport debate fields plus bull/bear MARM snippets scoped by ticker and debatePersona.", {
265
+ ticker: z.string().describe("Equity ticker symbol, e.g. NVDA."),
266
+ reportId: z.string().optional().describe("Optional DeskAnalysisReport id; defaults to latest canonical report for the ticker."),
267
+ slug: slugField,
268
+ }, async (args) => {
269
+ const params = new URLSearchParams({ ticker: args.ticker });
270
+ if (args.reportId)
271
+ params.set("reportId", args.reportId);
272
+ const s = await resolveToolSlug(args.slug);
273
+ return jsonResult(await get(`/agent-os/${s}/desk-debate-context?${params}`));
274
+ });
254
275
  register("agent_os_list_skills", "List AgentOS skills available to this workspace, including Kynver built-ins, user-authored skills, and per-AgentOS binding state. Pass view=\"manifest\" for the runtime read path: only enabled, runtime-eligible skills (enabled binding plus, for user skills, status=active) with metadata only — no instructions. Skill instructions are fetched on demand with agent_os_get_skill; do not inject every skill into every model context.", {
255
276
  view: z
256
277
  .enum(["full", "manifest"])
@@ -503,5 +524,67 @@ export function createAgentOsServer() {
503
524
  const s = await resolveToolSlug(slug);
504
525
  return jsonResult(await post(`/agent-os/${s}/memory`, body));
505
526
  });
527
+ register("agent_os_correct_memory", "Write an auditable correction memory that supersedes a stale or wrong memory by slug. Use when fixing an incorrect remembered claim rather than silently editing history.", {
528
+ targetSlug: z.string().describe("Slug/key of the memory being corrected."),
529
+ content: z.string().describe("Correct replacement content to remember."),
530
+ reason: z.string().describe("Why the target is wrong; recorded on the superseded target."),
531
+ key: z.string().optional().describe("Slug for the new correction memory. Defaults to <targetSlug>-correction."),
532
+ alsoInvalidates: z.array(z.string()).optional().describe("Additional memory slugs/ids to supersede alongside targetSlug."),
533
+ assertedBySessionId: z.string().optional(),
534
+ sourceId: z.string().optional(),
535
+ sourceRefs: z.array(sourceRefSchema).optional(),
536
+ memoryType: memoryTypeSchema.optional(),
537
+ confidence: confidenceSchema.optional(),
538
+ reviewStatus: reviewStatusSchema.optional(),
539
+ projectId: z.string().optional(),
540
+ goalId: z.string().optional(),
541
+ contactId: z.string().optional(),
542
+ skillId: z.string().optional(),
543
+ slug: slugField,
544
+ }, async (args) => {
545
+ const { slug, ...body } = args;
546
+ const s = await resolveToolSlug(slug);
547
+ return jsonResult(await post("/agent-os/" + s + "/memory/correct", body));
548
+ });
549
+ register("agent_os_record_state", "Record current active state for a live mutable entity such as a PR, branch, or deployment. Server derives the canonical entityKey from fields and upserts one active_state row per entity.", {
550
+ entityType: activeStateEntityTypeSchema,
551
+ fields: z.record(z.string(), z.unknown()).describe("Entity-specific current truth. pr_watch requires repo, pr, author, headOwner, headBranch, state. branch requires repo, branch, aheadBy, behindBy, lastCommit. deployment requires target, env, state, url, and version or sha."),
552
+ observedAt: z.string().optional().describe("ISO timestamp the state was observed. Defaults to now."),
553
+ content: z.string().optional().describe("Optional human summary; one is generated otherwise."),
554
+ sourceRefs: z.array(sourceRefSchema).optional(),
555
+ projectId: z.string().optional(),
556
+ confidence: confidenceSchema.optional(),
557
+ reviewStatus: reviewStatusSchema.optional(),
558
+ sourceId: z.string().optional(),
559
+ metadata: z.record(z.string(), z.unknown()).optional(),
560
+ slug: slugField,
561
+ }, async (args) => {
562
+ const { slug, ...body } = args;
563
+ const s = await resolveToolSlug(slug);
564
+ return jsonResult(await post("/agent-os/" + s + "/memory/state", body));
565
+ });
566
+ register("agent_os_get_state", "Get current active state records. Pass entityKey for one record, or entityType/projectId/limit to list active PR, branch, or deployment state.", {
567
+ entityKey: z.string().optional().describe("Canonical key of a single record, e.g. pr:openclaw/openclaw#83529. Omit to list records."),
568
+ entityType: activeStateEntityTypeSchema.optional(),
569
+ projectId: z.string().optional(),
570
+ limit: z.number().optional(),
571
+ slug: slugField,
572
+ }, async (args) => {
573
+ const { slug, entityKey, entityType, projectId, limit } = args;
574
+ const s = await resolveToolSlug(slug);
575
+ const qs = new URLSearchParams();
576
+ if (entityKey)
577
+ qs.set("entityKey", entityKey);
578
+ if (entityType)
579
+ qs.set("entityType", entityType);
580
+ if (projectId)
581
+ qs.set("projectId", projectId);
582
+ if (limit !== undefined)
583
+ qs.set("limit", String(limit));
584
+ const suffix = qs.toString() ? "?" + qs.toString() : "";
585
+ return jsonResult(await get("/agent-os/" + s + "/memory/state" + suffix));
586
+ });
587
+ // AgentTask board — batch 1 (CRUD) + batch 1.5 (steering / lease) tools.
588
+ registerTaskTools({ register, resolveToolSlug, jsonResult, compact });
506
589
  return server;
507
590
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * AgentTask board MCP tools — batch 1 (Ghost-facing CRUD) and batch 1.5
3
+ * (steering / lease). Each tool proxies to `/api/agent-os/{slug}/tasks/*`.
4
+ *
5
+ * Kept in its own module so server.ts does not grow monolithic. server.ts
6
+ * calls `registerTaskTools` with the helpers it already has in scope.
7
+ */
8
+ type ToolResult = {
9
+ content: {
10
+ type: "text";
11
+ text: string;
12
+ }[];
13
+ structuredContent?: unknown;
14
+ };
15
+ export interface TaskToolDeps {
16
+ /** server.ts's thin wrapper over `server.registerTool`. */
17
+ register: (name: string, description: string, inputSchema: Record<string, unknown>, cb: (args: any) => any) => void;
18
+ /** Resolve the effective AgentOS slug (explicit arg → env → discovered primary). */
19
+ resolveToolSlug: (explicitSlug?: string) => Promise<string>;
20
+ /** Wrap a value as an MCP text + structuredContent result. */
21
+ jsonResult: (data: unknown) => ToolResult;
22
+ /** Drop `undefined` entries from an object. */
23
+ compact: (source: Record<string, unknown>) => Record<string, unknown>;
24
+ }
25
+ export declare function registerTaskTools({ register, resolveToolSlug, jsonResult, compact, }: TaskToolDeps): void;
26
+ export {};
@@ -0,0 +1,167 @@
1
+ /**
2
+ * AgentTask board MCP tools — batch 1 (Ghost-facing CRUD) and batch 1.5
3
+ * (steering / lease). Each tool proxies to `/api/agent-os/{slug}/tasks/*`.
4
+ *
5
+ * Kept in its own module so server.ts does not grow monolithic. server.ts
6
+ * calls `registerTaskTools` with the helpers it already has in scope.
7
+ */
8
+ import { z } from "zod";
9
+ import { get, post, patch } from "../lib/kynver-client.js";
10
+ const taskStatus = z.enum([
11
+ "queued",
12
+ "running",
13
+ "blocked",
14
+ "needs_input",
15
+ "awaiting_review",
16
+ "done",
17
+ "failed",
18
+ "cancelled",
19
+ ]);
20
+ const taskPriority = z.enum(["low", "normal", "high", "critical"]);
21
+ const taskExecutor = z.enum(["inline", "harness", "acp", "manual"]);
22
+ const taskEventType = z.enum([
23
+ "created",
24
+ "started",
25
+ "worker_update",
26
+ "blocked",
27
+ "steer",
28
+ "artifact",
29
+ "review",
30
+ "done",
31
+ "failed",
32
+ ]);
33
+ const artifactVisibility = z.enum(["internal", "telegram_safe", "public_pr_safe"]);
34
+ export function registerTaskTools({ register, resolveToolSlug, jsonResult, compact, }) {
35
+ const slugField = z
36
+ .string()
37
+ .optional()
38
+ .describe("AgentOS slug. Omit to use env or the server-discovered primary.");
39
+ register("agent_os_task_create", "Create an AgentTask on the board. 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).", {
40
+ title: z.string().describe("Short task title (required)."),
41
+ description: z.string().optional(),
42
+ priority: taskPriority.optional(),
43
+ executor: taskExecutor.optional().describe("How the task runs: inline | harness | acp | manual."),
44
+ executorRef: z.string().optional().describe("Opaque ref to the executor process / unit."),
45
+ parentTaskId: z.string().optional().describe("Parent task id when this is one of 2+ coordinated workers."),
46
+ goalId: z.string().optional(),
47
+ projectId: z.string().optional(),
48
+ idempotencyKey: z.string().optional().describe("Dedupe key from the foreground turn. Strongly recommended."),
49
+ requestId: z.string().optional().describe("Raw foreground request id; used as the dedupe key if idempotencyKey is absent."),
50
+ slug: slugField,
51
+ }, async (args) => {
52
+ const { slug, ...body } = args;
53
+ const s = await resolveToolSlug(slug);
54
+ return jsonResult(await post(`/agent-os/${s}/tasks`, compact(body)));
55
+ });
56
+ register("agent_os_task_get", "Fetch one AgentTask by id, including lease, artifact, and steer-cursor fields.", { taskId: z.string(), slug: slugField }, async (args) => {
57
+ const s = await resolveToolSlug(args.slug);
58
+ return jsonResult(await get(`/agent-os/${s}/tasks/${args.taskId}`));
59
+ });
60
+ register("agent_os_task_list", "List AgentTasks on the board, optionally filtered by status, executor, or parent task.", {
61
+ status: taskStatus.optional(),
62
+ executor: taskExecutor.optional(),
63
+ parentTaskId: z.string().optional(),
64
+ limit: z.number().optional().describe("Max rows (default 50, max 200)."),
65
+ slug: slugField,
66
+ }, async (args) => {
67
+ const params = new URLSearchParams();
68
+ if (args.status)
69
+ params.set("status", args.status);
70
+ if (args.executor)
71
+ params.set("executor", args.executor);
72
+ if (args.parentTaskId)
73
+ params.set("parentTaskId", args.parentTaskId);
74
+ if (args.limit)
75
+ params.set("limit", String(args.limit));
76
+ const qs = params.toString();
77
+ const s = await resolveToolSlug(args.slug);
78
+ return jsonResult(await get(`/agent-os/${s}/tasks${qs ? `?${qs}` : ""}`));
79
+ });
80
+ register("agent_os_task_update", "Patch an AgentTask: status, priority, executor, links, progress summary, blocker, or first-class artifact refs (branch/worktreePath/prUrl/headCommit). Does not touch lease fields — use claim/renew for those.", {
81
+ taskId: z.string(),
82
+ title: z.string().optional(),
83
+ description: z.string().optional(),
84
+ status: taskStatus.optional(),
85
+ priority: taskPriority.optional(),
86
+ executor: taskExecutor.optional(),
87
+ executorRef: z.string().optional(),
88
+ parentTaskId: z.string().optional(),
89
+ goalId: z.string().optional(),
90
+ projectId: z.string().optional(),
91
+ lastSummary: z.string().optional().describe("Latest progress summary."),
92
+ blocker: z.string().optional(),
93
+ branch: z.string().optional(),
94
+ worktreePath: z.string().optional(),
95
+ prUrl: z.string().optional(),
96
+ headCommit: z.string().optional(),
97
+ slug: slugField,
98
+ }, async (args) => {
99
+ const { slug, taskId, ...body } = args;
100
+ const s = await resolveToolSlug(slug);
101
+ return jsonResult(await patch(`/agent-os/${s}/tasks/${taskId}`, compact(body)));
102
+ });
103
+ register("agent_os_task_log_event", "Append one event to a task's append-only journal: worker_update, blocked, artifact, review, etc. For artifact events set artifactVisibility (internal | telegram_safe | public_pr_safe) and put branch/prUrl/headCommit/worktreePath in payload — they are promoted to the task. Pass a stable eventKey so a board-down spool can replay without duplicates.", {
104
+ taskId: z.string(),
105
+ type: taskEventType.describe("Event type."),
106
+ payload: z.record(z.string(), z.unknown()).optional().describe("Structured event detail."),
107
+ artifactVisibility: artifactVisibility
108
+ .optional()
109
+ .describe("Required mental model for artifact events; defaults to internal (most conservative)."),
110
+ eventKey: z.string().optional().describe("Stable idempotency key for spool replay."),
111
+ slug: slugField,
112
+ }, async (args) => {
113
+ const { slug, taskId, ...body } = args;
114
+ const s = await resolveToolSlug(slug);
115
+ return jsonResult(await post(`/agent-os/${s}/tasks/${taskId}/events`, compact(body)));
116
+ });
117
+ register("agent_os_task_close", "Close a task into a terminal status (done | failed | cancelled, default done). Stamps closedAt, releases the lease, records the summary, and appends a terminal journal event.", {
118
+ taskId: z.string(),
119
+ status: z.enum(["done", "failed", "cancelled"]).optional(),
120
+ summary: z.string().optional().describe("Closing summary."),
121
+ slug: slugField,
122
+ }, async (args) => {
123
+ const { slug, taskId, ...body } = args;
124
+ const s = await resolveToolSlug(slug);
125
+ return jsonResult(await post(`/agent-os/${s}/tasks/${taskId}/close`, compact(body)));
126
+ });
127
+ register("agent_os_task_steer", "Append a steer event to a task. Steers are an ordered log — two concurrent steers never overwrite each other; the task's lastSteerEventId tracks the newest. The executor reads steers between its cursor and lastSteerEventId.", {
128
+ taskId: z.string(),
129
+ message: z.string().describe("The steering instruction."),
130
+ detail: z.record(z.string(), z.unknown()).optional(),
131
+ eventKey: z.string().optional().describe("Stable idempotency key for spool replay."),
132
+ slug: slugField,
133
+ }, async (args) => {
134
+ const { slug, taskId, ...body } = args;
135
+ const s = await resolveToolSlug(slug);
136
+ return jsonResult(await post(`/agent-os/${s}/tasks/${taskId}/steer`, compact(body)));
137
+ });
138
+ register("agent_os_task_abort", "Hard-abort a task: marks it cancelled and stamps a closing event. Local executor / worktree teardown is the executor's job — this only updates durable board state.", {
139
+ taskId: z.string(),
140
+ reason: z.string().optional(),
141
+ slug: slugField,
142
+ }, async (args) => {
143
+ const { slug, taskId, ...body } = args;
144
+ const s = await resolveToolSlug(slug);
145
+ return jsonResult(await post(`/agent-os/${s}/tasks/${taskId}/abort`, compact(body)));
146
+ });
147
+ register("agent_os_task_claim", "Atomically claim a task lease. Succeeds only when the task is non-terminal and its lease is free (never set or expired). The same conditional update increments `attempt` and flips the task to running. A failed claim returns ok=false with a reason (lease_held | terminal | not_found).", {
148
+ taskId: z.string(),
149
+ leaseOwner: z.string().describe("Unique id of the claiming executor."),
150
+ leaseDurationMs: z.number().optional().describe("Lease duration in ms (default 300000)."),
151
+ slug: slugField,
152
+ }, async (args) => {
153
+ const { slug, taskId, ...body } = args;
154
+ const s = await resolveToolSlug(slug);
155
+ return jsonResult(await post(`/agent-os/${s}/tasks/${taskId}/claim`, compact(body)));
156
+ });
157
+ register("agent_os_task_renew_lease", "Renew a lease the caller already owns — call periodically while the executor is alive. Succeeds only when leaseOwner matches and the lease has not expired. A failed renew returns ok=false with reason owner_mismatch or lease_expired; the caller must re-claim.", {
158
+ taskId: z.string(),
159
+ leaseOwner: z.string().describe("Must match the current lease owner."),
160
+ leaseDurationMs: z.number().optional().describe("New lease duration in ms (default 300000)."),
161
+ slug: slugField,
162
+ }, async (args) => {
163
+ const { slug, taskId, ...body } = args;
164
+ const s = await resolveToolSlug(slug);
165
+ return jsonResult(await post(`/agent-os/${s}/tasks/${taskId}/renew-lease`, compact(body)));
166
+ });
167
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kynver-app/mcp-agent-os",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
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": {