afpnews-mcp-server 1.3.10 → 2.0.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.
Files changed (65) hide show
  1. package/.dockerignore +6 -0
  2. package/.env.example +12 -0
  3. package/.serena/project.yml +135 -0
  4. package/CLAUDE.md +154 -0
  5. package/Dockerfile +19 -0
  6. package/README.md +34 -16
  7. package/bun.lock +301 -0
  8. package/docs/plans/2026-03-08-auth-refactor.md +740 -0
  9. package/docs/superpowers/plans/2026-03-17-bun-elysia-migration.md +1067 -0
  10. package/docs/superpowers/plans/2026-03-17-media-tools.md +1259 -0
  11. package/docs/superpowers/specs/2026-03-17-bun-elysia-migration-design.md +220 -0
  12. package/docs/superpowers/specs/2026-03-17-media-tools-design.md +404 -0
  13. package/package.json +18 -21
  14. package/skills-lock.json +10 -0
  15. package/src/__tests__/create-server.test.ts +74 -0
  16. package/src/__tests__/fixtures.ts +39 -0
  17. package/src/__tests__/format-media.test.ts +216 -0
  18. package/src/__tests__/format.test.ts +66 -0
  19. package/src/__tests__/get-media.test.ts +65 -0
  20. package/src/__tests__/index-auth.test.ts +45 -0
  21. package/src/__tests__/server.test.ts +385 -0
  22. package/src/__tests__/types-media.test.ts +49 -0
  23. package/{build/definitions.js → src/definitions.ts} +6 -4
  24. package/src/http/login-page.ts +113 -0
  25. package/src/http/server.ts +289 -0
  26. package/src/http/tokens.ts +45 -0
  27. package/src/index.ts +17 -0
  28. package/src/mcp-server.ts +62 -0
  29. package/src/prompts/comprehensive-analysis.ts +23 -0
  30. package/src/prompts/country-news.ts +20 -0
  31. package/src/prompts/daily-briefing.ts +23 -0
  32. package/src/prompts/factcheck.ts +22 -0
  33. package/src/prompts/index.ts +26 -0
  34. package/src/resources/index.ts +18 -0
  35. package/src/resources/topics.ts +139 -0
  36. package/src/stdio/server.ts +27 -0
  37. package/src/tools/find-similar.ts +63 -0
  38. package/{build/tools/get-article.js → src/tools/get-article.ts} +22 -17
  39. package/src/tools/get-media.ts +186 -0
  40. package/src/tools/index.ts +38 -0
  41. package/src/tools/list-facets.ts +94 -0
  42. package/src/tools/search-articles.ts +133 -0
  43. package/src/tools/search-media.ts +155 -0
  44. package/src/tools/shared.ts +112 -0
  45. package/src/utils/format-media.ts +108 -0
  46. package/src/utils/format.ts +201 -0
  47. package/src/utils/types.ts +86 -0
  48. package/tsconfig.json +13 -0
  49. package/build/index.js +0 -103
  50. package/build/prompts/comprehensive-analysis.js +0 -24
  51. package/build/prompts/country-news.js +0 -21
  52. package/build/prompts/daily-briefing.js +0 -22
  53. package/build/prompts/factcheck.js +0 -23
  54. package/build/prompts/index.js +0 -19
  55. package/build/resources/index.js +0 -10
  56. package/build/resources/topics.js +0 -15
  57. package/build/server.js +0 -21
  58. package/build/tools/find-similar.js +0 -57
  59. package/build/tools/index.js +0 -26
  60. package/build/tools/list-facets.js +0 -66
  61. package/build/tools/search-articles.js +0 -97
  62. package/build/tools/shared.js +0 -56
  63. package/build/utils/format.js +0 -104
  64. package/build/utils/topics.js +0 -132
  65. package/build/utils/types.js +0 -9
