/** * MCP tools for rSpace space discovery. * * Tools: list_spaces, list_modules * Resource: rspace://spaces/{slug} */ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { listCommunities, loadCommunity, getDocumentData, normalizeVisibility } from "../community-store"; import { getModuleInfoList } from "../../shared/module"; import { resolveAccess } from "./_auth"; export function registerSpacesTools(server: McpServer) { server.tool( "list_spaces", "List all rSpace spaces with metadata (name, visibility, owner, members, enabled modules)", { token: z.string().optional().describe("JWT auth token (shows private/permissioned spaces you have access to)"), }, async ({ token }) => { const slugs = await listCommunities(); const spaces = []; for (const slug of slugs) { await loadCommunity(slug); const data = getDocumentData(slug); if (!data) continue; const visibility = normalizeVisibility(data.meta.visibility || "private"); // Filter out spaces the caller can't see if (visibility !== "public") { const access = await resolveAccess(token, slug, false); if (!access.allowed) continue; } spaces.push({ slug, name: data.meta.name, visibility, ownerDID: data.meta.ownerDID, description: data.meta.description || null, enabledModules: data.meta.enabledModules || null, memberCount: Object.keys(data.members || {}).length, shapeCount: Object.keys(data.shapes || {}).length, createdAt: data.meta.createdAt, }); } return { content: [{ type: "text", text: JSON.stringify(spaces, null, 2) }], }; }, ); server.tool( "list_modules", "List all available rSpace modules (rApps) with metadata", {}, async () => { const modules = getModuleInfoList(); return { content: [{ type: "text", text: JSON.stringify(modules, null, 2) }], }; }, ); server.resource( "space_metadata", "rspace://spaces/{slug}", { description: "Get detailed metadata for a specific rSpace space" }, async (uri) => { const slug = uri.pathname.split("/").pop(); if (!slug) { return { contents: [{ uri: uri.href, mimeType: "application/json", text: '{"error":"Missing slug"}' }] }; } await loadCommunity(slug); const data = getDocumentData(slug); if (!data) { return { contents: [{ uri: uri.href, mimeType: "application/json", text: '{"error":"Space not found"}' }] }; } const result = { slug, name: data.meta.name, visibility: data.meta.visibility, ownerDID: data.meta.ownerDID, description: data.meta.description || null, enabledModules: data.meta.enabledModules || null, members: Object.values(data.members || {}), shapeCount: Object.keys(data.shapes || {}).length, nestedSpaceCount: Object.keys(data.nestedSpaces || {}).length, createdAt: data.meta.createdAt, }; return { contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(result, null, 2) }], }; }, ); }