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

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).
@@ -62,6 +75,17 @@ slug as `skillId`; user-authored skill memories use `AgentSkill.id`.
62
75
  summaries can then be synthesized from actual topics, actions, decisions, and
63
76
  follow-ups instead of generic lifecycle text.
64
77
 
78
+ AgentTask tools expose the durable task board. `agent_os_task_create` is
79
+ idempotent within an AgentOS — pass `idempotencyKey` (or `requestId`) from the
80
+ foreground turn so a retry or restart returns the existing task instead of
81
+ spawning a duplicate. `agent_os_task_log_event` accepts a stable `eventKey` so a
82
+ board-down executor can replay its local event spool without creating
83
+ duplicates; `artifact` events take an `artifactVisibility` tier (`internal` |
84
+ `telegram_safe` | `public_pr_safe`, default `internal`). `agent_os_task_claim`
85
+ and `agent_os_task_renew_lease` implement the lease model — an executor must
86
+ hold a live lease to own a task, and a claim is an atomic conditional update
87
+ that increments `attempt`.
88
+
65
89
  ## Env
66
90
 
67
91
  | 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
@@ -503,5 +504,7 @@ export function createAgentOsServer() {
503
504
  const s = await resolveToolSlug(slug);
504
505
  return jsonResult(await post(`/agent-os/${s}/memory`, body));
505
506
  });
507
+ // AgentTask board — batch 1 (CRUD) + batch 1.5 (steering / lease) tools.
508
+ registerTaskTools({ register, resolveToolSlug, jsonResult, compact });
506
509
  return server;
507
510
  }
@@ -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.5",
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": {