afpnews-mcp-server 1.3.0 → 1.3.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/build/ai-tools.js +0 -188
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afpnews-mcp-server",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
package/build/ai-tools.js DELETED
@@ -1,188 +0,0 @@
1
- import { tool } from 'ai';
2
- import { z } from 'zod';
3
- import { ApiCore } from 'afpnews-api';
4
- import { formatDocument, DEFAULT_FIELDS, GENRE_EXCLUSIONS, textContent, truncateIfNeeded, buildPaginationLine, } from './utils/format.js';
5
- import { getTopicLabel } from './utils/topics.js';
6
- import { DEFAULT_SEARCH_SIZE, DEFAULT_FACET_SIZE } from './utils/types.js';
7
- const SEARCH_PRESET_VALUES = ['a-la-une', 'agenda', 'previsions', 'major-stories'];
8
- const searchPresetEnum = z.enum(SEARCH_PRESET_VALUES);
9
- const LIST_PRESET_VALUES = ['trending-topics'];
10
- const listPresetEnum = z.enum(LIST_PRESET_VALUES);
11
- const langEnum = z.enum(['en', 'fr', 'de', 'pt', 'es', 'ar']);
12
- const SEARCH_PRESETS = {
13
- 'a-la-une': {
14
- product: ['news'],
15
- lang: ['fr'],
16
- slug: ['afp', 'actualites'],
17
- dateFrom: 'now-1d',
18
- size: 1,
19
- genreid: GENRE_EXCLUSIONS,
20
- },
21
- agenda: {
22
- product: ['news'],
23
- size: 5,
24
- genreid: ['afpattribute:Agenda'],
25
- },
26
- previsions: {
27
- product: ['news'],
28
- size: 5,
29
- genreid: ['afpattribute:Program', 'afpedtype:TextProgram'],
30
- },
31
- 'major-stories': {
32
- product: ['news'],
33
- genreid: ['afpattribute:Article'],
34
- },
35
- };
36
- function formatDocuments(documents, fullText) {
37
- return documents.map((doc) => formatDocument(doc, fullText));
38
- }
39
- function formatErrorMessage(context, error, hint) {
40
- const message = error instanceof Error ? error.message : String(error);
41
- return `Error ${context}: ${message}. ${hint}`;
42
- }
43
- function contentToText(content) {
44
- return content.map((c) => c.text).join('\n\n');
45
- }
46
- const searchParameters = z.object({
47
- preset: searchPresetEnum.optional().describe('Optional preset that applies predefined AFP filters.'),
48
- fullText: z.boolean().optional().describe('When true, returns the full article body. Default is false.'),
49
- query: z.string().optional().describe('Keywords to search for (in the language specified by lang).'),
50
- lang: langEnum.array().optional().describe("Language filter (e.g. ['en', 'fr'])."),
51
- dateFrom: z.string().optional().describe("Start date (ISO or relative e.g. 'now-1d')."),
52
- dateTo: z.string().optional().describe('End date (ISO format).'),
53
- size: z.number().optional().describe('Number of results (default 10, max 1000).'),
54
- sortOrder: z.enum(['asc', 'desc']).optional().describe("Sort order by date (default 'desc')."),
55
- offset: z.number().optional().describe('Pagination offset.'),
56
- country: z.string().array().optional().describe("Country code filter (e.g. ['fra', 'usa'])."),
57
- slug: z.string().array().optional().describe('Topic/slug filter.'),
58
- product: z
59
- .enum(['news', 'factcheck', 'photo', 'video', 'multimedia', 'graphic', 'videographic'])
60
- .array()
61
- .optional()
62
- .describe("Content type filter (default ['news', 'factcheck'])."),
63
- });
64
- const getArticleParameters = z.object({
65
- uno: z.string().describe('The unique identifier (UNO) of the article.'),
66
- });
67
- const findSimilarParameters = z.object({
68
- uno: z.string().describe('The UNO of the reference article.'),
69
- lang: langEnum.describe('Language for results.'),
70
- size: z.number().optional().describe('Number of similar articles (default 10).'),
71
- });
72
- const listFacetsParameters = z.object({
73
- preset: listPresetEnum.optional().describe('Optional preset (trending-topics).'),
74
- facet: z
75
- .string()
76
- .optional()
77
- .describe("Facet to list (e.g. 'slug', 'genre', 'country'). Required when no preset."),
78
- lang: langEnum.optional().describe('Language filter.'),
79
- size: z.number().optional().describe('Number of facet values to return.'),
80
- });
81
- export function createAiTools(token) {
82
- const apicore = new ApiCore();
83
- apicore.token = token;
84
- return {
85
- afp_search_articles: tool({
86
- description: `Search AFP news articles with filters and presets.
87
- Args: preset (a-la-une|agenda|previsions|major-stories), fullText, query, lang, dateFrom, dateTo, size, sortOrder, offset,
88
- country, slug, product.
89
- Returns: markdown-formatted articles with headline, metadata and body.`,
90
- parameters: searchParameters,
91
- execute: async ({ preset, fullText = false, query, lang, dateFrom, dateTo, size = DEFAULT_SEARCH_SIZE, sortOrder = 'desc', offset, country, slug, product = ['news', 'factcheck'], }) => {
92
- try {
93
- let request = {
94
- query,
95
- lang,
96
- product,
97
- dateFrom,
98
- dateTo,
99
- size,
100
- sortOrder,
101
- startAt: offset,
102
- country,
103
- slug,
104
- genreid: GENRE_EXCLUSIONS,
105
- };
106
- if (preset) {
107
- request = { ...request, ...SEARCH_PRESETS[preset] };
108
- fullText = true;
109
- }
110
- const { documents, count } = await apicore.search(request, [...DEFAULT_FIELDS]);
111
- if (count === 0)
112
- return 'No results found.';
113
- const currentOffset = offset ?? 0;
114
- const content = [
115
- textContent(buildPaginationLine(documents.length, count, currentOffset)),
116
- ...formatDocuments(documents, fullText),
117
- ];
118
- return contentToText(truncateIfNeeded(content));
119
- }
120
- catch (error) {
121
- return formatErrorMessage('searching AFP articles', error, 'Check your query parameters and try again.');
122
- }
123
- },
124
- }),
125
- afp_get_article: tool({
126
- description: 'Get a full AFP news article by its UNO identifier.',
127
- parameters: getArticleParameters,
128
- execute: async ({ uno }) => {
129
- try {
130
- const doc = await apicore.get(uno);
131
- return formatDocument(doc, true).text;
132
- }
133
- catch (error) {
134
- return formatErrorMessage(`fetching article "${uno}"`, error, 'Verify the UNO identifier is correct.');
135
- }
136
- },
137
- }),
138
- afp_find_similar: tool({
139
- description: 'Find AFP articles similar to a given article (More Like This).',
140
- parameters: findSimilarParameters,
141
- execute: async ({ uno, lang, size }) => {
142
- try {
143
- const { documents, count } = await apicore.mlt(uno, lang, size);
144
- if (count === 0)
145
- return 'No similar articles found.';
146
- const content = [textContent(`*Found ${count} similar articles.*`), ...formatDocuments(documents, false)];
147
- return contentToText(truncateIfNeeded(content));
148
- }
149
- catch (error) {
150
- return formatErrorMessage(`finding similar articles for "${uno}"`, error, 'Verify the UNO identifier is correct.');
151
- }
152
- },
153
- }),
154
- afp_list_facets: tool({
155
- description: `List facet values and article counts. Use preset 'trending-topics' or specify a facet like 'slug',
156
- 'genre', 'country'.`,
157
- parameters: listFacetsParameters,
158
- execute: async ({ preset, facet, lang, size }) => {
159
- try {
160
- const isTrendingTopics = preset === 'trending-topics';
161
- const resolvedFacet = isTrendingTopics ? 'slug' : facet;
162
- if (!resolvedFacet) {
163
- return "Missing required parameter: facet (e.g. 'slug', 'genre', 'country'). Alternatively, use preset: 'trending-topics'.";
164
- }
165
- const params = isTrendingTopics
166
- ? { langs: [lang ?? 'fr'], product: ['news'], dateFrom: 'now-1d' }
167
- : lang
168
- ? { langs: [lang] }
169
- : {};
170
- const resolvedSize = isTrendingTopics ? (size ?? DEFAULT_FACET_SIZE) : size;
171
- const rawResult = await apicore.list(resolvedFacet, params, resolvedSize);
172
- const results = rawResult?.keywords ?? rawResult ?? [];
173
- if (results.length === 0)
174
- return `No facet values found for "${resolvedFacet}".`;
175
- const heading = isTrendingTopics ? 'Trending Topics' : `Facet: ${resolvedFacet}`;
176
- const lines = results.map((item) => {
177
- const label = isTrendingTopics ? (getTopicLabel(item.key) ?? item.key) : item.key;
178
- return `- **${label}** — ${item.count} articles`;
179
- });
180
- return `## ${heading}\n\n${lines.join('\n')}`;
181
- }
182
- catch (error) {
183
- return formatErrorMessage('listing facet values', error, 'Check that the facet name is valid.');
184
- }
185
- },
186
- }),
187
- };
188
- }