@alfe.ai/news-mcp 0.2.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/dist/server.d.ts +1 -0
- package/dist/server.js +139 -0
- package/package.json +44 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { resolveConfig } from "@alfe.ai/config";
|
|
5
|
+
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/tools.ts
|
|
8
|
+
/**
|
|
9
|
+
* MCP tool registration for the News MCP server.
|
|
10
|
+
*
|
|
11
|
+
* Tools:
|
|
12
|
+
* news_search → client.newsSearch(...) (POST /agent/news/search)
|
|
13
|
+
* top_headlines → client.newsHeadlines(...) (POST /agent/news/headlines)
|
|
14
|
+
*
|
|
15
|
+
* Both take a strict zod input schema. `provider` is an ENUM
|
|
16
|
+
* (`apitube | newsdata`) defaulting to `apitube` — never free-text, because a
|
|
17
|
+
* typo'd provider server-side is an unpriceable product. All provider keys and
|
|
18
|
+
* billing live server-side in `services/news`; this server only forwards a
|
|
19
|
+
* typed body via `@alfe.ai/agent-api-client` and normalizes errors.
|
|
20
|
+
*
|
|
21
|
+
* Provider / billing / HTTP errors are surfaced as an `isError` MCP result
|
|
22
|
+
* carrying the error text — never a fake success.
|
|
23
|
+
*/
|
|
24
|
+
const providerSchema = z.enum(["apitube", "newsdata"]).default("apitube").describe("News provider to query. APITube (default) covers 500K+ sources with sentiment.");
|
|
25
|
+
function ok(data) {
|
|
26
|
+
return { content: [{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: JSON.stringify(data)
|
|
29
|
+
}] };
|
|
30
|
+
}
|
|
31
|
+
function fail(err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: JSON.stringify({ error: message })
|
|
37
|
+
}],
|
|
38
|
+
isError: true
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function registerTools(server, client) {
|
|
42
|
+
const register = server.registerTool.bind(server);
|
|
43
|
+
register("news_search", {
|
|
44
|
+
description: "Search recent news across a broad provider (APITube / NewsData). Returns normalized articles with title, url, source, publishedAt, snippet, and (APITube) sentiment.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
query: z.string().min(1).describe("Search query (keywords, entities, topics)."),
|
|
47
|
+
provider: providerSchema,
|
|
48
|
+
source: z.string().optional().describe("Restrict to a single source/domain (e.g. reuters.com)."),
|
|
49
|
+
from: z.string().optional().describe("Earliest publish date, ISO-8601 (e.g. 2026-07-01)."),
|
|
50
|
+
to: z.string().optional().describe("Latest publish date, ISO-8601."),
|
|
51
|
+
language: z.string().optional().describe("ISO-639-1 language code (e.g. en)."),
|
|
52
|
+
category: z.string().optional().describe("News category (e.g. business, technology)."),
|
|
53
|
+
limit: z.number().int().positive().max(100).optional().describe("Max articles to return.")
|
|
54
|
+
}
|
|
55
|
+
}, async (args) => {
|
|
56
|
+
try {
|
|
57
|
+
return ok(await client.newsSearch({
|
|
58
|
+
query: args.query,
|
|
59
|
+
provider: args.provider,
|
|
60
|
+
source: args.source,
|
|
61
|
+
from: args.from,
|
|
62
|
+
to: args.to,
|
|
63
|
+
language: args.language,
|
|
64
|
+
category: args.category,
|
|
65
|
+
limit: args.limit
|
|
66
|
+
}));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return fail(err);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
register("top_headlines", {
|
|
72
|
+
description: "Fetch current top headlines for a provider, optionally filtered by category, source, or language.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
provider: providerSchema,
|
|
75
|
+
category: z.string().optional().describe("News category (e.g. business, technology)."),
|
|
76
|
+
source: z.string().optional().describe("Restrict to a single source/domain."),
|
|
77
|
+
language: z.string().optional().describe("ISO-639-1 language code (e.g. en)."),
|
|
78
|
+
limit: z.number().int().positive().max(100).optional().describe("Max headlines to return.")
|
|
79
|
+
}
|
|
80
|
+
}, async (args) => {
|
|
81
|
+
try {
|
|
82
|
+
return ok(await client.newsHeadlines({
|
|
83
|
+
provider: args.provider,
|
|
84
|
+
category: args.category,
|
|
85
|
+
source: args.source,
|
|
86
|
+
language: args.language,
|
|
87
|
+
limit: args.limit
|
|
88
|
+
}));
|
|
89
|
+
} catch (err) {
|
|
90
|
+
return fail(err);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/server.ts
|
|
96
|
+
/**
|
|
97
|
+
* News MCP Server
|
|
98
|
+
*
|
|
99
|
+
* Standalone stdio MCP server for broad news search (APITube / NewsData behind
|
|
100
|
+
* a `provider` arg). Runtime-agnostic — spawned via `npx -y @alfe.ai/news-mcp`
|
|
101
|
+
* from an integration manifest, speaks MCP over stdio, so it runs under any
|
|
102
|
+
* runtime that can spawn `npx`.
|
|
103
|
+
*
|
|
104
|
+
* Architecture:
|
|
105
|
+
* Agent runtime ←(stdio/MCP)→ this server ←(HTTPS)→ services/news (metered)
|
|
106
|
+
*
|
|
107
|
+
* No provider credentials live on the box. At startup we self-fetch the agent's
|
|
108
|
+
* `{ apiKey, apiUrl }` via `resolveConfig()` and call the metered `services/news`
|
|
109
|
+
* Lambda through `AgentApiClient`. Provider keys + billing are server-side; the
|
|
110
|
+
* Lambda is the metering chokepoint.
|
|
111
|
+
*/
|
|
112
|
+
function log(msg) {
|
|
113
|
+
process.stderr.write(`[news-mcp] ${msg}\n`);
|
|
114
|
+
}
|
|
115
|
+
async function main() {
|
|
116
|
+
const { apiKey, apiUrl } = resolveConfig();
|
|
117
|
+
const client = new AgentApiClient({
|
|
118
|
+
apiKey,
|
|
119
|
+
apiUrl
|
|
120
|
+
});
|
|
121
|
+
const server = new McpServer({
|
|
122
|
+
name: "news-mcp-server",
|
|
123
|
+
version: "0.1.0"
|
|
124
|
+
});
|
|
125
|
+
registerTools(server, client);
|
|
126
|
+
const shutdown = () => {
|
|
127
|
+
process.exit(0);
|
|
128
|
+
};
|
|
129
|
+
for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, shutdown);
|
|
130
|
+
const transport = new StdioServerTransport();
|
|
131
|
+
await server.connect(transport);
|
|
132
|
+
log("News MCP server running (news_search, top_headlines)");
|
|
133
|
+
}
|
|
134
|
+
main().catch((err) => {
|
|
135
|
+
log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
138
|
+
//#endregion
|
|
139
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alfe.ai/news-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "News MCP server — broad news search (APITube / NewsData) with sentiment, metered server-side via Alfe",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"news-mcp-server": "./dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/server.d.ts",
|
|
13
|
+
"import": "./dist/server.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
|
+
"zod": "^4.0.5",
|
|
22
|
+
"@alfe.ai/agent-api-client": "0.11.0",
|
|
23
|
+
"@alfe.ai/config": "0.3.0"
|
|
24
|
+
},
|
|
25
|
+
"license": "UNLICENSED",
|
|
26
|
+
"homepage": "https://alfe.ai",
|
|
27
|
+
"author": "Alfe (https://alfe.ai)",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"alfe",
|
|
30
|
+
"ai-agents",
|
|
31
|
+
"agent",
|
|
32
|
+
"llm",
|
|
33
|
+
"mcp",
|
|
34
|
+
"model-context-protocol",
|
|
35
|
+
"news"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsdown",
|
|
39
|
+
"dev": "tsdown --watch",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"lint": "eslint ."
|
|
43
|
+
}
|
|
44
|
+
}
|