feat: update version to 1.1.0 and add new tools for blog, API keys, and sessions
CI / Build & Test (push) Successful in 36s
CI / Build & Test (push) Successful in 36s
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "betternews-mcp",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "MCP server for the BetterNews API",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
+7
-1
@@ -9,6 +9,9 @@ import { registerUserTools } from "./tools/user.js";
|
||||
import { registerModerationTools } from "./tools/moderation.js";
|
||||
import { registerTicketTools } from "./tools/tickets.js";
|
||||
import { registerLegalTools } from "./tools/legal.js";
|
||||
import { registerBlogTools } from "./tools/blog.js";
|
||||
import { registerApiKeyTools } from "./tools/api-keys.js";
|
||||
import { registerSessionTools } from "./tools/sessions.js";
|
||||
|
||||
const apiKey = process.env.BETTERNEWS_API_KEY;
|
||||
if (!apiKey) {
|
||||
@@ -24,7 +27,7 @@ const client = new BetterNewsClient(apiKey, baseUrl);
|
||||
|
||||
const server = new McpServer({
|
||||
name: "betternews",
|
||||
version: "1.0.0",
|
||||
version: "1.1.0",
|
||||
});
|
||||
|
||||
registerNewsTools(server, client);
|
||||
@@ -34,6 +37,9 @@ registerUserTools(server, client);
|
||||
registerModerationTools(server, client);
|
||||
registerTicketTools(server, client);
|
||||
registerLegalTools(server, client);
|
||||
registerBlogTools(server, client);
|
||||
registerApiKeyTools(server, client);
|
||||
registerSessionTools(server, client);
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
import type { BetterNewsClient } from "../client.js";
|
||||
import { handleTool } from "../utils.js";
|
||||
|
||||
const SCOPES = [
|
||||
"news:read",
|
||||
"news:write",
|
||||
"news:moderate",
|
||||
"news:comment",
|
||||
"sources:read",
|
||||
"sources:write",
|
||||
"bookmarks:write",
|
||||
"tickets:read",
|
||||
"tickets:write",
|
||||
"tickets:moderate",
|
||||
"mod:queue",
|
||||
"mod:stats",
|
||||
"mod:users",
|
||||
"blog:write",
|
||||
"legal:write",
|
||||
"user:profile",
|
||||
"user:apikeys",
|
||||
"privacy:read",
|
||||
"files:write",
|
||||
] as const;
|
||||
|
||||
export function registerApiKeyTools(server: McpServer, client: BetterNewsClient) {
|
||||
server.tool(
|
||||
"list_api_keys",
|
||||
"List API keys for the authenticated user. Requires user:apikeys scope.",
|
||||
{},
|
||||
handleTool(async () => {
|
||||
const data = await client.get<unknown>("/api/user/api-keys");
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"create_api_key",
|
||||
"Create a new API key. The raw key value is returned only once. Requires cookie-based session (not another API key) and user:apikeys scope.",
|
||||
{
|
||||
name: z.string().min(1).max(50).describe("Human-readable name for this key"),
|
||||
scopes: z
|
||||
.array(z.enum(SCOPES))
|
||||
.optional()
|
||||
.describe("Permission scopes. Empty array = full access (inherits your role)."),
|
||||
expiresAt: z
|
||||
.string()
|
||||
.datetime()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe("Expiry date (ISO 8601). Null = never expires."),
|
||||
},
|
||||
handleTool(async (body) => {
|
||||
const data = await client.post<unknown>("/api/user/api-keys", body);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"update_api_key",
|
||||
"Update an API key's name, scopes, or expiry. Requires user:apikeys scope.",
|
||||
{
|
||||
keyId: z.string().uuid().describe("API key UUID"),
|
||||
name: z.string().min(1).max(50).optional().describe("New name"),
|
||||
scopes: z.array(z.enum(SCOPES)).optional().describe("New scopes list"),
|
||||
expiresAt: z
|
||||
.string()
|
||||
.datetime()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe("New expiry date (ISO 8601). Null = never expires."),
|
||||
},
|
||||
handleTool(async ({ keyId, ...body }) => {
|
||||
const data = await client.patch<unknown>(`/api/user/api-keys/${keyId}`, body);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"revoke_api_key",
|
||||
"Revoke or permanently delete an API key. Pass hardDelete=true to permanently remove it; otherwise it is soft-revoked and stays visible in the list. Requires user:apikeys scope.",
|
||||
{
|
||||
keyId: z.string().uuid().describe("API key UUID"),
|
||||
hardDelete: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("True to permanently delete; false (default) to soft-revoke"),
|
||||
},
|
||||
handleTool(async ({ keyId, hardDelete }) => {
|
||||
const data = await client.delete<unknown>(
|
||||
`/api/user/api-keys/${keyId}${hardDelete ? "?hardDelete=true" : ""}`,
|
||||
);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
import type { BetterNewsClient } from "../client.js";
|
||||
import { handleTool } from "../utils.js";
|
||||
|
||||
export function registerBlogTools(server: McpServer, client: BetterNewsClient) {
|
||||
server.tool(
|
||||
"list_blog_posts",
|
||||
"List blog posts. Public sees published only; Mod/Admin see all statuses. No auth required for published posts.",
|
||||
{
|
||||
page: z.number().int().positive().optional().describe("Page number (default 1)"),
|
||||
limit: z.number().int().min(1).max(50).optional().describe("Items per page (default 20, max 50)"),
|
||||
status: z.enum(["draft", "published"]).optional().describe("Filter by status (Mod/Admin only for draft)"),
|
||||
},
|
||||
handleTool(async (args) => {
|
||||
const data = await client.get<unknown>("/api/blog", args);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"get_blog_post",
|
||||
"Get a single blog post by slug. Published posts are publicly accessible; drafts require Mod/Admin role.",
|
||||
{
|
||||
slug: z.string().describe("Blog post slug"),
|
||||
},
|
||||
handleTool(async ({ slug }) => {
|
||||
const data = await client.get<unknown>(`/api/blog/${slug}`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"create_blog_post",
|
||||
"Create a new blog post draft. Requires Mod/Admin role and blog:write scope.",
|
||||
{
|
||||
title: z.string().min(1).max(150).describe("Post title"),
|
||||
content: z.string().min(1).describe("Post body (markdown supported)"),
|
||||
excerpt: z.string().max(300).optional().describe("Short excerpt shown in listings"),
|
||||
imageUrl: z.string().url().optional().describe("Hero image URL"),
|
||||
},
|
||||
handleTool(async (body) => {
|
||||
const data = await client.post<unknown>("/api/blog", body);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"update_blog_post",
|
||||
"Update a blog post. Requires Mod/Admin role and blog:write scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("Blog post UUID"),
|
||||
title: z.string().min(1).max(150).optional().describe("Post title"),
|
||||
content: z.string().min(1).optional().describe("Post body"),
|
||||
excerpt: z.string().max(300).nullable().optional().describe("Short excerpt"),
|
||||
imageUrl: z.string().url().nullable().optional().describe("Hero image URL"),
|
||||
},
|
||||
handleTool(async ({ id, ...body }) => {
|
||||
const data = await client.patch<unknown>(`/api/blog/${id}`, body);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"delete_blog_post",
|
||||
"Delete a blog post. Requires Mod/Admin role and blog:write scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("Blog post UUID"),
|
||||
},
|
||||
handleTool(async ({ id }) => {
|
||||
const data = await client.delete<unknown>(`/api/blog/${id}`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"publish_blog_post",
|
||||
"Publish a draft blog post, making it publicly visible. Requires Mod/Admin role and blog:write scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("Blog post UUID"),
|
||||
},
|
||||
handleTool(async ({ id }) => {
|
||||
const data = await client.post<unknown>(`/api/blog/${id}/publish`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"unpublish_blog_post",
|
||||
"Take down a published blog post, reverting it to draft. Requires Mod/Admin role and blog:write scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("Blog post UUID"),
|
||||
},
|
||||
handleTool(async ({ id }) => {
|
||||
const data = await client.post<unknown>(`/api/blog/${id}/unpublish`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -43,4 +43,44 @@ export function registerCommentTools(server: McpServer, client: BetterNewsClient
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"like_comment",
|
||||
"Toggle a like on a comment. Requires news:comment scope.",
|
||||
{
|
||||
newsItemId: z.string().uuid().describe("News item UUID"),
|
||||
commentId: z.string().uuid().describe("Comment UUID"),
|
||||
},
|
||||
handleTool(async ({ newsItemId, commentId }) => {
|
||||
const data = await client.post<unknown>(`/api/news/${newsItemId}/comments/${commentId}/like`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"dislike_comment",
|
||||
"Toggle a dislike on a comment. Requires news:comment scope.",
|
||||
{
|
||||
newsItemId: z.string().uuid().describe("News item UUID"),
|
||||
commentId: z.string().uuid().describe("Comment UUID"),
|
||||
},
|
||||
handleTool(async ({ newsItemId, commentId }) => {
|
||||
const data = await client.post<unknown>(`/api/news/${newsItemId}/comments/${commentId}/dislike`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"hide_comment",
|
||||
"Hide or unhide a comment. Hidden comments are invisible to regular users but still visible to Mod/Admin. Requires Mod/Admin role and news:moderate scope.",
|
||||
{
|
||||
newsItemId: z.string().uuid().describe("News item UUID"),
|
||||
commentId: z.string().uuid().describe("Comment UUID"),
|
||||
hidden: z.boolean().describe("True to hide, false to unhide"),
|
||||
},
|
||||
handleTool(async ({ newsItemId, commentId, hidden }) => {
|
||||
const data = await client.patch<unknown>(`/api/news/${newsItemId}/comments/${commentId}`, { hidden });
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,4 +137,22 @@ export function registerModerationTools(server: McpServer, client: BetterNewsCli
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"list_mod_comments",
|
||||
"List all comments across the platform with moderation details (like/dislike counts, hidden status). Requires Mod/Admin role and mod:queue scope.",
|
||||
{
|
||||
page: z.number().int().positive().optional().describe("Page number (default 1)"),
|
||||
limit: z.number().int().min(1).max(50).optional().describe("Items per page (default 20, max 50)"),
|
||||
hidden: z.boolean().optional().describe("Filter by hidden status. Omit to see all."),
|
||||
sort: z
|
||||
.enum(["top", "most_disliked", "new", "oldest"])
|
||||
.optional()
|
||||
.describe("Sort order: top = most liked, most_disliked, new (default), oldest"),
|
||||
},
|
||||
handleTool(async (args) => {
|
||||
const data = await client.get<unknown>("/api/mod/comments", args);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
+115
-18
@@ -19,7 +19,7 @@ export function registerNewsTools(server: McpServer, client: BetterNewsClient) {
|
||||
"List published news items with optional pagination and filters. Moderators/Admins can also filter by status.",
|
||||
{
|
||||
page: z.number().int().positive().optional().describe("Page number (default 1)"),
|
||||
limit: z.number().int().min(1).max(100).optional().describe("Items per page (default 20, max 100)"),
|
||||
limit: z.number().int().min(1).max(50).optional().describe("Items per page (default 20, max 50)"),
|
||||
category: z.string().optional().describe("Filter by category slug"),
|
||||
tag: z.string().optional().describe("Filter by tag"),
|
||||
source: z.string().uuid().optional().describe("Filter by source UUID"),
|
||||
@@ -89,19 +89,19 @@ export function registerNewsTools(server: McpServer, client: BetterNewsClient) {
|
||||
"create_news_item",
|
||||
"Create a new draft news item. Requires news:write scope.",
|
||||
{
|
||||
title: z.string().min(1).max(200).describe("Article title"),
|
||||
title: z.string().min(3).max(150).describe("Article title"),
|
||||
readMoreUrl: z.string().url().describe("URL to the original article"),
|
||||
sourceId: z.string().uuid().describe("UUID of the source this article belongs to"),
|
||||
summary: z.string().max(1000).optional().describe("Short summary of the article"),
|
||||
longDescription: z.string().optional().describe("Detailed description"),
|
||||
authorsThought: z.string().max(500).optional().describe("Author commentary"),
|
||||
category: z.string().describe("Category slug"),
|
||||
tags: z.array(z.string()).optional().describe("Array of tag strings"),
|
||||
summary: z.string().max(200).optional().describe("Short summary of the article"),
|
||||
longDescription: z.string().max(1000).optional().describe("Detailed description"),
|
||||
authorsThought: z.string().max(250).optional().describe("Author commentary"),
|
||||
category: z.string().min(1).max(100).describe("Category slug"),
|
||||
tags: z.array(z.string().max(50)).max(3).optional().describe("Up to 3 tag strings"),
|
||||
language: z.string().optional().describe("ISO 639-1 language code"),
|
||||
country: z.string().optional().describe("ISO 3166-1 alpha-2 country code"),
|
||||
imageUrl: z.string().url().optional().describe("Hero image URL"),
|
||||
imageAltText: z.string().optional().describe("Image alt text"),
|
||||
imageCredits: z.string().optional().describe("Image attribution"),
|
||||
imageAltText: z.string().max(200).optional().describe("Image alt text"),
|
||||
imageCredits: z.string().max(200).optional().describe("Image attribution"),
|
||||
datePublished: z.string().datetime().optional().describe("Original publication date (ISO 8601)"),
|
||||
},
|
||||
handleTool(async (args) => {
|
||||
@@ -127,19 +127,19 @@ export function registerNewsTools(server: McpServer, client: BetterNewsClient) {
|
||||
"Edit a news item. Owners can only edit draft/needs_revision items. Mod/Admin can edit any. Requires news:write scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("News item UUID"),
|
||||
title: z.string().min(1).max(200).optional().describe("Article title"),
|
||||
title: z.string().min(3).max(150).optional().describe("Article title"),
|
||||
readMoreUrl: z.string().url().optional().describe("URL to the original article"),
|
||||
sourceId: z.string().uuid().optional().describe("Source UUID"),
|
||||
summary: z.string().max(1000).optional().describe("Short summary"),
|
||||
longDescription: z.string().optional().describe("Detailed description"),
|
||||
authorsThought: z.string().max(500).optional().describe("Author commentary"),
|
||||
category: z.string().optional().describe("Category slug"),
|
||||
tags: z.array(z.string()).optional().describe("Array of tag strings"),
|
||||
summary: z.string().max(200).optional().describe("Short summary"),
|
||||
longDescription: z.string().max(1000).optional().describe("Detailed description"),
|
||||
authorsThought: z.string().max(250).optional().describe("Author commentary"),
|
||||
category: z.string().min(1).max(100).optional().describe("Category slug"),
|
||||
tags: z.array(z.string().max(50)).max(3).optional().describe("Up to 3 tag strings"),
|
||||
language: z.string().optional().describe("ISO 639-1 language code"),
|
||||
country: z.string().optional().describe("ISO 3166-1 alpha-2 country code"),
|
||||
imageUrl: z.string().url().nullable().optional().describe("Hero image URL"),
|
||||
imageAltText: z.string().nullable().optional().describe("Image alt text"),
|
||||
imageCredits: z.string().nullable().optional().describe("Image attribution"),
|
||||
imageUrl: z.string().nullable().optional().describe("Hero image URL"),
|
||||
imageAltText: z.string().max(200).nullable().optional().describe("Image alt text"),
|
||||
imageCredits: z.string().max(200).nullable().optional().describe("Image attribution"),
|
||||
datePublished: z.string().datetime().nullable().optional().describe("Original publication date (ISO 8601)"),
|
||||
},
|
||||
handleTool(async ({ id, ...body }) => {
|
||||
@@ -221,4 +221,101 @@ export function registerNewsTools(server: McpServer, client: BetterNewsClient) {
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"list_topics",
|
||||
"List all unique topic tags from published news items. Results are cached for 1 hour. No auth required.",
|
||||
{},
|
||||
handleTool(async () => {
|
||||
const data = await client.get<unknown>("/api/news/topics");
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"publish_news_item",
|
||||
"Publish an approved news item, making it live on the feed. The item must be in the 'approved' state. Owner or Mod/Admin may call this. Requires news:moderate scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("News item UUID"),
|
||||
},
|
||||
handleTool(async ({ id }) => {
|
||||
const data = await client.post<unknown>(`/api/news/${id}/publish`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"unpublish_news_item",
|
||||
"Take down a published news item, archiving it. Owner or Mod/Admin may call this. Requires news:moderate scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("News item UUID"),
|
||||
},
|
||||
handleTool(async ({ id }) => {
|
||||
const data = await client.post<unknown>(`/api/news/${id}/unpublish`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"republish_news_item",
|
||||
"Re-publish an archived news item, putting it back on the live feed. Requires Mod/Admin role and news:moderate scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("News item UUID"),
|
||||
},
|
||||
handleTool(async ({ id }) => {
|
||||
const data = await client.post<unknown>(`/api/news/${id}/republish`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"archive_news_item",
|
||||
"Move a rejected news item to archived status so the owner can edit and resubmit it. Owner only. Requires news:moderate scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("News item UUID"),
|
||||
},
|
||||
handleTool(async ({ id }) => {
|
||||
const data = await client.post<unknown>(`/api/news/${id}/archive`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"direct_publish_news_item",
|
||||
"Publish a news item directly to the feed, bypassing the moderation queue. Any existing status is overridden. Requires Mod/Admin role and news:moderate scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("News item UUID"),
|
||||
},
|
||||
handleTool(async ({ id }) => {
|
||||
const data = await client.post<unknown>(`/api/news/${id}/direct-publish`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"direct_create_publish_news_item",
|
||||
"Create a news item and publish it immediately in one step, bypassing the moderation queue. Admin only. Requires news:moderate scope.",
|
||||
{
|
||||
title: z.string().min(3).max(150).describe("Article title"),
|
||||
readMoreUrl: z.string().url().describe("URL to the original article"),
|
||||
category: z.string().min(1).max(100).describe("Category slug"),
|
||||
sourceId: z.string().uuid().optional().describe("UUID of the source"),
|
||||
summary: z.string().max(200).optional().describe("Short summary"),
|
||||
longDescription: z.string().max(1000).optional().describe("Detailed description"),
|
||||
tags: z.array(z.string().max(50)).max(3).optional().describe("Up to 3 tag strings"),
|
||||
imageUrl: z.string().url().optional().describe("Hero image URL"),
|
||||
imageAltText: z.string().max(200).optional().describe("Image alt text"),
|
||||
imageCredits: z.string().max(200).optional().describe("Image attribution"),
|
||||
readTime: z.number().int().positive().optional().describe("Estimated read time in minutes"),
|
||||
authorsThought: z.string().max(250).optional().describe("Author commentary"),
|
||||
language: z.string().max(10).optional().describe("ISO 639-1 language code"),
|
||||
country: z.string().max(10).optional().describe("ISO 3166-1 alpha-2 country code"),
|
||||
datePublished: z.string().datetime().optional().describe("Original publication date (ISO 8601)"),
|
||||
createdAt: z.string().datetime().optional().describe("Override creation timestamp (ISO 8601)"),
|
||||
},
|
||||
handleTool(async (body) => {
|
||||
const data = await client.post<unknown>("/api/news/direct", body);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
import type { BetterNewsClient } from "../client.js";
|
||||
import { handleTool } from "../utils.js";
|
||||
|
||||
export function registerSessionTools(server: McpServer, client: BetterNewsClient) {
|
||||
server.tool(
|
||||
"list_sessions",
|
||||
"List all active sessions for the authenticated user. The current session is flagged with `current: true`.",
|
||||
{},
|
||||
handleTool(async () => {
|
||||
const data = await client.get<unknown>("/api/user/sessions");
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"revoke_all_sessions",
|
||||
"Revoke all sessions for the authenticated user, forcing re-login everywhere. The current session is also invalidated.",
|
||||
{},
|
||||
handleTool(async () => {
|
||||
const data = await client.delete<unknown>("/api/user/sessions");
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"revoke_session",
|
||||
"Revoke a single session by ID. Use list_sessions to find session IDs.",
|
||||
{
|
||||
sessionId: z.string().uuid().describe("Session UUID to revoke"),
|
||||
},
|
||||
handleTool(async ({ sessionId }) => {
|
||||
const data = await client.delete<unknown>(`/api/user/sessions/${sessionId}`);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
}
|
||||
+13
-13
@@ -9,7 +9,7 @@ export function registerSourceTools(server: McpServer, client: BetterNewsClient)
|
||||
"List curated news sources. Requires sources:read scope.",
|
||||
{
|
||||
page: z.number().int().positive().optional().describe("Page number (default 1)"),
|
||||
limit: z.number().int().min(1).max(100).optional().describe("Items per page (default 20)"),
|
||||
limit: z.number().int().min(1).max(50).optional().describe("Items per page (default 20, max 50)"),
|
||||
search: z.string().optional().describe("Text search over source name"),
|
||||
type: z.string().optional().describe("Filter by source type"),
|
||||
},
|
||||
@@ -35,13 +35,13 @@ export function registerSourceTools(server: McpServer, client: BetterNewsClient)
|
||||
"create_source",
|
||||
"Create a new news source. Requires Mod/Admin role and sources:write scope.",
|
||||
{
|
||||
name: z.string().min(1).max(100).describe("Source name"),
|
||||
type: z.string().describe("Source type (e.g. newspaper, blog, wire)"),
|
||||
name: z.string().min(1).max(200).describe("Source name"),
|
||||
type: z.string().min(1).max(50).describe("Source type (e.g. newspaper, blog, wire)"),
|
||||
url: z.string().url().optional().describe("Source homepage URL"),
|
||||
description: z.string().optional().describe("Short description"),
|
||||
credibilityScore: z.number().min(0).max(100).optional().describe("Credibility score 0–100"),
|
||||
country: z.string().optional().describe("ISO 3166-1 alpha-2 country code"),
|
||||
language: z.string().optional().describe("ISO 639-1 language code"),
|
||||
description: z.string().max(1000).optional().describe("Short description"),
|
||||
credibilityScore: z.number().min(0).max(10).optional().describe("Credibility score 0–10"),
|
||||
country: z.string().max(10).optional().describe("ISO 3166-1 alpha-2 country code"),
|
||||
language: z.string().max(10).optional().describe("ISO 639-1 language code"),
|
||||
contactEmail: z.string().email().optional().describe("Contact email"),
|
||||
logoUrl: z.string().url().optional().describe("Logo image URL"),
|
||||
},
|
||||
@@ -56,13 +56,13 @@ export function registerSourceTools(server: McpServer, client: BetterNewsClient)
|
||||
"Update a news source. Requires Mod/Admin role and sources:write scope.",
|
||||
{
|
||||
id: z.string().uuid().describe("Source UUID"),
|
||||
name: z.string().min(1).max(100).optional().describe("Source name"),
|
||||
type: z.string().optional().describe("Source type (e.g. newspaper, blog, wire)"),
|
||||
name: z.string().min(1).max(200).optional().describe("Source name"),
|
||||
type: z.string().min(1).max(50).optional().describe("Source type (e.g. newspaper, blog, wire)"),
|
||||
url: z.string().url().nullable().optional().describe("Source homepage URL"),
|
||||
description: z.string().nullable().optional().describe("Short description"),
|
||||
credibilityScore: z.number().min(0).max(100).nullable().optional().describe("Credibility score 0–100"),
|
||||
country: z.string().nullable().optional().describe("ISO 3166-1 alpha-2 country code"),
|
||||
language: z.string().nullable().optional().describe("ISO 639-1 language code"),
|
||||
description: z.string().max(1000).nullable().optional().describe("Short description"),
|
||||
credibilityScore: z.number().min(0).max(10).nullable().optional().describe("Credibility score 0–10"),
|
||||
country: z.string().max(10).nullable().optional().describe("ISO 3166-1 alpha-2 country code"),
|
||||
language: z.string().max(10).nullable().optional().describe("ISO 639-1 language code"),
|
||||
contactEmail: z.string().email().nullable().optional().describe("Contact email"),
|
||||
logoUrl: z.string().url().nullable().optional().describe("Logo image URL"),
|
||||
hidden: z.boolean().optional().describe("Hide/unhide this source"),
|
||||
|
||||
@@ -34,8 +34,8 @@ export function registerTicketTools(server: McpServer, client: BetterNewsClient)
|
||||
"create_ticket",
|
||||
"Open a new support ticket. Requires tickets:write scope.",
|
||||
{
|
||||
title: z.string().min(1).max(200).describe("Ticket title"),
|
||||
description: z.string().min(1).max(5000).describe("Detailed description of the issue"),
|
||||
title: z.string().min(3).max(100).describe("Ticket title"),
|
||||
description: z.string().min(10).max(5000).describe("Detailed description of the issue"),
|
||||
priority: z.enum(["low", "medium", "high", "critical"]).describe("Ticket priority"),
|
||||
},
|
||||
handleTool(async (body) => {
|
||||
|
||||
@@ -84,4 +84,21 @@ export function registerUserTools(server: McpServer, client: BetterNewsClient) {
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"update_privacy_settings",
|
||||
"Update the authenticated user's privacy settings. Requires user:profile scope.",
|
||||
{
|
||||
profilePublic: z.boolean().optional().describe("Whether the profile is publicly visible"),
|
||||
showEmail: z.boolean().optional().describe("Whether to show email address on public profile"),
|
||||
autoPublishAfterReview: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Automatically publish news items as soon as they are approved by a moderator"),
|
||||
},
|
||||
handleTool(async (body) => {
|
||||
const data = await client.patch<unknown>("/api/user/privacy", body);
|
||||
return JSON.stringify(data, null, 2);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user