openalmanac 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.
@@ -1,18 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { request } from "../auth.js";
3
- function coerceJson(schema) {
4
- return z.preprocess((val) => {
5
- if (typeof val === "string") {
6
- try {
7
- return JSON.parse(val);
8
- }
9
- catch {
10
- return val;
11
- }
12
- }
13
- return val;
14
- }, schema);
15
- }
3
+ import { coerceJson } from "../utils.js";
16
4
  export function registerTopicTools(server) {
17
5
  server.addTool({
18
6
  name: "list_topics",
@@ -28,24 +16,6 @@ export function registerTopicTools(server) {
28
16
  return JSON.stringify(await resp.json(), null, 2);
29
17
  },
30
18
  });
31
- server.addTool({
32
- name: "create_topic",
33
- description: "Create a topic in a wiki. Topics are lightweight categories — pages can belong to multiple topics. Requires wiki membership.",
34
- parameters: z.object({
35
- wiki_slug: z.string().describe("Wiki slug"),
36
- title: z.string().describe("Topic title"),
37
- description: z.string().default("").describe("Topic description"),
38
- image_url: z.string().url().max(2048).optional().describe("Topic image URL (https:// or http://)"),
39
- parent_slugs: coerceJson(z.array(z.string())).default([]).describe("Parent topic slugs"),
40
- }),
41
- async execute({ wiki_slug, title, description, image_url, parent_slugs }) {
42
- const resp = await request("POST", `/api/w/${wiki_slug}/topics`, {
43
- auth: true,
44
- json: { title, description, image_url, parent_slugs },
45
- });
46
- return JSON.stringify(await resp.json(), null, 2);
47
- },
48
- });
49
19
  server.addTool({
50
20
  name: "update_topic",
51
21
  description: "Update a topic's title, description, or image. Requires wiki moderator role.",
@@ -75,8 +45,8 @@ export function registerTopicTools(server) {
75
45
  },
76
46
  });
77
47
  server.addTool({
78
- name: "create_topics_batch",
79
- description: "Batch create topics in a wiki. Useful for bootstrapping a topic hierarchy. Requires wiki membership.",
48
+ name: "create_topics",
49
+ description: "Create one or more topics in a wiki. Topics are lightweight categories — pages can belong to multiple topics. Pass a single-element array for one topic: `[{ title: \"Security Pins\" }]`. Useful for bootstrapping a topic hierarchy. Requires wiki membership.",
80
50
  parameters: z.object({
81
51
  wiki_slug: z.string().describe("Wiki slug"),
82
52
  topics: coerceJson(z.array(z.object({
@@ -84,7 +54,7 @@ export function registerTopicTools(server) {
84
54
  description: z.string().default(""),
85
55
  image_url: z.string().url().max(2048).optional(),
86
56
  parent_slugs: z.array(z.string()).default([]),
87
- })).min(1).max(100)).describe("Topics to create"),
57
+ })).min(1).max(100)).describe("Topics to create (N=1 is fully supported)"),
88
58
  }),
