@pipeworx/mcp-ai-briefing 0.1.1 → 0.1.3
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/README.md +2 -2
- package/package.json +3 -3
- package/server.json +1 -1
- package/src/index.ts +89 -4
- package/src/server.ts +5 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
AI Briefing MCP — Keep AI models current on industry developments
|
|
4
4
|
|
|
5
|
-
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1786+ live data sources. This is an independent, unofficial integration — not affiliated with, endorsed by, or published by the upstream provider.
|
|
6
6
|
|
|
7
7
|
## Tools
|
|
8
8
|
|
|
@@ -61,7 +61,7 @@ directly, instead of just this one's:
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Both URLs reach the same gateway and the same
|
|
64
|
+
Both URLs reach the same gateway and the same 1786+ data sources. The
|
|
65
65
|
only difference is which pack's tools are listed **directly**; `ask_pipeworx`
|
|
66
66
|
reaches all of them from either one.
|
|
67
67
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipeworx/mcp-ai-briefing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "AI Briefing MCP — Keep AI models current on industry developments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@cloudflare/workers-types": "^4.20260405.1"
|
|
27
27
|
},
|
|
28
28
|
"pipeworx": {
|
|
29
|
-
"sourceHash": "v1-
|
|
30
|
-
"sourceCommit": "
|
|
29
|
+
"sourceHash": "v1-53d142ed192e8732003a3dd64e3466c4efc3a6498d947ccc435d84475aa2bbca",
|
|
30
|
+
"sourceCommit": "25e83bad9a91f94df238b7910cd6c46a8143d26d"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.pipeworx-io/ai-briefing",
|
|
4
4
|
"title": "Ai Briefing",
|
|
5
5
|
"description": "AI Briefing MCP — Keep AI models current on industry developments",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.3",
|
|
7
7
|
"websiteUrl": "https://pipeworx.io/packs/ai-briefing",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/pipeworx-io/mcp-ai-briefing",
|
package/src/index.ts
CHANGED
|
@@ -452,7 +452,42 @@ async function fetchWithTimeout(
|
|
|
452
452
|
),
|
|
453
453
|
);
|
|
454
454
|
}
|
|
455
|
-
|
|
455
|
+
// Fleet #2382. Everything that isn't a timeout/abort here is a genuine
|
|
456
|
+
// NETWORK-LEVEL failure — DNS resolution, connection refused, TLS handshake,
|
|
457
|
+
// Cloudflare's own "Network connection lost." — meaning `fetch()` itself
|
|
458
|
+
// threw and no HTTP response of any kind was ever received. Until this fix
|
|
459
|
+
// that raw exception was rethrown VERBATIM: a bare `TypeError: fetch failed`
|
|
460
|
+
// (or the Workers-runtime equivalent) names no upstream, carries no class
|
|
461
|
+
// token, and reads exactly like a defect in OUR code — because it says
|
|
462
|
+
// nothing about the call at all. It landed in `error`, the tier that means
|
|
463
|
+
// "Pipeworx has a defect", for every one of the (at the time of writing)
|
|
464
|
+
// ~470 packs that call this helper directly with no wrapper of their own.
|
|
465
|
+
//
|
|
466
|
+
// `dexscreener` hit this independently (fleet #1579) and fixed it with a
|
|
467
|
+
// bespoke per-pack try/catch around `fetchWithTimeout`. That fix is correct
|
|
468
|
+
// but only covers one pack; every other caller of this shared helper still
|
|
469
|
+
// leaked the raw exception. Moving the same fix HERE — the one place that
|
|
470
|
+
// already carries the timeout case — covers every pack that uses
|
|
471
|
+
// `fetchWithTimeout` without a wrapper, for free, and without widening
|
|
472
|
+
// `classifyToolError`'s regex list: the fix is giving the message a proper
|
|
473
|
+
// `upstream_down:` token at the point the two facts (no response was ever
|
|
474
|
+
// received, and which host we were trying to reach) are actually in hand,
|
|
475
|
+
// not teaching the classifier to guess from prose after the fact.
|
|
476
|
+
//
|
|
477
|
+
// Safe on the same grounds as the timeout branch above: no argument a
|
|
478
|
+
// caller passes can make `fetch()` itself throw a connection-level error,
|
|
479
|
+
// so this is always an availability failure, never a caller mistake. Same
|
|
480
|
+
// `markInternalOrigin` treatment — an origin we run that never answered is
|
|
481
|
+
// still ours, not a third party's outage.
|
|
482
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
483
|
+
throw new Error(
|
|
484
|
+
markInternalOrigin(
|
|
485
|
+
`upstream_down: could not reach ${name} at all (${raw.slice(0, 160)}). ` +
|
|
486
|
+
`No request reached ${name}, so this says NOTHING about whether the arguments you passed ` +
|
|
487
|
+
'are valid — do not re-check them on the strength of this error. Retry shortly.',
|
|
488
|
+
url,
|
|
489
|
+
),
|
|
490
|
+
);
|
|
456
491
|
}
|
|
457
492
|
}
|
|
458
493
|
|
|
@@ -678,6 +713,37 @@ async function querySupabase(path: string): Promise<unknown> {
|
|
|
678
713
|
return res.json();
|
|
679
714
|
}
|
|
680
715
|
|
|
716
|
+
/**
|
|
717
|
+
* fleet #3041: 4 of 5 AI-lab blog feeds were 404/403 for an unknown period
|
|
718
|
+
* and get_ai_news kept returning a clean 200 with a plausible-looking item
|
|
719
|
+
* count — a dead source read exactly like a quiet news day. The scraper
|
|
720
|
+
* (workers/ai-briefing-scraper) now writes one row per source to
|
|
721
|
+
* `ai_scrape_health` every run; this surfaces it in the response itself so
|
|
722
|
+
* a caller never has to take "no OpenAI/Anthropic/Meta/Mistral items" on
|
|
723
|
+
* faith. Failure here (table missing, query error) returns null rather
|
|
724
|
+
* than throwing — a health-surface outage must never break the main feed.
|
|
725
|
+
*/
|
|
726
|
+
async function getSourceHealth(): Promise<{ partial: boolean; failed_sources: string[]; checked_at: string | null } | null> {
|
|
727
|
+
try {
|
|
728
|
+
const rows = (await querySupabase(
|
|
729
|
+
`ai_scrape_health?select=source,ok,http_status,detail,checked_at&order=checked_at.desc`,
|
|
730
|
+
)) as Array<Record<string, unknown>>;
|
|
731
|
+
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
732
|
+
const failed = rows.filter((r) => r.ok === false);
|
|
733
|
+
const newest = rows.reduce<string | null>((acc, r) => {
|
|
734
|
+
const c = r.checked_at as string | undefined;
|
|
735
|
+
return c && (!acc || c > acc) ? c : acc;
|
|
736
|
+
}, null);
|
|
737
|
+
return {
|
|
738
|
+
partial: failed.length > 0,
|
|
739
|
+
failed_sources: failed.map((r) => `${r.source}: HTTP ${r.http_status ?? 'n/a'} — ${r.detail}`),
|
|
740
|
+
checked_at: newest,
|
|
741
|
+
};
|
|
742
|
+
} catch {
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
681
747
|
const tools: McpToolExport['tools'] = [
|
|
682
748
|
{
|
|
683
749
|
name: 'get_briefing',
|
|
@@ -1066,15 +1132,34 @@ async function getAiNews(days: number, limit: number): Promise<unknown> {
|
|
|
1066
1132
|
const newsCategories = ['model_release', 'claude_update', 'funding', 'acquisition', 'policy', 'benchmark', 'news', 'paper'];
|
|
1067
1133
|
const catFilter = newsCategories.map((c) => `category.eq.${c}`).join(',');
|
|
1068
1134
|
|
|
1069
|
-
const devs = await
|
|
1070
|
-
|
|
1071
|
-
|
|
1135
|
+
const [devs, sources] = await Promise.all([
|
|
1136
|
+
querySupabase(
|
|
1137
|
+
`ai_developments?or=(${catFilter})&published_at=gte.${since}&order=published_at.desc&limit=${limit}`
|
|
1138
|
+
) as Promise<Array<Record<string, unknown>>>,
|
|
1139
|
+
getSourceHealth(),
|
|
1140
|
+
]);
|
|
1072
1141
|
|
|
1073
1142
|
return {
|
|
1074
1143
|
feed: 'ai_news',
|
|
1075
1144
|
period: `last ${days} days`,
|
|
1076
1145
|
description: 'AI industry developments — models, funding, policy, research',
|
|
1077
1146
|
total: devs.length,
|
|
1147
|
+
// fleet #3041: a dead lab-blog feed used to read as "no news today" --
|
|
1148
|
+
// this says whether the LAST scrape run actually reached every source.
|
|
1149
|
+
// `sources` is omitted (not faked as healthy) when the health table
|
|
1150
|
+
// could not be read, rather than claiming a status we do not have.
|
|
1151
|
+
...(sources
|
|
1152
|
+
? {
|
|
1153
|
+
sources: {
|
|
1154
|
+
partial: sources.partial,
|
|
1155
|
+
note: sources.partial
|
|
1156
|
+
? `${sources.failed_sources.length} source(s) failed on the last scrape run -- counts above may undercount those sources, not reflect a quiet news day.`
|
|
1157
|
+
: 'all tracked sources reached on the last scrape run.',
|
|
1158
|
+
failed_sources: sources.failed_sources,
|
|
1159
|
+
checked_at: sources.checked_at,
|
|
1160
|
+
},
|
|
1161
|
+
}
|
|
1162
|
+
: {}),
|
|
1078
1163
|
items: devs.map((d) => ({
|
|
1079
1164
|
title: d.title,
|
|
1080
1165
|
summary: d.summary,
|
package/src/server.ts
CHANGED
|
@@ -8,8 +8,9 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
9
9
|
import pack from './index.js';
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
const server = new Server(
|
|
12
|
-
{ name: '@pipeworx/mcp-ai-briefing', version: '0.1.
|
|
13
|
+
{ name: '@pipeworx/mcp-ai-briefing', version: '0.1.3' },
|
|
13
14
|
{ capabilities: { tools: {} } },
|
|
14
15
|
);
|
|
15
16
|
|
|
@@ -24,7 +25,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
24
25
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
25
26
|
const { name, arguments: args } = request.params;
|
|
26
27
|
try {
|
|
27
|
-
const
|
|
28
|
+
const callArgs = { ...((args ?? {}) as Record<string, unknown>) };
|
|
29
|
+
|
|
30
|
+
const result = await pack.callTool(name, callArgs);
|
|
28
31
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
29
32
|
} catch (err) {
|
|
30
33
|
return {
|