@urovotest/urovosdk-docs-mcp 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -0
- package/docs/00-overview.md +37 -0
- package/docs/01-integration/android-permissions.md +22 -0
- package/docs/01-integration/quickstart-zh.md +54 -0
- package/docs/02-reference/emv/emv-api.md +3308 -0
- package/docs/02-reference/emv/emv-appendix.md +112 -0
- package/docs/02-reference/emv/emv-callback.md +1155 -0
- package/docs/02-reference/emv/emv-enum.md +324 -0
- package/docs/02-reference/error-codes.md +59 -0
- package/docs/02-reference/general/beeper.md +160 -0
- package/docs/02-reference/general/devicemanager.md +460 -0
- package/docs/02-reference/general/insertcardreader.md +307 -0
- package/docs/02-reference/general/install-manager.md +202 -0
- package/docs/02-reference/general/led-light.md +144 -0
- package/docs/02-reference/general/magcardreader.md +191 -0
- package/docs/02-reference/general/pinpad.md +3309 -0
- package/docs/02-reference/general/printer.md +1410 -0
- package/docs/02-reference/general/psam2.md +55 -0
- package/docs/02-reference/general/rfcardreader.md +1090 -0
- package/docs/02-reference/general/scanner-custom.md +196 -0
- package/docs/02-reference/general/scanner.md +214 -0
- package/docs/02-reference/general/sdk-log-output-management.md +124 -0
- package/docs/02-reference/general/serial-port-deprecated-suggest-using-module-14-serialtool.md +406 -0
- package/docs/02-reference/general/serial-port.md +326 -0
- package/docs/02-reference/general/sle4428-4436-4442.md +1890 -0
- package/docs/02-reference/general/system-manager.md +212 -0
- package/docs/02-reference/module-index.md +30 -0
- package/docs/02-reference/source-emv_sdk.md +3927 -0
- package/docs/02-reference/source-general_sdk.md +8978 -0
- package/docs/02-reference/undocumented/kiosk-lock-task.md +92 -0
- package/docs/03-demo/activity-map.md +27 -0
- package/docs/api-registry.json +4733 -0
- package/docs/search-index.json +2688 -0
- package/docs/source/emv_sdk.md +3927 -0
- package/docs/source/general_sdk.md +8978 -0
- package/docs/source/kiosk_lock_task.md +86 -0
- package/package.json +36 -0
- package/src/launch.js +22 -0
- package/src/lib/check-update.js +92 -0
- package/src/lib/create-mcp-server.js +90 -0
- package/src/lib/docs-tools.js +206 -0
- package/src/server.js +10 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Kiosk / Lock Task Mode
|
|
2
|
+
|
|
3
|
+
**DISCOVERED**: These APIs exist in `DeviceManager` and are used in the demo (`SystemActivity.java`), but are NOT documented in the official SDK reference docs. Found by searching demo source code.
|
|
4
|
+
|
|
5
|
+
## DeviceManager / setLockTaskMode
|
|
6
|
+
|
|
7
|
+
- **Package/class**: `com.urovo.device.DeviceManager`
|
|
8
|
+
- **Demo reference**: `assets/UrovoPosSdkDemo/.../view/SystemActivity.java` (lines 142-148)
|
|
9
|
+
|
|
10
|
+
### Purpose
|
|
11
|
+
Enable or disable Android Lock Task Mode (kiosk mode) for a specific app package. When enabled, the user is locked to the specified app — cannot press Home, Recent Apps, or swipe away. Navigation bar is hidden/disabled. Power button short-press is suppressed.
|
|
12
|
+
|
|
13
|
+
### Signature
|
|
14
|
+
```java
|
|
15
|
+
void setLockTaskMode(String packageName, boolean lock)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Parameters
|
|
19
|
+
| Name | Type | Description |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| packageName | String | The package name of the app to lock/unlock (use `getPackageName()`) |
|
|
22
|
+
| lock | boolean | `true` to enable lock task mode, `false` to disable |
|
|
23
|
+
|
|
24
|
+
### Example
|
|
25
|
+
```java
|
|
26
|
+
// Enable kiosk mode before transaction
|
|
27
|
+
new DeviceManager().setLockTaskMode(getPackageName(), true);
|
|
28
|
+
|
|
29
|
+
// Disable kiosk mode after transaction
|
|
30
|
+
new DeviceManager().setLockTaskMode(getPackageName(), false);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## DeviceManager / setLockTaskModePassword
|
|
36
|
+
|
|
37
|
+
- **Package/class**: `com.urovo.device.DeviceManager`
|
|
38
|
+
- **Demo reference**: `assets/UrovoPosSdkDemo/.../view/SystemActivity.java` (line 144)
|
|
39
|
+
|
|
40
|
+
### Purpose
|
|
41
|
+
Set the password required to exit Lock Task Mode. Should be called together with `setLockTaskMode(packageName, true)`.
|
|
42
|
+
|
|
43
|
+
### Signature
|
|
44
|
+
```java
|
|
45
|
+
void setLockTaskModePassword(String password)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Parameters
|
|
49
|
+
| Name | Type | Description |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| password | String | Password to unlock/exit kiosk mode (e.g., "123456") |
|
|
52
|
+
|
|
53
|
+
### Example
|
|
54
|
+
```java
|
|
55
|
+
new DeviceManager().setLockTaskMode(getPackageName(), true);
|
|
56
|
+
new DeviceManager().setLockTaskModePassword("123456");
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Recommended Pattern for Payment Apps
|
|
62
|
+
|
|
63
|
+
```java
|
|
64
|
+
// Transaction starts — lock user to payment app
|
|
65
|
+
new DeviceManager().setLockTaskMode(getPackageName(), true);
|
|
66
|
+
new DeviceManager().setLockTaskModePassword("123456");
|
|
67
|
+
|
|
68
|
+
// Optional: also hide status bar for cleaner UI
|
|
69
|
+
View decorView = getWindow().getDecorView();
|
|
70
|
+
decorView.setSystemUiVisibility(
|
|
71
|
+
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
72
|
+
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
73
|
+
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
74
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
75
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
76
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Transaction ends (success, cancel, or error) — unlock
|
|
80
|
+
new DeviceManager().setLockTaskMode(getPackageName(), false);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Related APIs (documented elsewhere)
|
|
84
|
+
|
|
85
|
+
- `SystemProviderImpl.setLockScreenNon()` — disable screen lock entirely (documented in general_sdk.md section 12.3)
|
|
86
|
+
- `BaseActivity.fullScreen()` — standard Android fullscreen helper (demo code)
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@urovotest/urovosdk-docs-mcp",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "1.0.1",
|
|
7
|
+
"description": "UrovoSDK documentation MCP server (stdio, bundled docs — npx ready)",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "src/launch.js",
|
|
10
|
+
"bin": {
|
|
11
|
+
"urovosdk-docs-mcp": "src/launch.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src",
|
|
15
|
+
"docs"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"bundle": "node scripts/bundle.js",
|
|
19
|
+
"prepack": "node scripts/bundle.js",
|
|
20
|
+
"verify": "node scripts/verify.js"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"urovosdk",
|
|
31
|
+
"urovo",
|
|
32
|
+
"pos"
|
|
33
|
+
],
|
|
34
|
+
"license": "UNLICENSED",
|
|
35
|
+
"private": false
|
|
36
|
+
}
|
package/src/launch.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UrovoSDK Docs MCP 入口:每次启动检查 npm 最新版并同步,再启动 stdio 服务
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { ensureLatestFromRegistry } from "./lib/check-update.js";
|
|
9
|
+
import { startMcpServer } from "./server.js";
|
|
10
|
+
|
|
11
|
+
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
+
const PACKAGE_NAME = JSON.parse(
|
|
13
|
+
readFileSync(path.join(pkgRoot, "package.json"), "utf-8")
|
|
14
|
+
).name;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
await ensureLatestFromRegistry(PACKAGE_NAME);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error(`>> ${PACKAGE_NAME}: update re-exec failed (${err.message}), using current`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await startMcpServer();
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 启动前检查 npm 最新版,若有更新则 npx 拉取并重 exec(stdio MCP 入口共用)
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
const SKIP_ENV = "DOCS_MCP_SKIP_UPDATE";
|
|
10
|
+
const REGISTRY_ENV = "DOCS_MCP_REGISTRY";
|
|
11
|
+
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
12
|
+
|
|
13
|
+
function readPackageVersion() {
|
|
14
|
+
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
15
|
+
const pkg = JSON.parse(readFileSync(path.join(pkgRoot, "package.json"), "utf-8"));
|
|
16
|
+
return { version: pkg.version, name: pkg.name, root: pkgRoot };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isNewer(latest, current) {
|
|
20
|
+
const a = latest.split(".").map((n) => parseInt(n, 10) || 0);
|
|
21
|
+
const b = current.split(".").map((n) => parseInt(n, 10) || 0);
|
|
22
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
23
|
+
const x = a[i] ?? 0;
|
|
24
|
+
const y = b[i] ?? 0;
|
|
25
|
+
if (x > y) return true;
|
|
26
|
+
if (x < y) return false;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** npm registry 路径:scoped 包需将 `/` 编码为 `%2F`(如 @urovotest/pkg → @urovotest%2Fpkg) */
|
|
32
|
+
function registryPackagePath(packageName) {
|
|
33
|
+
return packageName.replace("/", "%2F");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function fetchLatestVersion(packageName) {
|
|
37
|
+
const registry = (process.env[REGISTRY_ENV] || DEFAULT_REGISTRY).replace(/\/$/, "");
|
|
38
|
+
const url = `${registry}/${registryPackagePath(packageName)}/latest`;
|
|
39
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(8000) });
|
|
40
|
+
if (res.status === 404) return null;
|
|
41
|
+
if (!res.ok) throw new Error(`registry HTTP ${res.status}`);
|
|
42
|
+
const data = await res.json();
|
|
43
|
+
return data.version;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function reexecViaNpx(packageName, version) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const spec = `${packageName}@${version}`;
|
|
49
|
+
console.error(`>> ${packageName}: syncing to latest ${version} via npx...`);
|
|
50
|
+
const child = spawn("npx", ["-y", spec], {
|
|
51
|
+
stdio: "inherit",
|
|
52
|
+
shell: true,
|
|
53
|
+
env: { ...process.env, [SKIP_ENV]: "1" },
|
|
54
|
+
});
|
|
55
|
+
child.on("error", reject);
|
|
56
|
+
child.on("exit", (code) => {
|
|
57
|
+
process.exit(code ?? 0);
|
|
58
|
+
});
|
|
59
|
+
resolve(true);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @returns {Promise<boolean>} true = 已 spawn 新版本进程,当前进程即将退出
|
|
65
|
+
*/
|
|
66
|
+
export async function ensureLatestFromRegistry(packageName) {
|
|
67
|
+
if (process.env[SKIP_ENV] === "1") return false;
|
|
68
|
+
|
|
69
|
+
const { version: current } = readPackageVersion();
|
|
70
|
+
|
|
71
|
+
let latest;
|
|
72
|
+
try {
|
|
73
|
+
latest = await fetchLatestVersion(packageName);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(`>> ${packageName}: update check skipped (${err.message})`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!latest) {
|
|
80
|
+
console.error(`>> ${packageName}: not on registry, using bundled v${current}`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!isNewer(latest, current)) {
|
|
85
|
+
console.error(`>> ${packageName}: v${current} (latest)`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.error(`>> ${packageName}: v${current} -> v${latest}`);
|
|
90
|
+
await reexecViaNpx(packageName, latest);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { createDocsTools, callTool } from "./docs-tools.js";
|
|
4
|
+
|
|
5
|
+
export function createUrovoSdkMcpServer(options = {}) {
|
|
6
|
+
const tools = createDocsTools(options);
|
|
7
|
+
|
|
8
|
+
const server = new Server(
|
|
9
|
+
{ name: "urovosdk-docs", version: "1.0.0" },
|
|
10
|
+
{ capabilities: { tools: {} } }
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
14
|
+
tools: [
|
|
15
|
+
{
|
|
16
|
+
name: "search_urovosdk_docs",
|
|
17
|
+
description: "Search UrovoSDK documentation (Pinpad, Printer, Scanner, EMV, read card, install). Use for integration questions.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: { query: { type: "string" }, limit: { type: "number" } },
|
|
21
|
+
required: ["query"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "list_urovosdk_docs",
|
|
26
|
+
description: "List all UrovoSDK documentation files.",
|
|
27
|
+
inputSchema: { type: "object", properties: {} },
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "get_urovosdk_doc",
|
|
31
|
+
description: "Get full Markdown doc by path, e.g. 02-reference/general/pinpad.md",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: { path: { type: "string" } },
|
|
35
|
+
required: ["path"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "lookup_urovosdk_api",
|
|
40
|
+
description: "Precise API lookup by module/method/classPath/keyword/demoActivity. Example: module=Pinpad method=getLastErrorCode",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
domain: { type: "string", description: "general | emv | undocumented" },
|
|
45
|
+
module: { type: "string", description: "e.g. Pinpad, Printer, Scanner, EMV API" },
|
|
46
|
+
method: { type: "string" },
|
|
47
|
+
classPath: { type: "string" },
|
|
48
|
+
keyword: { type: "string" },
|
|
49
|
+
demoActivity: { type: "string" },
|
|
50
|
+
limit: { type: "number" },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "lookup_urovosdk_emv",
|
|
56
|
+
description: "Lookup EMV kernel API, callbacks, enums. Same params as lookup_urovosdk_api; defaults domain=emv.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
module: { type: "string" },
|
|
61
|
+
method: { type: "string" },
|
|
62
|
+
keyword: { type: "string" },
|
|
63
|
+
limit: { type: "number" },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "lookup_urovosdk_error_code",
|
|
69
|
+
description: "Look up Pinpad or Printer error code (decimal or hex like 0xF0).",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: { code: { type: ["number", "string"] } },
|
|
73
|
+
required: ["code"],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
80
|
+
const { name, arguments: args } = request.params;
|
|
81
|
+
try {
|
|
82
|
+
const result = await callTool(tools, name, args ?? {});
|
|
83
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return server;
|
|
90
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UrovoSDK Docs MCP — 共享工具逻辑
|
|
3
|
+
*/
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const DEFAULT_DOCS_PATH = path.resolve(__dirname, "../../docs");
|
|
10
|
+
|
|
11
|
+
export function createDocsTools(options = {}) {
|
|
12
|
+
const remoteOnly = options.remoteOnly === true || process.env.MCP_REMOTE_ONLY === "1";
|
|
13
|
+
const DOCS_PATH = options.docsPath
|
|
14
|
+
? path.resolve(options.docsPath)
|
|
15
|
+
: process.env.DOCS_PATH
|
|
16
|
+
? path.resolve(process.env.DOCS_PATH)
|
|
17
|
+
: DEFAULT_DOCS_PATH;
|
|
18
|
+
const DOCS_URL = (options.docsUrl || process.env.DOCS_URL || "").replace(/\/$/, "");
|
|
19
|
+
const useRemote = remoteOnly || (!!DOCS_URL && (options.preferRemote || process.env.DOCS_PREFER_REMOTE === "1"));
|
|
20
|
+
|
|
21
|
+
async function readLocalFile(relativePath) {
|
|
22
|
+
return fs.readFile(path.join(DOCS_PATH, relativePath), "utf-8");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchRemote(relativePath) {
|
|
26
|
+
if (!DOCS_URL) throw new Error("DOCS_URL is not configured");
|
|
27
|
+
const url = `${DOCS_URL}/${relativePath.replace(/\\/g, "/")}`;
|
|
28
|
+
const res = await fetch(url);
|
|
29
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
|
|
30
|
+
return res.text();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readDocFile(relativePath) {
|
|
34
|
+
if (useRemote || DOCS_URL) {
|
|
35
|
+
try {
|
|
36
|
+
return await fetchRemote(relativePath);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (remoteOnly || useRemote) throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return readLocalFile(relativePath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ERROR_CODES = {
|
|
45
|
+
0: { category: "pinpad", constant: "SUCCESS", message: "Success" },
|
|
46
|
+
1: { category: "pinpad", constant: "UNSUPPORTED_COMMAND", message: "Unsupported command" },
|
|
47
|
+
2: { category: "pinpad", constant: "COMMAND_LENGTH_ERROR", message: "Command length error" },
|
|
48
|
+
100: { category: "pinpad", constant: "PARAM_NULL", message: "Parameter is null or empty" },
|
|
49
|
+
101: { category: "pinpad", constant: "MISMATCH_DATA_LENGTH", message: "Mismatch data length" },
|
|
50
|
+
102: { category: "pinpad", constant: "INVALID_DATA_LENGTH", message: "Invalid data length" },
|
|
51
|
+
103: { category: "pinpad", constant: "INVALID_KEY_INDEX", message: "Invalid key index" },
|
|
52
|
+
104: { category: "pinpad", constant: "INVALID_PARAMETER", message: "Invalid parameter" },
|
|
53
|
+
105: { category: "pinpad", constant: "INVALID_RSA_PUBLIC_KEY", message: "Invalid RSA public key" },
|
|
54
|
+
106: { category: "pinpad", constant: "RUNTIME_EXCEPTION", message: "Runtime exception" },
|
|
55
|
+
0xF0: { category: "printer", constant: "NO_PAPER", message: "No paper" },
|
|
56
|
+
240: { category: "printer", constant: "NO_PAPER", message: "No paper" },
|
|
57
|
+
0xF3: { category: "printer", constant: "OVER_HEAT", message: "Over heat" },
|
|
58
|
+
243: { category: "printer", constant: "OVER_HEAT", message: "Over heat" },
|
|
59
|
+
0xE1: { category: "printer", constant: "LOW_VOLTAGE", message: "Low voltage" },
|
|
60
|
+
225: { category: "printer", constant: "LOW_VOLTAGE", message: "Low voltage" },
|
|
61
|
+
0xF2: { category: "printer", constant: "HARDWARE_ERROR", message: "Hardware error" },
|
|
62
|
+
242: { category: "printer", constant: "HARDWARE_ERROR", message: "Hardware error" },
|
|
63
|
+
0xF7: { category: "printer", constant: "PRINTER_BUSY", message: "Printer busy" },
|
|
64
|
+
247: { category: "printer", constant: "PRINTER_BUSY", message: "Printer busy" },
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function tokenize(text) {
|
|
68
|
+
return text.toLowerCase().split(/[\s\-_/\\.:#]+/).filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function scoreEntry(entry, queryTokens) {
|
|
72
|
+
const hay = `${entry.title} ${entry.path} ${entry.snippet || ""}`.toLowerCase();
|
|
73
|
+
let score = 0;
|
|
74
|
+
for (const t of queryTokens) {
|
|
75
|
+
if (hay.includes(t)) score += t.length > 3 ? 3 : 1;
|
|
76
|
+
}
|
|
77
|
+
return score;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function loadIndex() {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(await readDocFile("search-index.json"));
|
|
83
|
+
} catch {
|
|
84
|
+
return { entries: [] };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function loadRegistry() {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(await readDocFile("api-registry.json"));
|
|
91
|
+
} catch {
|
|
92
|
+
return { items: [] };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function scoreApiItem(item, tokens) {
|
|
97
|
+
const hay = [
|
|
98
|
+
item.domain, item.module, item.method, item.title, item.classPath,
|
|
99
|
+
item.demoActivity, item.purpose, item.signature, ...(item.keywords || []),
|
|
100
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
101
|
+
let score = 0;
|
|
102
|
+
for (const t of tokens) {
|
|
103
|
+
if (hay.includes(t)) score += t.length >= 3 ? 3 : 1;
|
|
104
|
+
}
|
|
105
|
+
return score;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function searchDocs(query, limit = 5) {
|
|
109
|
+
const index = await loadIndex();
|
|
110
|
+
const tokens = tokenize(query);
|
|
111
|
+
if (!tokens.length) return index.entries.slice(0, limit);
|
|
112
|
+
const ranked = index.entries
|
|
113
|
+
.map((e) => ({ ...e, score: scoreEntry(e, tokens) }))
|
|
114
|
+
.filter((e) => e.score > 0)
|
|
115
|
+
.sort((a, b) => b.score - a.score)
|
|
116
|
+
.slice(0, limit);
|
|
117
|
+
|
|
118
|
+
const results = [];
|
|
119
|
+
for (const item of ranked) {
|
|
120
|
+
let excerpt = item.snippet || "";
|
|
121
|
+
try {
|
|
122
|
+
const full = await readDocFile(item.path);
|
|
123
|
+
const idx = full.toLowerCase().indexOf(tokens[0]);
|
|
124
|
+
excerpt = idx >= 0 ? full.slice(Math.max(0, idx - 80), idx + 400) : full.slice(0, 500);
|
|
125
|
+
} catch { /* keep snippet */ }
|
|
126
|
+
results.push({ path: item.path, title: item.title, score: item.score, excerpt });
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function listDocs() {
|
|
132
|
+
const index = await loadIndex();
|
|
133
|
+
return index.entries.map(({ path: p, title }) => ({ path: p, title }));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function getDoc(relativePath) {
|
|
137
|
+
const normalized = relativePath.replace(/\\/g, "/").replace(/^\//, "");
|
|
138
|
+
if (normalized.includes("..")) throw new Error("Invalid path");
|
|
139
|
+
return readDocFile(normalized);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function lookupErrorCode(code) {
|
|
143
|
+
const num = typeof code === "string" && code.startsWith("0x")
|
|
144
|
+
? parseInt(code, 16)
|
|
145
|
+
: Number(code);
|
|
146
|
+
const info = ERROR_CODES[num] ?? ERROR_CODES[code];
|
|
147
|
+
if (!info) return { found: false, code };
|
|
148
|
+
return { found: true, code: num, ...info, doc: "02-reference/error-codes.md" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function lookupApi({ domain, module, method, classPath, keyword, demoActivity, limit = 10 }) {
|
|
152
|
+
const registry = await loadRegistry();
|
|
153
|
+
const parts = [domain, module, method, classPath, keyword, demoActivity].filter(Boolean);
|
|
154
|
+
const tokens = parts.join(" ").toLowerCase().split(/[\s\-_/\\.:#]+/).filter(Boolean);
|
|
155
|
+
if (!tokens.length) {
|
|
156
|
+
return { error: "Provide at least one of: domain, module, method, classPath, keyword, demoActivity" };
|
|
157
|
+
}
|
|
158
|
+
const ranked = registry.items
|
|
159
|
+
.map((item) => ({ ...item, score: scoreApiItem(item, tokens) }))
|
|
160
|
+
.filter((item) => item.score > 0)
|
|
161
|
+
.sort((a, b) => b.score - a.score)
|
|
162
|
+
.slice(0, limit);
|
|
163
|
+
return {
|
|
164
|
+
query: { domain, module, method, classPath, keyword, demoActivity },
|
|
165
|
+
matchCount: ranked.length,
|
|
166
|
+
results: ranked,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function lookupEmv(args) {
|
|
171
|
+
return lookupApi({ ...args, domain: args.domain || "emv" });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
docsPath: DOCS_PATH,
|
|
176
|
+
docsUrl: DOCS_URL,
|
|
177
|
+
remoteOnly,
|
|
178
|
+
searchDocs,
|
|
179
|
+
listDocs,
|
|
180
|
+
getDoc,
|
|
181
|
+
lookupErrorCode,
|
|
182
|
+
lookupApi,
|
|
183
|
+
lookupEmv,
|
|
184
|
+
loadIndex,
|
|
185
|
+
loadRegistry,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function callTool(tools, name, args = {}) {
|
|
190
|
+
switch (name) {
|
|
191
|
+
case "search_urovosdk_docs":
|
|
192
|
+
return { query: args.query, results: await tools.searchDocs(args.query, args.limit ?? 5) };
|
|
193
|
+
case "list_urovosdk_docs":
|
|
194
|
+
return await tools.listDocs();
|
|
195
|
+
case "get_urovosdk_doc":
|
|
196
|
+
return { path: args.path, content: await tools.getDoc(args.path) };
|
|
197
|
+
case "lookup_urovosdk_error_code":
|
|
198
|
+
return tools.lookupErrorCode(args.code);
|
|
199
|
+
case "lookup_urovosdk_api":
|
|
200
|
+
return await tools.lookupApi(args);
|
|
201
|
+
case "lookup_urovosdk_emv":
|
|
202
|
+
return await tools.lookupEmv(args);
|
|
203
|
+
default:
|
|
204
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
package/src/server.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UrovoSDK Docs MCP — stdio 服务(方案 C:bundled docs)
|
|
3
|
+
*/
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { createUrovoSdkMcpServer } from "./lib/create-mcp-server.js";
|
|
6
|
+
|
|
7
|
+
export async function startMcpServer() {
|
|
8
|
+
const server = createUrovoSdkMcpServer();
|
|
9
|
+
await server.connect(new StdioServerTransport());
|
|
10
|
+
}
|