Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/common/Def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export type DaemonTrigger = typeof DaemonTrigger.Type
export type ContextTrigger = typeof ContextTrigger.Type
export type TimerTrigger = typeof TimerTrigger.Type

export const EchoSchedulerId = 'echoDaemon'; // TODO 他のMCPと重ならないユニーク名

/*
AsMessage ====================================
Expand Down Expand Up @@ -309,13 +310,15 @@ const McpConfig = Schema.Struct({
buildIn: Schema.mutable(Schema.Boolean),
});

const McpConfigMutable = Schema.mutable(McpConfig);
const McpConfigList = Schema.mutable(Schema.Array(McpConfig));

export type McpInfo = typeof McpInfo.Type
export type McpToolInfo = typeof McpToolInfo.Type
export type McpPromptInfo = typeof McpPromptInfo.Type
export type McpResourceInfo = typeof McpResourceInfo.Type
export type McpConfig = typeof McpConfig.Type
export type McpConfigMutable = typeof McpConfigMutable.Type
export type McpConfigList = typeof McpConfigList.Type

export type McpServerDef = typeof McpServerDef.Type
Expand Down Expand Up @@ -421,7 +424,7 @@ export const AvatarMcpSetting = Schema.Struct({
export type AvatarMcpSetting = typeof AvatarMcpSetting.Type

export const AvatarMcpSettingMutable = Schema.mutable(AvatarMcpSetting);
// export type AvatarMcpSettingMutable = typeof AvatarMcpSettingMutable.Type
export type AvatarMcpSettingMutable = typeof AvatarMcpSettingMutable.Type

export const AvatarMcpSettingList = Schema.Record({
key: Schema.String,
Expand Down
3 changes: 1 addition & 2 deletions packages/main/src/BuildInMcpService.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/*! avatar-shell | Apache-2.0 License | https://github.com/mfukushim/avatar-shell */
import {Effect} from 'effect';
import {McpConfig, McpToolInfo} from '../../common/Def.js';
import {EchoSchedulerId, McpConfig, McpToolInfo} from '../../common/Def.js';
import {app} from 'electron';

export const EchoSchedulerId = 'echoDaemon'; // TODO 他のMCPと重ならないユニーク名

export const setTaskWhenIdling = {
def: {
Expand Down
28 changes: 20 additions & 8 deletions packages/main/src/McpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
import {StreamableHTTPClientTransport} from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import {
AlertTask, AvatarMcpSetting,
AvatarMcpSettingList,
AvatarMcpSettingList, AvatarMcpSettingMutable,
AvatarSetting,
DaemonTrigger, LabelError,
McpConfigList,
AvatarSettingMutable,
DaemonTrigger, LabelError, McpConfig,
McpConfigList, McpConfigMutable,
type McpEnable,
McpInfo, McpStdioServerDef, McpStreamHttpServerDef,
SysConfig, ToolCallParam,
Expand Down Expand Up @@ -49,7 +50,7 @@ export class McpService extends Effect.Service<McpService>()('avatar-shell/McpSe
def: value[1].def
}
})
serverInfoList = yield *Effect.validateAll(servers, a1 => {
const res = yield* Effect.forEach(servers, a1 => {
return Effect.gen(function* () {
const client = new Client(
{
Expand All @@ -71,10 +72,10 @@ export class McpService extends Effect.Service<McpService>()('avatar-shell/McpSe
(x) => Either.isRight(Schema.decodeUnknownEither(McpStreamHttpServerDef)(x)),
(y) => Effect.succeed(new StreamableHTTPClientTransport(new URL((y as McpStreamHttpServerDef).url)))
),
Match.orElse(() => Effect.fail(new Error('MCP define error')))
Match.orElse(() => Effect.fail(new Error(`${a1.name} define error`)))
).pipe(Effect.flatMap((transport) =>Effect.tryPromise({
try: () => client.connect(transport),
catch: error => new LabelError(`${a1.name} MCP server Connect Error`,`${error}`,'Please,check MCP System Config.'),
catch: error => new Error(`${a1.name} Connect Error`),
})))
const capabilities = client.getServerCapabilities();
const tools = (capabilities?.tools ? yield* Effect.tryPromise(() =>client.listTools()) : {tools: []});
Expand All @@ -88,10 +89,18 @@ export class McpService extends Effect.Service<McpService>()('avatar-shell/McpSe
resources: resources.resources,
buildIn: false,
};
});
}).pipe(Effect.catchAll(e => Effect.succeed(e)))
})
const isError = (v: unknown): v is Error => v instanceof Error
const errors = res.filter((r):r is Error => r instanceof Error)
serverInfoList = res.filter((r): r is Exclude<typeof r, Error> => !isError(r))

const buildInList = yield* BuildInMcpService.getDefines();
serverInfoList.push(...buildInList);
const errorMes = errors.map(e => e.message).join(',');
if (errorMes) {
return yield *Effect.fail(new LabelError(`${errorMes}\nMCP server Error`,'Please,check MCP System Config.'))
}
});
}

