briefing-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +72 -0
- package/index.js +124 -0
- package/package.json +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jordan Shelley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# briefing-mcp
|
|
2
|
+
|
|
3
|
+
[](https://mcpservers.org/servers/briefing-service-wholemind-workers-dev) [](https://glama.ai/mcp/servers/jshelley/briefing-mcp) [](https://tensorblock.co/mcp/servers/github-jshelley-briefing-mcp-3acc4c9f)
|
|
4
|
+
|
|
5
|
+
MCP server for [Briefing Service](https://briefing-service.wholemind.workers.dev): every hour an LLM editor ranks ~100 feeds per topic (AI, frontier labs, markets, US sports, European football, US news, world) and publishes the result as structured JSON, a rendered 480x800 e-ink page, a multi-page Morning Paper PDF, and a free provenance dump of every candidate it saw.
|
|
6
|
+
|
|
7
|
+
This package runs locally over stdio and calls the public REST API. The same tools are served remotely (streamable HTTP, no install) at `https://briefing-service.wholemind.workers.dev/mcp`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Claude Desktop / Claude Code / Cursor / any stdio client:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"briefings": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["-y", "github:jshelley/briefing-mcp"],
|
|
19
|
+
"env": { "BRIEFING_KEY": "" }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Claude Code one-liner:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude mcp add briefings -- npx -y github:jshelley/briefing-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Claude Desktop, one click: download [briefing-mcp-0.1.0.mcpb](https://github.com/jshelley/briefing-mcp/releases/download/v0.1.0/briefing-mcp-0.1.0.mcpb) and open it (MCP bundle; it asks for the optional Reader key).
|
|
32
|
+
|
|
33
|
+
## Tools
|
|
34
|
+
|
|
35
|
+
| tool | what it returns | cost |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `list_briefings` | keys, names and prices | free |
|
|
38
|
+
| `get_briefing(key)` | lead, ranked stories, research, why-it-matters, key points | 25 free/IP/day, then key or x402 |
|
|
39
|
+
| `get_summary(key)` | headlines and one-liners | free |
|
|
40
|
+
| `get_candidates(key)` | every candidate this hour: canonical link, outlets, `selected`, collapsed duplicates with `duplicate_of` | free |
|
|
41
|
+
| `get_page(key, page, wide)` | 480x800 1-bit PNG (or 800x480 wide front page) | 25 free/IP/day |
|
|
42
|
+
| `render_briefing(feeds, persona)` | rank and render your own feeds | Reader key or x402 |
|
|
43
|
+
|
|
44
|
+
## Pricing
|
|
45
|
+
|
|
46
|
+
- Free: 25 JSON/page/tool calls per IP per day, plus the summary and candidates endpoints.
|
|
47
|
+
- Reader key: $9/month, unlimited reads, Morning Paper delivery, 10 custom renders/day. Set it as `BRIEFING_KEY`. Buy at https://briefing-service.wholemind.workers.dev/billing/checkout
|
|
48
|
+
- Per call: x402 (USDC on Base) against the remote endpoint.
|
|
49
|
+
|
|
50
|
+
Docs for agents: https://briefing-service.wholemind.workers.dev/llms.txt
|
|
51
|
+
|
|
52
|
+
## How it works
|
|
53
|
+
|
|
54
|
+
`index.js` is the whole server: it registers six tools with `@modelcontextprotocol/sdk` and answers each by calling the public Briefing Service REST API over HTTPS (`GET /v1/briefings/...`, `POST /v1/render`). It stores nothing, runs no shell commands, and reads only two environment variables (`BRIEFING_KEY`, `BRIEFING_BASE_URL`). The `Dockerfile` builds the same thing; `docker run -i` speaks MCP over stdio.
|
|
55
|
+
|
|
56
|
+
Run the smoke test locally:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install
|
|
60
|
+
node client-test.mjs # lists tools and calls list_briefings, get_summary, get_candidates, get_page, get_briefing
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Write-up of the pipeline (collect, entity-overlap dedupe, LLM editor, 1-bit render) and what the first week taught: https://dev.to/jshelley/i-built-an-hourly-newspaper-for-e-ink-and-turned-the-pipeline-into-an-mcp-server-1ebg. Each briefing also has an Atom feed at `/v1/briefings/{key}/feed.xml`; the Morning Paper PDF carries an outline per story, front-page go-to links and a clickable source URL on every story page.
|
|
64
|
+
|
|
65
|
+
## Environment
|
|
66
|
+
|
|
67
|
+
- `BRIEFING_KEY`: optional Reader key (sent as `Authorization: Bearer`).
|
|
68
|
+
- `BRIEFING_BASE_URL`: override the API base (default: the public service).
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// briefing-mcp: stdio MCP server for Briefing Service (hourly LLM-ranked news
|
|
3
|
+
// briefings, rendered e-ink pages, Morning Paper, provenance dump).
|
|
4
|
+
// It is a thin client over the public REST API; the same tools are also served
|
|
5
|
+
// remotely at https://briefing-service.wholemind.workers.dev/mcp.
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
const BASE = (process.env.BRIEFING_BASE_URL || "https://briefing-service.wholemind.workers.dev").replace(/\/$/, "");
|
|
11
|
+
const KEY = process.env.BRIEFING_KEY || "";
|
|
12
|
+
const VERSION = "0.1.0";
|
|
13
|
+
|
|
14
|
+
function headers(extra = {}) {
|
|
15
|
+
const h = { "user-agent": `briefing-mcp/${VERSION}`, accept: "application/json", ...extra };
|
|
16
|
+
if (KEY) h.authorization = `Bearer ${KEY}`;
|
|
17
|
+
return h;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function get(path, accept) {
|
|
21
|
+
const res = await fetch(`${BASE}${path}`, { headers: headers(accept ? { accept } : {}) });
|
|
22
|
+
return res;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function paywallText(res, body) {
|
|
26
|
+
if (res.status === 402) {
|
|
27
|
+
return (
|
|
28
|
+
`Payment required (free trial of ${res.headers.get("x-trial-per-day") || "25"} calls per IP per day is used up). ` +
|
|
29
|
+
`Options: set BRIEFING_KEY to a Reader key ($9/month, ${BASE}/billing/checkout) or pay per call with x402 (USDC on Base) against the remote endpoint ${BASE}/mcp. ` +
|
|
30
|
+
`Docs: ${BASE}/llms.txt`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return `error ${res.status}: ${body.slice(0, 500)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const server = new McpServer(
|
|
37
|
+
{ name: "briefing-mcp", version: VERSION, title: "Briefing Service: hourly ranked news" },
|
|
38
|
+
{
|
|
39
|
+
instructions:
|
|
40
|
+
"Hourly LLM-ranked news briefings (AI, frontier labs, markets, US sports, European football, US news, world) as structured JSON, rendered e-ink pages and a provenance dump. " +
|
|
41
|
+
"Call list_briefings first, then get_briefing(key). get_page returns a 480x800 1-bit PNG. get_candidates returns every source item the editor saw this hour with dedupe metadata. " +
|
|
42
|
+
"render_briefing ranks your own feeds (paid). Free: 25 calls per IP per day; then a Reader key in BRIEFING_KEY ($9/month) or x402 on the remote endpoint. Docs: " + BASE + "/llms.txt",
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
server.registerTool(
|
|
47
|
+
"list_briefings",
|
|
48
|
+
{ description: "List the hourly briefings available (key and name), with prices.", inputSchema: {} },
|
|
49
|
+
async () => {
|
|
50
|
+
const res = await get("/v1/briefings");
|
|
51
|
+
const text = await res.text();
|
|
52
|
+
return res.ok ? { content: [{ type: "text", text }] } : { content: [{ type: "text", text: paywallText(res, text) }], isError: true };
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
server.registerTool(
|
|
57
|
+
"get_briefing",
|
|
58
|
+
{
|
|
59
|
+
description: "One briefing as structured JSON: lead story, ranked stories and research items with summaries, why-it-matters and key points, plus when it was ranked.",
|
|
60
|
+
inputSchema: { key: z.string().describe("briefing key from list_briefings, e.g. 'ai'") },
|
|
61
|
+
},
|
|
62
|
+
async ({ key }) => {
|
|
63
|
+
const res = await get(`/v1/briefings/${encodeURIComponent(key)}`);
|
|
64
|
+
const text = await res.text();
|
|
65
|
+
return res.ok ? { content: [{ type: "text", text }] } : { content: [{ type: "text", text: paywallText(res, text) }], isError: true };
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
server.registerTool(
|
|
70
|
+
"get_summary",
|
|
71
|
+
{
|
|
72
|
+
description: "Free, small: headlines and one-line summaries of a briefing (for widgets and quick reads).",
|
|
73
|
+
inputSchema: { key: z.string().describe("briefing key, e.g. 'world'") },
|
|
74
|
+
},
|
|
75
|
+
async ({ key }) => {
|
|
76
|
+
const res = await get(`/v1/briefings/${encodeURIComponent(key)}/summary`);
|
|
77
|
+
const text = await res.text();
|
|
78
|
+
return res.ok ? { content: [{ type: "text", text }] } : { content: [{ type: "text", text: paywallText(res, text) }], isError: true };
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
server.registerTool(
|
|
83
|
+
"get_candidates",
|
|
84
|
+
{
|
|
85
|
+
description: "Free provenance dump: every candidate the editor saw this hour with canonical_link, published time, feed weight, the other outlets that ran the story (also_in), a selected flag, and collapsed duplicates (duplicate_of, dup_rule, dup_score).",
|
|
86
|
+
inputSchema: { key: z.string().describe("briefing key, e.g. 'ai'") },
|
|
87
|
+
},
|
|
88
|
+
async ({ key }) => {
|
|
89
|
+
const res = await get(`/v1/briefings/${encodeURIComponent(key)}/candidates`);
|
|
90
|
+
const text = await res.text();
|
|
91
|
+
return res.ok ? { content: [{ type: "text", text }] } : { content: [{ type: "text", text: paywallText(res, text) }], isError: true };
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
server.registerTool(
|
|
96
|
+
"get_page",
|
|
97
|
+
{
|
|
98
|
+
description: "One rendered page of a briefing as a 480x800 1-bit PNG (page 0 is the front page; story pages follow). Made for e-ink and small displays. wide=true returns the 800x480 landscape front page.",
|
|
99
|
+
inputSchema: { key: z.string(), page: z.number().int().min(0).max(32).default(0), wide: z.boolean().default(false) },
|
|
100
|
+
},
|
|
101
|
+
async ({ key, page, wide }) => {
|
|
102
|
+
const q = wide && page === 0 ? "?size=wide" : "";
|
|
103
|
+
const res = await get(`/v1/briefings/${encodeURIComponent(key)}/pages/${page}.png${q}`, "image/png");
|
|
104
|
+
if (!res.ok) return { content: [{ type: "text", text: paywallText(res, await res.text()) }], isError: true };
|
|
105
|
+
const data = Buffer.from(await res.arrayBuffer()).toString("base64");
|
|
106
|
+
return { content: [{ type: "image", data, mimeType: "image/png" }] };
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
server.registerTool(
|
|
111
|
+
"render_briefing",
|
|
112
|
+
{
|
|
113
|
+
description: "Rank and render a briefing from your own feeds and persona (one editor run; needs a Reader key with render credits, or use x402 on the remote endpoint). Returns JSON and page URLs.",
|
|
114
|
+
inputSchema: { feeds: z.array(z.string().url()).min(1).max(30), persona: z.string().max(2000) },
|
|
115
|
+
},
|
|
116
|
+
async ({ feeds, persona }) => {
|
|
117
|
+
const res = await fetch(`${BASE}/v1/render`, { method: "POST", headers: headers({ "content-type": "application/json" }), body: JSON.stringify({ feeds, persona }) });
|
|
118
|
+
const text = await res.text();
|
|
119
|
+
return res.ok ? { content: [{ type: "text", text }] } : { content: [{ type: "text", text: paywallText(res, text) }], isError: true };
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const transport = new StdioServerTransport();
|
|
124
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "briefing-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Briefing Service: hourly LLM-ranked news briefings (AI, markets, sports, world) as JSON, rendered e-ink pages and a free provenance dump",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "briefing-mcp": "index.js" },
|
|
7
|
+
"files": ["index.js", "README.md", "LICENSE"],
|
|
8
|
+
"engines": { "node": ">=18" },
|
|
9
|
+
"keywords": ["mcp", "model-context-protocol", "news", "briefing", "e-ink", "x402"],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": { "type": "git", "url": "https://github.com/jshelley/briefing-mcp" },
|
|
12
|
+
"homepage": "https://briefing-service.wholemind.workers.dev",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
15
|
+
"zod": "^3.24.0"
|
|
16
|
+
}
|
|
17
|
+
}
|