package/.dockerignore ADDED
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ build
3
+ .env
4
+ .git
5
+ .continue
6
+ .claude
package/.env.example ADDED
@@ -0,0 +1,12 @@
1
+ # AFP API
2
+ APICORE_API_KEY=your-afp-api-key
3
+ APICORE_BASE_URL= # optionnel
4
+
5
+ # Authentification
6
+ JWT_SECRET= # générer avec: openssl rand -base64 32 (min 32 chars)
7
+
8
+ # Serveur
9
+ MCP_SERVER_URL=https://news-mcp.example.com
10
+ PORT=3000 # optionnel, défaut: 3000
11
+ MCP_TRANSPORT=http # stdio ou http
12
+ MCP_SESSION_TTL=3600000 # optionnel, ms, défaut: 1h
@@ -0,0 +1,135 @@
1
+ # the name by which the project can be referenced within Serena
2
+ project_name: "afpnews-mcp-server"
3
+
4
+
5
+ # list of languages for which language servers are started; choose from:
6
+ # al bash clojure cpp csharp
7
+ # csharp_omnisharp dart elixir elm erlang
8
+ # fortran fsharp go groovy haskell
9
+ # java julia kotlin lua markdown
10
+ # matlab nix pascal perl php
11
+ # php_phpactor powershell python python_jedi r
12
+ # rego ruby ruby_solargraph rust scala
13
+ # swift terraform toml typescript typescript_vts
14
+ # vue yaml zig
15
+ # (This list may be outdated. For the current list, see values of Language enum here:
16
+ # https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
17
+ # For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
18
+ # Note:
19
+ # - For C, use cpp
20
+ # - For JavaScript, use typescript
21
+ # - For Free Pascal/Lazarus, use pascal
22
+ # Special requirements:
23
+ # Some languages require additional setup/installations.
24
+ # See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
25
+ # When using multiple languages, the first language server that supports a given file will be used for that file.
26
+ # The first language is the default language and the respective language server will be used as a fallback.
27
+ # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
28
+ languages:
29
+ - typescript
30
+
31
+ # the encoding used by text files in the project
32
+ # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
33
+ encoding: "utf-8"
34
+
35
+ # The language backend to use for this project.
36
+ # If not set, the global setting from serena_config.yml is used.
37
+ # Valid values: LSP, JetBrains
38
+ # Note: the backend is fixed at startup. If a project with a different backend
39
+ # is activated post-init, an error will be returned.
40
+ language_backend:
41
+
42
+ # whether to use project's .gitignore files to ignore files
43
+ ignore_all_files_in_gitignore: true
44
+
45
+ # list of additional paths to ignore in this project.
46
+ # Same syntax as gitignore, so you can use * and **.
47
+ # Note: global ignored_paths from serena_config.yml are also applied additively.
48
+ ignored_paths: []
49
+
50
+ # whether the project is in read-only mode
51
+ # If set to true, all editing tools will be disabled and attempts to use them will result in an error
52
+ # Added on 2025-04-18
53
+ read_only: false
54
+
55
+ # list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
56
+ # Below is the complete list of tools for convenience.
57
+ # To make sure you have the latest list of tools, and to view their descriptions,
58
+ # execute `uv run scripts/print_tool_overview.py`.
59
+ #
60
+ # * `activate_project`: Activates a project by name.
61
+ # * `check_onboarding_performed`: Checks whether project onboarding was already performed.
62
+ # * `create_text_file`: Creates/overwrites a file in the project directory.
63
+ # * `delete_lines`: Deletes a range of lines within a file.
64
+ # * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
65
+ # * `execute_shell_command`: Executes a shell command.
66
+ # * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
67
+ # * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
68
+ # * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
69
+ # * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
70
+ # * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
71
+ # * `initial_instructions`: Gets the initial instructions for the current project.
72
+ # Should only be used in settings where the system prompt cannot be set,
73
+ # e.g. in clients you have no control over, like Claude Desktop.
74
+ # * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
75
+ # * `insert_at_line`: Inserts content at a given line in a file.
76
+ # * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
77
+ # * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
78
+ # * `list_memories`: Lists memories in Serena's project-specific memory store.
79
+ # * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
80
+ # * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
81
+ # * `read_file`: Reads a file within the project directory.
82
+ # * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
83
+ # * `remove_project`: Removes a project from the Serena configuration.
84
+ # * `replace_lines`: Replaces a range of lines within a file with new content.
85
+ # * `replace_symbol_body`: Replaces the full definition of a symbol.
86
+ # * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
87
+ # * `search_for_pattern`: Performs a search for a pattern in the project.
88
+ # * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
89
+ # * `switch_modes`: Activates modes by providing a list of their names
90
+ # * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
91
+ # * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
92
+ # * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
93
+ # * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
94
+ excluded_tools: []
95
+
96
+ # list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
97
+ included_optional_tools: []
98
+
99
+ # fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
100
+ # This cannot be combined with non-empty excluded_tools or included_optional_tools.
101
+ fixed_tools: []
102
+
103
+ # list of mode names to that are always to be included in the set of active modes
104
+ # The full set of modes to be activated is base_modes + default_modes.
105
+ # If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
106
+ # Otherwise, this setting overrides the global configuration.
107
+ # Set this to [] to disable base modes for this project.
108
+ # Set this to a list of mode names to always include the respective modes for this project.
109
+ base_modes:
110
+
111
+ # list of mode names that are to be activated by default.
112
+ # The full set of modes to be activated is base_modes + default_modes.
113
+ # If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
114
+ # Otherwise, this overrides the setting from the global configuration (serena_config.yml).
115
+ # This setting can, in turn, be overridden by CLI parameters (--mode).
116
+ default_modes:
117
+
118
+ # initial prompt for the project. It will always be given to the LLM upon activating the project
119
+ # (contrary to the memories, which are loaded on demand).
120
+ initial_prompt: ""
121
+
122
+ # time budget (seconds) per tool call for the retrieval of additional symbol information
123
+ # such as docstrings or parameter information.
124
+ # This overrides the corresponding setting in the global configuration; see the documentation there.
125
+ # If null or missing, use the setting from the global configuration.
126
+ symbol_info_budget:
127
+
128
+ # list of regex patterns which, when matched, mark a memory entry as read‑only.
129
+ # Extends the list from the global configuration, merging the two lists.
130
+ read_only_memory_patterns: []
131
+
132
+ # line ending convention to use when writing source files.
133
+ # Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
134
+ # This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
135
+ line_ending:
package/CLAUDE.md ADDED
@@ -0,0 +1,154 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
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.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ bun install # Install dependencies
13
+ bun src/index.ts # Run the MCP server (stdio transport by default)
14
+ bun test # Run tests
15
+ ```
16
+
17
+ ## Architecture
18
+
19
+ TypeScript project organized in `src/`:
20
+
21
+ ```
22
+ src/
23
+ ├── index.ts # Entry point — dispatches to stdio or HTTP transport
24
+ ├── mcp-server.ts # createServer + ServerContext
25
+ ├── definitions.ts # Server-agnostic exports (tools/prompts/resources)
26
+ ├── tools/
27
+ │ ├── index.ts # registerTools() MCP glue
28
+ │ ├── shared.ts # Shared enums/constants/helpers
29
+ │ ├── search-articles.ts
30
+ │ ├── get-article.ts
31
+ │ ├── find-similar.ts
32
+ │ ├── list-facets.ts
33
+ │ ├── search-media.ts
34
+ │ └── get-media.ts
35
+ ├── prompts/
36
+ │ ├── index.ts # registerPrompts() MCP glue
37
+ │ ├── daily-briefing.ts
38
+ │ ├── comprehensive-analysis.ts
39
+ │ ├── factcheck.ts
40
+ │ └── country-news.ts
41
+ ├── resources/
42
+ │ ├── index.ts # registerResources() MCP glue
43
+ │ └── topics.ts # TOPICS inline + resource handler
44
+ ├── http/
45
+ │ ├── server.ts # Elysia HTTP server + OAuth2 PKCE auth
46
+ │ ├── tokens.ts # JWT helpers (encrypt/decrypt AFP tokens)
47
+ │ └── login-page.ts # OAuth login page + redirect URI helpers
48
+ ├── stdio/
49
+ │ └── server.ts # stdio transport entry
50
+ └── utils/
51
+ ├── format.ts # formatDocument, textContent, toolError, truncateIfNeeded, buildPaginationLine
52
+ ├── format-media.ts # extractRenditions, formatMediaDocument
53
+ └── types.ts # AFPDocument, TextContent, FormattedContent, constants
54
+ ```
55
+
56
+ 1. Creates an `ApiCore` client from `afpnews-api` using `APICORE_API_KEY`
57
+ 2. Registers MCP tools via `@modelcontextprotocol/sdk`: `afp_search_articles`, `afp_get_article`, `afp_find_similar`, `afp_list_facets`, `afp_search_media`, `afp_get_media`
58
+ 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 Elysia + Streamable HTTP with OAuth2 PKCE per-session)
60
+
61
+ ### Definitions-First Pattern
62
+
63
+ - Tool/prompt/resource files are server-agnostic definitions only.
64
+ - MCP-specific glue (`server.registerTool`, `server.registerPrompt`, `server.registerResource`) lives only in each domain `index.ts`.
65
+ - Aggregated non-MCP exports are provided in `src/definitions.ts`.
66
+ - Published subpath export: `afpnews-mcp-server/definitions`.
67
+
68
+ ## Environment Variables
69
+
70
+ ### Stdio mode
71
+
72
+ Required:
73
+ - `APICORE_API_KEY` — AFP API key
74
+ - `APICORE_USERNAME` — AFP account username
75
+ - `APICORE_PASSWORD` — AFP account password
76
+
77
+ Optional:
78
+ - `APICORE_BASE_URL` — AFP API base URL (overrides SDK default)
79
+
80
+ ### HTTP mode
81
+
82
+ Required:
83
+ - `APICORE_API_KEY` — AFP API key
84
+ - `APICORE_BASE_URL` — AFP API base URL
85
+ - `JWT_SECRET` — Secret for JWT signing/encryption (min 32 characters)
86
+ - `MCP_SERVER_URL` — Public URL of the server (e.g. `https://news-mcp.example.com`)
87
+ - `MCP_TRANSPORT=http`
88
+
89
+ Optional:
90
+ - `PORT` — HTTP server port (default: 3000)
91
+ - `MCP_SESSION_TTL` — Session duration in milliseconds (default: 3600000 = 1h)
92
+ - `MCP_ALLOWED_REDIRECT_URIS` — Comma-separated list of allowed OAuth redirect URIs
93
+
94
+ ## Key Details
95
+
96
+ - **Package manager**: bun (v1.3.10)
97
+ - **Runtime**: Bun — TypeScript executed directly, no build step
98
+ - **Module system**: ESM (`"type": "module"`)
99
+ - **Tests**: `bun test`
100
+ - Document types from `afpnews-api` are untyped — the code uses `(doc as any)` casts
101
+ - Package exports:
102
+ - `afpnews-mcp-server` → MCP runtime entry (`src/index.ts`)
103
+ - `afpnews-mcp-server/definitions` → pure definitions (`src/definitions.ts`)
104
+
105
+ ## Outils MCP disponibles
106
+
107
+ | Outil | Description |
108
+ |------------------------|--------------------------------------------------------------------|
109
+ | `afp_search_articles` | Outil principal de recherche d'articles (filtres + presets + mode fullText) |
110
+ | `afp_get_article` | Récupération d'un article complet par UNO (texte non tronqué) |
111
+ | `afp_find_similar` | Articles similaires (More Like This) à partir d'un UNO |
112
+ | `afp_list_facets` | Liste des valeurs d'une facette (slug, genre, country) avec fréquence (preset disponible) |
113
+ | `afp_search_media` | Recherche de médias AFP (photos, vidéos, graphiques) |
114
+ | `afp_get_media` | Récupération d'un média complet par UNO, avec embed base64 optionnel |
115
+
116
+ ### Presets
117
+
118
+ #### `afp_search_articles.preset`
119
+
120
+ Presets disponibles:
121
+ - `a-la-une`
122
+ - `agenda`
123
+ - `previsions`
124
+ - `major-stories`
125
+
126
+ Comportement:
127
+ - Chaque preset applique automatiquement un jeu de filtres `afp_search_articles` adapté.
128
+ - Le preset force `fullText=true`.
129
+ - Le preset peut être affiné via les autres paramètres (`lang`, `size`, etc.), selon les champs déjà fixés par le preset.
130
+
131
+ #### `afp_list_facets.preset`
132
+
133
+ Preset disponible:
134
+ - `trending-topics` (équivalent de la logique "topics tendance")
135
+
136
+ Comportement:
137
+ - Si `preset=trending-topics`, `facet` est ignoré et remplacé en interne par `slug`.
138
+ - Sans preset, `facet` est requis.
139
+
140
+ ### Paramètre `fullText` (tool `afp_search_articles`)
141
+
142
+ - Type: `boolean`
143
+ - Défaut global: `false`
144
+ - Overridé à `true` par les presets
145
+
146
+ Règles:
147
+ - `fullText=false` -> extrait (premiers paragraphes)
148
+ - `fullText=true` -> texte complet de la dépêche
149
+
150
+ ## Roadmap — Améliorations fonctionnelles
151
+
152
+ ### Priorité basse
153
+
154
+ - **Notifications / veille** — exploiter le Notification Center de l'API (`registerService`, `addSubscription`) pour créer des alertes par webhook ou email (bloqué : l'API côté serveur ne fonctionne pas actuellement)
package/Dockerfile ADDED
@@ -0,0 +1,19 @@
1
+ FROM oven/bun:1.3.10-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ # 1. Installer les deps
6
+ COPY package.json bun.lock ./
7
+ RUN bun install --frozen-lockfile --production
8
+
9
+ # 2. Copier le code de l’app
10
+ COPY . .
11
+
12
+ # 3. User non-root
13
+ RUN addgroup -S appgroup && adduser -S appuser -G appgroup
14
+ USER appuser
15
+
16
+ ENV MCP_TRANSPORT=http
17
+ EXPOSE 3000
18
+
19
+ CMD ["bun", "src/index.js"]
package/README.md CHANGED
@@ -6,8 +6,7 @@ The package can also be used as a library without MCP server glue via `afpnews-m
6
6
 
7
7
  ## Prerequisites
8
8
 
9
- - Node.js 22+
10
- - [pnpm](https://pnpm.io/)
9
+ - [Bun](https://bun.sh/) 1.3+
11
10
  - An AFP API account (API key + username/password)
12
11
 
13
12
  ## Setup
@@ -15,8 +14,7 @@ The package can also be used as a library without MCP server glue via `afpnews-m
15
14
  ```bash
16
15
  git clone https://github.com/julesbonnard/afpnews-mcp-server.git
17
16
  cd afpnews-mcp-server
18
- pnpm install
19
- pnpm run build
17
+ bun install
20
18
  ```