89
59
  async execute({ wiki_slug, topics }) {
90
60
  const resp = await request("POST", `/api/w/${wiki_slug}/topics/batch`, {
@@ -1,18 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { request } from "../auth.js";
3
- function coerceJson(schema) {
4
- return z.preprocess((val) => {
5
- if (typeof val === "string") {
6
- try {
7
- return JSON.parse(val);
8
- }
9
- catch {
10
- return val;
11
- }
12
- }
13
- return val;
14
- }, schema);
15
- }
3
+ import { coerceJson } from "../utils.js";
16
4
  // Mirrors backend `NavItem` in src/schemas/wiki_settings_schemas.py. The
17
5
  // refinement matches the `exactly_one_target` @model_validator there —
18
6
  // agents get a clear error pre-flight instead of a 422 round-trip.
@@ -94,7 +82,7 @@ export function registerWikiTools(server) {
94
82
  });
95
83
  server.addTool({
96
84
  name: "update_wiki_settings",
97
- description: "Update a wiki's settings (nav, cover_image_url, theme). Each NavItem must have exactly one of `page`, `topic`, or `link`. Use `auto` (only on topic items) to auto-populate children from the topic DAG. Requires moderator access.",
85
+ description: "Update a wiki's settings. Pass any combination of `nav`, `cover_image_url`, and `theme` — omitted fields are preserved (the backend uses exclude_unset merge). For example, `{nav: [...]}` updates navigation only without touching theme or cover_image_url. Each NavItem must have exactly one of `page`, `topic`, or `link`. Use `auto` (only on topic items) to auto-populate children from the topic DAG. Requires moderator access.",
98
86
  parameters: z.object({
99
87
  wiki_slug: z.string().describe("Wiki slug"),
100
88
  settings: coerceJson(z.object({
@@ -112,22 +100,32 @@ export function registerWikiTools(server) {
112
100
  },
113
101
  });
114
102
  server.addTool({
115
- name: "update_nav",
116
- description: "Update just the navigation tree for a wiki. Shorthand for updating settings.nav. Requires moderator access.",
103
+ name: "join_wiki",
104
+ description: "Join a wiki as a member. After joining you can create and edit pages. Requires login.",
117
105
  parameters: z.object({
118
106
  wiki_slug: z.string().describe("Wiki slug"),
119
- nav: coerceJson(z.array(navItemSchema)).describe("Nav items"),
120
107
  }),
121
- async execute({ wiki_slug, nav }) {
122
- // Single atomic PATCH the backend's update_settings service uses
123
- // model_dump(exclude_unset=True) to merge partial bodies, so sending
124
- // only {nav} doesn't touch theme or cover_image_url. Previously this
125
- // did a GET-then-PATCH, which raced with concurrent settings updates.
126
- const resp = await request("PATCH", `/api/w/${wiki_slug}/settings`, {
127
- auth: true,
128
- json: { nav },
129
- });
130
- return JSON.stringify(await resp.json(), null, 2);
108
+ async execute({ wiki_slug }) {
109
+ const resp = await request("POST", `/api/w/${wiki_slug}/join`, { auth: true });
110
+ const data = (await resp.json());
111
+ const role = data.role ?? "member";
112
+ return `Joined wiki "${wiki_slug}" as ${role}.`;
113
+ },
114
+ });
115
+ server.addTool({
116
+ name: "get_wiki_membership",
117
+ description: "Check your membership status in a wiki. Returns your role if you are a member. Requires login.",
118
+ parameters: z.object({
119
+ wiki_slug: z.string().describe("Wiki slug"),
120
+ }),
121
+ async execute({ wiki_slug }) {
122
+ const resp = await request("GET", `/api/w/${wiki_slug}/membership/me`, { auth: true });
123
+ const data = (await resp.json());
124
+ if (!data.is_member) {
125
+ return `You are not a member of "${wiki_slug}".`;
126
+ }
127
+ const role = data.role ?? "member";
128
+ return `You are a member of "${wiki_slug}" as ${role}.`;
131
129
  },
132
130
  });
133
131
  }
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Workaround for Claude Agent SDK MCP transport bug (#18260):
4
+ * Array/object parameters are sometimes serialized as JSON strings
5
+ * instead of native values. This preprocessor coerces them back.
6
+ */
7
+ export declare function coerceJson<T extends z.ZodTypeAny>(schema: T): z.ZodEffects<T, T["_output"], unknown>;
package/dist/utils.js ADDED
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Workaround for Claude Agent SDK MCP transport bug (#18260):
4
+ * Array/object parameters are sometimes serialized as JSON strings
5
+ * instead of native values. This preprocessor coerces them back.
6
+ */
7
+ export function coerceJson(schema) {
8
+ return z.preprocess((val) => {
9
+ if (typeof val === "string") {
10
+ try {
11
+ return JSON.parse(val);
12
+ }
13
+ catch {
14
+ return val;
15
+ }
16
+ }
17
+ return val;
18
+ }, schema);
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openalmanac",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "OpenAlmanac — pull, edit, and push articles to the open knowledge base",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,2 +0,0 @@
1
- import { FastMCP } from "fastmcp";
2
- export declare function registerArticleTools(server: FastMCP): void;