initial-commit (#1)
All checks were successful
CD / test (push) Successful in 21s
CD / Check changes (push) Successful in 31s
CD / Build and push (amd64) (push) Successful in 47s
CD / Build and push (arm64) (push) Successful in 3m6s
CD / Create manifest (push) Successful in 55s

Reviewed-on: #1
Co-authored-by: Timo Behrendt <t.behrendt@t00n.de>
Co-committed-by: Timo Behrendt <t.behrendt@t00n.de>
This commit was merged in pull request #1.
This commit is contained in:
2025-07-16 07:21:12 +02:00
committed by t.behrendt
parent 4b4d2c66d9
commit e2081a19b5
199 changed files with 28879 additions and 123 deletions

28
src/tools/collection.ts Normal file
View File

@@ -0,0 +1,28 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { handleSuccess, handleError } from "./utils";
import type { ToolsFactory } from "./toolsFactory";
import type { CollectionsApi } from "../gen/api/outline";
import type { Logger } from "pino";
export const registerCollectionTools: ToolsFactory<CollectionsApi> = (server: McpServer, client: CollectionsApi, logger: Logger) => {
server.registerTool(
"collections_list",
{
title: "List Collections",
description: "List all collections in the Outline workspace",
inputSchema: {},
},
async () => {
try {
const response = await client.collectionsList();
return handleSuccess(response, logger.child({ tool: "collections_list" }));
} catch (err) {
const error = handleError(err, logger.child({ tool: "collections_list" }));
logger.error(error);
return error;
}
}
);
};

19
src/tools/common.ts Normal file
View File

@@ -0,0 +1,19 @@
import { z } from "zod";
const positiveInteger = z.number().int().positive();
const uuid = z.string().uuid();
const optionalUuid = uuid.optional();
// Pagination
export const offset = positiveInteger.optional();
export const limit = positiveInteger.optional().default(10);
export const sort = z.enum(["createdAt", "updatedAt", "title"]).optional();
export const direction = z
.enum(["asc", "desc"])
.optional()
.transform((val) => val?.toUpperCase() as "ASC" | "DESC" | undefined);
export const collectionId = optionalUuid;
export const userId = optionalUuid;
export const backlinkDocumentId = optionalUuid;
export const parentDocumentId = optionalUuid;

68
src/tools/document.ts Normal file
View File

@@ -0,0 +1,68 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { backlinkDocumentId, collectionId, direction, limit, offset, parentDocumentId, sort, userId } from "./common";
import { handleSuccess, handleError } from "./utils";
import type { ToolsFactory } from "./toolsFactory";
import type { DocumentsApi } from "../gen/api/outline";
import type { Logger } from "pino";
export const registerDocumentTools: ToolsFactory<DocumentsApi> = (server: McpServer, client: DocumentsApi, logger: Logger): void => {
server.registerTool(
"documents_info",
{
title: "Get Document Info",
description: "Retrieve a document by its UUID, urlId, or shareId. At least one of these parameters must be provided.",
inputSchema: {
id: z.string().optional().describe("Unique identifier for the document. Either the UUID or the urlId is acceptable."),
shareId: z.string().optional().describe("Unique identifier for a document share, a shareId may be used in place of a document UUID"),
},
},
async (args) => {
try {
const response = await client.documentsInfo({
documentsInfoRequest: {
id: args?.id,
shareId: args?.shareId,
},
});
return handleSuccess(response, logger.child({ tool: "documents_info" }));
} catch (error) {
return handleError(error, logger.child({ tool: "documents_info" }));
}
}
);
const documentsListSchema = z.object({
offset,
limit,
sort,
direction,
collectionId,
userId,
backlinkDocumentId,
parentDocumentId,
template: z.boolean().optional(),
});
server.registerTool(
"documents_list",
{
title: "List Documents",
description: "This method will list all published documents and draft documents belonging to the current user.",
inputSchema: documentsListSchema.shape,
},
async (args) => {
try {
const validatedArgs = documentsListSchema.parse(args);
const response = await client.documentsList({
documentsListRequest: validatedArgs,
});
return handleSuccess(response, logger.child({ tool: "documents_list" }));
} catch (error) {
return handleError(error, logger.child({ tool: "documents_list" }));
}
}
);
};

View File

@@ -0,0 +1,5 @@
import type { BaseAPI } from "../gen/api/outline";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { Logger } from "pino";
export type ToolsFactory<T extends BaseAPI> = (server: McpServer, client: T, logger: Logger) => void;

28
src/tools/utils.ts Normal file
View File

@@ -0,0 +1,28 @@
import type { Logger } from "pino";
export const handleError = (error: unknown, logger: Logger) => {
const message = `Error: ${error instanceof Error ? error.message : "Unknown error"}`;
logger.error(message);
return {
content: [
{
type: "text" as const,
text: message,
},
],
};
};
const handleApiResponse = (response: unknown) => {
return JSON.stringify(response, null, 2);
};
export const handleSuccess = (data: unknown, logger: Logger) => {
const message = handleApiResponse(data);
logger.debug(message);
return {
content: [{ type: "text" as const, text: message }],
};
};