21
19
 
22
20
  Create a `.env` file:
@@ -37,8 +35,8 @@ For local MCP clients like Claude Code or Claude Desktop:
37
35
  {
38
36
  "mcpServers": {
39
37
  "afpnews": {
40
- "command": "node",
41
- "args": ["build/index.js"],
38
+ "command": "bun",
39
+ "args": ["src/index.ts"],
42
40
  "cwd": "/absolute/path/to/afpnews-mcp-server",
43
41
  "env": {
44
42
  "APICORE_API_KEY": "your-api-key",
@@ -52,22 +50,41 @@ For local MCP clients like Claude Code or Claude Desktop:
52
50
 
53
51
  ### HTTP transport
54
52
 
55
- For remote or multi-user deployments. Each session authenticates independently via HTTP Basic Auth (username/password are your AFP credentials).
53
+ For remote or multi-user deployments. Users authenticate via OAuth2 PKCE using their AFP credentials.
54
+
55
+ Required environment variables for HTTP mode:
56
+
57
+ ```
58
+ APICORE_API_KEY=your-api-key
59
+ APICORE_BASE_URL=https://api.afp.com
60
+ MCP_SERVER_URL=https://news-mcp.example.com
61
+ JWT_SECRET=a-random-string-of-at-least-32-characters
62
+ MCP_TRANSPORT=http
63
+ PORT=3000
64
+ ```
65
+
66
+ Optional:
67
+ - `MCP_SESSION_TTL` — session duration in milliseconds (default: 3600000 = 1h)
68
+ - `MCP_ALLOWED_REDIRECT_URIS` — comma-separated list of allowed OAuth redirect URIs
56
69
 
57
70
  ```bash
58
- MCP_TRANSPORT=http PORT=3000 pnpm run start
71
+ bun src/index.ts
59
72
  ```
60
73
 
61
- Notes:
62
- - Keep `APICORE_API_KEY` set in the server environment (`.env` or runtime env).
63
- - If you expose the server remotely, use HTTPS.
74
+ If you expose the server remotely, use HTTPS.
64
75
 
65
76
  ### Docker
66
77
 
67
78
  ```bash
68
- pnpm run build
69
79
  docker build -t afpnews-mcp .
70
- docker run -e APICORE_API_KEY=your-api-key -p 3000:3000 afpnews-mcp
80
+ docker run \
81
+ -e APICORE_API_KEY=your-api-key \
82
+ -e APICORE_BASE_URL=https://api.afp.com \
83
+ -e MCP_SERVER_URL=https://news-mcp.example.com \
84
+ -e JWT_SECRET=your-secret-32-chars-minimum \
85
+ -e MCP_TRANSPORT=http \
86
+ -p 3000:3000 \
87
+ afpnews-mcp
71
88
  ```
72
89
 
73
90
  ### As a library (without MCP server dependency)
@@ -103,6 +120,8 @@ Each definition is framework-agnostic:
103
120
  | `afp_get_article` | Get a full article by its UNO identifier |
104
121
  | `afp_find_similar` | Find similar articles (More Like This) from a UNO |
105
122
  | `afp_list_facets` | List facet values (topics, genres, countries) with frequency counts |
123
+ | `afp_search_media` | Search AFP media documents (photos, videos, graphics) |
124
+ | `afp_get_media` | Get a full media document by UNO, with optional base64 image embed |
106
125
 
107
126
  ### Search presets
108
127
 
@@ -139,9 +158,8 @@ By default, `afp_search_articles` returns excerpts (first 4 paragraphs). Set `fu
139
158
  ## Development
140
159
 
141
160
  ```bash
142
- pnpm install
143
- pnpm run build
144
- pnpm test
161
+ bun install
162
+ bun test
145
163
  ```
146
164
 
147
165
  ## Internal Architecture