afpnews-mcp-server 1.3.0 → 1.3.2
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.
|
@@ -20,11 +20,11 @@ Returns:
|
|
|
20
20
|
Examples:
|
|
21
21
|
- Find similar articles in French: { uno: "NEWS-FR-123456-ABC", lang: "fr" }
|
|
22
22
|
- Get 5 similar English articles: { uno: "NEWS-EN-789", lang: "en", size: 5 }`,
|
|
23
|
-
inputSchema: {
|
|
23
|
+
inputSchema: z.object({
|
|
24
24
|
uno: z.string().describe('The UNO of the reference article'),
|
|
25
25
|
lang: langEnum.describe("Language for results (e.g. 'en', 'fr')"),
|
|
26
26
|
size: z.number().optional().describe('Number of similar articles to return (default 10)'),
|
|
27
|
-
},
|
|
27
|
+
}),
|
|
28
28
|
handler: async (apicore, { uno, lang, size }) => {
|
|
29
29
|
try {
|
|
30
30
|
const { documents, count } = await apicore.mlt(uno, lang, size);
|
|
@@ -17,9 +17,9 @@ Returns:
|
|
|
17
17
|
|
|
18
18
|
Examples:
|
|
19
19
|
- Get a specific article: { uno: "NEWS-FR-123456-ABC" }`,
|
|
20
|
-
inputSchema: {
|
|
20
|
+
inputSchema: z.object({
|
|
21
21
|
uno: z.string().describe('The unique identifier (UNO) of the article'),
|
|
22
|
-
},
|
|
22
|
+
}),
|
|
23
23
|
handler: async (apicore, { uno }) => {
|
|
24
24
|
try {
|
|
25
25
|
const doc = await apicore.get(uno);
|
|
@@ -23,12 +23,12 @@ Examples:
|
|
|
23
23
|
- Trending topics in English: { preset: "trending-topics", lang: "en" }
|
|
24
24
|
- List available genres: { facet: "genre" }
|
|
25
25
|
- List countries: { facet: "country", size: 30 }`,
|
|
26
|
-
inputSchema: {
|
|
26
|
+
inputSchema: z.object({
|
|
27
27
|
preset: listPresetEnum.optional().describe('Optional preset for list queries. Available preset: trending-topics.'),
|
|
28
28
|
facet: z.string().optional().describe("Facet to list (e.g. 'slug', 'genre', 'country'). Required when no preset is used."),
|
|
29
29
|
lang: langEnum.optional().describe("Language filter (e.g. 'en', 'fr')"),
|
|
30
30
|
size: z.number().optional().describe('Number of facet values to return'),
|
|
31
|
-
},
|
|
31
|
+
}),
|
|
32
32
|
handler: async (apicore, { preset, facet, lang, size }) => {
|
|
33
33
|
try {
|
|
34
34
|
const isTrendingTopics = preset === 'trending-topics';
|
|
@@ -31,7 +31,7 @@ Examples:
|
|
|
31
31
|
- French front page: { preset: "a-la-une" }
|
|
32
32
|
- Recent photos: { product: ["photo"], lang: ["en"], size: 5 }
|
|
33
33
|
- Page 2 of results: { query: "economy", size: 10, offset: 10 }`,
|
|
34
|
-
inputSchema: {
|
|
34
|
+
inputSchema: z.object({
|
|
35
35
|
preset: searchPresetEnum.optional().describe('Optional preset that applies predefined AFP filters. Available presets: a-la-une, agenda, previsions, major-stories.'),
|
|
36
36
|
fullText: z.boolean().optional().describe('When true, returns the full article body. Default is false. If omitted and a preset is used, fullText defaults to true.'),
|
|
37
37
|
query: z.string().optional().describe("List of keywords to search for in the news articles (e.g. 'climate change'), in the language specified by the 'lang' parameter. If not specified, the search will be performed in all languages. Do not use keywords in multiple languages."),
|
|
@@ -44,7 +44,7 @@ Examples:
|
|
|
44
44
|
country: z.string().array().optional().describe("Country filter (e.g. 'fra', 'usa')"),
|
|
45
45
|
slug: z.string().array().optional().describe("Topic/slug filter (e.g. 'economy', 'sports')"),
|
|
46
46
|
product: z.enum(['news', 'factcheck', 'photo', 'video', 'multimedia', 'graphic', 'videographic']).array().optional().describe("Content type filter (default ['news', 'factcheck'])"),
|
|
47
|
-
},
|
|
47
|
+
}),
|
|
48
48
|
handler: async (apicore, { preset, fullText = false, query, lang, dateFrom, dateTo, size = DEFAULT_SEARCH_SIZE, sortOrder = 'desc', offset, country, slug, product = ['news', 'factcheck'] }) => {
|
|
49
49
|
try {
|
|
50
50
|
let request = {
|
package/package.json
CHANGED
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
|
-
}
|