diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index c3933a417e..a073d789a2 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -533,6 +533,8 @@ generateAccessToken: "アクセストークンの発行"
 permission: "権限"
 enableAll: "全て有効にする"
 disableAll: "全て無効にする"
+tokenRequested: "アカウントへのアクセス許可"
+pluginTokenRequestedDescription: "このプラグインはここで設定した権限を行使できるようになります。"
 
 _theme:
   explore: "テーマを探す"
diff --git a/src/client/components/token-generate-window.vue b/src/client/components/token-generate-window.vue
index 5486ae92e7..e58dcbda78 100644
--- a/src/client/components/token-generate-window.vue
+++ b/src/client/components/token-generate-window.vue
@@ -2,6 +2,9 @@
 <x-window ref="window" :width="400" :height="450" :no-padding="true" @closed="() => { $emit('closed'); destroyDom(); }" :with-ok-button="true" :ok-button-disabled="false" @ok="ok()" :can-close="false">
 	<template #header>{{ title || $t('generateAccessToken') }}</template>
 	<div class="ugkkpisj">
+		<div>
+			<mk-info warn v-if="information">{{ information }}</mk-info>
+		</div>
 		<div>
 			<mk-input v-model="name">{{ $t('name') }}</mk-input>
 		</div>
@@ -9,7 +12,7 @@
 			<div style="margin-bottom: 16px;"><b>{{ $t('permission') }}</b></div>
 			<mk-button inline @click="disableAll">{{ $t('disableAll') }}</mk-button>
 			<mk-button inline @click="enableAll">{{ $t('enableAll') }}</mk-button>
-			<mk-switch v-for="kind in kinds" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</mk-switch>
+			<mk-switch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</mk-switch>
 		</div>
 	</div>
 </x-window>
@@ -23,6 +26,7 @@ import MkInput from './ui/input.vue';
 import MkTextarea from './ui/textarea.vue';
 import MkSwitch from './ui/switch.vue';
 import MkButton from './ui/button.vue';
