feat: first cut at mcp and mcp apps
This commit is contained in:
512
TODO.md
512
TODO.md
@@ -256,13 +256,20 @@ In `PageRenderer` and `BlogGenerationEngine`:
|
||||
|
||||
---
|
||||
|
||||
## 3. MCP Server
|
||||
## 3. MCP Server — Agent-Assisted Content Creation
|
||||
|
||||
### Goal
|
||||
|
||||
Host an MCP (Model Context Protocol) server inside the application so external
|
||||
AI agents (Claude Code, Cursor, etc.) can connect and use bDS tools to query
|
||||
and manage blog content.
|
||||
AI agents (Claude Code, Cursor, etc.) can connect and work with bDS. The core
|
||||
principle: **read access is open, write access goes through user-reviewable
|
||||
drafts**. External agents never silently mutate data — they propose changes
|
||||
that the user accepts or discards inside the agent's UI.
|
||||
|
||||
This enables powerful workflows: an agent can read existing posts, draft a new
|
||||
one with pre-filled content, propose a Python script or Liquid template, or
|
||||
suggest image metadata improvements — and the user always has final say before
|
||||
anything enters the system.
|
||||
|
||||
### Current State
|
||||
|
||||
@@ -270,14 +277,93 @@ and manage blog content.
|
||||
tools with full implementations (`getToolDefinitions()`, `executeTool()`).
|
||||
- `PreviewServer` provides the architectural pattern for an in-process HTTP
|
||||
server with lifecycle management.
|
||||
- No MCP SDK dependency exists.
|
||||
- Posts already have a `draft` status (DB-only, no disk writes until publish)
|
||||
— the exact pattern needed for MCP draft posts.
|
||||
- Scripts and templates are file-first (immediately persisted) — MCP proposals
|
||||
need an in-memory staging layer before acceptance.
|
||||
- **`@modelcontextprotocol/sdk` v1.27.1 and `@modelcontextprotocol/ext-apps`
|
||||
v1.1.2 are installed.**
|
||||
- **`ProposalStore` engine is implemented and tested (18 tests).**
|
||||
- **`MCPServer` engine is implemented and tested (37 tests).** Standalone HTTP
|
||||
server on port 4124, stateless mode (new `McpServer` per request), registers
|
||||
5 static resources, 6 resource templates, 8 tools, 3 prompts, and 4
|
||||
`ui://` review-app resources.
|
||||
- **`mcp-views.ts` provides review HTML** for posts, scripts, templates, and
|
||||
metadata diffs via `@modelcontextprotocol/ext-apps` App class.
|
||||
- **Lifecycle integrated in `main.ts`** — MCP server starts on app ready and
|
||||
cleans up on before-quit.
|
||||
|
||||
### Design Principles
|
||||
|
||||
1. **Use all three MCP primitives** — expose blog data via the appropriate
|
||||
MCP primitive for each use case:
|
||||
- **Resources** for passive, read-only data (post content, media metadata,
|
||||
category/tag lists, blog stats). These are application-controlled — the
|
||||
host decides when to fetch them.
|
||||
- **Tools** for parameterized actions (search, drafting, proposing changes).
|
||||
These are model-controlled — the LLM decides when to call them.
|
||||
- **Prompts** for user-triggered workflow templates (e.g., "draft a blog
|
||||
post", "audit content quality"). These surface as slash commands in the
|
||||
host UI.
|
||||
|
||||
2. **Tool annotations** — every tool declares MCP `annotations` to advertise
|
||||
its behavior to the host:
|
||||
- Read-only tools: `{ readOnlyHint: true, openWorldHint: false }`
|
||||
- Proposal tools: `{ readOnlyHint: false, destructiveHint: false }`
|
||||
- `accept_proposal`: `{ readOnlyHint: false, destructiveHint: false,
|
||||
idempotentHint: true }`
|
||||
- `discard_proposal`: `{ readOnlyHint: false, destructiveHint: true,
|
||||
idempotentHint: true }`
|
||||
|
||||
All annotations are hints — hosts must not make security decisions based
|
||||
on them alone, but they help hosts choose appropriate UI (e.g.,
|
||||
auto-approving reads, showing confirmation for destructive actions).
|
||||
|
||||
3. **Tool metadata** — every tool includes `title` (human-readable display
|
||||
name) and `description` (detailed explanation of what it does and when to
|
||||
use it). Descriptions are critical — they are what the LLM reads to decide
|
||||
whether and when to call a tool.
|
||||
|
||||
4. **Draft/Propose pattern for writes via MCP Apps** — every mutation goes
|
||||
through a user-gated flow using the MCP Apps extension:
|
||||
- Agent calls a `draft_*` or `propose_*` tool. These tools declare a
|
||||
`_meta.ui.resourceUri` pointing to a review UI (`ui://` resource).
|
||||
- The host (Claude Desktop, VS Code, etc.) renders the review app as a
|
||||
sandboxed iframe inline in the conversation.
|
||||
- The app shows the proposal (post preview, code block, metadata diff)
|
||||
with accept/discard buttons. The agent LLM is **not** in the loop.
|
||||
- User clicks accept → the app calls `accept_proposal` tool via the
|
||||
MCP App bridge (postMessage) → server commits the change.
|
||||
- User clicks discard → the app calls `discard_proposal` tool →
|
||||
server cleans up.
|
||||
- The tool result flows back to the agent so it can continue.
|
||||
|
||||
5. **Aligned with internal editors** — the MCP App review UIs mirror what
|
||||
the app's own editors show (post metadata + content, script content +
|
||||
validation, template content + validation, image metadata diff). This
|
||||
keeps the user experience consistent whether they work inside bDS or
|
||||
through an external agent.
|
||||
|
||||
6. **Capability negotiation** — during MCP initialization, the server
|
||||
declares its supported capabilities: `tools`, `resources`, `prompts`.
|
||||
For MCP Apps, the extension capability `io.modelcontextprotocol/ui` is
|
||||
negotiated via the `extensions` field. The server checks whether the
|
||||
client supports the Apps extension and adjusts proposal tool responses
|
||||
accordingly (structured preview data for Apps-capable hosts, formatted
|
||||
text for others).
|
||||
|
||||
7. **Input validation and rate limiting** — all tool inputs are validated
|
||||
at the MCP boundary before forwarding to engine methods. The server
|
||||
rate-limits tool invocations to prevent abuse. Do not rely solely on
|
||||
downstream engine validation.
|
||||
|
||||
### Implementation Plan
|
||||
|
||||
#### 3.1 Dependencies
|
||||
|
||||
Add `@modelcontextprotocol/sdk` to `package.json`. This provides the standard
|
||||
MCP server implementation with transport handling.
|
||||
- `@modelcontextprotocol/sdk` — standard MCP server with transport handling.
|
||||
- `@modelcontextprotocol/ext-apps` — MCP Apps extension for serving
|
||||
interactive UI resources.
|
||||
|
||||
#### 3.2 Engine Class — `MCPServer`
|
||||
|
||||
@@ -288,72 +374,416 @@ src/main/engine/MCPServer.ts
|
||||
```
|
||||
|
||||
- Constructor accepts dependency injection (engines via getters).
|
||||
- `start(port)` — create HTTP server implementing MCP protocol, or use stdio
|
||||
transport for local agent integration.
|
||||
- `start(port)` — create standalone HTTP server on port 4124 using
|
||||
`StreamableHTTPServerTransport` in stateless mode.
|
||||
- `stop()` — clean shutdown.
|
||||
- `getToolDefinitions()` — convert OpenCodeManager's Anthropic-format tool
|
||||
definitions to MCP schema format.
|
||||
- `executeTool(name, args)` — delegate to OpenCodeManager's `executeTool()`.
|
||||
- Manages an in-memory `ProposalStore` for pending proposals (scripts,
|
||||
templates, metadata changes). Posts use the existing draft mechanism instead.
|
||||
- Serves `ui://` resources for the MCP App review UIs.
|
||||
- Exposes three MCP primitive types:
|
||||
- **Resources** — read-only blog data (posts, media, tags, categories,
|
||||
stats) accessible via URI-based `resources/read`.
|
||||
- **Tools** — parameterized actions (search, draft, propose, accept,
|
||||
discard). Agent-facing tools are visible to the LLM; app-internal
|
||||
tools (`accept_proposal`, `discard_proposal`) are called only by the
|
||||
MCP App via the App Bridge.
|
||||
- **Prompts** — user-triggered workflow templates surfaced as slash
|
||||
commands in the host.
|
||||
|
||||
#### 3.3 Tool Mapping
|
||||
#### 3.3 Resources (Read-Only Data)
|
||||
|
||||
Map the existing OpenCodeManager tools to MCP tools. The tool signatures are
|
||||
nearly identical between Anthropic tool_use format and MCP — both use JSON
|
||||
Schema for input definitions. The mapping is mechanical:
|
||||
Expose blog data as MCP Resources using URI templates. Resources are
|
||||
application-controlled — the host fetches them for context, they are not
|
||||
actions. Each resource is registered via `resources/list` and read via
|
||||
`resources/read`.
|
||||
|
||||
| OpenCodeManager Tool | MCP Tool Name |
|
||||
|------------------------|------------------------|
|
||||
| search_posts | search_posts |
|
||||
| read_post | read_post |
|
||||
| list_posts | list_posts |
|
||||
| get_media | get_media |
|
||||
| list_media | list_media |
|
||||
| update_post_metadata | update_post_metadata |
|
||||
| update_media_metadata | update_media_metadata |
|
||||
| list_tags | list_tags |
|
||||
| list_categories | list_categories |
|
||||
| get_blog_stats | get_blog_stats |
|
||||
| view_image | view_image |
|
||||
| get_post_backlinks | get_post_backlinks |
|
||||
| get_post_outlinks | get_post_outlinks |
|
||||
| get_post_media | get_post_media |
|
||||
| get_media_posts | get_media_posts |
|
||||
| Resource URI | Source | Description |
|
||||
|----------------------------------|----------------------------------|------------------------------------|
|
||||
| `bds://posts/{id}` | OpenCodeManager.read_post | Full post content + metadata |
|
||||
| `bds://posts` | OpenCodeManager.list_posts | Paginated post list |
|
||||
| `bds://media/{id}` | OpenCodeManager.get_media | Media item metadata |
|
||||
| `bds://media` | OpenCodeManager.list_media | Paginated media list |
|
||||
| `bds://tags` | OpenCodeManager.list_tags | All tags with counts |
|
||||
| `bds://categories` | OpenCodeManager.list_categories | All categories |
|
||||
| `bds://stats` | OpenCodeManager.get_blog_stats | Blog-wide statistics |
|
||||
| `bds://posts/{id}/backlinks` | OpenCodeManager.get_post_backlinks | Posts linking to this post |
|
||||
| `bds://posts/{id}/outlinks` | OpenCodeManager.get_post_outlinks | Posts this post links to |
|
||||
| `bds://posts/{id}/media` | OpenCodeManager.get_post_media | Media attached to a post |
|
||||
| `bds://media/{id}/posts` | OpenCodeManager.get_media_posts | Posts using a media item |
|
||||
| `bds://media/{id}/image` | OpenCodeManager.view_image | Image binary (for visual context) |
|
||||
|
||||
Exclude A2UI render tools (they are UI-specific and not useful for external
|
||||
Use `bds://` as the custom URI scheme. Parameterized URIs use MCP resource
|
||||
templates (`resources/templates/list`). Emit `notifications/resources/
|
||||
list_changed` when posts, media, or tags are created/updated/deleted so
|
||||
the host can refresh cached data.
|
||||
|
||||
List resources (`bds://posts`, `bds://media`) support cursor-based
|
||||
pagination following the MCP pagination spec. The initial response
|
||||
includes a `nextCursor` if more results exist; the host passes it back
|
||||
on the next `resources/read` call.
|
||||
|
||||
#### 3.4 Read Tools (Parameterized Queries)
|
||||
|
||||
Not all read operations fit the Resources model. Parameterized queries
|
||||
that accept complex search criteria are better modeled as tools with
|
||||
`readOnlyHint: true`.
|
||||
|
||||
| MCP Tool Name | Source | Annotations |
|
||||
|------------------------|----------------------------------|------------------------------------------|
|
||||
| search_posts | OpenCodeManager.search_posts | `readOnlyHint: true, openWorldHint: false` |
|
||||
|
||||
Each tool includes a `title`, a `description` explaining when it should
|
||||
be used, and a JSON Schema `inputSchema`. `search_posts` accepts query
|
||||
text, filters (status, category, tag, date range), and pagination
|
||||
parameters (cursor, limit).
|
||||
|
||||
Exclude `update_post_metadata`, `update_media_metadata` (writes go through
|
||||
proposals), and A2UI render tools (UI-specific, not useful for external
|
||||
agents).
|
||||
|
||||
#### 3.4 Transport
|
||||
#### 3.5 Proposal Tools (Draft-Based Writes)
|
||||
|
||||
These tools stage content for user review. Each declares a `_meta.ui` field
|
||||
pointing to a review UI resource, so the host renders an MCP App inline in the
|
||||
conversation. Proposals have a TTL (e.g., 30 min) and are auto-discarded if
|
||||
not accepted. All proposal tools use annotations
|
||||
`{ readOnlyHint: false, destructiveHint: false }`.
|
||||
|
||||
##### 3.5.1 `draft_post`
|
||||
|
||||
Creates a draft post using the existing PostEngine draft workflow.
|
||||
|
||||
**Input:** `{ title, content (markdown), excerpt?, tags?, categoryId? }`
|
||||
|
||||
**Tool definition `_meta`:**
|
||||
```json
|
||||
{ "ui": { "resourceUri": "ui://bds/review-post" } }
|
||||
```
|
||||
|
||||
**Action:**
|
||||
1. Call `PostEngine.createPost()` with status `draft`.
|
||||
2. Set tags and category if provided.
|
||||
3. Return `{ proposalId (= postId), type: 'draftPost', preview: { title,
|
||||
excerpt, tags, category, content, wordCount } }`.
|
||||
|
||||
**On user accept (via app):** `PostEngine.publishPost(postId)` — post
|
||||
transitions to published, markdown file is written to disk.
|
||||
|
||||
**On user discard (via app):** `PostEngine.deletePost(postId)` — draft is
|
||||
removed from the database.
|
||||
|
||||
##### 3.5.2 `propose_script`
|
||||
|
||||
Stages a Python script in memory for review.
|
||||
|
||||
**Input:** `{ title, content (python source), description? }`
|
||||
|
||||
**Tool definition `_meta`:**
|
||||
```json
|
||||
{ "ui": { "resourceUri": "ui://bds/review-script" } }
|
||||
```
|
||||
|
||||
**Action:**
|
||||
1. Validate Python syntax (basic parse check).
|
||||
2. Store in `ProposalStore` with a generated ID.
|
||||
3. Return `{ proposalId, type: 'script', preview: { title, slug (generated),
|
||||
description, content, syntaxValid, syntaxErrors? } }`.
|
||||
|
||||
**On user accept (via app):** `ScriptEngine.createScript()` — file + DB entry
|
||||
created.
|
||||
|
||||
**On user discard (via app):** Remove from `ProposalStore` — nothing was
|
||||
persisted.
|
||||
|
||||
##### 3.5.3 `propose_template`
|
||||
|
||||
Stages a Liquid template in memory for review.
|
||||
|
||||
**Input:** `{ title, content (liquid source), kind ('post'|'list'|'not-found'|
|
||||
'partial') }`
|
||||
|
||||
**Tool definition `_meta`:**
|
||||
```json
|
||||
{ "ui": { "resourceUri": "ui://bds/review-template" } }
|
||||
```
|
||||
|
||||
**Action:**
|
||||
1. Validate Liquid syntax via `TemplateEngine.validateTemplate()`.
|
||||
2. Store in `ProposalStore`.
|
||||
3. Return `{ proposalId, type: 'template', preview: { title, slug, kind,
|
||||
content, syntaxValid, syntaxErrors? } }`.
|
||||
|
||||
**On user accept (via app):** `TemplateEngine.createTemplate()` — file + DB
|
||||
entry created.
|
||||
|
||||
**On user discard (via app):** Remove from `ProposalStore`.
|
||||
|
||||
##### 3.5.4 `propose_media_metadata`
|
||||
|
||||
Stages metadata changes for an existing media item.
|
||||
|
||||
**Input:** `{ mediaId, title?, alt?, caption? }`
|
||||
|
||||
**Tool definition `_meta`:**
|
||||
```json
|
||||
{ "ui": { "resourceUri": "ui://bds/review-metadata" } }
|
||||
```
|
||||
|
||||
**Action:**
|
||||
1. Load current metadata via `MediaEngine`.
|
||||
2. Compute diff (current vs proposed for each changed field).
|
||||
3. Store in `ProposalStore`.
|
||||
4. Return `{ proposalId, type: 'mediaMetadata', preview: { filename,
|
||||
diff: { field, current, proposed }[] } }`.
|
||||
|
||||
**On user accept (via app):** Apply changes via `MediaEngine.updateMedia()`.
|
||||
|
||||
**On user discard (via app):** Remove from `ProposalStore`.
|
||||
|
||||
##### 3.5.5 `propose_post_metadata`
|
||||
|
||||
Stages metadata changes for an existing post (title, excerpt, slug, tags).
|
||||
|
||||
**Input:** `{ postId, title?, excerpt?, slug?, tags? }`
|
||||
|
||||
**Tool definition `_meta`:**
|
||||
```json
|
||||
{ "ui": { "resourceUri": "ui://bds/review-metadata" } }
|
||||
```
|
||||
|
||||
**Action:**
|
||||
1. Load current post via `PostEngine`.
|
||||
2. Compute diff.
|
||||
3. Store in `ProposalStore`.
|
||||
4. Return `{ proposalId, type: 'postMetadata', preview: { currentTitle,
|
||||
diff: { field, current, proposed }[] } }`.
|
||||
|
||||
**On user accept (via app):** Apply via `PostEngine.updatePost()`.
|
||||
|
||||
**On user discard (via app):** Remove from `ProposalStore`.
|
||||
|
||||
#### 3.6 App-Internal Tools (Accept / Discard)
|
||||
|
||||
These tools are called by the MCP App (via the App Bridge's `tools/call`
|
||||
mechanism), **not** by the agent LLM. The host forwards the call from the
|
||||
sandboxed iframe to the MCP server. They are not listed in `tools/list`
|
||||
responses to agents.
|
||||
|
||||
##### `accept_proposal`
|
||||
|
||||
**Input:** `{ proposalId }`
|
||||
|
||||
**Annotations:** `{ readOnlyHint: false, destructiveHint: false,
|
||||
idempotentHint: true }`
|
||||
|
||||
Looks up the proposal type (draftPost, script, template, mediaMetadata,
|
||||
postMetadata) and executes the commit action:
|
||||
- draftPost → `PostEngine.publishPost()`
|
||||
- script → `ScriptEngine.createScript()`
|
||||
- template → `TemplateEngine.createTemplate()`
|
||||
- mediaMetadata → `MediaEngine.updateMedia()`
|
||||
- postMetadata → `PostEngine.updatePost()`
|
||||
|
||||
Returns `{ success, message }`.
|
||||
|
||||
##### `discard_proposal`
|
||||
|
||||
**Input:** `{ proposalId }`
|
||||
|
||||
**Annotations:** `{ readOnlyHint: false, destructiveHint: true,
|
||||
idempotentHint: true }`
|
||||
|
||||
Executes the cleanup action:
|
||||
- draftPost → `PostEngine.deletePost()`
|
||||
- All others → remove from `ProposalStore`
|
||||
|
||||
Returns `{ success, message }`.
|
||||
|
||||
#### 3.7 MCP App Review UIs
|
||||
|
||||
The review UIs are HTML pages served as `ui://` resources by the MCP server.
|
||||
They render inside the host's sandboxed iframe and use the
|
||||
`@modelcontextprotocol/ext-apps` App class for bidirectional communication.
|
||||
|
||||
Each review app:
|
||||
1. Receives the tool result data (proposal preview) from the host.
|
||||
2. Renders a focused review interface:
|
||||
- **Post review** (`ui://bds/review-post`): title, metadata fields,
|
||||
rendered markdown content preview, word count.
|
||||
- **Script review** (`ui://bds/review-script`): title, syntax-highlighted
|
||||
Python code, validation status/errors.
|
||||
- **Template review** (`ui://bds/review-template`): title, kind badge,
|
||||
syntax-highlighted Liquid code, validation status/errors.
|
||||
- **Metadata review** (`ui://bds/review-metadata`): side-by-side diff of
|
||||
current vs proposed values for each changed field.
|
||||
3. Shows accept and discard buttons.
|
||||
4. On user action, calls `accept_proposal` or `discard_proposal` via the
|
||||
App Bridge's `tools/call`.
|
||||
5. Updates its UI to show the outcome ("Published", "Created", "Discarded").
|
||||
|
||||
These apps are small, self-contained HTML pages — the "slim MCP apps" concept.
|
||||
They can share a common layout/style and differ only in the content they
|
||||
render. Since the host provides the sandboxing, the apps don't need their own
|
||||
authentication or security layer.
|
||||
|
||||
The review UIs should visually align with what the bDS internal editors show,
|
||||
so the user experience is consistent. Where practical, share CSS/component
|
||||
patterns between the bDS renderer UI and the MCP App review UIs.
|
||||
|
||||
#### 3.8 ProposalStore
|
||||
|
||||
In-memory store for pending proposals (not posts — those use the DB draft
|
||||
mechanism):
|
||||
|
||||
```typescript
|
||||
interface Proposal {
|
||||
id: string;
|
||||
type: 'script' | 'template' | 'mediaMetadata' | 'postMetadata';
|
||||
data: Record<string, unknown>; // type-specific payload
|
||||
createdAt: number;
|
||||
ttlMs: number; // default 30 minutes
|
||||
}
|
||||
```
|
||||
|
||||
- Simple `Map<string, Proposal>` with periodic cleanup of expired entries.
|
||||
- On app shutdown, all pending proposals are discarded (they were never
|
||||
committed).
|
||||
- No persistence needed — proposals are ephemeral by design.
|
||||
|
||||
For draft posts, the `proposalId` is the post ID itself. The `ProposalStore`
|
||||
tracks a mapping from `proposalId → 'draftPost'` so the accept/discard
|
||||
tools know to call PostEngine rather than look in the store.
|
||||
|
||||
#### 3.9 Transport
|
||||
|
||||
Support two transports:
|
||||
|
||||
- **stdio** — for local integration (agent runs `bds --mcp` or connects via
|
||||
named pipe). This is the standard for MCP in coding agents.
|
||||
- **HTTP/SSE** — for network access, running alongside PreviewServer on a
|
||||
different port (e.g., 5174).
|
||||
named pipe). This is the standard for MCP in coding agents. Credentials
|
||||
come from the environment.
|
||||
- **Streamable HTTP** — for network access, running alongside PreviewServer
|
||||
on a different port (e.g., 5174). Uses the current MCP Streamable HTTP
|
||||
transport: a single HTTP endpoint that accepts JSON-RPC POST requests and
|
||||
responds with either `application/json` (single response) or
|
||||
`text/event-stream` (SSE stream for multiple messages). Supports session
|
||||
management via `Mcp-Session-Id` headers and requires
|
||||
`MCP-Protocol-Version` headers after initialization.
|
||||
|
||||
Start with stdio since that is what Claude Code and Cursor use.
|
||||
|
||||
#### 3.5 Lifecycle Integration
|
||||
**Security requirements for Streamable HTTP:**
|
||||
|
||||
- Bind to `127.0.0.1` (localhost only) when running locally.
|
||||
- Validate the `Origin` header on all requests to prevent DNS rebinding
|
||||
attacks.
|
||||
- Use cryptographically random, non-deterministic session IDs.
|
||||
- Implement a session token or shared secret for authentication (generated
|
||||
on server start, displayed in the settings UI for the user to configure
|
||||
in their agent).
|
||||
- Rate-limit incoming requests.
|
||||
- Set appropriate timeouts for tool invocations.
|
||||
|
||||
#### 3.10 Lifecycle Integration
|
||||
|
||||
In `main.ts`:
|
||||
|
||||
- Initialize `MCPServer` in `initialize()`.
|
||||
- Start alongside `PreviewServer` in `app.whenReady()`.
|
||||
- Stop in `before-quit` handler.
|
||||
- Stop in `before-quit` handler (discard all pending proposals).
|
||||
- Respect active project context (tools operate on the active project).
|
||||
|
||||
#### 3.6 Configuration
|
||||
#### 3.11 Configuration
|
||||
|
||||
In `SettingsView`, add an "MCP Server" section:
|
||||
|
||||
- Enable/disable toggle.
|
||||
- Port number (for HTTP transport).
|
||||
- Show connection instructions (stdio command or URL).
|
||||
- Proposal TTL setting (default 30 min).
|
||||
|
||||
#### 3.7 Testing
|
||||
#### 3.12 MCP Prompts (Workflow Templates)
|
||||
|
||||
Expose MCP Prompts for common agent workflows. Prompts are user-controlled —
|
||||
they surface as slash commands or command palette entries in the host and
|
||||
produce pre-structured messages that guide the LLM.
|
||||
|
||||
| Prompt Name | Arguments | Description |
|
||||
|------------------------|----------------------------|--------------------------------------|
|
||||
| `draft-blog-post` | `topic?`, `category?` | Guides agent through reading existing posts, understanding the blog's style, and drafting a new post on the given topic |
|
||||
| `improve-media-metadata` | `scope` (`all`\|`missing`) | Guides agent through reviewing media items and proposing alt text, captions, and titles |
|
||||
| `content-audit` | `category?` | Guides agent through reviewing posts for quality, broken links, missing metadata, and suggesting fixes |
|
||||
|
||||
Each prompt returns a structured message array (system + user messages) that
|
||||
sets up the LLM with context about the blog and clear instructions for the
|
||||
workflow. The host renders the prompt as a one-click action.
|
||||
|
||||
This is a lower-priority addition — prompts enhance discoverability but the
|
||||
core read/write flow works without them.
|
||||
|
||||
#### 3.13 Example Workflows
|
||||
|
||||
**Agent creates a blog post:**
|
||||
1. Agent LLM reads `bds://categories` and `bds://tags` resources to
|
||||
understand the blog.
|
||||
2. Agent LLM calls `draft_post({ title: "...", content: "...", tags: [...] })`.
|
||||
3. MCP server creates draft post (DB only), returns preview data.
|
||||
4. Host sees `_meta.ui.resourceUri`, fetches `ui://bds/review-post`, renders
|
||||
sandboxed iframe inline in conversation.
|
||||
5. Review app shows the full post preview with accept/discard buttons.
|
||||
6. User clicks accept → app calls `accept_proposal` via App Bridge →
|
||||
server publishes post → app shows "Published" → agent is informed.
|
||||
|
||||
**Agent writes a Python script:**
|
||||
1. Agent LLM reads `bds://posts` resource to understand the content model.
|
||||
2. Agent LLM calls `propose_script({ title: "Tag Cleanup", content: "..." })`.
|
||||
3. MCP server validates syntax, stages in ProposalStore, returns preview.
|
||||
4. Host renders `ui://bds/review-script` — shows syntax-highlighted code
|
||||
with validation results.
|
||||
5. User reviews code, clicks accept → app calls `accept_proposal` →
|
||||
server creates script via ScriptEngine.
|
||||
|
||||
**Agent suggests image metadata:**
|
||||
1. Agent LLM reads `bds://media/{id}/image` resource to see the image.
|
||||
2. Agent LLM calls `propose_media_metadata({ mediaId, alt: "...",
|
||||
caption: "..." })`.
|
||||
3. MCP server computes diff, stages in ProposalStore, returns diff preview.
|
||||
4. Host renders `ui://bds/review-metadata` — shows current vs proposed diff.
|
||||
5. User reviews diff, clicks accept → metadata is updated.
|
||||
|
||||
#### 3.14 MCP Apps Client Support
|
||||
|
||||
MCP Apps are currently supported by Claude (claude.ai), Claude Desktop,
|
||||
VS Code GitHub Copilot, Goose, Postman, and MCPJam. For hosts that don't
|
||||
support MCP Apps, the proposal tools still return structured text content
|
||||
that the agent can present as formatted text — the user would then need
|
||||
to instruct the agent to accept or discard via conversation, falling back
|
||||
to a simpler flow.
|
||||
|
||||
For hosts without Apps support, the MCP **Elicitation** primitive
|
||||
(`elicitation/request`) can serve as a lighter-weight confirmation
|
||||
mechanism — the server asks the user to confirm or reject a proposal via
|
||||
a simple dialog rather than a full review UI. This requires the host to
|
||||
support the `elicitation` capability. When neither Apps nor Elicitation is
|
||||
available, fall back to text-based accept/discard in the conversation.
|
||||
|
||||
#### 3.15 Testing
|
||||
|
||||
- Unit tests for tool definition mapping (Anthropic → MCP format).
|
||||
- Unit tests for resource URI resolution and resource template listing.
|
||||
- Unit tests for `ProposalStore` (create, accept, discard, TTL expiry).
|
||||
- Unit tests for accept/discard tool handlers.
|
||||
- Unit tests for tool annotations (verify correct hints on each tool).
|
||||
- Unit tests for prompt templates (verify message structure and arguments).
|
||||
- Integration tests: draft_post → app accept flow, propose_script → app
|
||||
discard flow.
|
||||
- Integration tests: start MCP server, send tool calls, verify responses.
|
||||
- Integration tests: capability negotiation (with/without Apps extension).
|
||||
- Integration tests: resource read, resource list with pagination, resource
|
||||
change notifications.
|
||||
- MCP App UI tests: render review apps, simulate user actions, verify
|
||||
tool calls through App Bridge.
|
||||
- Security tests: Origin header validation, session management, rate
|
||||
limiting (for Streamable HTTP transport).
|
||||
- Follow existing engine test patterns with mocked dependencies.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user