rspace-online/server/mcp-tools/rpubs.ts

82 lines
2.4 KiB
TypeScript

/**
* MCP tools for rPubs (publication drafts).
*
* Tools: rpubs_list_drafts, rpubs_get_draft
*/
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import type { SyncServer } from "../local-first/sync-server";
import type { PubsDoc } from "../../modules/rpubs/schemas";
import { resolveAccess, accessDeniedResponse } from "./_auth";
const PUBS_PREFIX = ":pubs:drafts:";
function findPubsDocIds(syncServer: SyncServer, space: string): string[] {
const prefix = `${space}${PUBS_PREFIX}`;
return syncServer.getDocIds().filter(id => id.startsWith(prefix));
}
export function registerPubsTools(server: McpServer, syncServer: SyncServer) {
server.tool(
"rpubs_list_drafts",
"List publication drafts in a space",
{
space: z.string().describe("Space slug"),
token: z.string().optional().describe("JWT auth token"),
},
async ({ space, token }) => {
const access = await resolveAccess(token, space, false);
if (!access.allowed) return accessDeniedResponse(access.reason!);
const docIds = findPubsDocIds(syncServer, space);
const drafts = [];
for (const docId of docIds) {
const doc = syncServer.getDoc<PubsDoc>(docId);
if (!doc?.draft) continue;
drafts.push({
id: doc.draft.id,
title: doc.draft.title,
author: doc.draft.author,
format: doc.draft.format,
contentPreview: (doc.content || "").slice(0, 200),
createdAt: doc.draft.createdAt,
updatedAt: doc.draft.updatedAt,
});
}
return { content: [{ type: "text", text: JSON.stringify(drafts, null, 2) }] };
},
);
server.tool(
"rpubs_get_draft",
"Get full publication draft content",
{
space: z.string().describe("Space slug"),
token: z.string().optional().describe("JWT auth token"),
draft_id: z.string().describe("Draft ID"),
},
async ({ space, token, draft_id }) => {
const access = await resolveAccess(token, space, false);
if (!access.allowed) return accessDeniedResponse(access.reason!);
const docId = `${space}${PUBS_PREFIX}${draft_id}`;
const doc = syncServer.getDoc<PubsDoc>(docId);
if (!doc?.draft) {
return { content: [{ type: "text", text: JSON.stringify({ error: "Draft not found" }) }] };
}
return {
content: [{
type: "text",
text: JSON.stringify({
draft: doc.draft,
content: doc.content || "",
}, null, 2),
}],
};
},
);
}