@hanmariyang/drafting 1.6.2 → 1.6.3
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/README.md +2 -0
- package/api/dist/mcp.js +103 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Drafting — AI 기획 워크스페이스
|
|
2
2
|
|
|
3
|
+
> 📓 **Build log** · how this was built, on the [AIP Lab blog](https://aiplab.kr/blog/drafting-terminal-mode.html).
|
|
4
|
+
|
|
3
5
|
> IT 기획자·PM이 **단계별 AI 인터뷰**로 PRD·기능명세서 같은 기획 문서를 빠르게 완성하는
|
|
4
6
|
> **셀프호스팅 오픈소스** 도구. 자신의 AI 키를 연결(BYOK)해 로컬/서버에 설치하고, 결과물을
|
|
5
7
|
> MD·HTML로 팀·클라이언트와 공유한다.
|
package/api/dist/mcp.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Drafting MCP 서버 (stdio) — 에이전트가 앱 밖에서 기획 문서를 만들고 내보내는 통로.
|
|
2
|
+
// 실행: npm run mcp 또는 node --experimental-strip-types api/src/mcp.ts
|
|
3
|
+
// DB 는 서버와 동일한 config.databasePath (환경변수 DATABASE_PATH 로 교체 가능).
|
|
4
|
+
// 모든 도구는 서비스 계층 직결(in-process) — HTTP 서버 없이 단독 동작한다.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { config } from "./lib/config.js";
|
|
11
|
+
import { getDb } from "./db/index.js";
|
|
12
|
+
import * as repo from "./db/repos.js";
|
|
13
|
+
import { documentToMarkdown } from "./lib/render.js";
|
|
14
|
+
import { lintReport } from "./lib/lint-service.js";
|
|
15
|
+
const DOC_TYPES = ['prd', 'feature-spec', 'ia', 'user-flow', 'design-system'];
|
|
16
|
+
function json(data) {
|
|
17
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 1) }] };
|
|
18
|
+
}
|
|
19
|
+
function fail(message) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: 'text', text: JSON.stringify({ error: message }) }],
|
|
22
|
+
isError: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function docSummary(d) {
|
|
26
|
+
return { id: d.id, type: d.type, title: d.title, status: d.status };
|
|
27
|
+
}
|
|
28
|
+
export function buildMcpServer() {
|
|
29
|
+
const server = new McpServer({ name: 'drafting', version: config.version ?? '0.0.0' });
|
|
30
|
+
server.tool('drafting-list-projects', '기획 프로젝트 목록을 반환한다.', {}, () => json({ projects: repo.listProjects().map((p) => ({ id: p.id, name: p.name, description: p.description })) }));
|
|
31
|
+
server.tool('drafting-create-project', '새 기획 프로젝트를 만든다. 반환된 id 로 문서를 추가한다.', { name: z.string().min(1).describe('프로젝트 이름'), description: z.string().optional() }, ({ name, description }) => {
|
|
32
|
+
const p = repo.createProject(name, description ?? '');
|
|
33
|
+
return json({ id: p.id, name: p.name });
|
|
34
|
+
});
|
|
35
|
+
server.tool('drafting-list-documents', '프로젝트의 문서 목록(id·type·title·status)을 반환한다.', { projectId: z.string() }, ({ projectId }) => {
|
|
36
|
+
if (!repo.getProject(projectId))
|
|
37
|
+
return fail('project not found');
|
|
38
|
+
return json({ documents: repo.listDocuments(projectId).map(docSummary) });
|
|
39
|
+
});
|
|
40
|
+
server.tool('drafting-create-document', '프로젝트에 기획 문서를 만든다. type: prd | feature-spec | ia | user-flow | design-system. 내용은 drafting-add-section 으로 채운다.', {
|
|
41
|
+
projectId: z.string(),
|
|
42
|
+
type: z.enum(DOC_TYPES),
|
|
43
|
+
title: z.string().min(1),
|
|
44
|
+
parentDocumentId: z.string().optional().describe('파생 문서일 때 부모 문서 id'),
|
|
45
|
+
}, ({ projectId, type, title, parentDocumentId }) => {
|
|
46
|
+
if (!repo.getProject(projectId))
|
|
47
|
+
return fail('project not found');
|
|
48
|
+
if (parentDocumentId && !repo.getDocument(parentDocumentId))
|
|
49
|
+
return fail('parent document not found');
|
|
50
|
+
const d = repo.createDocument({
|
|
51
|
+
projectId,
|
|
52
|
+
type: type,
|
|
53
|
+
title,
|
|
54
|
+
parentDocumentId: parentDocumentId ?? null,
|
|
55
|
+
});
|
|
56
|
+
return json(docSummary(d));
|
|
57
|
+
});
|
|
58
|
+
server.tool('drafting-add-section', '문서에 섹션(heading + 마크다운 body)을 순서대로 추가한다. 추가 즉시 수락 상태로 문서 본문이 된다.', {
|
|
59
|
+
documentId: z.string(),
|
|
60
|
+
heading: z.string().min(1).describe('섹션 제목 (## 레벨)'),
|
|
61
|
+
body: z.string().min(1).describe('섹션 본문 마크다운'),
|
|
62
|
+
}, ({ documentId, heading, body }) => {
|
|
63
|
+
if (!repo.getDocument(documentId))
|
|
64
|
+
return fail('document not found');
|
|
65
|
+
const s = repo.createSection(documentId, heading, body);
|
|
66
|
+
return json({ id: s.id, position: s.position, heading: s.heading });
|
|
67
|
+
});
|
|
68
|
+
server.tool('drafting-read-document', '문서 전체(섹션 포함)를 마크다운으로 반환한다.', { documentId: z.string() }, ({ documentId }) => {
|
|
69
|
+
const d = repo.getDocument(documentId);
|
|
70
|
+
if (!d)
|
|
71
|
+
return fail('document not found');
|
|
72
|
+
return json({ ...docSummary(d), markdown: documentToMarkdown(documentId) });
|
|
73
|
+
});
|
|
74
|
+
server.tool('drafting-compile', '기획 컴파일 — 프로젝트의 참조 무결성 검사(lint) 리포트를 반환한다. gatePasses 가 true 면 출하 가능.', { projectId: z.string() }, ({ projectId }) => {
|
|
75
|
+
if (!repo.getProject(projectId))
|
|
76
|
+
return fail('project not found');
|
|
77
|
+
const r = lintReport(projectId);
|
|
78
|
+
return json({
|
|
79
|
+
effectiveCount: r.effectiveCount,
|
|
80
|
+
waivedCount: r.waivedCount,
|
|
81
|
+
gatePasses: r.gatePasses,
|
|
82
|
+
violations: r.violations.filter((v) => !v.waived).slice(0, 20),
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
server.tool('drafting-export', '문서를 마크다운 파일로 내보낸다. 데이터 폴더의 exports/ 에 쓰고 절대 경로를 반환한다.', { documentId: z.string() }, ({ documentId }) => {
|
|
86
|
+
const d = repo.getDocument(documentId);
|
|
87
|
+
if (!d)
|
|
88
|
+
return fail('document not found');
|
|
89
|
+
const dir = path.join(path.dirname(config.databasePath), 'exports');
|
|
90
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
91
|
+
const slug = d.title.replace(/[^\p{L}\p{N}]+/gu, '_').replace(/^_+|_+$/g, '').slice(0, 60) || 'document';
|
|
92
|
+
const file = path.join(dir, `${slug}-${d.id.slice(0, 6)}.md`);
|
|
93
|
+
fs.writeFileSync(file, documentToMarkdown(documentId), 'utf8');
|
|
94
|
+
return json({ path: file, sections: repo.listSections(documentId).length });
|
|
95
|
+
});
|
|
96
|
+
return server;
|
|
97
|
+
}
|
|
98
|
+
// stdio 엔트리 (테스트에서 import 만 할 때는 붙지 않도록 가드)
|
|
99
|
+
if (process.argv[1] && import.meta.url.endsWith(path.basename(process.argv[1]))) {
|
|
100
|
+
getDb(); // 스키마 부트스트랩
|
|
101
|
+
const server = buildMcpServer();
|
|
102
|
+
await server.connect(new StdioServerTransport());
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanmariyang/drafting",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "AI Planning Workspace — self-hosted, BYOK. Draft PRDs & feature specs via staged AI interviews. Run: npx @hanmariyang/drafting serve",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/hanmariyang/drafting",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"build": "npm run build --workspace web && npm run build --workspace api",
|
|
41
41
|
"start": "node api/dist/index.js",
|
|
42
42
|
"test": "npm run test --workspace api",
|
|
43
|
-
"prepublishOnly": "npm run build"
|
|
43
|
+
"prepublishOnly": "npm run build",
|
|
44
|
+
"mcp": "node --experimental-strip-types api/src/mcp.ts"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
47
|
"@fastify/cors": "^10.0.1",
|