@milcho0604/velog-mcp 0.4.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.
- package/LICENSE +21 -0
- package/README.ko.md +366 -0
- package/README.md +381 -0
- package/dist/auth.d.ts +57 -0
- package/dist/auth.js +124 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities.d.ts +50 -0
- package/dist/capabilities.js +60 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/client.d.ts +113 -0
- package/dist/client.js +322 -0
- package/dist/client.js.map +1 -0
- package/dist/format.d.ts +31 -0
- package/dist/format.js +66 -0
- package/dist/format.js.map +1 -0
- package/dist/graphql.d.ts +29 -0
- package/dist/graphql.js +82 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/me.d.ts +23 -0
- package/dist/me.js +35 -0
- package/dist/me.js.map +1 -0
- package/dist/ownership.d.ts +42 -0
- package/dist/ownership.js +62 -0
- package/dist/ownership.js.map +1 -0
- package/dist/plugin-env.d.ts +67 -0
- package/dist/plugin-env.js +102 -0
- package/dist/plugin-env.js.map +1 -0
- package/dist/ratelimit.d.ts +48 -0
- package/dist/ratelimit.js +79 -0
- package/dist/ratelimit.js.map +1 -0
- package/dist/render/chrome.d.ts +77 -0
- package/dist/render/chrome.js +287 -0
- package/dist/render/chrome.js.map +1 -0
- package/dist/render/cover.d.ts +29 -0
- package/dist/render/cover.js +195 -0
- package/dist/render/cover.js.map +1 -0
- package/dist/render/icons.d.ts +22 -0
- package/dist/render/icons.js +158 -0
- package/dist/render/icons.js.map +1 -0
- package/dist/render/index.d.ts +32 -0
- package/dist/render/index.js +137 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/page.d.ts +89 -0
- package/dist/render/page.js +761 -0
- package/dist/render/page.js.map +1 -0
- package/dist/render/tones.d.ts +30 -0
- package/dist/render/tones.js +46 -0
- package/dist/render/tones.js.map +1 -0
- package/dist/slug.d.ts +42 -0
- package/dist/slug.js +79 -0
- package/dist/slug.js.map +1 -0
- package/dist/tools/discover.d.ts +6 -0
- package/dist/tools/discover.js +106 -0
- package/dist/tools/discover.js.map +1 -0
- package/dist/tools/drafts.d.ts +23 -0
- package/dist/tools/drafts.js +227 -0
- package/dist/tools/drafts.js.map +1 -0
- package/dist/tools/export.d.ts +21 -0
- package/dist/tools/export.js +132 -0
- package/dist/tools/export.js.map +1 -0
- package/dist/tools/images.d.ts +34 -0
- package/dist/tools/images.js +556 -0
- package/dist/tools/images.js.map +1 -0
- package/dist/tools/posts.d.ts +14 -0
- package/dist/tools/posts.js +82 -0
- package/dist/tools/posts.js.map +1 -0
- package/dist/tools/profile-edit.d.ts +15 -0
- package/dist/tools/profile-edit.js +216 -0
- package/dist/tools/profile-edit.js.map +1 -0
- package/dist/tools/profile.d.ts +9 -0
- package/dist/tools/profile.js +133 -0
- package/dist/tools/profile.js.map +1 -0
- package/dist/tools/publish.d.ts +16 -0
- package/dist/tools/publish.js +424 -0
- package/dist/tools/publish.js.map +1 -0
- package/dist/tools/stats.d.ts +32 -0
- package/dist/tools/stats.js +154 -0
- package/dist/tools/stats.js.map +1 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/docs/PRD.md +146 -0
- package/docs/api-reference.md +329 -0
- package/docs/architecture.md +112 -0
- package/docs/decisions/0001-why-build-our-own.md +89 -0
- package/docs/decisions/0002-draft-only-write.md +84 -0
- package/docs/decisions/0003-token-env-only.md +109 -0
- package/docs/decisions/0004-capability-model.md +123 -0
- package/docs/decisions/0005-render-in-server.md +117 -0
- package/docs/decisions/0006-ship-as-plugin.md +532 -0
- package/docs/security.md +384 -0
- package/docs/tools.md +404 -0
- package/npm-shrinkwrap.json +2345 -0
- package/package.json +61 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 글 백업 — 벨로그 글을 프론트매터 붙인 마크다운으로 내려받는다.
|
|
3
|
+
*
|
|
4
|
+
* ★ 이 레포에서 파일을 쓰는 유일한 모듈이다. 쓰는 내용은 '글 본문'뿐이고
|
|
5
|
+
* 토큰은 절대 닿지 않는다. safety.test.ts 가 이 파일만 예외로 둔다.
|
|
6
|
+
*
|
|
7
|
+
* 벨로그에 공식 내보내기가 없어서 만든다. 서비스가 사라져도 글은 남아야 한다.
|
|
8
|
+
*/
|
|
9
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
10
|
+
import { resolve, join } from 'node:path';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { QUERY_POST } from "../graphql.js";
|
|
13
|
+
import { textResult } from "../format.js";
|
|
14
|
+
import { slugify } from "../slug.js";
|
|
15
|
+
import { fetchAllPosts } from "./stats.js";
|
|
16
|
+
import { READ_ONLY } from "./posts.js";
|
|
17
|
+
import { resolveMyUsername } from "../me.js";
|
|
18
|
+
/** YAML 문자열 값. 따옴표·백슬래시를 이스케이프해 프론트매터가 깨지지 않게 한다. */
|
|
19
|
+
function yamlString(value) {
|
|
20
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
21
|
+
}
|
|
22
|
+
export function toMarkdown(post, username) {
|
|
23
|
+
// 공식 스키마상 title·url_slug 는 nullable 이다. 여기서 막지 않으면
|
|
24
|
+
// yamlString(null) 이 프론트매터를 깨뜨린다.
|
|
25
|
+
const title = post.title ?? '(제목 없음)';
|
|
26
|
+
const slug = post.url_slug ?? post.id;
|
|
27
|
+
const front = [
|
|
28
|
+
'---',
|
|
29
|
+
`title: ${yamlString(title)}`,
|
|
30
|
+
`date: ${post.released_at ?? post.created_at ?? ''}`,
|
|
31
|
+
`slug: ${yamlString(slug)}`,
|
|
32
|
+
`url: ${yamlString(`https://velog.io/@${username}/${slug}`)}`,
|
|
33
|
+
];
|
|
34
|
+
if (post.tags?.length) {
|
|
35
|
+
front.push(`tags: [${post.tags.map(yamlString).join(', ')}]`);
|
|
36
|
+
}
|
|
37
|
+
if (post.series?.name)
|
|
38
|
+
front.push(`series: ${yamlString(post.series.name)}`);
|
|
39
|
+
front.push(`likes: ${post.likes ?? 0}`, `views: ${post.views ?? 0}`);
|
|
40
|
+
if (post.is_private)
|
|
41
|
+
front.push('private: true');
|
|
42
|
+
if (post.is_temp)
|
|
43
|
+
front.push('draft: true');
|
|
44
|
+
front.push('---', '');
|
|
45
|
+
return `${front.join('\n')}\n${post.body ?? ''}\n`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 파일명 안전화.
|
|
49
|
+
*
|
|
50
|
+
* 슬러그에 경로 구분자가 들어오면 지정한 디렉터리 밖에 쓸 수 있다.
|
|
51
|
+
* slugify 가 이미 `/` 를 제거하지만, 여기서 한 번 더 막는다 —
|
|
52
|
+
* 파일을 쓰는 코드는 이중으로 방어한다.
|
|
53
|
+
*/
|
|
54
|
+
export function safeFileName(slug, index) {
|
|
55
|
+
const base = slugify(slug).replace(/[/\\]/g, '-').slice(0, 100);
|
|
56
|
+
const prefix = String(index).padStart(3, '0');
|
|
57
|
+
return `${prefix}-${base || 'untitled'}.md`;
|
|
58
|
+
}
|
|
59
|
+
export function registerExportTools(server, client) {
|
|
60
|
+
server.registerTool('velog_export_posts', {
|
|
61
|
+
title: '글 마크다운 백업',
|
|
62
|
+
description: '한 사용자의 벨로그 글을 프론트매터가 붙은 마크다운 파일로 로컬에 저장한다. ' +
|
|
63
|
+
'벨로그에 공식 내보내기가 없어서 만든 기능이다. ' +
|
|
64
|
+
'★ 같은 이름의 기존 파일이 있으면 덮어쓴다 — 전용 디렉터리를 쓰는 것을 권한다. ' +
|
|
65
|
+
'글 본문을 한 편씩 받아오므로 글이 많으면 시간이 걸린다.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
username: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('@ 없이. 생략하면 인증된 내 계정을 쓴다'),
|
|
71
|
+
out_dir: z.string().describe('저장할 디렉터리 (절대경로 권장). 없으면 만든다'),
|
|
72
|
+
limit: z
|
|
73
|
+
.number()
|
|
74
|
+
.int()
|
|
75
|
+
.min(1)
|
|
76
|
+
.max(200)
|
|
77
|
+
.default(50)
|
|
78
|
+
.describe('내보낼 최대 글 수'),
|
|
79
|
+
},
|
|
80
|
+
// 벨로그 쪽 상태는 안 바뀌지만 로컬 파일은 '덮어쓴다'. MCP 명세상
|
|
81
|
+
// destructiveHint:false 는 '추가만 한다'는 뜻이라 거짓이 된다.
|
|
82
|
+
annotations: { ...READ_ONLY, readOnlyHint: false, destructiveHint: true },
|
|
83
|
+
}, async ({ username, out_dir, limit }) => {
|
|
84
|
+
const target = username ?? (await resolveMyUsername(client));
|
|
85
|
+
const dir = resolve(out_dir);
|
|
86
|
+
await mkdir(dir, { recursive: true });
|
|
87
|
+
const maxPages = Math.ceil(limit / 50);
|
|
88
|
+
const { posts } = await fetchAllPosts(client, target, maxPages);
|
|
89
|
+
const targets = posts.slice(0, limit);
|
|
90
|
+
if (targets.length === 0)
|
|
91
|
+
return textResult(`@${target} 의 공개 글이 없습니다.`);
|
|
92
|
+
const written = [];
|
|
93
|
+
const failed = [];
|
|
94
|
+
for (const [index, summary] of targets.entries()) {
|
|
95
|
+
// 벨로그 커넥션 풀(limit 5)을 배려한다. 몰아치면 상대가 먼저 죽는다.
|
|
96
|
+
if (index > 0)
|
|
97
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
98
|
+
try {
|
|
99
|
+
// 목록에는 body 가 없으므로 한 편씩 상세를 받는다.
|
|
100
|
+
const data = await client.request(QUERY_POST,
|
|
101
|
+
// url_slug 가 없으면 id 로 조회한다.
|
|
102
|
+
summary.url_slug
|
|
103
|
+
? { input: { username: target, url_slug: summary.url_slug } }
|
|
104
|
+
: { input: { id: summary.id } });
|
|
105
|
+
if (!data.post) {
|
|
106
|
+
failed.push(`${summary.title} (본문 조회 실패)`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const name = safeFileName(summary.url_slug ?? summary.id, index + 1);
|
|
110
|
+
await writeFile(join(dir, name), toMarkdown(data.post, target), 'utf8');
|
|
111
|
+
written.push(name);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
115
|
+
failed.push(`${summary.title} — ${reason}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const lines = [
|
|
119
|
+
`✅ ${written.length}편을 저장했습니다.`,
|
|
120
|
+
'',
|
|
121
|
+
`- 위치: ${dir}`,
|
|
122
|
+
`- 형식: YAML 프론트매터 + 마크다운 본문`,
|
|
123
|
+
];
|
|
124
|
+
if (failed.length > 0) {
|
|
125
|
+
lines.push('', `⚠️ ${failed.length}편 실패:`, ...failed.slice(0, 10).map((f) => ` - ${f}`));
|
|
126
|
+
if (failed.length > 10)
|
|
127
|
+
lines.push(` ... 외 ${failed.length - 10}편`);
|
|
128
|
+
}
|
|
129
|
+
return textResult(lines.join('\n'));
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/tools/export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 그림 도구 — 다이어그램·표지를 그리고 벨로그에 올린다.
|
|
3
|
+
*
|
|
4
|
+
* ## 왜 모델이 HTML 을 쓰게 하지 않았나
|
|
5
|
+
*
|
|
6
|
+
* 그림 스타일은 서버가 쥔다. 모델은 **무엇이 있고 무엇이 어디로 흐르는지**만 주고,
|
|
7
|
+
* 색·여백·글자 실측·선 라운딩·자가감사는 렌더러가 한다. 이유는 두 가지다.
|
|
8
|
+
*
|
|
9
|
+
* ① 품질 — 매번 다시 그리면 매번 다르게 생긴다. 규칙을 코드에 박아두면
|
|
10
|
+
* 어느 글에 넣어도 같은 인상이 나온다.
|
|
11
|
+
* ② 안전 — 모델이 임의 HTML/JS 를 넘겨 브라우저에서 실행시킬 수 있으면
|
|
12
|
+
* 그건 그냥 코드 실행 통로다. 데이터만 받으면 그 통로가 없다.
|
|
13
|
+
*
|
|
14
|
+
* ## 업로드에 건 방어
|
|
15
|
+
*
|
|
16
|
+
* `velog_upload_image` 는 로컬 파일을 받아 **공개 CDN** 으로 보낸다. 경로를 그대로
|
|
17
|
+
* 믿으면 "이미지를 올려줘" 한 마디로 아무 파일이나 인터넷에 올라갈 수 있다.
|
|
18
|
+
* 그래서 확장자가 아니라 **파일 앞부분 시그니처**로 진짜 이미지인지 확인한다.
|
|
19
|
+
* SVG 는 통과시키지 않는다 — 텍스트라 시그니처로 가릴 수 없고, 스크립트를 품은 채
|
|
20
|
+
* velcdn 도메인에서 서빙되면 그 자체가 문제가 된다.
|
|
21
|
+
*/
|
|
22
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
23
|
+
import type { VelogClient } from '../client.ts';
|
|
24
|
+
interface Sniffed {
|
|
25
|
+
readonly mime: string;
|
|
26
|
+
readonly ext: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 파일 앞부분으로 형식을 판정한다. 확장자는 보지 않는다 — 이름은 누구나 바꾼다.
|
|
30
|
+
* 여기서 통과하지 못하면 업로드하지 않는다.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sniffImage(bytes: Uint8Array): Sniffed | null;
|
|
33
|
+
export declare function registerImageTools(server: McpServer, client: VelogClient): void;
|
|
34
|
+
export {};
|