Expand Down Expand Up @@ -147,6 +156,7 @@ export class McpService extends Effect.Service<McpService>()('avatar-shell/McpSe
}

function updateAvatarMcpSetting(configMcp: AvatarSetting) {
Object.keys(configMcp.mcp).forEach(k =>(configMcp.mcp[k] as unknown as AvatarMcpSettingMutable).serverEnable = undefined)
const mcpServers = getMcpServerInfos();
const mcps: Record<string, AvatarMcpSetting> = {};
mcpServers.forEach(value => {
Expand All @@ -167,9 +177,11 @@ export class McpService extends Effect.Service<McpService>()('avatar-shell/McpSe
},
};
});
const deepMerge1: Record<string, AvatarMcpSetting> = deepMerge(mcps, configMcp.mcp) as Record<string, AvatarMcpSetting>;
Object.keys(deepMerge1).forEach(k =>(deepMerge1[k] as unknown as AvatarMcpSettingMutable).serverEnable = mcps[k]?.serverEnable)
return Effect.succeed({
...configMcp,
mcp: deepMerge(mcps, configMcp.mcp) as Record<string, AvatarMcpSetting>,
mcp: deepMerge1,
} as AvatarSetting);
}

Expand Down
1 change: 0 additions & 1 deletion packages/main/src/generators/EmptyGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {MediaService} from '../MediaService.js';
import {ContextGenerator} from './ContextGenerator.js';
import short from 'short-uuid';
import {SysConfig} from '../../../common/Def.js';
import {sysConfig} from '@app/preload';


