openalmanac 0.4.3 → 0.4.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.
@@ -1,14 +1,34 @@
1
1
  import type { FastMCP } from "fastmcp";
2
2
  type ToolGroup = "read" | "write" | "research" | "wiki_admin" | "account";
3
+ type EntityType = "account" | "image" | "page" | "topic" | "web" | "wiki";
4
+ type MutationScope = "none" | "local" | "backend" | "backend_dry_run";
3
5
  type IntentDomain = "auth" | "page_read" | "page_write" | "search" | "topic_management" | "web_research" | "wiki_management";
4
6
  export interface McpToolCallEvent {
5
7
  tool_name: string;
6
8
  tool_group: ToolGroup;
7
9
  intent_domain: IntentDomain;
10
+ operation: string;
11
+ entity_type: EntityType;
12
+ mutation_scope: MutationScope;
8
13
  success: boolean;
9
14
  duration_ms: number;
10
15
  mcp_version: string;
11
16
  error_type?: string;
17
+ wiki_slug?: string;
18
+ dry_run?: boolean;
19
+ requested_count?: number;
20
+ result_count?: number;
21
+ result_created_count?: number;
22
+ result_updated_count?: number;
23
+ result_renamed_count?: number;
24
+ result_unchanged_count?: number;
25
+ result_error_count?: number;
26
+ result_skipped_count?: number;
27
+ result_stub_created_count?: number;
28
+ planned_created_count?: number;
29
+ planned_updated_count?: number;
30
+ planned_renamed_count?: number;
31
+ planned_error_count?: number;
12
32
  }
