Files
jschoubben db8b60f4e4 Convert confluence and jira into nox catalog modules
Port the HAL confluence and jira integrations to the tools-only,
outbound-only external-SaaS pattern proven by the merged gitlab module:
runtime-only containers (container-runtime capability), own-secret token +
broker, a settings-managed config.json for public config, and a
per-module runtime image.

Each module carries its own Atlassian API client and tools (ADR 0039),
translated from HAL's @hal/sdk zod-schema/MCP-content shape into
mesh-sdk's input/run-returns-data shape. Public config (ATLASSIAN_URL,
ATLASSIAN_EMAIL) lives in config.json; the API token is the one
own-secret. Clients are built lazily and never throw at registration, so
each runtime serves its full tool surface with no credentials (the
Servarr lesson) — confluence serves 3 tools, jira serves 8.

jira's periodic ticket-poller (update-tickets.service/.timer) is NOT
ported: the mesh has no scheduled-task primitive yet (a pending
decision). Only jira's tools are ported; a top-of-file note records the
deferral.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-06 01:49:22 +02:00

71 lines
2.8 KiB
TypeScript

// confluence's tools — confluence's own code (novox/hq ADR 0039), importing confluence's own
// Confluence API client. They return structured data; the mesh serves them through the sdk's tool
// harness. confluence is tools-only and outbound-only: no service, no events, no listener — it
// reaches out to an Atlassian Confluence instance and exposes its content search, pages and spaces.
//
// Every tool is registered unconditionally, even with no credentials configured (the Servarr
// lesson): the client is built lazily and never throws, so the runtime always comes up and serves
// the full tool surface — a call made before the URL/email/token are set fails with a clear error,
// but the runtime does not refuse to serve. The install proof is the runtime logging
// `[mesh-tools] serving N tool(s)`.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
import { ConfluenceClient } from "../client.js";
function str(value: unknown): string {
return String(value ?? "");
}
function num(value: unknown, fallback: number): number {
const n = Number(value);
return Number.isFinite(n) ? n : fallback;
}
export function getConfluenceTools(confluence: ConfluenceClient): ToolDefinition[] {
return [
{
name: "confluence_search",
description: "Search Confluence content using CQL.",
input: {
cql: { type: "string", description: "CQL query string" },
limit: { type: "number", description: "max results (default 25)" },
},
run: async (args) => ({ results: await confluence.search(str(args.cql), num(args.limit, 25)) }),
},
{
name: "confluence_get_page",
description: "Get a Confluence page by ID.",
input: {
page_id: { type: "string", description: "Page ID" },
},
run: async (args) => ({ page: await confluence.getPage(str(args.page_id)) }),
},
{
name: "confluence_list_spaces",
description: "List Confluence spaces.",
input: {
page: { type: "number", description: "page number, 1-based (default 1)" },
limit: { type: "number", description: "results per page (default 25)" },
},
run: async (args) => {
const page = num(args.page, 1);
const limit = num(args.limit, 25);
const cursor = String((page - 1) * limit);
return { spaces: await confluence.listSpaces({ cursor, limit: String(limit) }) };
},
},
];
}
// confluence always registers its full tool surface: the client is built lazily and never throws, so
// the runtime comes up and serves every tool even before the URL/email/token are configured (the lab
// has no real Atlassian). A tool called before the module is configured fails with a clear error from
// that call.
registerModuleTools("confluence", (env) => {
try {
return getConfluenceTools(ConfluenceClient.fromEnv(env));
} catch {
return [];
}
});