mcpServer MCP 服务
mcpServer MCP 服务
mcpServer 是 JSXHook 脚本侧的 MCP 服务桥接层。你可以把它理解成一句人话:
把脚本里已经有的能力,包装成外部 AI / 编辑器 / 客户端可以调用的 Tool、Resource、Prompt。
如果你想直接翻应用内置手册里的 MCP 示例,可以看这里:[内置手册全量示例 - MCP 服务与编码工作流](/reference/manual-full.html mcp-服务与编码工作流)。
先用一句话理解 MCP
第一次接触时,不要先背协议名词,先这样理解:
- Tool:外部让我“做一件事”
- Resource:外部来“读一份内容”
- Prompt:外部来“拿一段任务模板/上下文模板”
而 mcpServer 做的事,就是把这三类东西从 JSXHook 脚本里挂出去。
第一个最小可用服务
如果你只想先把服务跑起来,再让客户端成功调用一个工具,用下面这段就够了:
mcpServer.quickTool(
"run",
"运行指定脚本",
[
{ name: "path", type: "string", required: true, description: "脚本路径" }
],
function(args, ctx) {
return {
structuredContent: {
ok: true,
path: args.path,
tool: ctx.toolName
},
content: [
{ type: "text", text: "执行完成" }
]
};
}
);
const state = mcpServer.start({
port: 5698,
allowLan: true,
token: "",
endpoint: "/mcp",
name: "jsxhook-tools",
version: "1.0.0",
instructions: "这是一个由 JSXHook 脚本提供的 MCP 服务"
});
log(state.url);
log(JSON.stringify(mcpServer.state(), null, 2));
这段代码跑起来以后,你最先关心的是两件事:
state.url有没有打印出来。- 客户端能不能先调通一个最简单的 Tool。
先和 httpServer 区分开
很多人第一次看到 mcpServer,会把它和 httpServer 当成同一个东西。它们确实都跑在 HTTP 之上,但你写代码时的思路其实不一样。
httpServer:你自己定义 URL、方法、请求体和返回结构,适合普通本地接口、局域网接口、Webhook、调试面板回调这些场景。mcpServer:你不是单纯在“开接口”,而是在注册 Tool、Resource、Prompt,让支持 MCP 的客户端按协议发现和调用。- 如果对方只会发普通 HTTP 请求,看不懂 MCP 协议,就该用
httpServer。 - 如果对方本身就是 AI 客户端、编辑器插件、MCP Host,想直接识别工具列表、资源列表、提示模板,就该用
mcpServer。
你可以用一句最省事的话记:
httpServer是“我自己设计接口给别人调”mcpServer是“我把脚本能力注册成 MCP 能懂的能力给别人调”
mcpServer.start(config)
mcpServer.start(...) 负责真正启动服务。
const state = mcpServer.start({
port: 5698,
endpoint: "/mcp",
token: "secret-token",
name: "jsxhook-mcp"
});
log(state.url);
常用参数:
host:显式指定监听地址。allowLan:是否允许局域网访问。port:端口,默认5698。token:请求令牌,可为空字符串。endpoint:路径,默认"/mcp"。name:服务名,默认"jsxhook-mcp"。version:服务版本,默认"1.0.0"。instructions:给客户端展示的服务说明。allowedOrigins:允许的跨域来源列表。
你最该记住的默认行为:
- 默认端口是
5698 - 默认 endpoint 是
"/mcp" - 默认 name 是
"jsxhook-mcp" - 默认 version 是
"1.0.0" allowLan: true时,默认监听0.0.0.0- 不开
allowLan时,默认更偏向本机127.0.0.1
也就是说:
- 只在本机调试,默认本地监听就够了。
- 想让电脑或局域网其他设备访问手机里的服务,再开
allowLan: true。
mcpServer.stop()
停止 MCP 服务:
mcpServer.stop();
如果你只是热更新脚本、重新注册一整套 Tool,习惯上也可以先 stop() 再重启。
mcpServer.state()
查看当前服务状态:
const state = mcpServer.state();
log(JSON.stringify(state, null, 2));
常见会看到这些字段:
endpointnameversionsessionIdtoolCountresourceCountresourceTemplateCountpromptCountprotocolVersionurl
对新手最有用的通常就是:
url:客户端到底该连哪个地址toolCount:你的 Tool 有没有真注册进去resourceCount/promptCount:注册数量对不对
mcpServer.tool(name, options?, handler)
这是“标准写法”,适合你想把 inputSchema、outputSchema、注解、线程要求都写清楚时使用。
mcpServer.tool(
"echo",
{
title: "Echo",
description: "回显输入文本",
inputSchema: {
type: "object",
properties: {
text: { type: "string", description: "要回显的文本" }
},
required: ["text"]
}
},
function(args, ctx) {
return {
structuredContent: {
ok: true,
text: args.text,
tool: ctx.toolName,
packageName: ctx.packageName,
processName: ctx.processName
},
content: [
{ type: "text", text: `echo: ${args.text}` }
]
};
}
);
handler 会收到两个参数:
args:客户端传进来的入参对象ctx:当前工具上下文,常见有toolName、packageName、processName、protocolVersion、serverUrlTool 最推荐的返回格式
最推荐你直接显式返回:
{
structuredContent: { ... },
content: [
{ type: "text", text: "..." }
]
}
为什么推荐这样写?
structuredContent方便客户端继续结构化处理。content方便人直接看。
即使你直接返回普通对象,桥接层也会帮你做兜底包装;但对初学者来说,显式写标准结构最清晰。
`mainThread: true` 什么时候开
如果你的 Tool 里要做这些事,建议开:
- 访问或操作
activity - 调 UI
- 依赖主线程的 Android API
例如:
mcpServer.tool(
"showToast",
{
description: "在当前宿主里弹一个 Toast",
mainThread: true,
inputSchema: {
type: "object",
properties: {
text: { type: "string" }
},
required: ["text"]
}
},
function(args) {
toast(args.text || "hello");
return {
structuredContent: { ok: true }
};
}
);
如果只是读文件、做字符串处理、跑普通逻辑,一般不用开。
mcpServer.registerTool(...)
它只是 tool(...) 的别名,参数和行为一致。
mcpServer.quickTool(name, description?, fields?, handler)
这是新手最容易上手的一种写法。
核心思路就是:你不用自己手写完整 inputSchema,直接用简化字段定义生成它。
对象式字段定义
mcpServer.quickTool(
"sum",
"两个数字相加",
{
a: { type: "number", required: true, description: "第一个数字" },
b: { type: "number", required: true, description: "第二个数字" }
},
function(args) {
return {
structuredContent: {
result: args.a + args.b
}
};
}
);
数组式字段定义
mcpServer.quickTool(
"read",
"读取文件",
[
{ name: "path", type: "string", required: true, description: "文件路径" }
],
function(args) {
return {
structuredContent: {
path: args.path
}
};
}
);
纯字符串字段定义
如果你只想快速声明“这些字段都是字符串且必填”,可以更短:
mcpServer.quickTool(
"grepLog",
"按关键字过滤日志",
["keyword"],
function(args) {
return {
structuredContent: {
keyword: args.keyword
}
};
}
);
对第一次搭 MCP 的人来说,quickTool(...) 通常是最顺手的入口。
mcpServer.remove(name) / removeTool(name)
删除某个 Tool:
mcpServer.remove("echo");
mcpServer.clear() / clearTools()
清空全部 Tool:
const removedCount = mcpServer.clear();
log(`removed=${removedCount}`);
mcpServer.listTools()
列出当前已注册的 Tool:
log(JSON.stringify(mcpServer.listTools(), null, 2));
这对排查“我到底注册进去没有”很有帮助。
mcpServer.resource(uri, options?, handler?)
Resource 适合暴露“可读取内容”。它可以是动态的,也可以是静态的。
动态 Resource
mcpServer.resource(
"jsxhook://status",
{
name: "status",
mimeType: "application/json; charset=utf-8"
},
function(req, ctx) {
return {
text: JSON.stringify({
packageName: lpparam.packageName,
processName: lpparam.processName,
resourceUri: ctx.resourceUri
}, null, 2)
};
}
);
handler 会收到两个参数:
req:通常包含uri和paramsctx:通常包含resourceUri、registeredUri、packageName、processName、protocolVersion、serverUrl静态 Resource
如果你不需要动态逻辑,也可以直接放静态内容:
mcpServer.resource("jsxhook://hello", {
name: "hello",
mimeType: "text/plain; charset=utf-8",
text: "Hello from JSXHook MCP resource"
});
这很适合放:
- 运行状态快照
- 固定说明文本
- 某份只读配置
mcpServer.resourceTemplate(uriTemplate, options?, handler)
当 URI 里有变量时用它,比如 jsxhook://project/{name}。
mcpServer.resourceTemplate(
"jsxhook://project/{name}",
{
name: "project-detail",
mimeType: "application/json; charset=utf-8"
},
function(req, ctx) {
const projectName = req.params.name;
return {
text: JSON.stringify({
requestedUri: req.uri,
projectName: projectName,
uriTemplate: ctx.uriTemplate
}, null, 2)
};
}
);
这里最该记住的是:
req.uri:客户端真正请求的 URIreq.params:模板里占位符解析出来的参数
也就是说,请求 jsxhook://project/demo 时,req.params.name 就是 "demo"。
资源管理相关方法
除了注册,你还会经常用到这些:
removeResource(uri)clearResources()listResources()removeResourceTemplate(uriTemplate)clearResourceTemplates()listResourceTemplates()
排查问题时,listResources() 和 listResourceTemplates() 非常有用。
mcpServer.prompt(name, options?, handler?)
Prompt 适合暴露“给模型的任务模板、上下文模板、说明模板”。
动态 Prompt
mcpServer.prompt(
"code-task-context",
{
description: "给模型描述当前 JSXHook 编码环境和目标",
arguments: [
{
name: "goal",
description: "这次希望模型完成什么任务",
required: true
},
{
name: "focusPath",
description: "这次重点处理的文件或目录",
required: false
}
]
},
function(args, ctx) {
const focusText = args.focusPath ? `\n重点路径: ${args.focusPath}` : "";
return {
description: "JSXHook 编码任务上下文",
messages: [
{
role: "user",
content: {
type: "text",
text:
"你正在处理 JSXHook 脚本环境" +
`\n宿主包名: ${ctx.packageName}` +
`\n进程名: ${ctx.processName}` +
`\n目标: ${args.goal}` +
focusText
}
}
]
};
}
);
静态 Prompt
mcpServer.prompt("simple-help", {
description: "固定帮助文本",
text: "你是一个帮助我分析 JSXHook 项目的助手。"
});
它适合做:
编码任务模板
上下文说明模板
约束/规范说明
Prompt 管理相关方法
removePrompt(name)clearPrompts()listPrompts()allowedOrigins/token/ 局域网访问
这三个经常一起被问到,可以连起来理解。
`token`
如果你给 start(...) 配了 token,客户端请求时就要带这个令牌。
这适合你把服务暴露到局域网时做一层最基本的保护。
`allowLan`
如果你只是手机本机或宿主内部访问,不一定要开。
如果你想让电脑连手机上的 MCP 服务,一般要开:
allowLan: true
`allowedOrigins`
如果你是从浏览器或带 Origin 的客户端跨域访问,非本机来源需要放进白名单:
mcpServer.start({
allowLan: true,
allowedOrigins: [
"http://localhost:3000",
"http://192.168.1.10:3000"
]
});
对于本机常见来源,桥接层本身会更宽松一些;但你只要涉及浏览器跨域调试,最好还是显式配清楚。
mcpServer.clearAll()
一次性清空 Tool、Resource、ResourceTemplate 和 Prompt:
const summary = mcpServer.clearAll();
log(JSON.stringify(summary, null, 2));
这个方法特别适合:
你在调试过程中想“全部重置再来一遍”
你做的是脚本热重载
你不想自己一项一项清
一个适合新手的搭建顺序
如果你完全是第一次搭 MCP,建议顺序就按这个来:
- 先只写一个
quickTool(...) - 用
mcpServer.start(...)把服务跑起来 - 用
mcpServer.state()确认url和toolCount - 客户端先调用通 Tool
- 再逐步补
resource(...) - 最后再补
prompt(...)
这样最不容易一上来就把问题混在一起。
如果你在做 APK 分析链路,可以继续对照 内置 MCP 工具总览 和 DexKit 与 APK 分析 一起看。
