diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue
index d7e7c6dcb5..f33173da6f 100644
--- a/src/client/app/common/views/components/messaging-room.message.vue
+++ b/src/client/app/common/views/components/messaging-room.message.vue
@@ -32,7 +32,7 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
 
 export default Vue.extend({
 	props: {
diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.ts b/src/client/app/common/views/components/misskey-flavored-markdown.ts
index 61dbc02d9a..30ad3ce3f2 100644
--- a/src/client/app/common/views/components/misskey-flavored-markdown.ts
+++ b/src/client/app/common/views/components/misskey-flavored-markdown.ts
@@ -1,6 +1,6 @@
 import Vue from 'vue';
 import * as emojilib from 'emojilib';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
 import getAcct from '../../../../../acct/render';
 import { url } from '../../../config';
 import MkUrl from './url.vue';
diff --git a/src/client/app/desktop/views/components/note-detail.vue b/src/client/app/desktop/views/components/note-detail.vue
index 017f599e31..a9640ff9c4 100644
--- a/src/client/app/desktop/views/components/note-detail.vue
+++ b/src/client/app/desktop/views/components/note-detail.vue
@@ -83,7 +83,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import dateStringify from '../../../common/scripts/date-stringify';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
 
 import MkPostFormWindow from './post-form-window.vue';
 import MkRenoteFormWindow from './renote-form-window.vue';
diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue
index 661223ee25..b96ae2da5a 100644
--- a/src/client/app/desktop/views/components/notes.note.vue
+++ b/src/client/app/desktop/views/components/notes.note.vue
@@ -76,7 +76,7 @@
 import Vue from 'vue';
 import dateStringify from '../../../common/scripts/date-stringify';
 import canHideText from '../../../common/scripts/can-hide-text';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
 
 import MkPostFormWindow from './post-form-window.vue';
 import MkRenoteFormWindow from './renote-form-window.vue';
diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue
index 33f2288e06..3832e5b38c 100644
--- a/src/client/app/desktop/views/components/post-form.vue
+++ b/src/client/app/desktop/views/components/post-form.vue
@@ -49,7 +49,7 @@ import Vue from 'vue';
 import * as XDraggable from 'vuedraggable';
 import getKao from '../../../common/scripts/get-kao';
 import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
 import { host } from '../../../config';
 
 export default Vue.extend({
diff --git a/src/client/app/desktop/views/pages/deck/deck.note.vue b/src/client/app/desktop/views/pages/deck/deck.note.vue
index 067297af37..d50fc3c235 100644
--- a/src/client/app/desktop/views/pages/deck/deck.note.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.note.vue
@@ -67,7 +67,7 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import parse from '../../../../../../text/parse';
+import parse from '../../../../../../mfm/parse';
 import canHideText from '../../../../common/scripts/can-hide-text';
 
 import MkNoteMenu from '../../../../common/views/components/note-menu.vue';
diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue
index 6499f78f1b..76633490fd 100644
--- a/src/client/app/mobile/views/components/note-detail.vue
+++ b/src/client/app/mobile/views/components/note-detail.vue
@@ -83,7 +83,7 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
 
 import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue
index 77513a6591..ecdfee3b44 100644
--- a/src/client/app/mobile/views/components/note.vue
+++ b/src/client/app/mobile/views/components/note.vue
@@ -68,7 +68,7 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
 import canHideText from '../../../common/scripts/can-hide-text';
 
 import MkNoteMenu from '../../../common/views/components/note-menu.vue';
diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue
index 62fa185085..1015a44115 100644
--- a/src/client/app/mobile/views/components/post-form.vue
+++ b/src/client/app/mobile/views/components/post-form.vue
@@ -45,7 +45,7 @@ import Vue from 'vue';
 import * as XDraggable from 'vuedraggable';
 import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
 import getKao from '../../../common/scripts/get-kao';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
 import { host } from '../../../config';
 
 export default Vue.extend({
diff --git a/src/mfm/html-to-mfm.ts b/src/mfm/html-to-mfm.ts
new file mode 100644
index 0000000000..540635036a
--- /dev/null
+++ b/src/mfm/html-to-mfm.ts
@@ -0,0 +1,71 @@
+const parse5 = require('parse5');
+
+export default function(html: string): string {
+	const dom = parse5.parseFragment(html);
+
+	let text = '';
+
+	dom.childNodes.forEach((n: any) => analyze(n));
+
+	return text.trim();
+
+	function getText(node: any) {
+		if (node.nodeName == '#text') return node.value;
+
+		if (node.childNodes) {
+			return node.childNodes.map((n: any) => getText(n)).join('');
+		}
+
+		return '';
+	}
+
+	function analyze(node: any) {
+		switch (node.nodeName) {
+			case '#text':
+				text += node.value;
+				break;
+
+			case 'br':
+				text += '\n';
+				break;
+
+			case 'a':
+				const txt = getText(node);
+
+				// メンション
+				if (txt.startsWith('@')) {
+					const part = txt.split('@');
+
+					if (part.length == 2) {
+						//#region ホスト名部分が省略されているので復元する
+						const href = new URL(node.attrs.find((x: any) => x.name == 'href').value);
+						const acct = txt + '@' + href.hostname;
+						text += acct;
+						break;
+						//#endregion
+					} else if (part.length == 3) {
+						text += txt;
+						break;
+					}
+				}
+
+				if (node.childNodes) {
+					node.childNodes.forEach((n: any) => analyze(n));
+				}
+				break;
+
+			case 'p':
+				text += '\n\n';
+				if (node.childNodes) {
+					node.childNodes.forEach((n: any) => analyze(n));
+				}
+				break;
+
+			default:
+				if (node.childNodes) {
+					node.childNodes.forEach((n: any) => analyze(n));
+				}
+				break;
+		}
+	}
+}
diff --git a/src/text/html.ts b/src/mfm/html.ts
similarity index 100%
rename from src/text/html.ts
rename to src/mfm/html.ts
diff --git a/src/text/parse/core/syntax-highlighter.ts b/src/mfm/parse/core/syntax-highlighter.ts
similarity index 100%
rename from src/text/parse/core/syntax-highlighter.ts
rename to src/mfm/parse/core/syntax-highlighter.ts
diff --git a/src/text/parse/elements/bold.ts b/src/mfm/parse/elements/bold.ts
similarity index 100%
rename from src/text/parse/elements/bold.ts
rename to src/mfm/parse/elements/bold.ts
diff --git a/src/text/parse/elements/code.ts b/src/mfm/parse/elements/code.ts
similarity index 100%
rename from src/text/parse/elements/code.ts
rename to src/mfm/parse/elements/code.ts
diff --git a/src/text/parse/elements/emoji.ts b/src/mfm/parse/elements/emoji.ts
similarity index 100%
rename from src/text/parse/elements/emoji.ts
rename to src/mfm/parse/elements/emoji.ts
diff --git a/src/text/parse/elements/hashtag.ts b/src/mfm/parse/elements/hashtag.ts
similarity index 100%
rename from src/text/parse/elements/hashtag.ts
rename to src/mfm/parse/elements/hashtag.ts
diff --git a/src/text/parse/elements/inline-code.ts b/src/mfm/parse/elements/inline-code.ts
similarity index 100%
rename from src/text/parse/elements/inline-code.ts
rename to src/mfm/parse/elements/inline-code.ts
diff --git a/src/text/parse/elements/link.ts b/src/mfm/parse/elements/link.ts
similarity index 100%
rename from src/text/parse/elements/link.ts
rename to src/mfm/parse/elements/link.ts
diff --git a/src/text/parse/elements/mention.ts b/src/mfm/parse/elements/mention.ts
similarity index 100%
rename from src/text/parse/elements/mention.ts
rename to src/mfm/parse/elements/mention.ts
diff --git a/src/text/parse/elements/quote.ts b/src/mfm/parse/elements/quote.ts
similarity index 100%
rename from src/text/parse/elements/quote.ts
rename to src/mfm/parse/elements/quote.ts
diff --git a/src/text/parse/elements/search.ts b/src/mfm/parse/elements/search.ts
similarity index 100%
rename from src/text/parse/elements/search.ts
rename to src/mfm/parse/elements/search.ts
diff --git a/src/text/parse/elements/title.ts b/src/mfm/parse/elements/title.ts
similarity index 100%
rename from src/text/parse/elements/title.ts
rename to src/mfm/parse/elements/title.ts
diff --git a/src/text/parse/elements/url.ts b/src/mfm/parse/elements/url.ts
similarity index 100%
rename from src/text/parse/elements/url.ts
rename to src/mfm/parse/elements/url.ts
diff --git a/src/text/parse/index.ts b/src/mfm/parse/index.ts
similarity index 100%
rename from src/text/parse/index.ts
rename to src/mfm/parse/index.ts
diff --git a/src/remote/activitypub/misc/get-note-html.ts b/src/remote/activitypub/misc/get-note-html.ts
index 0ceecdd00b..8df440930b 100644
--- a/src/remote/activitypub/misc/get-note-html.ts
+++ b/src/remote/activitypub/misc/get-note-html.ts
@@ -1,6 +1,6 @@
 import { INote } from '../../../models/note';
-import toHtml from '../../../text/html';
-import parse from '../../../text/parse';
+import toHtml from '../../../mfm/html';
+import parse from '../../../mfm/parse';
 import config from '../../../config';
 
 export default function(note: INote) {
diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts
index b0fe045e6d..85a8f89bc8 100644
--- a/src/remote/activitypub/models/note.ts
+++ b/src/remote/activitypub/models/note.ts
@@ -1,5 +1,4 @@
 import * as mongo from 'mongodb';
-const parse5 = require('parse5');
 import * as debug from 'debug';
 
 import config from '../../../config';
@@ -10,79 +9,10 @@ import { INote as INoteActivityStreamsObject, IObject } from '../type';
 import { resolvePerson, updatePerson } from './person';
 import { resolveImage } from './image';
 import { IRemoteUser, IUser } from '../../../models/user';
+import htmlToMFM from '../../../mfm/html-to-mfm';
 
 const log = debug('misskey:activitypub');
 
-function parse(html: string): string {
-	const dom = parse5.parseFragment(html);
-
-	let text = '';
-
-	dom.childNodes.forEach((n: any) => analyze(n));
-
-	return text.trim();
-
-	function getText(node: any) {
-		if (node.nodeName == '#text') return node.value;
-
-		if (node.childNodes) {
-			return node.childNodes.map((n: any) => getText(n)).join('');
-		}
-
-		return '';
-	}
-
-	function analyze(node: any) {
-		switch (node.nodeName) {
-			case '#text':
-				text += node.value;
-				break;
-
-			case 'br':
-				text += '\n';
-				break;
-
-			case 'a':
-				const txt = getText(node);
-
-				// メンション
-				if (txt.startsWith('@')) {
-					const part = txt.split('@');
-
-					if (part.length == 2) {
-						//#region ホスト名部分が省略されているので復元する
-						const href = new URL(node.attrs.find((x: any) => x.name == 'href').value);
-						const acct = txt + '@' + href.hostname;
-						text += acct;
-						break;
-						//#endregion
-					} else if (part.length == 3) {
-						text += txt;
-						break;
-					}
-				}
-
-				if (node.childNodes) {
-					node.childNodes.forEach((n: any) => analyze(n));
-				}
-				break;
-
-			case 'p':
-				text += '\n\n';
-				if (node.childNodes) {
-					node.childNodes.forEach((n: any) => analyze(n));
-				}
-				break;
-
-			default:
-				if (node.childNodes) {
-					node.childNodes.forEach((n: any) => analyze(n));
-				}
-				break;
-		}
-	}
-}
-
 /**
  * Noteをフェッチします。
  *
@@ -158,7 +88,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
 	const reply = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver) : null;
 
 	// テキストのパース
-	const text = parse(note.content);
+	const text = htmlToMFM(note.content);
 
 	// ユーザーの情報が古かったらついでに更新しておく
 	if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts
index 42ee5a27df..f233346395 100644
--- a/src/remote/activitypub/models/person.ts
+++ b/src/remote/activitypub/models/person.ts
@@ -1,5 +1,4 @@
 import * as mongo from 'mongodb';
-import { JSDOM } from 'jsdom';
 import { toUnicode } from 'punycode';
 import * as debug from 'debug';
 
@@ -11,6 +10,7 @@ import { resolveImage } from './image';
 import { isCollectionOrOrderedCollection, IObject, IPerson } from '../type';
 import { IDriveFile } from '../../../models/drive-file';
 import Meta from '../../../models/meta';
+import htmlToMFM from '../../../mfm/html-to-mfm';
 
 const log = debug('misskey:activitypub');
 
@@ -80,7 +80,6 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
 	]);
 
 	const host = toUnicode(finger.subject.replace(/^.*?@/, '')).toLowerCase();
-	const summaryDOM = JSDOM.fragment(person.summary);
 
 	// Create user
 	let user: IRemoteUser;
@@ -89,7 +88,7 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
 			avatarId: null,
 			bannerId: null,
 			createdAt: Date.parse(person.published) || null,
-			description: summaryDOM.textContent,
+			description: htmlToMFM(person.summary),
 			followersCount,
 			followingCount,
 			notesCount,
@@ -211,8 +210,6 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver)
 		)
 	]);
 
-	const summaryDOM = JSDOM.fragment(person.summary);
-
 	// アイコンとヘッダー画像をフェッチ
 	const [avatar, banner] = (await Promise.all<IDriveFile>([
 		person.icon,
@@ -231,7 +228,7 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver)
 			bannerId: banner ? banner._id : null,
 			avatarUrl: avatar && avatar.metadata.isMetaOnly ? avatar.metadata.url : null,
 			bannerUrl: banner && banner.metadata.isMetaOnly ? banner.metadata.url : null,
-			description: summaryDOM.textContent,
+			description: htmlToMFM(person.summary),
 			followersCount,
 			followingCount,
 			notesCount,
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index ef03c4e044..782ac5a4fe 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -14,7 +14,7 @@ import watch from './watch';
 import Mute from '../../models/mute';
 import pushSw from '../../publishers/push-sw';
 import event from '../../publishers/stream';
-import parse from '../../text/parse';
+import parse from '../../mfm/parse';
 import { IApp } from '../../models/app';
 import UserList from '../../models/user-list';
 import resolveUser from '../../remote/resolve-user';
diff --git a/test/text.ts b/test/mfm.ts
similarity index 96%
rename from test/text.ts
rename to test/mfm.ts
index a64999fc0b..de722bffb3 100644
--- a/test/text.ts
+++ b/test/mfm.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert';
 
-import analyze from '../src/text/parse';
-import syntaxhighlighter from '../src/text/parse/core/syntax-highlighter';
+import analyze from '../src/mfm/parse';
+import syntaxhighlighter from '../src/mfm/parse/core/syntax-highlighter';
 
 describe('Text', () => {
 	it('can be analyzed', () => {