Skip to main content
Glama
julesbonnard

afpnews-mcp-server

by julesbonnard
README.md
# afpnews-mcp

MCP (Model Context Protocol) server that exposes [AFP](https://www.afp.com/) news content as tools for AI assistants. Works with any MCP-compatible client.

The package can also be used as a library without MCP server glue via `afpnews-mcp-server/definitions`.

## Prerequisites

- [Bun](https://bun.sh/) 1.3+
- An AFP API account (API key + username/password)

## Setup

```bash
git clone https://github.com/julesbonnard/afpnews-mcp-server.git
cd afpnews-mcp-server
bun install
```

Create a `.env` file:

```
APICORE_API_KEY=your-api-key
APICORE_USERNAME=your-username
APICORE_PASSWORD=your-password
```

## Usage

### Stdio transport (default)

For local MCP clients like Claude Code or Claude Desktop:

```json
{
  "mcpServers": {
    "afpnews": {
      "command": "bun",
      "args": ["src/index.ts"],
      "cwd": "/absolute/path/to/afpnews-mcp-server",
      "env": {
        "APICORE_API_KEY": "your-api-key",
        "APICORE_USERNAME": "your-username",
        "APICORE_PASSWORD": "your-password"
      }
    }
  }
}
```

### HTTP transport

For remote or multi-user deployments. Stateless: each request is served by a fresh MCP server
instance built from that request's own bearer token, so any number of instances can sit behind
a plain round-robin load balancer with no sticky sessions or shared store. Users authenticate via
OAuth2 PKCE using their AFP credentials.

Required environment variables for HTTP mode:

```
APICORE_API_KEY=your-api-key
APICORE_BASE_URL=https://api.afp.com
MCP_SERVER_URL=https://news-mcp.example.com
JWT_SECRET=a-random-string-of-at-least-32-characters
MCP_TRANSPORT=http
PORT=3000
```

Optional:
- `MCP_ALLOWED_REDIRECT_URIS` — comma-separated list of allowed OAuth redirect URIs.
  `localhost`/`127.0.0.1` (any port) is always allowed regardless of this list —
  it covers any client that runs a local OAuth callback server (Claude Code,
  MCP Inspector, most IDEs). This var is the single place to allowlist clients
  with a fixed, non-localhost callback URL. Known ones:
  - Claude: `https://claude.ai/api/mcp/auth_callback`, `https://claude.com/api/mcp/auth_callback`
  - ChatGPT: `https://chatgpt.com/connector_platform_oauth_redirect`, `https://chatgpt.com/oauth/callback`, `https://chat.openai.com/oauth/callback`
  - Mistral (Le Chat): `https://callback.mistral.ai/v1/integrations_auth/oauth2_callback`

  Add others as you connect new clients — the exact `redirect_uri` shows up in
  the failed `/oauth/authorize` request when a client isn't allowlisted yet.

```bash
bun src/index.ts
```

If you expose the server remotely, use HTTPS.

### Docker

```bash
docker build -t afpnews-mcp .
docker run \
  -e APICORE_API_KEY=your-api-key \
  -e APICORE_BASE_URL=https://api.afp.com \
  -e MCP_SERVER_URL=https://news-mcp.example.com \
  -e JWT_SECRET=your-secret-32-chars-minimum \
  -e MCP_TRANSPORT=http \
  -p 3000:3000 \
  afpnews-mcp
```

### Cloudflare Workers

The HTTP transport is built as a plain [Hono](https://hono.dev) app with no server-side state
(the OAuth authorization code and the bearer tokens are all self-contained), so it also runs as a
Cloudflare Worker via `src/http/worker.ts` — no KV, Durable Objects, or other bindings needed.

```bash
bunx wrangler login
bunx wrangler secret put APICORE_API_KEY
bunx wrangler secret put JWT_SECRET
```

Edit `wrangler.toml`'s `[vars]` (`APICORE_BASE_URL`, `MCP_SERVER_URL` — the latter must match your
actual `*.workers.dev` subdomain or custom domain), then:

```bash
bun run dev:worker      # local dev server via workerd (bunx wrangler dev)
bun run deploy:worker   # bunx wrangler deploy
```

Rate limiting isn't implemented in-process (it would be per-isolate and largely pointless on the
edge) — use [Cloudflare's own Rate Limiting](https://developers.cloudflare.com/waf/rate-limiting-rules/)
in front of the Worker instead.

### As a library (without MCP server dependency)

You can import pure definitions (tools, prompts, resources) and wire them into your own runtime:

```ts
import { AFP_DEFINITIONS } from 'afpnews-mcp-server/definitions';

const { tools, prompts, resources } = AFP_DEFINITIONS;
```

Or import each collection directly:

```ts
import {
  TOOL_DEFINITIONS,
  PROMPT_DEFINITIONS,
  RESOURCE_DEFINITIONS,
} from 'afpnews-mcp-server/definitions';
```

Each definition is framework-agnostic:
- `tools`: `name`, `title`, `description`, `inputSchema`, `handler(apicore, args)`
- `prompts`: `name`, `title`, `description`, `argsSchema`, `handler(args)`
- `resources`: `name`, `uri`, `description`, `mimeType`, `handler()`

## Tools

| Tool | Description |
|------|-------------|
| `afp_search_articles` | Search AFP articles with filters, presets, and full-text mode |
| `afp_get_article` | Get a full article by its UNO identifier |
| `afp_find_similar` | Find similar articles (More Like This) from a UNO |
| `afp_list_facets` | List facet values (topics, genres, countries) with frequency counts |
| `afp_search_media` | Search AFP media documents (photos, videos, graphics) |
| `afp_get_media` | Get a full media document by UNO, with optional base64 image embed |

### Search presets

The `afp_search_articles` tool supports presets that apply predefined filters:

- **`a-la-une`** — Top story (French, last 24h)
- **`agenda`** — Upcoming events
- **`previsions`** — Editorial planning / forecasts
- **`major-stories`** — Major articles

### List preset

- **`trending-topics`** — Trending topics from the last 24h

### Full text

By default, `afp_search_articles` returns excerpts (first 2 paragraphs). Set `fullText: true` to get the complete article body. Presets default to full text.

### Pagination

Use `offset` to paginate through results (e.g. `offset: 10` to skip the first 10). For large chronological scans, prefer narrowing `dateFrom`/`dateTo` ranges in `facets` over high offsets. Keep `size` small (10–20) for best performance.

## Prompts

| Prompt | Description |
|--------|-------------|
| `daily-briefing` | Generate a daily news briefing |
| `comprehensive-analysis` | In-depth analysis on a topic |
| `factcheck` | Verify facts using AFP factchecks |
| `country-news` | News summary for a specific country |

## Resources

| Resource | Description |
|----------|-------------|
| `afp://topics` | AFP Stories topic catalog by language |

## Development

```bash
bun install
bun test
```

## Internal Architecture

- `src/tools/*.ts`, `src/prompts/*.ts`, `src/resources/*.ts` contain pure definitions.
- `src/tools/index.ts`, `src/prompts/index.ts`, `src/resources/index.ts` contain MCP registration glue.
- `src/definitions.ts` exports aggregated, server-agnostic definitions:
  - `AFP_DEFINITIONS`
  - `TOOL_DEFINITIONS`
  - `PROMPT_DEFINITIONS`
  - `RESOURCE_DEFINITIONS`

## License

ISC

Maintenance

ActivityMaintained
ResponsivenessResponsive