/**
Expand Down
11 changes: 8 additions & 3 deletions packages/preload/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface McpResource {
}

// region --- Variables ---
export let sysConfig: any = {};
let sysConfig: SysConfig | undefined = undefined;
export let avatarId = '';
export let userName: string | undefined;
export let avatarName: string | undefined;
Expand Down Expand Up @@ -233,10 +233,13 @@ export async function setSysConfig(conf: SysConfig) {
*/
export async function getSysConfig(): Promise<SysConfig> {
try {
if (sysConfig && sysConfig?.version) {
if (sysConfig) { // && sysConfig?.version
return sysConfig;
}
sysConfig = await ipcRenderer.invoke('getSysConfig') as any | undefined;
if (!sysConfig) {
sysConfig = defaultSysSetting;
}
} catch (_) {
sysConfig = defaultSysSetting;
}
Expand All @@ -263,6 +266,7 @@ export async function updateMutableSetting(conf: MutableSysConfig) {
* 変更可能なシステム設定を取得する
* @returns 設定項目
*/
/*
export async function getMutableSetting() {
try {
sysConfig = await ipcRenderer.invoke('getMutableSetting') as MutableSysConfig | undefined;
Expand All @@ -271,6 +275,7 @@ export async function getMutableSetting() {
}
return sysConfig;
}
*/

/**
* 現在のユーザー名を取得する
Expand Down Expand Up @@ -343,7 +348,7 @@ export async function readMcpResource(name: string, url: string) {
function makeSocket() {
if (avatarSetting?.general.remoteServer) {
socket = io(avatarSetting?.general.remoteServer);
} else if (sysConfig.websocket.useServer) {
} else if (sysConfig?.websocket.useServer) {
socket = io(`http://127.0.0.1:${sysConfig.websocket.serverPort || 3010}`);
}
}
Expand Down
37 changes: 29 additions & 8 deletions packages/renderer/src/components/AvatarSettingPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ import {
AvatarSettingMutable,
type SchedulerListMutable,
DaemonTriggerList,
type McpInfo,
type McpInfo, type SysConfig, EchoSchedulerId,
} from '../../../common/Def.ts';
import {Either, ParseResult, Schema} from 'effect';
import short from 'short-uuid';
import {getAvatarConfigMcpUpdate, setAvatarConfig, getGeneratorList, getMcpServerInfos} from '@app/preload';
import {
getAvatarConfigMcpUpdate,
setAvatarConfig,
getGeneratorList,
getMcpServerInfos,
getSysConfig,
} from '@app/preload';
import {AsClassList, AsContextLinesList, AsRoleList} from '../../../common/DefGenerators.ts';
import {useI18n} from 'vue-i18n';

Expand All @@ -34,6 +40,7 @@ const errorMes = ref('');
const mcpServers = ref<McpInfo[]>([])
const saving = ref(false);
const mcpEnableList = ref<{label:string,value:string}[]>([])
const sysConfig = ref<SysConfig>()

// --- Functions ---

Expand All @@ -58,6 +65,7 @@ const doOpen = async (templateId: string) => {
mcpServers.value = await getMcpServerInfos()
// TODO ちょっとreadonlyをごまかしているがどうするか
mcpEnableList.value = McpEnableList.map(value => ({value,label:t(`mcpPermissionSet.${value}`)}))
sysConfig.value = await getSysConfig()

show.value = true;
};
Expand All @@ -68,22 +76,35 @@ const doOpen = async (templateId: string) => {
* @param name ツール名
* @returns ツール情報、または空のオブジェクト/文字列
*/
const getMcpInfo = (id:string,name?:string):any => {
const getMcpToolInfo = (id:string,name?:string):any => {
const mcp = mcpServers.value.find(value => value.id === id)
if(mcp){
return mcp.tools.find(v => v.name === name) || ''
}
return {}
}

const isSysEnableMcp = (id:string) => {
// const mcp = mcpServers.value.find(value => value.id === id)
// if(!mcp) return true
if (id === EchoSchedulerId) {
return true;
}
if(sysConfig.value) {
if(sysConfig.value.mcpServers[id]?.enable) return true;
}
return false
}


/**
* MCPツールのラベルを取得する(表示用)
* @param id MCPサーバーID
* @param name ツール名
* @returns ラベル文字列
*/
const getMcpLabel = (id:string,name?:string) => {
const mcp = getMcpInfo(id,name)
const mcp = getMcpToolInfo(id,name)
if(mcp){
return mcp.title ? `${mcp.title} (${name})` : name
}
Expand Down Expand Up @@ -276,7 +297,7 @@ onMounted(async () => {
no-caps
class="bg-indigo-8 text-white shadow-2"
>
<q-tab v-for="(mcp) in Object.entries(editingSettings!!.mcp!!)" :key="mcp[0]" :label="mcp[0]" :name="mcp[0]" :alert="!mcp[1].serverEnable || mcp[1].enable" :alert-icon="!mcp[1].serverEnable ? 'block' : 'flash_on'">
<q-tab v-for="(mcp) in Object.entries(editingSettings!!.mcp!!)" :key="mcp[0]" :label="mcp[0]" :name="mcp[0]" :alert="!mcp[1].serverEnable || mcp[1].enable || !isSysEnableMcp(mcp[0])" :alert-icon="!isSysEnableMcp(mcp[0]) ? 'delete_outline' :!mcp[1].serverEnable ? 'block':'flash_on'">
</q-tab>
</q-tabs>
<q-separator />
Expand All @@ -291,8 +312,8 @@ onMounted(async () => {
<q-card-section>
<div class="text-subtitle2">{{ mcp[0] }}</div>
<div class="row">
<q-toggle v-model="mcp[1].enable" :label="t('enableHide')" :disable="!mcp[1].serverEnable" :data-testid="`mcp-${mcp[0]}-enable`" />
<q-space/><q-btn icon="delete" @click="deleteMcpDef(mcp[0])" :disable="mcp[1].serverEnable" data-testid="mcp-delete-btn">{{ t('delete') }}</q-btn>
<q-toggle v-model="mcp[1].enable" :label="t('enableHide')" :disable="!mcp[1].serverEnable || !isSysEnableMcp(mcp[0])" :data-testid="`mcp-${mcp[0]}-enable`" />
<q-space/><q-btn icon="delete" @click="deleteMcpDef(mcp[0])" :disable="isSysEnableMcp(mcp[0])" data-testid="mcp-delete-btn">{{ t('delete') }}</q-btn>
</div>
<div v-if="mcp[1].notice" class="text-red">{{ mcp[1].notice }}</div>
</q-card-section>
Expand All @@ -305,7 +326,7 @@ onMounted(async () => {
<q-toggle class="col-6" v-model="tool[1].enable" :label="t('enableHide')" :disable="!mcp[1].enable" :data-testid="`mcp-${mcp[0]}-tool-${tool[0]}-enable`" />
<q-select class="col-6" v-model="tool[1].allow" :options="mcpEnableList" :disable="!mcp[1].enable" label="Permission" emit-value map-options :data-testid="`mcp-${mcp[0]}-tool-${tool[0]}-allow`"/>
<div class="text-caption q-ma-sm">
{{getMcpInfo(mcp[0],tool[0])?.description}}
{{getMcpToolInfo(mcp[0],tool[0])?.description}}
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/renderer/src/components/SysSettingPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ onBeforeUnmount(() => {
no-caps
class="bg-indigo-8 text-white shadow-2"
>
<q-tab v-for="(server) in mcpConfig" :key="server.id" :label="server.id" :name="server.id" :alert="!server.enable" alert-icon="block">
<q-tab v-for="(server) in mcpConfig" :key="server.id" :label="server.id" :name="server.id" :alert="!server.enable" alert-icon="horizontal_rule">
</q-tab>
</q-tabs>
<q-separator />
Expand Down
Loading