完成 Resources 的支持
This commit is contained in:
parent
4947a0d2c2
commit
d4e6775a47
@ -69,6 +69,8 @@ class MessageBridge {
|
||||
|
||||
this.postMessage = (message) => {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
console.log(message);
|
||||
|
||||
this.ws.send(JSON.stringify(message));
|
||||
}
|
||||
};
|
||||
@ -118,10 +120,6 @@ const messageBridge = new MessageBridge();
|
||||
export function useMessageBridge() {
|
||||
const bridge = messageBridge;
|
||||
|
||||
onUnmounted(() => {
|
||||
bridge.destroy();
|
||||
});
|
||||
|
||||
return {
|
||||
postMessage: bridge.postMessage.bind(bridge),
|
||||
addCommandListener: bridge.addCommandListener.bind(bridge),
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { reactive } from 'vue';
|
||||
|
||||
|
||||
import Resource from './resource/index.vue';
|
||||
import Chat from './chat/index.vue';
|
||||
import Resource from './chat/index.vue';
|
||||
import Prompt from './prompt/index.vue';
|
||||
import Tool from './tool/index.vue';
|
||||
|
||||
|
@ -1,16 +1,23 @@
|
||||
<template>
|
||||
<div class="resource-module">
|
||||
<h2>资源模块</h2>
|
||||
<div class="left">
|
||||
<h2>
|
||||
<span class="iconfont icon-file"></span>
|
||||
资源模块
|
||||
</h2>
|
||||
<h3><code>resources/templates/list</code></h3>
|
||||
|
||||
<ResourceTemplates></ResourceTemplates>
|
||||
</div>
|
||||
<div class="right">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import ResourceTemplates from './resource-templates.vue';
|
||||
|
||||
defineComponent({ name: 'resource' });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -18,4 +25,14 @@ defineComponent({ name: 'resource' });
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.resource-module .left {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.resource-module .right {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="resource-template-container-scrollbar">
|
||||
<el-scrollbar height="500px">
|
||||
<div class="resource-template-container">
|
||||
<div
|
||||
class="item"
|
||||
v-for="template of resourcesManager.templates"
|
||||
:key="template.name"
|
||||
>
|
||||
<span>{{ template.name }}</span>
|
||||
<span>{{ template.description || '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { CasualRestAPI, ResourceTemplatesListResponse } from '@/hook/type';
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { resourcesManager } from './resources';
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
let cancelListener: undefined | (() => void) = undefined;
|
||||
|
||||
function reloadResources() {
|
||||
bridge.postMessage({
|
||||
command: 'resources/templates/list'
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
cancelListener = bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
|
||||
resourcesManager.templates = data.msg.resourceTemplates;
|
||||
});
|
||||
reloadResources();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (cancelListener) {
|
||||
cancelListener();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.resource-template-container-scrollbar {
|
||||
background-color: var(--background);
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
.resource-template-container {
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.resource-template-container > .item {
|
||||
margin: 3px;
|
||||
padding: 5px 10px;
|
||||
border-radius: .3em;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
transition: var(--animation-3s);
|
||||
}
|
||||
|
||||
.resource-template-container > .item:hover {
|
||||
background-color: var(--main-light-color);
|
||||
transition: var(--animation-3s);
|
||||
}
|
||||
|
||||
.resource-template-container > .item > span:first-child {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.resource-template-container > .item > span:last-child {
|
||||
opacity: 0.6;
|
||||
font-size: 12.5px;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
</style>
|
11
app/src/components/main-panel/resource/resources.ts
Normal file
11
app/src/components/main-panel/resource/resources.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
|
||||
export const resourcesManager = reactive<{
|
||||
current: ResourceTemplate | undefined
|
||||
templates: ResourceTemplate[]
|
||||
}>({
|
||||
current: undefined,
|
||||
templates: []
|
||||
});
|
@ -30,8 +30,6 @@ function isActive(name: string) {
|
||||
}
|
||||
|
||||
function gotoOption(ident: string) {
|
||||
console.log(router);
|
||||
|
||||
router.push('/' + ident);
|
||||
}
|
||||
|
||||
|
110
app/src/hook/type.ts
Normal file
110
app/src/hook/type.ts
Normal file
@ -0,0 +1,110 @@
|
||||
// ==================== 基础类型定义 ====================
|
||||
export interface SchemaProperty {
|
||||
title: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface InputSchema {
|
||||
type: string;
|
||||
properties: Record<string, SchemaProperty>;
|
||||
required?: string[];
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface Argument {
|
||||
name: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export interface Content {
|
||||
uri: string;
|
||||
mimeType: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface MessageContent {
|
||||
type: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface CasualRestAPI<T> {
|
||||
code: number
|
||||
msg: T
|
||||
}
|
||||
|
||||
// ==================== 响应接口定义 ====================
|
||||
export interface ToolsListResponse {
|
||||
tools: Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: InputSchema;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface PromptsListResponse {
|
||||
prompts: Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
arguments: Argument[];
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ResourceTemplate {
|
||||
uriTemplate: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface ResourceTemplatesListResponse {
|
||||
resourceTemplates: ResourceTemplate[]
|
||||
}
|
||||
|
||||
export interface ResourcesListResponse {
|
||||
resources: any[]; // 根据示例返回空数组,可进一步定义具体类型
|
||||
}
|
||||
|
||||
export interface ResourcesReadResponse {
|
||||
contents: Content[];
|
||||
}
|
||||
|
||||
export interface PromptsGetResponse {
|
||||
messages: Array<{
|
||||
role: string;
|
||||
content: MessageContent;
|
||||
}>;
|
||||
}
|
||||
|
||||
// ==================== 请求接口定义 ====================
|
||||
export interface BaseRequest {
|
||||
method: string;
|
||||
params: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ResourcesReadRequest extends BaseRequest {
|
||||
method: 'resources/read';
|
||||
params: {
|
||||
uri: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PromptsGetRequest extends BaseRequest {
|
||||
method: 'prompts/get';
|
||||
params: {
|
||||
name: string;
|
||||
arguments: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== 合并类型定义 ====================
|
||||
export type APIResponse =
|
||||
| ToolsListResponse
|
||||
| PromptsListResponse
|
||||
| ResourceTemplatesListResponse
|
||||
| ResourcesListResponse
|
||||
| ResourcesReadResponse
|
||||
| PromptsGetResponse;
|
||||
|
||||
export type APIRequest =
|
||||
| BaseRequest
|
||||
| ResourcesReadRequest
|
||||
| PromptsGetRequest;
|
@ -36,7 +36,7 @@ const { t } = useI18n();
|
||||
user-select: text;
|
||||
cursor: text;
|
||||
font-size: 15px;
|
||||
line-height: 1.3;
|
||||
line-height: 1.5;
|
||||
background-color: var(--sidebar);
|
||||
}
|
||||
</style>
|
@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<div style="height: 100%;">
|
||||
<Welcome v-if="!tabs.activeTab.component"></Welcome>
|
||||
<component v-else :is="tabs.activeTab.component" />
|
||||
<Welcome v-show="!tabs.activeTab.component"></Welcome>
|
||||
|
||||
<!-- 如果存在激活标签页,则根据标签页进行渲染 -->
|
||||
<div v-show="tabs.activeTab.component">
|
||||
<component
|
||||
v-for="(tab, index) of tabs.content"
|
||||
v-show="tab === tabs.activeTab"
|
||||
:key="index"
|
||||
:is="tab.component"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -11,7 +20,7 @@ import { defineComponent } from 'vue';
|
||||
import Welcome from './welcome.vue';
|
||||
import { tabs } from '@/components/main-panel/panel';
|
||||
|
||||
defineComponent({ name: 'TEMPLATE_NAME' });
|
||||
defineComponent({ name: 'debug' });
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -25,3 +25,90 @@ def get_greeting(name: str) -> str:
|
||||
)
|
||||
def translate(message: str) -> str:
|
||||
return f'请将下面的话语翻译成中文:\n\n{message}'
|
||||
|
||||
@mcp.tool(
|
||||
name='multiply',
|
||||
description='对两个数字进行实数域的乘法运算'
|
||||
)
|
||||
def multiply(a: float, b: float) -> float:
|
||||
"""返回 a 和 b 的乘积"""
|
||||
return a * b
|
||||
|
||||
@mcp.tool(
|
||||
name='is_even',
|
||||
description='判断一个整数是否为偶数'
|
||||
)
|
||||
def is_even(number: int) -> bool:
|
||||
"""返回 True 如果数字是偶数,否则 False"""
|
||||
return number % 2 == 0
|
||||
|
||||
@mcp.tool(
|
||||
name='capitalize',
|
||||
description='将字符串首字母大写'
|
||||
)
|
||||
def capitalize(text: str) -> str:
|
||||
"""返回首字母大写的字符串"""
|
||||
return text.capitalize()
|
||||
|
||||
@mcp.resource(
|
||||
uri="weather://{city}",
|
||||
name='weather',
|
||||
description='获取指定城市的天气信息'
|
||||
)
|
||||
def get_weather(city: str) -> str:
|
||||
"""模拟天气查询协议,返回格式化字符串"""
|
||||
return f"Weather in {city}: Sunny, 25°C"
|
||||
|
||||
@mcp.resource(
|
||||
uri="user://{user_id}",
|
||||
name='user_profile',
|
||||
description='获取用户基本信息'
|
||||
)
|
||||
def get_user_profile(user_id: str) -> dict:
|
||||
"""模拟用户协议,返回字典数据"""
|
||||
return {
|
||||
"id": user_id,
|
||||
"name": "张三",
|
||||
"role": "developer"
|
||||
}
|
||||
|
||||
@mcp.resource(
|
||||
uri="book://{isbn}",
|
||||
name='book_info',
|
||||
description='通过ISBN查询书籍信息'
|
||||
)
|
||||
def get_book_info(isbn: str) -> dict:
|
||||
"""模拟书籍协议,返回结构化数据"""
|
||||
return {
|
||||
"isbn": isbn,
|
||||
"title": "Python编程:从入门到实践",
|
||||
"author": "Eric Matthes"
|
||||
}
|
||||
|
||||
@mcp.prompt(
|
||||
name='summarize',
|
||||
description='生成文本摘要的提示词模板'
|
||||
)
|
||||
def summarize(text: str) -> str:
|
||||
"""返回摘要生成提示词"""
|
||||
return f"请用一句话总结以下内容:\n\n{text}"
|
||||
|
||||
@mcp.prompt(
|
||||
name='code_explanation',
|
||||
description='解释代码功能的提示词模板'
|
||||
)
|
||||
def explain_code(code: str) -> str:
|
||||
"""返回代码解释提示词"""
|
||||
return f"请解释以下代码的功能:\n```python\n{code}\n```"
|
||||
|
||||
@mcp.prompt(
|
||||
name='email_generator',
|
||||
description='生成正式邮件的提示词模板'
|
||||
)
|
||||
def generate_email(context: str) -> str:
|
||||
"""返回邮件生成提示词"""
|
||||
return (
|
||||
"根据以下需求撰写一封正式邮件:\n"
|
||||
f"需求描述:{context}\n"
|
||||
"要求:使用礼貌用语,长度不超过200字"
|
||||
)
|
@ -91,6 +91,11 @@ export class MCPClient {
|
||||
return await this.client.listResources();
|
||||
}
|
||||
|
||||
// 列出所有模板资源
|
||||
public async listResourceTemplates() {
|
||||
return await this.client.listResourceTemplates();
|
||||
}
|
||||
|
||||
// 读取资源
|
||||
public async readResource(uri: string) {
|
||||
return await this.client.readResource({
|
||||
|
@ -115,6 +115,40 @@ export async function listResources(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 列出所有resources
|
||||
*/
|
||||
export async function listResourceTemplates(
|
||||
client: MCPClient | undefined,
|
||||
webview: VSCodeWebViewLike
|
||||
) {
|
||||
if (!client) {
|
||||
const connectResult = {
|
||||
code: 501,
|
||||
msg: 'mcp client 尚未连接'
|
||||
};
|
||||
webview.postMessage({ command: 'resources/templates/list', data: connectResult });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resources = await client.listResourceTemplates();
|
||||
const result = {
|
||||
code: 200,
|
||||
msg: resources
|
||||
};
|
||||
webview.postMessage({ command: 'resources/templates/list', data: result });
|
||||
} catch (error) {
|
||||
const result = {
|
||||
code: 500,
|
||||
msg: (error as any).toString()
|
||||
};
|
||||
webview.postMessage({ command: 'resources/templates/list', data: result });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 读取特定resource
|
||||
*/
|
||||
|
@ -1,7 +1,8 @@
|
||||
|
||||
import { VSCodeWebViewLike } from '../adapter';
|
||||
import { connect, MCPClient, type MCPOptions } from './connect';
|
||||
import { callTool, getPrompt, listPrompts, listResources, readResource } from './handler';
|
||||
import { callTool, getPrompt, listPrompts, listResources, listResourceTemplates, readResource } from './handler';
|
||||
import { ping } from './util';
|
||||
|
||||
|
||||
// TODO: 支持更多的 client
|
||||
@ -48,6 +49,10 @@ export function messageController(command: string, data: any, webview: VSCodeWeb
|
||||
listResources(client, webview);
|
||||
break;
|
||||
|
||||
case 'resources/templates/list':
|
||||
listResourceTemplates(client, webview);
|
||||
break;
|
||||
|
||||
case 'resources/read':
|
||||
readResource(client, data, webview);
|
||||
break;
|
||||
@ -56,6 +61,10 @@ export function messageController(command: string, data: any, webview: VSCodeWeb
|
||||
callTool(client, data, webview);
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
ping(client, webview);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
103
test/src/controller/protocol.type.ts
Normal file
103
test/src/controller/protocol.type.ts
Normal file
@ -0,0 +1,103 @@
|
||||
// ==================== 基础类型定义 ====================
|
||||
export interface SchemaProperty {
|
||||
title: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface InputSchema {
|
||||
type: string;
|
||||
properties: Record<string, SchemaProperty>;
|
||||
required?: string[];
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface Argument {
|
||||
name: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export interface Content {
|
||||
uri: string;
|
||||
mimeType: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface MessageContent {
|
||||
type: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
// ==================== 响应接口定义 ====================
|
||||
export interface ToolsListResponse {
|
||||
tools: Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: InputSchema;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface PromptsListResponse {
|
||||
prompts: Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
arguments: Argument[];
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ResourceTemplatesListResponse {
|
||||
resourceTemplates: Array<{
|
||||
uriTemplate: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ResourcesListResponse {
|
||||
resources: any[]; // 根据示例返回空数组,可进一步定义具体类型
|
||||
}
|
||||
|
||||
export interface ResourcesReadResponse {
|
||||
contents: Content[];
|
||||
}
|
||||
|
||||
export interface PromptsGetResponse {
|
||||
messages: Array<{
|
||||
role: string;
|
||||
content: MessageContent;
|
||||
}>;
|
||||
}
|
||||
|
||||
// ==================== 请求接口定义 ====================
|
||||
export interface BaseRequest {
|
||||
method: string;
|
||||
params: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ResourcesReadRequest extends BaseRequest {
|
||||
method: 'resources/read';
|
||||
params: {
|
||||
uri: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PromptsGetRequest extends BaseRequest {
|
||||
method: 'prompts/get';
|
||||
params: {
|
||||
name: string;
|
||||
arguments: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== 合并类型定义 ====================
|
||||
export type APIResponse =
|
||||
| ToolsListResponse
|
||||
| PromptsListResponse
|
||||
| ResourceTemplatesListResponse
|
||||
| ResourcesListResponse
|
||||
| ResourcesReadResponse
|
||||
| PromptsGetResponse;
|
||||
|
||||
export type APIRequest =
|
||||
| BaseRequest
|
||||
| ResourcesReadRequest
|
||||
| PromptsGetRequest;
|
20
test/src/controller/util.ts
Normal file
20
test/src/controller/util.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { VSCodeWebViewLike } from "../adapter";
|
||||
import { MCPClient } from "./connect";
|
||||
|
||||
export function ping(client: MCPClient | undefined, webview: VSCodeWebViewLike) {
|
||||
if (!client) {
|
||||
const connectResult = {
|
||||
code: 501,
|
||||
msg: 'mcp client 尚未连接'
|
||||
};
|
||||
webview.postMessage({ command: 'ping', data: connectResult });
|
||||
return;
|
||||
}
|
||||
|
||||
webview.postMessage({
|
||||
command: 'ping', data: {
|
||||
code: 200,
|
||||
msg: {}
|
||||
}
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user