zerno-mcp 0.1.4 → 0.1.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
@@ -1,13 +1,37 @@
1
1
  # zerno-mcp
2
2
 
3
- Local stdio MCP server for ZERNO code-agent integrations.
3
+ MCP server for ZERNO code-agent integrations.
4
4
 
5
- ## Usage
5
+ ZERNO gives AI coding agents project-scoped product context: backlog tasks,
6
+ stage, execution policy, product memory, checkpoints, and run telemetry.
7
+
8
+ ## Hosted server
9
+
10
+ Use the hosted Streamable HTTP endpoint when your client supports remote MCP:
11
+
12
+ ```text
13
+ https://zerno.one/mcp
14
+ ```
15
+
16
+ Authentication uses OAuth with PKCE. The public discovery documents are:
17
+
18
+ ```text
19
+ https://zerno.one/.well-known/mcp
20
+ https://zerno.one/.well-known/oauth-protected-resource/mcp
21
+ https://zerno.one/.well-known/oauth-authorization-server
22
+ ```
23
+
24
+ The OAuth scope is `mcp`. Tokens are project-scoped and grant only ZERNO MCP
25
+ permissions: read project context/tasks/memory, write task status, plans,
26
+ reports, checkpoints, memory notes, intake tasks, and agent run telemetry.
27
+ They do not grant repository, filesystem, shell, or source-code access.
28
+
29
+ ## Local stdio server
6
30
 
7
31
  Recommended setup:
8
32
 
9
33
  ```bash
10
- npx -y zerno-mcp@0.1.2 setup
34
+ npx -y zerno-mcp@0.1.5 setup
11
35
  ```
12
36
 
13
37
  The setup command opens ZERNO in the browser, lets the user choose a project,
@@ -16,7 +40,7 @@ creates the project-scoped MCP token, and writes local agent configs.
16
40
  Agent runtime:
17
41
 
18
42
  ```bash
19
- npx -y zerno-mcp@0.1.2
43
+ npx -y zerno-mcp@0.1.5
20
44
  ```
21
45
 
22
46
  Required environment:
@@ -26,3 +50,118 @@ ZERNO_API_URL=https://zerno.one
26
50
  ZERNO_API_TOKEN=<project-token>
27
51
  ZERNO_PROJECT_ID=<project-id>