13
33
  interface McpToolDefinition {
14
34
  name: string;
@@ -21,6 +41,8 @@ interface ToolTrackingDeps {
21
41
  trackToolCall?: (event: McpToolCallEvent) => Promise<void>;
22
42
  now?: () => number;
23
43
  }
44
+ export type McpTrackingDetails = Partial<Omit<McpToolCallEvent, "tool_name" | "tool_group" | "intent_domain" | "success" | "duration_ms" | "mcp_version" | "error_type">>;
45
+ export declare function setMcpTrackingDetails(details: McpTrackingDetails): void;
24
46
  export declare function postMcpToolCall(event: McpToolCallEvent): Promise<void>;
25
47
  export declare function wrapMcpToolDefinition<T extends McpToolDefinition>(definition: T, deps: ToolTrackingDeps): T;
26
48
  export declare function installMcpToolTracking(server: FastMCP, mcpVersion: string): FastMCP;
@@ -1,31 +1,45 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import { request, requireValidApiKey } from "./auth.js";
2
3
  const AUTH_EXEMPT_TOOLS = new Set(["login", "logout"]);
4
+ const trackingDetailsStorage = new AsyncLocalStorage();
5
+ export function setMcpTrackingDetails(details) {
6
+ const store = trackingDetailsStorage.getStore();
7
+ if (store) {
8
+ store.details = { ...store.details, ...details };
9
+ }
10
+ }
3
11
  const TOOL_METADATA = {
4
- search_pages: { tool_group: "read", intent_domain: "search" },
5
- search_topics: { tool_group: "read", intent_domain: "search" },
6
- list_pages: { tool_group: "read", intent_domain: "page_read" },
7
- list_topics: { tool_group: "read", intent_domain: "page_read" },
8
- list_wikis: { tool_group: "read", intent_domain: "wiki_management" },
9
- download: { tool_group: "read", intent_domain: "page_read" },
10
- read_page: { tool_group: "read", intent_domain: "page_read" },
11
- search_web: { tool_group: "research", intent_domain: "web_research" },
12
- read_webpage: { tool_group: "research", intent_domain: "web_research" },
13
- search_images: { tool_group: "research", intent_domain: "web_research" },
14
- view_images: { tool_group: "research", intent_domain: "web_research" },
15
- new: { tool_group: "write", intent_domain: "page_write" },
16
- publish: { tool_group: "write", intent_domain: "page_write" },
17
- delete_pages: { tool_group: "write", intent_domain: "page_write" },
18
- create_topics: { tool_group: "write", intent_domain: "topic_management" },
19
- update_topic: { tool_group: "write", intent_domain: "topic_management" },
20
- create_wiki: { tool_group: "wiki_admin", intent_domain: "wiki_management" },
21
- get_wiki_settings: { tool_group: "wiki_admin", intent_domain: "wiki_management" },
22
- update_wiki_settings: { tool_group: "wiki_admin", intent_domain: "wiki_management" },
23
- join_wiki: { tool_group: "wiki_admin", intent_domain: "wiki_management" },
24
- get_wiki_membership: { tool_group: "wiki_admin", intent_domain: "wiki_management" },
25
- whoami: { tool_group: "account", intent_domain: "auth" },
12
+ search_pages: { tool_group: "read", intent_domain: "search", operation: "page_search", entity_type: "page", mutation_scope: "none" },
13
+ search_topics: { tool_group: "read", intent_domain: "search", operation: "topic_search", entity_type: "topic", mutation_scope: "none" },
14
+ list_pages: { tool_group: "read", intent_domain: "page_read", operation: "page_list", entity_type: "page", mutation_scope: "none" },
15
+ list_topics: { tool_group: "read", intent_domain: "page_read", operation: "topic_list", entity_type: "topic", mutation_scope: "none" },
16
+ list_wikis: { tool_group: "read", intent_domain: "wiki_management", operation: "wiki_list", entity_type: "wiki", mutation_scope: "none" },
17
+ download: { tool_group: "read", intent_domain: "page_read", operation: "page_download", entity_type: "page", mutation_scope: "local" },
18
+ read_page: { tool_group: "read", intent_domain: "page_read", operation: "page_read", entity_type: "page", mutation_scope: "none" },
19
+ search_web: { tool_group: "research", intent_domain: "web_research", operation: "web_search", entity_type: "web", mutation_scope: "none" },
20
+ read_webpage: { tool_group: "research", intent_domain: "web_research", operation: "webpage_read", entity_type: "web", mutation_scope: "none" },
21
+ search_images: { tool_group: "research", intent_domain: "web_research", operation: "image_search", entity_type: "image", mutation_scope: "none" },
22
+ view_images: { tool_group: "research", intent_domain: "web_research", operation: "image_view", entity_type: "image", mutation_scope: "none" },
23
+ new: { tool_group: "write", intent_domain: "page_write", operation: "page_scaffold", entity_type: "page", mutation_scope: "local" },
24
+ publish: { tool_group: "write", intent_domain: "page_write", operation: "page_publish", entity_type: "page", mutation_scope: "backend" },
25
+ delete_pages: { tool_group: "write", intent_domain: "page_write", operation: "page_delete", entity_type: "page", mutation_scope: "backend" },
26
+ create_topics: { tool_group: "write", intent_domain: "topic_management", operation: "topic_create", entity_type: "topic", mutation_scope: "backend" },
27
+ update_topic: { tool_group: "write", intent_domain: "topic_management", operation: "topic_update", entity_type: "topic", mutation_scope: "backend" },
28
+ create_wiki: { tool_group: "wiki_admin", intent_domain: "wiki_management", operation: "wiki_create", entity_type: "wiki", mutation_scope: "backend" },
29
+ get_wiki_settings: { tool_group: "wiki_admin", intent_domain: "wiki_management", operation: "wiki_settings_read", entity_type: "wiki", mutation_scope: "none" },
30
+ update_wiki_settings: { tool_group: "wiki_admin", intent_domain: "wiki_management", operation: "wiki_settings_update", entity_type: "wiki", mutation_scope: "backend" },
31
+ join_wiki: { tool_group: "wiki_admin", intent_domain: "wiki_management", operation: "wiki_join", entity_type: "wiki", mutation_scope: "backend" },
32
+ get_wiki_membership: { tool_group: "wiki_admin", intent_domain: "wiki_management", operation: "wiki_membership_read", entity_type: "wiki", mutation_scope: "none" },
33
+ whoami: { tool_group: "account", intent_domain: "auth", operation: "account_read", entity_type: "account", mutation_scope: "none" },
26
34
  };
27
35
  function metadataForTool(toolName) {
28
- return TOOL_METADATA[toolName] ?? { tool_group: "account", intent_domain: "auth" };
36
+ return TOOL_METADATA[toolName] ?? {
37
+ tool_group: "account",
38
+ intent_domain: "auth",
39
+ operation: "unknown",
40
+ entity_type: "account",
41
+ mutation_scope: "none",
42
+ };
29
43
  }
30
44
  function errorType(error) {
31
45
  if (error instanceof Error && error.name)
@@ -52,10 +66,11 @@ export function wrapMcpToolDefinition(definition, deps) {
52
66
  async execute(...args) {
53
67
  await validateAuth();
54
68
  const startedAt = now();
69
+ const trackingState = { details: {} };
55
70
  let success = false;
56
71
  let caught;
57
72
  try {
58
- const result = await execute(...args);
73
+ const result = await trackingDetailsStorage.run(trackingState, async () => execute(...args));
59
74
  success = true;
60
75
  return result;
61
76
  }
@@ -67,6 +82,7 @@ export function wrapMcpToolDefinition(definition, deps) {
67
82
  const event = {
68
83
  tool_name: definition.name,
69
84
  ...metadata,
85
+ ...trackingState.details,
70
86
  success,
71
87
  duration_ms: Math.max(0, Math.round(now() - startedAt)),
72
88
  mcp_version: deps.mcpVersion,
@@ -3,10 +3,34 @@ import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from
3
3
  import { stringify as yamlStringify } from "yaml";
4
4
  import { request } from "../../auth.js";
5
5
  import { openBrowser } from "../../browser.js";
6
+ import { setMcpTrackingDetails } from "../../tool-tracking.js";
6
7
  import { coerceJson } from "../../utils.js";
7
8
  import { formatPublishResults } from "./publish-format.js";
8
9
  import { resolvePageDir, resolvePagePaths, SLUG_RE } from "./workspace.js";
9
10
  import { WRITING_GUIDE } from "./writing-guide.js";
11
+ function summarizePublishTracking(results, requestedCount, wiki_slug, dryRun) {
12
+ const details = {
13
+ wiki_slug,
14
+ dry_run: dryRun,
15
+ mutation_scope: dryRun ? "backend_dry_run" : "backend",
16
+ requested_count: requestedCount,
17
+ result_count: results.length,
18
+ result_error_count: results.filter(result => result.status === "error").length,
19
+ result_stub_created_count: results.reduce((count, result) => count + (result.stubs_created?.length ?? 0), 0),
20
+ };
21
+ if (dryRun) {
22
+ details.planned_created_count = results.filter(result => result.plan?.action === "create").length;
23
+ details.planned_updated_count = results.filter(result => result.plan?.action === "update").length;
24
+ details.planned_renamed_count = results.filter(result => result.plan?.action === "rename").length;
25
+ details.planned_error_count = results.filter(result => result.plan?.action === "error").length;
26
+ return details;
27
+ }
28
+ details.result_created_count = results.filter(result => result.status === "created").length;
29
+ details.result_updated_count = results.filter(result => result.status === "updated").length;
30
+ details.result_renamed_count = results.filter(result => result.status === "renamed").length;
31
+ details.result_unchanged_count = results.filter(result => result.status === "unchanged").length;
32
+ return details;
33
+ }
10
34
  export function registerPageTools(server) {
11
35
  server.addTool({
12
36
  name: "search_pages",
@@ -198,6 +222,13 @@ export function registerPageTools(server) {
198
222
  nudges.length > 0 ? nudges.join("\n") : "",
199
223
  WRITING_GUIDE,
200
224
  ];
225
+ setMcpTrackingDetails({
226
+ wiki_slug,
227
+ requested_count: pages.length,
228
+ result_count: created.length + skipped.length,
229
+ result_created_count: created.length,
230
+ result_skipped_count: skipped.length,
231
+ });
201
232
  return parts.filter(Boolean).join("\n\n");
202
233
  },
203
234
  });
@@ -253,8 +284,11 @@ export function registerPageTools(server) {
253
284
  });
254
285
  const results = (await resp.json());
255
286
  const summary = formatPublishResults(results, targetSlugs, wiki_slug, dry_run ?? false);
287
+ const dryRun = dry_run ?? false;
288
+ const details = summarizePublishTracking(results, targetSlugs.length, wiki_slug, dryRun);
289
+ setMcpTrackingDetails(details);
256
290
  // Open browser on single-page publish success (non-GUI, non-dry-run).
257
- if (!dry_run && targetSlugs.length === 1 && process.env.OPENALMANAC_GUI !== "1") {
291
+ if (!dryRun && targetSlugs.length === 1 && process.env.OPENALMANAC_GUI !== "1") {
258
292
  const r = results[0];
259
293
  if (r && r.status !== "error") {
260
294
  const resultSlug = r.slug;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openalmanac",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "OpenAlmanac — pull, edit, and push pages to the open knowledge base",
5
5
  "type": "module",
6
6
  "bin": {