afpnews-mcp-server 2.1.0 → 3.0.0-next.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/.env.example +4 -1
- package/CHANGELOG.md +114 -0
- package/CLAUDE.md +10 -8
- package/README.md +39 -3
- package/bun.lock +137 -158
- package/package.json +15 -10
- package/src/__tests__/fixtures.ts +53 -3
- package/src/__tests__/format-media.test.ts +40 -34
- package/src/__tests__/format.test.ts +78 -25
- package/src/__tests__/get-media-handler.test.ts +49 -22
- package/src/__tests__/get-media.test.ts +33 -0
- package/src/__tests__/http-server.test.ts +50 -0
- package/src/__tests__/oauth-flow.test.ts +118 -0
- package/src/__tests__/search-media-handler.test.ts +143 -0
- package/src/__tests__/server.test.ts +61 -16
- package/src/__tests__/stdio-server.test.ts +38 -0
- package/src/__tests__/tokens.test.ts +87 -0
- package/src/__tests__/tool-defaults.test.ts +10 -7
- package/src/http/login-page.ts +16 -11
- package/src/http/server.ts +257 -220
- package/src/http/tokens.ts +69 -9
- package/src/http/worker.ts +31 -0
- package/src/mcp-server.ts +8 -4
- package/src/stdio/server.ts +1 -1
- package/src/tools/find-similar.ts +9 -3
- package/src/tools/get-article.ts +3 -2
- package/src/tools/get-media.ts +32 -90
- package/src/tools/list-facets.ts +30 -8
- package/src/tools/search-articles.ts +7 -5
- package/src/tools/search-media.ts +66 -10
- package/src/tools/shared.ts +5 -20
- package/src/utils/embed-media.ts +65 -0
- package/src/utils/format-media.ts +32 -31
- package/src/utils/format.ts +76 -47
- package/src/utils/types.ts +3 -20
- package/tsconfig.json +3 -1
- package/wrangler.toml +28 -0
- package/skills-lock.json +0 -10
package/.env.example
CHANGED
|
@@ -9,4 +9,7 @@ JWT_SECRET= # générer avec: openssl rand -base64 32
|
|
|
9
9
|
MCP_SERVER_URL=https://news-mcp.example.com
|
|
10
10
|
PORT=3000 # optionnel, défaut: 3000
|
|
11
11
|
MCP_TRANSPORT=http # stdio ou http
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
# Callback OAuth des clients IA distants (hors localhost, toujours autorisé).
|
|
14
|
+
# Liste unique, séparée par des virgules — voir README.md pour les valeurs connues.
|
|
15
|
+
MCP_ALLOWED_REDIRECT_URIS=https://claude.ai/api/mcp/auth_callback,https://claude.com/api/mcp/auth_callback
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. Format loosely follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [3.0.0] — 2026-08-25
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- **HTTP transport is now stateless.** The previous session-map model
|
|
12
|
+
(`mcp-session-id`, `MCP_SESSION_TTL`) is gone — every request now gets a
|
|
13
|
+
fresh `McpServer` built straight from its own bearer token, so any number
|
|
14
|
+
of instances can sit behind a plain load balancer with no sticky sessions.
|
|
15
|
+
Deployments that relied on `MCP_SESSION_TTL` or session affinity need to
|
|
16
|
+
drop that configuration.
|
|
17
|
+
- Migrated from `@modelcontextprotocol/sdk` (v1) to the split v2 packages
|
|
18
|
+
(`@modelcontextprotocol/server`, `@modelcontextprotocol/hono`), targeting
|
|
19
|
+
the 2026-07-28 MCP spec.
|
|
20
|
+
- HTTP framework: **Elysia → Hono**. Framework-specific behavior (error
|
|
21
|
+
response shapes in particular) changed accordingly.
|
|
22
|
+
- `/oauth/token` now only accepts `application/x-www-form-urlencoded`
|
|
23
|
+
request bodies (RFC 6749 §4.1.3), not JSON.
|
|
24
|
+
- Access tokens are now bound to this server's own `/mcp` resource via an
|
|
25
|
+
`aud` claim (RFC 8707); tokens minted by a different deployment are no
|
|
26
|
+
longer accepted even if it shares the same `JWT_SECRET`.
|
|
27
|
+
- 401 responses on `/mcp` now carry a spec-compliant `WWW-Authenticate`
|
|
28
|
+
header pointing at the protected-resource metadata document, replacing
|
|
29
|
+
the previous ad hoc header/body shape.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Deployable as a **Cloudflare Worker** (`src/http/worker.ts`, `wrangler.toml`)
|
|
34
|
+
with no KV/Durable Objects/other bindings — the OAuth authorization code
|
|
35
|
+
and bearer tokens are all self-contained.
|
|
36
|
+
- `src/http/server.ts` split into `resolveHttpConfig()` / `createHttpApp()` /
|
|
37
|
+
`startHttpServer()`, so the Hono app itself has no Bun or Node dependency.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- The OAuth authorization code is now a self-contained token (same JWE
|
|
42
|
+
pattern as the access/refresh tokens) instead of server-side state.
|
|
43
|
+
- `node:crypto` dropped in favor of Web Crypto (`crypto.subtle`) for HKDF
|
|
44
|
+
key derivation and the PKCE challenge check — portable across Bun,
|
|
45
|
+
browsers, and Workers.
|
|
46
|
+
- Rate limiting (`elysia-rate-limit`) removed; left to the hosting platform
|
|
47
|
+
(e.g. Cloudflare's own Rate Limiting) rather than a hand-rolled,
|
|
48
|
+
per-isolate in-process limiter.
|
|
49
|
+
- `/.well-known/oauth-authorization-server` and
|
|
50
|
+
`/.well-known/oauth-protected-resource/mcp` are now served via the SDK's
|
|
51
|
+
own metadata helpers instead of hand-written JSON.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- The JWE's registered `exp` claim was silently overwritten by
|
|
56
|
+
`setExpirationTime()`, so decrypted tokens never carried the real AFP
|
|
57
|
+
token expiry — moved to a dedicated `texp` claim.
|
|
58
|
+
- A `globalThis.fetch` mock in `get-media-handler.test.ts` was leaking into
|
|
59
|
+
whichever test file ran next (test-isolation bug).
|
|
60
|
+
|
|
61
|
+
## [2.2.0] — 2026-08-01
|
|
62
|
+
|
|
63
|
+
- Snapshot prior to the SDK v2 / Hono / Cloudflare Workers work above.
|
|
64
|
+
|
|
65
|
+
## [2.0.2] — 2026-06-09
|
|
66
|
+
|
|
67
|
+
- Fix: `lang` facet examples corrected to `langs` in docs and tests.
|
|
68
|
+
- Improved typing, text truncation, and test infrastructure.
|
|
69
|
+
|
|
70
|
+
## [2.0.0] — 2026-03-17
|
|
71
|
+
|
|
72
|
+
- Migrated package manager from pnpm to **Bun**; test runner from Vitest to
|
|
73
|
+
`bun test`.
|
|
74
|
+
- HTTP transport rewritten on **Elysia** (replacing Express), split into
|
|
75
|
+
`src/http/` submodules; stdio transport extracted to `src/stdio/server.ts`.
|
|
76
|
+
- OAuth2/OIDC hardening: AFP refresh token stored in a JWE (no credentials
|
|
77
|
+
persisted server-side), strict `redirect_uri` allowlist, and a round of
|
|
78
|
+
security-audit fixes.
|
|
79
|
+
- Added `afp_search_media` / `afp_get_media` tools, with base64 image embed
|
|
80
|
+
for vision models and dedicated media formatting (`format-media.ts`).
|
|
81
|
+
- `product` facet replaced with `class` throughout; added `event` field to
|
|
82
|
+
the AFP document model.
|
|
83
|
+
- `src/mcp-server.ts` renamed from `server.ts` to avoid a naming collision
|
|
84
|
+
with `http/server.ts`.
|
|
85
|
+
|
|
86
|
+
## [1.3.10] — 2026-02-22
|
|
87
|
+
|
|
88
|
+
- `baseUrl` support (`APICORE_BASE_URL`) added and covered by tests.
|
|
89
|
+
|
|
90
|
+
## [1.3.7] — 2026-02-20
|
|
91
|
+
|
|
92
|
+
- `afpshortid` field added; `afp_list_facets` fixes.
|
|
93
|
+
|
|
94
|
+
## [1.3.4] — 2026-02-17
|
|
95
|
+
|
|
96
|
+
- Tool definitions' JSON Schemas generated via Zod v4's built-in `toJSONSchema`.
|
|
97
|
+
|
|
98
|
+
## [1.3.2] and earlier
|
|
99
|
+
|
|
100
|
+
- Initial tool set (search, get article, similar articles, facets),
|
|
101
|
+
prompts (daily briefing, comprehensive analysis, factcheck, country news),
|
|
102
|
+
and the AFP Stories topics resource.
|
|
103
|
+
- HTTP transport added alongside stdio; auth token caching.
|
|
104
|
+
- Package renamed to `afpnews-mcp-server`; pre-converted JSON Schemas
|
|
105
|
+
exported for cross-instance compatibility.
|
|
106
|
+
- A Notification Center implementation was added and then reverted (the
|
|
107
|
+
underlying AFP API endpoint wasn't functional).
|
|
108
|
+
|
|
109
|
+
[3.0.0]: https://github.com/julesbonnard/afpnews-mcp-server/compare/v2.0.2...v3.0.0
|
|
110
|
+
[2.0.2]: https://github.com/julesbonnard/afpnews-mcp-server/compare/v2.0.0...v2.0.2
|
|
111
|
+
[2.0.0]: https://github.com/julesbonnard/afpnews-mcp-server/compare/v1.3.10...v2.0.0
|
|
112
|
+
[1.3.10]: https://github.com/julesbonnard/afpnews-mcp-server/compare/v1.3.7...v1.3.10
|
|
113
|
+
[1.3.7]: https://github.com/julesbonnard/afpnews-mcp-server/compare/v1.3.4...v1.3.7
|
|
114
|
+
[1.3.4]: https://github.com/julesbonnard/afpnews-mcp-server/compare/v1.3.2...v1.3.4
|
package/CLAUDE.md
CHANGED
|
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
|
-
MCP (Model Context Protocol) server that exposes AFP news tools for LLM-based editors (Continue, Claude Code, etc.). Supports stdio and HTTP (Streamable HTTP) transports.
|
|
7
|
+
MCP (Model Context Protocol) server that exposes AFP news tools for LLM-based editors (Continue, Claude Code, etc.). Supports stdio and HTTP (stateless Streamable HTTP) transports, targeting the 2026-07-28 MCP spec via the `@modelcontextprotocol/server` v2 SDK.
|
|
8
8
|
|
|
9
9
|
## Commands
|
|
10
10
|
|
|
@@ -42,8 +42,9 @@ src/
|
|
|
42
42
|
│ ├── index.ts # registerResources() MCP glue
|
|
43
43
|
│ └── topics.ts # TOPICS inline + resource handler
|
|
44
44
|
├── http/
|
|
45
|
-
│ ├── server.ts #
|
|
46
|
-
│ ├──
|
|
45
|
+
│ ├── server.ts # resolveHttpConfig() + createHttpApp() (Hono, platform-agnostic) + startHttpServer() (Bun)
|
|
46
|
+
│ ├── worker.ts # Cloudflare Worker entry point — same createHttpApp(), config from the `env` binding
|
|
47
|
+
│ ├── tokens.ts # JWT helpers (encrypt/decrypt AFP tokens, self-contained auth code)
|
|
47
48
|
│ └── login-page.ts # OAuth login page + redirect URI helpers
|
|
48
49
|
├── stdio/
|
|
49
50
|
│ └── server.ts # stdio transport entry
|
|
@@ -54,9 +55,9 @@ src/
|
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
1. Creates an `ApiCore` client from `afpnews-api` using `APICORE_API_KEY`
|
|
57
|
-
2. Registers MCP tools via `@modelcontextprotocol/
|
|
58
|
+
2. Registers MCP tools via `@modelcontextprotocol/server`: `afp_search_articles`, `afp_get_article`, `afp_find_similar`, `afp_list_facets`, `afp_search_media`, `afp_get_media`
|
|
58
59
|
3. Authenticates with username/password on first call, then reuses or refreshes the token for subsequent queries
|
|
59
|
-
4. Supports two transports: stdio (default) and HTTP (`MCP_TRANSPORT=http`, uses
|
|
60
|
+
4. Supports two transports: stdio (default) and HTTP (`MCP_TRANSPORT=http`, uses Hono + `createMcpHandler()` — a fresh `McpServer` per request, no session store or other server-side state, OAuth2 PKCE per-request bearer token). HTTP has two entry points sharing the same `createHttpApp()`: `startHttpServer()` (Bun, `Bun.serve`) and `worker.ts` (Cloudflare Worker, `export default { fetch }`) — see "Cloudflare Workers" in README.md for deployment.
|
|
60
61
|
|
|
61
62
|
### Definitions-First Pattern
|
|
62
63
|
|
|
@@ -87,9 +88,10 @@ Required:
|
|
|
87
88
|
- `MCP_TRANSPORT=http`
|
|
88
89
|
|
|
89
90
|
Optional:
|
|
90
|
-
- `PORT` — HTTP server port (default: 3000)
|
|
91
|
-
- `
|
|
92
|
-
|
|
91
|
+
- `PORT` — HTTP server port (default: 3000; ignored on Cloudflare Workers, which has no port to bind)
|
|
92
|
+
- `MCP_ALLOWED_REDIRECT_URIS` — Comma-separated list of allowed OAuth redirect URIs. The single place to allowlist AI clients with a fixed callback URL (Claude, ChatGPT, Mistral...) — `localhost`/`127.0.0.1` is always allowed separately, no entry needed for local clients. See README.md for known values.
|
|
93
|
+
|
|
94
|
+
On Workers, these are read from the `env` binding (`wrangler.toml` `[vars]` + `wrangler secret put`) instead of `process.env`, which doesn't exist there — see `src/http/worker.ts` and README.md.
|
|
93
95
|
|
|
94
96
|
## Key Details
|
|
95
97
|
|
package/README.md
CHANGED
|
@@ -50,7 +50,10 @@ For local MCP clients like Claude Code or Claude Desktop:
|
|
|
50
50
|
|
|
51
51
|
### HTTP transport
|
|
52
52
|
|
|
53
|
-
For remote or multi-user deployments.
|
|
53
|
+
For remote or multi-user deployments. Stateless: each request is served by a fresh MCP server
|
|
54
|
+
instance built from that request's own bearer token, so any number of instances can sit behind
|
|
55
|
+
a plain round-robin load balancer with no sticky sessions or shared store. Users authenticate via
|
|
56
|
+
OAuth2 PKCE using their AFP credentials.
|
|
54
57
|
|
|
55
58
|
Required environment variables for HTTP mode:
|
|
56
59
|
|
|
@@ -64,8 +67,17 @@ PORT=3000
|
|
|
64
67
|
```
|
|
65
68
|
|
|
66
69
|
Optional:
|
|
67
|
-
- `
|
|
68
|
-
|
|
70
|
+
- `MCP_ALLOWED_REDIRECT_URIS` — comma-separated list of allowed OAuth redirect URIs.
|
|
71
|
+
`localhost`/`127.0.0.1` (any port) is always allowed regardless of this list —
|
|
72
|
+
it covers any client that runs a local OAuth callback server (Claude Code,
|
|
73
|
+
MCP Inspector, most IDEs). This var is the single place to allowlist clients
|
|
74
|
+
with a fixed, non-localhost callback URL. Known ones:
|
|
75
|
+
- Claude: `https://claude.ai/api/mcp/auth_callback`, `https://claude.com/api/mcp/auth_callback`
|
|
76
|
+
- ChatGPT: `https://chatgpt.com/connector_platform_oauth_redirect`, `https://chatgpt.com/oauth/callback`, `https://chat.openai.com/oauth/callback`
|
|
77
|
+
- Mistral (Le Chat): `https://callback.mistral.ai/v1/integrations_auth/oauth2_callback`
|
|
78
|
+
|
|
79
|
+
Add others as you connect new clients — the exact `redirect_uri` shows up in
|
|
80
|
+
the failed `/oauth/authorize` request when a client isn't allowlisted yet.
|
|
69
81
|
|
|
70
82
|
```bash
|
|
71
83
|
bun src/index.ts
|
|
@@ -87,6 +99,30 @@ docker run \
|
|
|
87
99
|
afpnews-mcp
|
|
88
100
|
```
|
|
89
101
|
|
|
102
|
+
### Cloudflare Workers
|
|
103
|
+
|
|
104
|
+
The HTTP transport is built as a plain [Hono](https://hono.dev) app with no server-side state
|
|
105
|
+
(the OAuth authorization code and the bearer tokens are all self-contained), so it also runs as a
|
|
106
|
+
Cloudflare Worker via `src/http/worker.ts` — no KV, Durable Objects, or other bindings needed.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
bunx wrangler login
|
|
110
|
+
bunx wrangler secret put APICORE_API_KEY
|
|
111
|
+
bunx wrangler secret put JWT_SECRET
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Edit `wrangler.toml`'s `[vars]` (`APICORE_BASE_URL`, `MCP_SERVER_URL` — the latter must match your
|
|
115
|
+
actual `*.workers.dev` subdomain or custom domain), then:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
bun run dev:worker # local dev server via workerd (bunx wrangler dev)
|
|
119
|
+
bun run deploy:worker # bunx wrangler deploy
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Rate limiting isn't implemented in-process (it would be per-isolate and largely pointless on the
|
|
123
|
+
edge) — use [Cloudflare's own Rate Limiting](https://developers.cloudflare.com/waf/rate-limiting-rules/)
|
|
124
|
+
in front of the Worker instead.
|
|
125
|
+
|
|
90
126
|
### As a library (without MCP server dependency)
|
|
91
127
|
|
|
92
128
|
You can import pure definitions (tools, prompts, resources) and wire them into your own runtime:
|