28
52
  ```
53
+
54
+ For local stdio, `ZERNO_PROJECT_ID` may also come from a non-secret repository
55
+ binding file:
56
+
57
+ ```json
58
+ {
59
+ "project_id": "<project-id>",
60
+ "workspace_id": "<workspace-id>",
61
+ "origin": "git@github.com:org/repo.git"
62
+ }
63
+ ```
64
+
65
+ Place it at `.zerno/project.json`. Environment variables still win, so CI and
66
+ manual project-scoped setups remain deterministic.
67
+
68
+ ## Client examples
69
+
70
+ Codex remote MCP:
71
+
72
+ ```bash
73
+ codex mcp add zerno --url https://zerno.one/mcp
74
+ codex mcp login zerno --scopes mcp
75
+ ```
76
+
77
+ Claude Code remote MCP:
78
+
79
+ ```bash
80
+ claude mcp add --transport http zerno https://zerno.one/mcp
81
+ ```
82
+
83
+ Then run `/mcp` inside Claude Code to complete OAuth and confirm that ZERNO
84
+ shows connected with tools available.
85
+
86
+ Claude Desktop local stdio:
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "zerno": {
92
+ "command": "npx",
93
+ "args": ["-y", "zerno-mcp@0.1.5"],
94
+ "env": {
95
+ "ZERNO_API_URL": "https://zerno.one",
96
+ "ZERNO_API_TOKEN": "<project-token>",
97
+ "ZERNO_PROJECT_ID": "<project-id>"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ Cursor local stdio uses the same `mcpServers.zerno` block in `.mcp.json`.
105
+
106
+ OpenCode local stdio:
107
+
108
+ ```json
109
+ {
110
+ "mcp": {
111
+ "zerno": {
112
+ "type": "local",
113
+ "command": ["npx", "-y", "zerno-mcp@0.1.5"],
114
+ "enabled": true,
115
+ "environment": {
116
+ "ZERNO_API_URL": "https://zerno.one",
117
+ "ZERNO_API_TOKEN": "<project-token>",
118
+ "ZERNO_PROJECT_ID": "<project-id>"
119
+ }
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ ## Main tools
126
+
127
+ - `zerno_get_project_context`
128
+ - `zerno_list_tasks`
129
+ - `zerno_get_task`
130
+ - `zerno_resume_task`
131
+ - `zerno_checkpoint`
132
+ - `zerno_update_task_status`
133
+ - `zerno_memory_snapshot`
134
+ - `zerno_run_start`
135
+
136
+ ## Compatibility notes
137
+
138
+ - Remote transport: Streamable HTTP at `/mcp`.
139
+ - Local transport: stdio via the official MCP TypeScript SDK.
140
+ - Server instructions are returned during initialization so Claude Code Tool
141
+ Search can decide when ZERNO tools are relevant.
142
+ - Tool text output is capped to keep Claude Code and Codex context usage
143
+ predictable. Full structured data remains available in `structuredContent`
144
+ on the hosted HTTP transport.
145
+ - The local stdio runtime does not write logs to stdout.
146
+
147
+ ## Verification
148
+
149
+ ```bash
150
+ npm run build
151
+ npm pack --dry-run
152
+ ```
153
+
154
+ For the hosted endpoint:
155
+
156
+ ```bash
157
+ curl -i https://zerno.one/.well-known/mcp
158
+ curl -i https://zerno.one/.well-known/oauth-protected-resource/mcp
159
+ curl -i -X POST https://zerno.one/mcp \
160
+ -H 'content-type: application/json' \
161
+ --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
162
+ ```
163
+
164
+ ## Registry metadata
165
+
166
+ The MCP Registry server name is `io.github.zernozerno/zerno-mcp`.
167
+ Package verification is declared with `mcpName` in `package.json`.
package/dist/index.js CHANGED
@@ -22,7 +22,12 @@ if (process.argv[2] === "setup") {
22
22
  process.exit(0);
23
23
  }
24
24
  const client = await import("./zerno-client.js");
25
- const server = new Server({ name: "zerno-mcp", version: "0.1.4" }, { capabilities: { tools: {} } });
25
+ const SERVER_INSTRUCTIONS = "ZERNO provides project-scoped product context for coding agents: backlog tasks, stage, execution policy, product memory, checkpoints, and run telemetry. Use these tools when the user asks to work from ZERNO tasks, inspect backlog/product context, resume prior agent work, record checkpoints, or report task progress. ZERNO never grants repository, filesystem, shell, or source-code access.";
26
+ const MAX_TEXT_CHARS = 12000;
27
+ const server = new Server({ name: "zerno-mcp", version: "0.1.5" }, {
28
+ capabilities: { tools: {} },
29
+ instructions: SERVER_INSTRUCTIONS,
30
+ });
26
31
  function asRecord(value) {
27
32
  if (value && typeof value === "object") {
28
33
  return value;
@@ -42,6 +47,12 @@ function asTaskList(value) {
42
47
  const items = rec.tasks ?? rec.items ?? rec.results;
43
48
  return Array.isArray(items) ? items : [];
44
49
  }
50
+ function capText(value) {
51
+ if (value.length <= MAX_TEXT_CHARS)
52
+ return value;
53
+ const suffix = `\n\n[truncated: ${value.length - MAX_TEXT_CHARS} chars omitted]`;
54
+ return `${value.slice(0, Math.max(0, MAX_TEXT_CHARS - suffix.length))}${suffix}`;
55
+ }
45
56
  function renderForTool(name, args, result) {
46
57
  switch (name) {
47
58
  case "zerno_get_task":
@@ -812,7 +823,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
812
823
  throw new Error(`Unknown tool: ${name}`);
813
824
  }
814
825
  const rendered = renderForTool(name, (args ?? {}), result);
815
- const fallbackJson = JSON.stringify(result, null, 2);
826
+ const fallbackJson = capText(JSON.stringify(result, null, 2));
816
827
  const content = [];
817
828
  if (rendered?.html) {
818
829
  content.push({
@@ -828,10 +839,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
828
839
  });
829
840
  }
830
841
  if (rendered?.markdown) {
831
- content.push({ type: "text", text: rendered.markdown });
842
+ content.push({ type: "text", text: capText(rendered.markdown) });
832
843
  }
833
844
  content.push({ type: "text", text: fallbackJson });
834
- return { content };
845
+ return { content, _meta: { "anthropic/maxResultSizeChars": MAX_TEXT_CHARS } };
835
846
  }
836
847
  catch (err) {
837
848
  return {
package/dist/setup.js CHANGED
@@ -5,7 +5,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { homedir, platform } from "node:os";
6
6
  import { dirname, join } from "node:path";
7
7
  const DEFAULT_API_URL = "https://zerno.one";
8
- const PACKAGE_SPEC = "zerno-mcp@0.1.4";
8
+ const PACKAGE_SPEC = "zerno-mcp@0.1.5";
9
9
  const MANAGED_START = "# ZERNO MCP:START";
10
10
  const MANAGED_END = "# ZERNO MCP:END";
11
11
  function argValue(args, name) {
@@ -240,7 +240,7 @@ function writeClaudeCode(env) {
240
240
  export async function runSetup(args) {
241
241
  if (args.includes("--help") || args.includes("-h")) {
242
242
  console.log([
243
- "Usage: npx -y zerno-mcp@0.1.4 setup [--api-url https://zerno.one]",
243
+ "Usage: npx -y zerno-mcp@0.1.5 setup [--api-url https://zerno.one]",
244
244
  "",
245
245
  "Opens the hosted ZERNO OAuth consent screen, lets the user choose a project,",
246
246
  "and writes local Codex, Claude Desktop, Claude Code, and OpenCode MCP configs.",
@@ -21,22 +21,38 @@ function readKey(jsonLike, key) {
21
21
  const rx = new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`);
22
22
  return jsonLike.match(rx)?.[1] ?? "";
23
23
  }
24
- function readLocalFallbackConfig() {
25
- const opencodePath = findUp("opencode.jsonc");
26
- const zernoConfigPath = findUp(".zerno/config.json");
27
- const opencodeText = opencodePath ? readFileSync(opencodePath, "utf8") : "";
28
- const zernoConfigText = zernoConfigPath ? readFileSync(zernoConfigPath, "utf8") : "";
29
- let zernoConfigProjectId = "";
24
+ function readJsonFile(path) {
25
+ if (!path)
26
+ return {};
30
27
  try {
31
- zernoConfigProjectId = zernoConfigText ? JSON.parse(zernoConfigText).projectId ?? "" : "";
28
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
29
+ return parsed && typeof parsed === "object" ? parsed : {};
32
30
  }
33
31
  catch {
34
- zernoConfigProjectId = "";
32
+ return {};
33
+ }
34
+ }
35
+ function readStringField(obj, ...keys) {
36
+ for (const key of keys) {
37
+ const value = obj[key];
38
+ if (typeof value === "string" && value.trim())
39
+ return value.trim();
35
40
  }
41
+ return "";
42
+ }
43
+ function readLocalFallbackConfig() {
44
+ const opencodePath = findUp("opencode.jsonc");
45
+ const zernoConfigPath = findUp(".zerno/config.json");
46
+ const zernoProjectPath = findUp(".zerno/project.json");
47
+ const opencodeText = opencodePath ? readFileSync(opencodePath, "utf8") : "";
48
+ const zernoConfig = readJsonFile(zernoConfigPath);
49
+ const zernoProject = readJsonFile(zernoProjectPath);
36
50
  return {
37
51
  apiUrl: readKey(opencodeText, "ZERNO_API_URL"),
38
52
  apiToken: readKey(opencodeText, "ZERNO_API_TOKEN"),
39
- projectId: readKey(opencodeText, "ZERNO_PROJECT_ID") || zernoConfigProjectId,
53
+ projectId: readKey(opencodeText, "ZERNO_PROJECT_ID") ||
54
+ readStringField(zernoProject, "project_id", "projectId") ||
55
+ readStringField(zernoConfig, "project_id", "projectId"),
40
56
  connectionId: readKey(opencodeText, "ZERNO_CONNECTION_ID"),
41
57
  };
42
58
  }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "zerno-mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
+ "mcpName": "io.github.zernozerno/zerno-mcp",
4
5
  "description": "MCP server connecting OpenCode to ZERNO cloud product brain",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
@@ -9,8 +10,18 @@
9
10
  },
10
11
  "files": [
11
12
  "dist",
12
- "README.md"
13
+ "README.md",
14
+ "server.json"
13
15
  ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/zernozerno/betboard-console.git",
19
+ "directory": "packages/zerno-mcp"
20
+ },
21
+ "homepage": "https://zerno.one",
22
+ "bugs": {
23
+ "url": "https://github.com/zernozerno/betboard-console/issues"
24
+ },
14
25
  "publishConfig": {
15
26
  "access": "public"
16
27
  },
package/server.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.zernozerno/zerno-mcp",
4
+ "title": "ZERNO",
5
+ "description": "Project backlog, memory, and execution context for AI coding agents.",
6
+ "version": "0.1.5",
7
+ "websiteUrl": "https://zerno.one",
8
+ "repository": {
9
+ "url": "https://github.com/zernozerno/betboard-console",
10
+ "source": "github"
11
+ },
12
+ "remotes": [
13
+ {
14
+ "type": "streamable-http",
15
+ "url": "https://zerno.one/mcp"
16
+ }
17
+ ],
18
+ "packages": [
19
+ {
20
+ "registryType": "npm",
21
+ "identifier": "zerno-mcp",
22
+ "version": "0.1.5",
23
+ "transport": {
24
+ "type": "stdio"
25
+ }
26
+ }
27
+ ]
28
+ }