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

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
@@ -70,6 +70,7 @@ follow-ups instead of generic lifecycle text.
70
70
  | `KYNVER_API_KEY` | Bearer token for external authentication |
71
71
  | `KYNVER_AGENT_OS_SLUG` | Default agent slug when the tool's `slug` arg is omitted (defaults to `"ghost"`) |
72
72
  | `KYNVER_SERVICE_SECRET` | Required when running under SSE and forwarding internal `userId` headers |
73
+ | `MCP_SSE_HOST` | SSE bind address (defaults to `127.0.0.1`) |
73
74
  | `PORT` | SSE port (defaults to `3101`) |
74
75
 
75
76
  ## 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/sse.d.ts CHANGED
@@ -1,13 +1 @@
1
- /**
2
- * SSE entry point for the Agentic OS MCP server.
3
- *
4
- * Supports two authentication modes:
5
- * 1. Internal (Kynver dashboard): userId + KYNVER_SERVICE_SECRET query params
6
- * 2. External (Claude Code, Cursor, any MCP client): Kynver API key via
7
- * query param (?apiKey=…) or Authorization header
8
- *
9
- * External keys are validated by calling GET /api/users/me on the Kynver API.
10
- * Once authenticated, tool calls execute on behalf of the resolved user via
11
- * the requestContext AsyncLocalStorage.
12
- */
13
1
  export {};
package/dist/sse.js CHANGED
@@ -1,130 +1,9 @@
1
- /**
2
- * SSE entry point for the Agentic OS MCP server.
3
- *
4
- * Supports two authentication modes:
5
- * 1. Internal (Kynver dashboard): userId + KYNVER_SERVICE_SECRET query params
6
- * 2. External (Claude Code, Cursor, any MCP client): Kynver API key via
7
- * query param (?apiKey=…) or Authorization header
8
- *
9
- * External keys are validated by calling GET /api/users/me on the Kynver API.
10
- * Once authenticated, tool calls execute on behalf of the resolved user via
11
- * the requestContext AsyncLocalStorage.
12
- */
13
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
14
- import { createServer } from "node:http";
1
+ import { startKynverMcpSseServer } from "@kynver-app/mcp-sse-lib";
15
2
  import { createAgentOsServer } from "./server.js";
16
3
  import { requestContext } from "./lib/kynver-client.js";
17
- const PORT = parseInt(process.env.PORT ?? "3101", 10);
18
- const BASE_URL = process.env.KYNVER_API_URL ?? "https://kynver.com";
19
- const SERVICE_SECRET = process.env.KYNVER_SERVICE_SECRET ?? "";
20
- // ---------------------------------------------------------------------------
21
- // Session tracking — maps sessionId → { transport, userId }
22
- // ---------------------------------------------------------------------------
23
- const sessions = {};
24
- // ---------------------------------------------------------------------------
25
- // Helpers
26
- // ---------------------------------------------------------------------------
27
- /** Validate an external API key by calling the Kynver API. */
28
- async function resolveUser(apiKey) {
29
- try {
30
- const res = await fetch(`${BASE_URL}/api/users/me`, {
31
- headers: { Authorization: `Bearer ${apiKey}` },
32
- });
33
- if (!res.ok)
34
- return null;
35
- const data = (await res.json());
36
- return data.id ?? null;
37
- }
38
- catch {
39
- return null;
40
- }
41
- }
42
- /** Read and JSON-parse the request body (needed for raw http.Server). */
43
- function parseBody(req) {
44
- return new Promise((resolve, reject) => {
45
- const chunks = [];
46
- req.on("data", (chunk) => chunks.push(chunk));
47
- req.on("end", () => {
48
- try {
49
- resolve(JSON.parse(Buffer.concat(chunks).toString()));
50
- }
51
- catch (e) {
52
- reject(e);
53
- }
54
- });
55
- req.on("error", reject);
56
- });
57
- }
58
- function parseUrl(req) {
59
- return new URL(req.url ?? "/", `http://localhost:${PORT}`);
60
- }
61
- // ---------------------------------------------------------------------------
62
- // HTTP server
63
- // ---------------------------------------------------------------------------
64
- const httpServer = createServer(async (req, res) => {
65
- const url = parseUrl(req);
66
- const path = url.pathname;
67
- // ── SSE connection ──────────────────────────────────────────────────
68
- if (req.method === "GET" && path === "/sse") {
69
- // Internal auth: Kynver dashboard passes userId + service secret
70
- const internalUserId = url.searchParams.get("userId");
71
- const internalSecret = url.searchParams.get("secret");
72
- // External auth: API key via query param or Authorization header
73
- const apiKey = url.searchParams.get("apiKey") ??
74
- req.headers.authorization?.replace("Bearer ", "") ??
75
- "";
76
- let userId = null;
77
- if (internalUserId && SERVICE_SECRET && internalSecret === SERVICE_SECRET) {
78
- userId = internalUserId;
79
- }
80
- else if (apiKey) {
81
- userId = await resolveUser(apiKey);
82
- }
83
- if (!userId) {
84
- res.writeHead(401, { "Content-Type": "application/json" });
85
- res.end(JSON.stringify({
86
- error: "Unauthorized. Provide a valid Kynver API key.",
87
- }));
88
- return;
89
- }
90
- console.log(`[MCP SSE] New connection (userId: ${userId})`);
91
- const transport = new SSEServerTransport("/messages", res);
92
- sessions[transport.sessionId] = { transport, userId };
93
- res.on("close", () => {
94
- console.log(`[MCP SSE] Connection closed: ${transport.sessionId}`);
95
- delete sessions[transport.sessionId];
96
- });
97
- const server = createAgentOsServer();
98
- await server.connect(transport);
99
- return;
100
- }
101
- // ── Message handler (client → server JSON-RPC messages) ─────────────
102
- if (req.method === "POST" && path === "/messages") {
103
- const sessionId = url.searchParams.get("sessionId");
104
- const entry = sessionId ? sessions[sessionId] : undefined;
105
- if (!entry) {
106
- res.writeHead(400, { "Content-Type": "application/json" });
107
- res.end(JSON.stringify({
108
- error: "No active SSE session for this sessionId",
109
- }));
110
- return;
111
- }
112
- const body = await parseBody(req);
113
- // Wrap in request context so kynver-client sends the right user headers
114
- await requestContext.run({ userId: entry.userId }, async () => {
115
- await entry.transport.handlePostMessage(req, res, body);
116
- });
117
- return;
118
- }
119
- // ── Health check ────────────────────────────────────────────────────
120
- if (req.method === "GET" && path === "/health") {
121
- res.writeHead(200, { "Content-Type": "text/plain" });
122
- res.end("ok");
123
- return;
124
- }
125
- res.writeHead(404, { "Content-Type": "application/json" });
126
- res.end(JSON.stringify({ error: "Not found" }));
127
- });
128
- httpServer.listen(PORT, () => {
129
- 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,
130
9
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kynver-app/mcp-agent-os",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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
  },