feat(mcp): servidor MCP para controlar la app desde agentes

Nuevo paquete en mcp-server/ (TypeScript SDK, stdio) que envuelve la API HTTP de Story Studio
y expone 36 tools con prefijo story_ para conducir todo el pipeline: proyectos, idea motriz,
personajes (+assets de imagen), capítulos, guión, pre-escaleta, escaleta, prompts, fondos,
imágenes, vídeos y render. Base URL configurable via STORY_STUDIO_URL (default localhost:3000).

Excluido del build de Next (.dockerignore) — es una herramienta aparte, no se despliega con la app.
Verificado end-to-end contra la app desplegada (list_projects, get_idea_motriz).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Narro
2026-08-13 02:55:16 +02:00
parent 2c08d47a0f
commit 5aa5129865
9 changed files with 2452 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
// Test manual del MCP server: lanza el server por stdio, lista tools y llama una de lectura.
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
const url = process.env.STORY_STUDIO_URL || 'https://story-studio.carlosnarro.com';
const transport = new StdioClientTransport({
command: 'node',
args: ['dist/index.js'],
env: { ...process.env, STORY_STUDIO_URL: url },
});
const client = new Client({ name: 'test-client', version: '1.0.0' });
await client.connect(transport);
const { tools } = await client.listTools();
console.log(`\nTOOLS (${tools.length}):`);
console.log(tools.map((t) => t.name).join(', '));
console.log('\n== call story_list_projects ==');
const res = await client.callTool({ name: 'story_list_projects', arguments: {} });
const text = res.content?.[0]?.text ?? '';
console.log('isError:', res.isError === true);
console.log(text.slice(0, 600));
// Prueba una segunda tool de lectura con un slug real si hay proyectos.
try {
const projs = JSON.parse(text);
const slug = Array.isArray(projs) && projs[0]?.slug;
if (slug) {
console.log(`\n== call story_get_idea_motriz (slug=${slug}) ==`);
const r2 = await client.callTool({ name: 'story_get_idea_motriz', arguments: { slug } });
console.log('isError:', r2.isError === true, '| len:', (r2.content?.[0]?.text ?? '').length);
}
} catch { /* ignore */ }
await client.close();
console.log('\nOK');