@seaxlab/archery-mcp 1.1.0 → 1.3.0
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 +19 -2
- package/dist/config.d.ts +6 -0
- package/dist/config.js +1 -1
- package/dist/index.js +1 -1
- package/dist/metadata-service.d.ts +5 -1
- package/dist/metadata-service.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,11 +20,27 @@
|
|
|
20
20
|
| `ARCHERY_METADATA_CACHE_ENABLED` | 否 | `true` | 是否启用实例/库元数据缓存 |
|
|
21
21
|
| `ARCHERY_METADATA_CACHE_TTL_SECONDS` | 否 | `604800` | 缓存有效期(秒),默认 7 天 |
|
|
22
22
|
| `ARCHERY_METADATA_CACHE_PATH` | 否 | `~/.cache/archery-mcp/metadata-cache.json` | 缓存文件路径 |
|
|
23
|
+
| `ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_NAMES` | 否 | 空 | 不写入缓存的实例名黑名单,多个用英文逗号分隔 |
|
|
24
|
+
| `ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_IDS` | 否 | 空 | 不写入缓存的实例 ID 黑名单,多个用英文逗号分隔 |
|
|
23
25
|
|
|
24
26
|
## MCP 客户端配置
|
|
25
27
|
|
|
26
28
|
在客户端的 MCP 配置里为进程设置与上表相同的 `env`。若使用已发布或已安装的包,可按下述配置。
|
|
27
29
|
|
|
30
|
+
## 元数据缓存
|
|
31
|
+
|
|
32
|
+
`archery_list_instances` 和 `resource_type=database` 的 `archery_list_resources` 默认使用本地缓存。实例列表会先从 Archery 全量拉取,再在写入本地缓存前按 `ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_NAMES` 和 `ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_IDS` 排除黑名单实例。
|
|
33
|
+
|
|
34
|
+
刷新数据库列表缓存时,如果没有显式指定 `instance_id` 或 `instance_name`,也会基于过滤后的实例列表刷新,避免对黑名单实例调用库列表接口。显式指定实例时按入参执行,便于临时排查单个实例。
|
|
35
|
+
|
|
36
|
+
默认缓存路径:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
~/.cache/archery-mcp/metadata-cache.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
默认缓存有效期为 7 天。需要实时刷新时,可以在工具入参中传 `force_refresh=true`,也可以调用 `archery_refresh_metadata_cache`。
|
|
43
|
+
|
|
28
44
|
### 使用已安装的包(推荐)
|
|
29
45
|
|
|
30
46
|
全局安装或作为项目依赖安装 `@seaxlab/archery-mcp` 后,可用 `npx` 拉起(会使用包内 `bin`):
|
|
@@ -40,10 +56,11 @@
|
|
|
40
56
|
"ARCHERY_USERNAME": "mcp_service",
|
|
41
57
|
"ARCHERY_PASSWORD": "replace-me",
|
|
42
58
|
"ARCHERY_DEFAULT_LIMIT": "100",
|
|
43
|
-
"ARCHERY_MAX_LIMIT": "500"
|
|
59
|
+
"ARCHERY_MAX_LIMIT": "500",
|
|
60
|
+
"ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_NAMES": "instance-a,instance-b",
|
|
61
|
+
"ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_IDS": "1,2"
|
|
44
62
|
}
|
|
45
63
|
}
|
|
46
64
|
}
|
|
47
65
|
}
|
|
48
66
|
```
|
|
49
|
-
|
package/dist/config.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ declare const envSchema: z.ZodObject<{
|
|
|
8
8
|
ARCHERY_METADATA_CACHE_ENABLED: z.ZodDefault<z.ZodEffects<z.ZodBoolean, boolean, unknown>>;
|
|
9
9
|
ARCHERY_METADATA_CACHE_TTL_SECONDS: z.ZodDefault<z.ZodNumber>;
|
|
10
10
|
ARCHERY_METADATA_CACHE_PATH: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
|
|
11
|
+
ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_NAMES: z.ZodEffects<z.ZodOptional<z.ZodString>, string[], string | undefined>;
|
|
12
|
+
ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_IDS: z.ZodEffects<z.ZodOptional<z.ZodString>, number[], string | undefined>;
|
|
11
13
|
}, "strip", z.ZodTypeAny, {
|
|
12
14
|
ARCHERY_BASE_URL: string;
|
|
13
15
|
ARCHERY_USERNAME: string;
|
|
@@ -16,6 +18,8 @@ declare const envSchema: z.ZodObject<{
|
|
|
16
18
|
ARCHERY_MAX_LIMIT: number;
|
|
17
19
|
ARCHERY_METADATA_CACHE_ENABLED: boolean;
|
|
18
20
|
ARCHERY_METADATA_CACHE_TTL_SECONDS: number;
|
|
21
|
+
ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_NAMES: string[];
|
|
22
|
+
ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_IDS: number[];
|
|
19
23
|
ARCHERY_METADATA_CACHE_PATH?: string | undefined;
|
|
20
24
|
}, {
|
|
21
25
|
ARCHERY_BASE_URL: string;
|
|
@@ -26,6 +30,8 @@ declare const envSchema: z.ZodObject<{
|
|
|
26
30
|
ARCHERY_METADATA_CACHE_ENABLED?: unknown;
|
|
27
31
|
ARCHERY_METADATA_CACHE_TTL_SECONDS?: number | undefined;
|
|
28
32
|
ARCHERY_METADATA_CACHE_PATH?: string | undefined;
|
|
33
|
+
ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_NAMES?: string | undefined;
|
|
34
|
+
ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_IDS?: string | undefined;
|
|
29
35
|
}>;
|
|
30
36
|
export type ArcheryMcpConfig = z.infer<typeof envSchema>;
|
|
31
37
|
export declare function loadConfig(env?: NodeJS.ProcessEnv): ArcheryMcpConfig;
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as
|
|
1
|
+
import{z as e}from"zod";const i=e.preprocess(n=>{if(!(n===void 0||n==="")){if(typeof n=="boolean")return n;if(typeof n=="string"){const r=n.trim().toLowerCase();if(["true","1","yes","y","on"].includes(r))return!0;if(["false","0","no","n","off"].includes(r))return!1}return n}},e.boolean()).default(!0),E=e.string().optional().transform(n=>{const r=n?.trim();return r||void 0}),A=e.string().optional().transform(n=>(n??"").split(",").map(r=>r.trim()).filter(Boolean)),a=e.string().optional().transform((n,r)=>(n??"").split(",").map(t=>t.trim()).filter(Boolean).map(t=>{if(!/^\d+$/.test(t))return r.addIssue({code:e.ZodIssueCode.custom,message:`Invalid positive integer: ${t}`}),e.NEVER;const o=Number(t);return!Number.isSafeInteger(o)||o<=0?(r.addIssue({code:e.ZodIssueCode.custom,message:`Invalid positive integer: ${t}`}),e.NEVER):o})),_=e.object({ARCHERY_BASE_URL:e.string().url(),ARCHERY_USERNAME:e.string().min(1),ARCHERY_PASSWORD:e.string().min(1),ARCHERY_DEFAULT_LIMIT:e.coerce.number().int().positive().default(100),ARCHERY_MAX_LIMIT:e.coerce.number().int().positive().default(500),ARCHERY_METADATA_CACHE_ENABLED:i,ARCHERY_METADATA_CACHE_TTL_SECONDS:e.coerce.number().int().positive().default(604800),ARCHERY_METADATA_CACHE_PATH:E,ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_NAMES:A,ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_IDS:a});export function loadConfig(n=process.env){const r=_.safeParse(n);if(!r.success){const o=r.error.issues.map(s=>`${s.path.join(".")}: ${s.message}`).join("; ");throw new Error(`ARCHERY_CONFIG_MISSING: ${o}`)}const t=r.data;if(t.ARCHERY_DEFAULT_LIMIT>t.ARCHERY_MAX_LIMIT)throw new Error("ARCHERY_CONFIG_MISSING: ARCHERY_DEFAULT_LIMIT must be less than or equal to ARCHERY_MAX_LIMIT");return{...t,ARCHERY_BASE_URL:t.ARCHERY_BASE_URL.replace(/\/+$/,"")}}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Server as c}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as i}from"@modelcontextprotocol/sdk/server/stdio.js";import{ArcheryClient as s}from"./archery-client.js";import{loadConfig as m}from"./config.js";import{MetadataCache as p}from"./metadata-cache.js";import{MetadataService as f}from"./metadata-service.js";import{registerTools as l}from"./tools.js";async function d(){const t=m(),e=new s(t),r=new p(t),a=new f(e,r),o=new c({name:"archery-mcp",version:"0.1.0"},{capabilities:{tools:{}}});l(o,e,t,a);const n=new i;await o.connect(n)}d().catch(t=>{const e=t instanceof Error?t.message:String(t);console.error(`archery-mcp failed to start: ${e}`),process.exit(1)});
|
|
2
|
+
import{Server as c}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as i}from"@modelcontextprotocol/sdk/server/stdio.js";import{ArcheryClient as s}from"./archery-client.js";import{loadConfig as m}from"./config.js";import{MetadataCache as p}from"./metadata-cache.js";import{MetadataService as f}from"./metadata-service.js";import{registerTools as l}from"./tools.js";async function d(){const t=m(),e=new s(t),r=new p(t),a=new f(e,r,t),o=new c({name:"archery-mcp",version:"0.1.0"},{capabilities:{tools:{}}});l(o,e,t,a);const n=new i;await o.connect(n)}d().catch(t=>{const e=t instanceof Error?t.message:String(t);console.error(`archery-mcp failed to start: ${e}`),process.exit(1)});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ArcheryClient } from "./archery-client.js";
|
|
2
|
+
import type { ArcheryMcpConfig } from "./config.js";
|
|
2
3
|
import type { CacheState, MetadataCache } from "./metadata-cache.js";
|
|
3
4
|
type RefreshScope = "instances" | "databases" | "all";
|
|
4
5
|
export interface CacheOptions {
|
|
@@ -33,10 +34,13 @@ export interface RefreshPayload {
|
|
|
33
34
|
export declare class MetadataService {
|
|
34
35
|
private readonly client;
|
|
35
36
|
private readonly cache;
|
|
36
|
-
|
|
37
|
+
private readonly excludedInstanceNames;
|
|
38
|
+
private readonly excludedInstanceIds;
|
|
39
|
+
constructor(client: ArcheryClient, cache: MetadataCache, config: ArcheryMcpConfig);
|
|
37
40
|
listInstances(options?: CacheOptions): Promise<CachedPayload>;
|
|
38
41
|
listDatabases(target: DatabaseTarget, options?: CacheOptions): Promise<CachedPayload>;
|
|
39
42
|
refreshMetadataCache(options?: RefreshOptions): Promise<RefreshPayload>;
|
|
40
43
|
private wrapRealtime;
|
|
44
|
+
private filterInstances;
|
|
41
45
|
}
|
|
42
46
|
export {};
|
package/dist/metadata-service.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export class MetadataService{client;cache;constructor(e,
|
|
1
|
+
export class MetadataService{client;cache;excludedInstanceNames;excludedInstanceIds;constructor(e,s,t){this.client=e,this.cache=s,this.excludedInstanceNames=new Set(t.ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_NAMES),this.excludedInstanceIds=new Set(t.ARCHERY_METADATA_CACHE_EXCLUDE_INSTANCE_IDS)}async listInstances(e={}){if(!this.cache.isEnabled())return this.wrapRealtime(await this.client.listInstances());if(!e.force_refresh){const t=await this.cache.readInstances();if(t)return t}const s=this.filterInstances(await this.client.listInstances());return this.cache.writeInstances(s)}async listDatabases(e,s={}){const t=h(e);if(!this.cache.isEnabled())return this.wrapRealtime(await this.client.listResources({...e,resource_type:"database"}));if(t&&!s.force_refresh){const a=await this.cache.readDatabases(t);if(a)return a}const i=await this.client.listResources({...e,resource_type:"database"});return t?this.cache.writeDatabases(t,i):this.wrapRealtime(i)}async refreshMetadataCache(e={}){const s=e.scope??"all",t={instances:!1,databases:[]};let i;if((s==="instances"||s==="all")&&(i=this.filterInstances(await this.client.listInstances()),this.cache.isEnabled()&&await this.cache.writeInstances(i),t.instances=!0),s==="databases"||s==="all"){const a=e.instance_id!==void 0||e.instance_name!==void 0?[{instance_id:e.instance_id,instance_name:e.instance_name}]:m(i??this.filterInstances(await this.client.listInstances()));for(const c of a)try{const r=await this.client.listResources({...c,resource_type:"database"}),o=h(c);this.cache.isEnabled()&&o&&await this.cache.writeDatabases(o,r),t.databases.push({...c,ok:!0})}catch(r){t.databases.push({...c,ok:!1,message:r instanceof Error?r.message:String(r)})}}return{cache:{enabled:this.cache.isEnabled(),path:this.cache.getPath()},refreshed:t}}wrapRealtime(e){return{cache:this.cache.realtimeState(),data:e}}filterInstances(e){return this.excludedInstanceNames.size===0&&this.excludedInstanceIds.size===0?e:d(e,this.excludedInstanceNames,this.excludedInstanceIds)}}function d(n,e,s){if(Array.isArray(n))return n.filter(a=>!u(a,e,s)).map(a=>d(a,e,s));if(!n||typeof n!="object")return n;const t=n,i={};for(const[a,c]of Object.entries(t))i[a]=d(c,e,s);return i}function u(n,e,s){if(!n||typeof n!="object")return!1;const t=n,i=t.instance_id??t.instanceId??t.id,a=t.instance_name??t.instanceName;if(typeof a=="string"&&e.has(a.trim()))return!0;const c=l(i);return c!==void 0&&s.has(c)}function l(n){if(typeof n=="number"&&Number.isSafeInteger(n)&&n>0)return n;if(typeof n=="string"&&/^\d+$/.test(n)){const e=Number(n);return Number.isSafeInteger(e)&&e>0?e:void 0}}function h(n){return n.instance_name||n.instance_id}function m(n){const e=new Map;return f(n,e),[...e.values()].sort((s,t)=>{const i=s.instance_name??"",a=t.instance_name??"";return i.localeCompare(a,"zh-Hans-CN")})}function f(n,e){if(Array.isArray(n)){for(const c of n)f(c,e);return}if(!n||typeof n!="object")return;const s=n,t=s.instance_id??s.instanceId??s.id,i=s.instance_name??s.instanceName,a={};if(typeof t=="number"&&Number.isInteger(t)&&t>0&&(a.instance_id=t),typeof t=="string"&&/^\d+$/.test(t)){const c=Number(t);c>0&&(a.instance_id=c)}typeof i=="string"&&i.trim()&&(a.instance_name=i),(a.instance_id!==void 0||a.instance_name!==void 0)&&e.set(a.instance_name??String(a.instance_id),a);for(const c of["data","rows","result","list"])c in s&&f(s[c],e)}
|