@kynver-app/mcp-agent-os 0.3.3 → 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 +25 -0
- package/SECURITY.md +31 -0
- package/dist/server.js +4 -1
- package/dist/sse.d.ts +0 -17
- package/dist/sse.js +6 -137
- package/dist/tools/tasks.d.ts +26 -0
- package/dist/tools/tasks.js +167 -0
- package/package.json +5 -3
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 |
|
|
@@ -70,6 +94,7 @@ follow-ups instead of generic lifecycle text.
|
|
|
70
94
|
| `KYNVER_API_KEY` | Bearer token for external authentication |
|
|
71
95
|
| `KYNVER_AGENT_OS_SLUG` | Default agent slug when the tool's `slug` arg is omitted (defaults to `"ghost"`) |
|
|
72
96
|
| `KYNVER_SERVICE_SECRET` | Required when running under SSE and forwarding internal `userId` headers |
|
|
97
|
+
| `MCP_SSE_HOST` | SSE bind address (defaults to `127.0.0.1`) |
|
|
73
98
|
| `PORT` | SSE port (defaults to `3101`) |
|
|
74
99
|
|
|
75
100
|
## OpenClaw / Claude Code config
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security — Kynver MCP packages
|
|
2
|
+
|
|
3
|
+
## Recommended deployment
|
|
4
|
+
|
|
5
|
+
- **Prefer stdio** (`npx @kynver-app/mcp-*`) from Cursor, Claude Code, or OpenClaw. The MCP host process owns the trust boundary.
|
|
6
|
+
- **Avoid exposing SSE** (`start:sse`) to the public internet unless you terminate TLS and restrict access at a reverse proxy.
|
|
7
|
+
|
|
8
|
+
## Secrets
|
|
9
|
+
|
|
10
|
+
- Store `KYNVER_API_KEY` and `KYNVER_SERVICE_SECRET` in environment variables or your MCP host's secret store — never in git, query strings, or shared logs.
|
|
11
|
+
- `KYNVER_SERVICE_SECRET` plus a valid user API key can impersonate any user on your Kynver deployment. Treat it as tier-0.
|
|
12
|
+
|
|
13
|
+
## SSE mode (`start:sse`)
|
|
14
|
+
|
|
15
|
+
When you run the optional HTTP/SSE server:
|
|
16
|
+
|
|
17
|
+
| Control | Default | Purpose |
|
|
18
|
+
|---------|---------|---------|
|
|
19
|
+
| `MCP_SSE_HOST` | `127.0.0.1` | Bind address — keep localhost unless behind a proxy |
|
|
20
|
+
| `PORT` | package-specific | Listen port |
|
|
21
|
+
|
|
22
|
+
Authentication rules:
|
|
23
|
+
|
|
24
|
+
- **Headers only** for internal auth on `GET /sse` — `X-Kynver-User-Id` + `X-Kynver-Service-Secret`. Never pass `secret` or `userId` in query strings.
|
|
25
|
+
- **External API keys** may use `Authorization: Bearer <apiKey>` (preferred) or `?apiKey=` on `GET /sse` when your MCP client cannot set headers (EventSource). Query keys may appear in access logs.
|
|
26
|
+
- **`POST /messages` requires the same API key or internal headers** used to open the SSE session — not `sessionId` alone.
|
|
27
|
+
- Request bodies are capped at **1 MB**.
|
|
28
|
+
|
|
29
|
+
## Reporting
|
|
30
|
+
|
|
31
|
+
Report vulnerabilities to Kynver through your usual security channel for the Kynver deployment you operate.
|
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.
|
|
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
|
}
|
package/dist/sse.d.ts
CHANGED
|
@@ -1,18 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SSE entry point for the Agentic OS MCP server.
|
|
3
|
-
*
|
|
4
|
-
* Supports two authentication modes:
|
|
5
|
-
* 1. Internal (Kynver dashboard): X-Kynver-User-Id + X-Kynver-Service-Secret
|
|
6
|
-
* request headers. The legacy userId/secret query params are still
|
|
7
|
-
* accepted as a deprecated transitional fallback so an older web-tier
|
|
8
|
-
* build keeps working during a rolling deploy — remove once all callers
|
|
9
|
-
* send headers. Secrets must never travel in the query string in steady
|
|
10
|
-
* state (they leak via access logs, proxies, and referrers).
|
|
11
|
-
* 2. External (Claude Code, Cursor, any MCP client): Kynver API key via
|
|
12
|
-
* query param (?apiKey=…) or Authorization header
|
|
13
|
-
*
|
|
14
|
-
* External keys are validated by calling GET /api/users/me on the Kynver API.
|
|
15
|
-
* Once authenticated, tool calls execute on behalf of the resolved user via
|
|
16
|
-
* the requestContext AsyncLocalStorage.
|
|
17
|
-
*/
|
|
18
1
|
export {};
|
package/dist/sse.js
CHANGED
|
@@ -1,140 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
* SSE entry point for the Agentic OS MCP server.
|
|
3
|
-
*
|
|
4
|
-
* Supports two authentication modes:
|
|
5
|
-
* 1. Internal (Kynver dashboard): X-Kynver-User-Id + X-Kynver-Service-Secret
|
|
6
|
-
* request headers. The legacy userId/secret query params are still
|
|
7
|
-
* accepted as a deprecated transitional fallback so an older web-tier
|
|
8
|
-
* build keeps working during a rolling deploy — remove once all callers
|
|
9
|
-
* send headers. Secrets must never travel in the query string in steady
|
|
10
|
-
* state (they leak via access logs, proxies, and referrers).
|
|
11
|
-
* 2. External (Claude Code, Cursor, any MCP client): Kynver API key via
|
|
12
|
-
* query param (?apiKey=…) or Authorization header
|
|
13
|
-
*
|
|
14
|
-
* External keys are validated by calling GET /api/users/me on the Kynver API.
|
|
15
|
-
* Once authenticated, tool calls execute on behalf of the resolved user via
|
|
16
|
-
* the requestContext AsyncLocalStorage.
|
|
17
|
-
*/
|
|
18
|
-
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
19
|
-
import { createServer } from "node:http";
|
|
1
|
+
import { startKynverMcpSseServer } from "@kynver-app/mcp-sse-lib";
|
|
20
2
|
import { createAgentOsServer } from "./server.js";
|
|
21
3
|
import { requestContext } from "./lib/kynver-client.js";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
const sessions = {};
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Helpers
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
/** Validate an external API key by calling the Kynver API. */
|
|
33
|
-
async function resolveUser(apiKey) {
|
|
34
|
-
try {
|
|
35
|
-
const res = await fetch(`${BASE_URL}/api/users/me`, {
|
|
36
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
37
|
-
});
|
|
38
|
-
if (!res.ok)
|
|
39
|
-
return null;
|
|
40
|
-
const data = (await res.json());
|
|
41
|
-
return data.id ?? null;
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/** Read and JSON-parse the request body (needed for raw http.Server). */
|
|
48
|
-
function parseBody(req) {
|
|
49
|
-
return new Promise((resolve, reject) => {
|
|
50
|
-
const chunks = [];
|
|
51
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
52
|
-
req.on("end", () => {
|
|
53
|
-
try {
|
|
54
|
-
resolve(JSON.parse(Buffer.concat(chunks).toString()));
|
|
55
|
-
}
|
|
56
|
-
catch (e) {
|
|
57
|
-
reject(e);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
req.on("error", reject);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
function parseUrl(req) {
|
|
64
|
-
return new URL(req.url ?? "/", `http://localhost:${PORT}`);
|
|
65
|
-
}
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
// HTTP server
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
const httpServer = createServer(async (req, res) => {
|
|
70
|
-
const url = parseUrl(req);
|
|
71
|
-
const path = url.pathname;
|
|
72
|
-
// ── SSE connection ──────────────────────────────────────────────────
|
|
73
|
-
if (req.method === "GET" && path === "/sse") {
|
|
74
|
-
// Internal auth: Kynver web tier passes userId + service secret as headers.
|
|
75
|
-
// Query params are a deprecated transitional fallback (see file header).
|
|
76
|
-
const headerValue = (name) => {
|
|
77
|
-
const v = req.headers[name];
|
|
78
|
-
return typeof v === "string" ? v : Array.isArray(v) ? (v[0] ?? null) : null;
|
|
79
|
-
};
|
|
80
|
-
const internalUserId = headerValue("x-kynver-user-id") ?? url.searchParams.get("userId");
|
|
81
|
-
const internalSecret = headerValue("x-kynver-service-secret") ?? url.searchParams.get("secret");
|
|
82
|
-
// External auth: API key via query param or Authorization header
|
|
83
|
-
const apiKey = url.searchParams.get("apiKey") ??
|
|
84
|
-
req.headers.authorization?.replace("Bearer ", "") ??
|
|
85
|
-
"";
|
|
86
|
-
let userId = null;
|
|
87
|
-
if (internalUserId && SERVICE_SECRET && internalSecret === SERVICE_SECRET) {
|
|
88
|
-
userId = internalUserId;
|
|
89
|
-
}
|
|
90
|
-
else if (apiKey) {
|
|
91
|
-
userId = await resolveUser(apiKey);
|
|
92
|
-
}
|
|
93
|
-
if (!userId) {
|
|
94
|
-
res.writeHead(401, { "Content-Type": "application/json" });
|
|
95
|
-
res.end(JSON.stringify({
|
|
96
|
-
error: "Unauthorized. Provide a valid Kynver API key.",
|
|
97
|
-
}));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
console.log(`[MCP SSE] New connection (userId: ${userId})`);
|
|
101
|
-
const transport = new SSEServerTransport("/messages", res);
|
|
102
|
-
sessions[transport.sessionId] = { transport, userId };
|
|
103
|
-
res.on("close", () => {
|
|
104
|
-
console.log(`[MCP SSE] Connection closed: ${transport.sessionId}`);
|
|
105
|
-
delete sessions[transport.sessionId];
|
|
106
|
-
});
|
|
107
|
-
const server = createAgentOsServer();
|
|
108
|
-
await server.connect(transport);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
// ── Message handler (client → server JSON-RPC messages) ─────────────
|
|
112
|
-
if (req.method === "POST" && path === "/messages") {
|
|
113
|
-
const sessionId = url.searchParams.get("sessionId");
|
|
114
|
-
const entry = sessionId ? sessions[sessionId] : undefined;
|
|
115
|
-
if (!entry) {
|
|
116
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
117
|
-
res.end(JSON.stringify({
|
|
118
|
-
error: "No active SSE session for this sessionId",
|
|
119
|
-
}));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const body = await parseBody(req);
|
|
123
|
-
// Wrap in request context so kynver-client sends the right user headers
|
|
124
|
-
await requestContext.run({ userId: entry.userId }, async () => {
|
|
125
|
-
await entry.transport.handlePostMessage(req, res, body);
|
|
126
|
-
});
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
// ── Health check ────────────────────────────────────────────────────
|
|
130
|
-
if (req.method === "GET" && path === "/health") {
|
|
131
|
-
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
132
|
-
res.end("ok");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
136
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
137
|
-
});
|
|
138
|
-
httpServer.listen(PORT, () => {
|
|
139
|
-
console.log(`[MCP SSE] Agentic OS server listening on port ${PORT}`);
|
|
4
|
+
startKynverMcpSseServer({
|
|
5
|
+
label: "Agentic OS server",
|
|
6
|
+
defaultPort: 3101,
|
|
7
|
+
createMcpServer: createAgentOsServer,
|
|
8
|
+
requestContext,
|
|
140
9
|
});
|
|
@@ -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.
|
|
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": {
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"README.md"
|
|
11
|
+
"README.md",
|
|
12
|
+
"SECURITY.md"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
|
-
"build": "node --max-old-space-size=8192 ../../node_modules/typescript/bin/tsc",
|
|
15
|
+
"build": "npm run build -w @kynver-app/mcp-sse-lib && node --max-old-space-size=8192 ../../node_modules/typescript/bin/tsc",
|
|
15
16
|
"dev": "node --max-old-space-size=8192 ../../node_modules/typescript/bin/tsc --watch",
|
|
16
17
|
"start": "node dist/index.js",
|
|
17
18
|
"start:sse": "node dist/sse.js"
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
"node": ">=18"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
24
|
+
"@kynver-app/mcp-sse-lib": "*",
|
|
23
25
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
24
26
|
"zod": "^3.23.0"
|
|
25
27
|
},
|