+import MkInfo from './ui/info.vue';
 
 export default Vue.extend({
 	components: {
@@ -31,6 +35,7 @@ export default Vue.extend({
 		MkTextarea,
 		MkSwitch,
 		MkButton,
+		MkInfo,
 	},
 
 	props: {
@@ -38,20 +43,41 @@ export default Vue.extend({
 			type: String,
 			required: false,
 			default: null
+		},
+		information: {
+			type: String,
+			required: false,
+			default: null
+		},
+		initialName: {
+			type: String,
+			required: false,
+			default: null
+		},
+		initialPermissions: {
+			type: Array,
+			required: false,
+			default: null
 		}
 	},
 
 	data() {
 		return {
-			name: null,
+			name: this.initialName,
 			permissions: {},
 			kinds
 		};
 	},
 
 	created() {
-		for (const kind of this.kinds) {
-			Vue.set(this.permissions, kind, false);
+		if (this.initialPermissions) {
+			for (const kind of this.initialPermissions) {
+				Vue.set(this.permissions, kind, true);
+			}
+		} else {
+			for (const kind of this.kinds) {
+				Vue.set(this.permissions, kind, false);
+			}
 		}
 	},
 
diff --git a/src/client/pages/preferences/plugins.vue b/src/client/pages/preferences/plugins.vue
index afe7c8cafa..ee0ac3652c 100644
--- a/src/client/pages/preferences/plugins.vue
+++ b/src/client/pages/preferences/plugins.vue
@@ -30,7 +30,10 @@
 					<div>{{ $t('description') }}:</div>
 					<div>{{ selectedPlugin.description }}</div>
 				</div>
-				<mk-button @click="uninstall()" style="margin-top: 8px;"><fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</mk-button>
+				<div style="margin-top: 8px;">
+					<mk-button @click="config()" inline v-if="selectedPlugin.config"><fa :icon="faCog"/> {{ $t('settings') }}</mk-button>
+					<mk-button @click="uninstall()" inline><fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</mk-button>
+				</div>
 			</template>
 		</details>
 	</div>
@@ -39,7 +42,7 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import { faPlug, faSave, faTrashAlt, faFolderOpen, faDownload } from '@fortawesome/free-solid-svg-icons';
+import { faPlug, faSave, faTrashAlt, faFolderOpen, faDownload, faCog } from '@fortawesome/free-solid-svg-icons';
 import MkButton from '../../components/ui/button.vue';
 import MkTextarea from '../../components/ui/textarea.vue';
 import MkSelect from '../../components/ui/select.vue';
@@ -58,7 +61,7 @@ export default Vue.extend({
 		return {
 			script: '',
 			selectedPluginId: null,
-			faPlug, faSave, faTrashAlt, faFolderOpen, faDownload
+			faPlug, faSave, faTrashAlt, faFolderOpen, faDownload, faCog
 		}
 	},
 
@@ -70,7 +73,7 @@ export default Vue.extend({
 	},
 
 	methods: {
-		install() {
+		async install() {
 			let ast;
 			try {
 				ast = parse(this.script);
@@ -82,7 +85,6 @@ export default Vue.extend({
 				return;
 			}
 			const meta = AiScript.collectMetadata(ast);
-			console.log(meta);
 			if (meta == null) {
 				this.$root.dialog({
 					type: 'error',
@@ -98,7 +100,7 @@ export default Vue.extend({
 				});
 				return;
 			}
-			const { id, name, version, author, description } = data;
+			const { id, name, version, author, description, permissions, config } = data;
 			if (id == null || name == null || version == null || author == null) {
 				this.$root.dialog({
 					type: 'error',
@@ -106,16 +108,40 @@ export default Vue.extend({
 				});
 				return;
 			}
+
+			const token = permissions == null || permissions.length === 0 ? null : await new Promise(async (res, rej) => {
+				this.$root.new(await import('../../components/token-generate-window.vue').then(m => m.default), {
+					title: this.$t('tokenRequested'),
+					information: this.$t('pluginTokenRequestedDescription'),
+					initialName: name,
+					initialPermissions: permissions
+				}).$on('ok', async ({ name, permissions }) => {
+					const { token } = await this.$root.api('miauth/gen-token', {
+						session: null,
+						name: name,
+						permission: permissions,
+					});
+
+					res(token);
+				});
+			});
+
 			this.$store.commit('deviceUser/installPlugin', {
 				meta: {
-					id, name, version, author, description
+					id, name, version, author, description, permissions, config
 				},
-				ast
+				token,
+				ast // TODO: astにはMapが含まれることがあり、MapはJSONとしてシリアライズできないのでバグる。どうにかする
 			});
+
 			this.$root.dialog({
 				type: 'success',
 				iconOnly: true, autoClose: true
 			});
+
+			this.$nextTick(() => {
+				location.reload();
+			});
 		},
 
 		uninstall() {
@@ -124,6 +150,29 @@ export default Vue.extend({
 				type: 'success',
 				iconOnly: true, autoClose: true
 			});
+			this.$nextTick(() => {
+				location.reload();
+			});
+		},
+
+		// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする
+		async config() {
+			const config = this.selectedPlugin.config;
+			for (const key in this.selectedPlugin.configData) {
+				config[key].default = this.selectedPlugin.configData[key];
+			}
+
+			const { canceled, result } = await this.$root.form(this.selectedPlugin.name, config);
+			if (canceled) return;
+
+			this.$store.commit('deviceUser/configPlugin', {
+				id: this.selectedPluginId,
+				config: result
+			});
+
+			this.$nextTick(() => {
+				location.reload();
+			});
 		}
 	},
 });
diff --git a/src/client/scripts/aiscript/api.ts b/src/client/scripts/aiscript/api.ts
index 29baa25b1a..0db02c2a21 100644
--- a/src/client/scripts/aiscript/api.ts
+++ b/src/client/scripts/aiscript/api.ts
@@ -1,4 +1,5 @@
 import { utils, values } from '@syuilo/aiscript';
+import { jsToVal } from '@syuilo/aiscript/built/interpreter/util';
 
 export function createAiScriptEnv(vm, opts) {
 	let apiRequests = 0;
@@ -26,7 +27,7 @@ export function createAiScriptEnv(vm, opts) {
 			if (token) utils.assertString(token);
 			apiRequests++;
 			if (apiRequests > 16) return values.NULL;
-			const res = await vm.$root.api(ep.value, utils.valToJs(param), token ? token.value : null);
+			const res = await vm.$root.api(ep.value, utils.valToJs(param), token ? token.value : (opts.token || null));
 			return utils.jsToVal(res);
 		}),
 		'Mk:save': values.FN_NATIVE(([key, value]) => {
@@ -42,8 +43,14 @@ export function createAiScriptEnv(vm, opts) {
 }
 
 export function createPluginEnv(vm, opts) {
+	const config = new Map();
+	for (const key in opts.plugin.config) {
+		const val = opts.plugin.configData[key] || opts.plugin.config[key].default;
+		config.set(key, jsToVal(val));
+	}
+
 	return {
-		...createAiScriptEnv(vm, opts),
+		...createAiScriptEnv(vm, { ...opts, token: opts.plugin.token }),
 		'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => {
 			vm.$store.commit('registerPostFormAction', { pluginId: opts.plugin.id, title: title.value, handler });
 		}),
@@ -53,5 +60,6 @@ export function createPluginEnv(vm, opts) {
 		'Mk:register_note_action': values.FN_NATIVE(([title, handler]) => {
 			vm.$store.commit('registerNoteAction', { pluginId: opts.plugin.id, title: title.value, handler });
 		}),
+		'Plugin:config': values.OBJ(config),
 	};
 }
diff --git a/src/client/store.ts b/src/client/store.ts
index c7b8e84e9d..2cd2c8cf3c 100644
--- a/src/client/store.ts
+++ b/src/client/store.ts
@@ -587,13 +587,11 @@ export default () => new Vuex.Store({
 				},
 				//#endregion
 
-				installPlugin(state, { meta, ast }) {
+				installPlugin(state, { meta, ast, token }) {
 					state.plugins.push({
-						id: meta.id,
-						name: meta.name,
-						version: meta.version,
-						author: meta.author,
-						description: meta.description,
+						...meta,
+						configData: {},
+						token: token,
 						ast: ast
 					});
 				},
@@ -601,6 +599,10 @@ export default () => new Vuex.Store({
 				uninstallPlugin(state, id) {
 					state.plugins = state.plugins.filter(x => x.id != id);
 				},
+
+				configPlugin(state, { id, config }) {
+					state.plugins.find(p => p.id === id).configData = config;
+				},
 			}
 		},