@seaxlab/archery-mcp 1.0.0 → 1.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Archery MCP
2
2
 
3
- 基于 MCP stdio、通过 Archery HTTP 接口访问内部 Archery。
3
+ 基于 MCP stdio、通过 Archery HTTP 接口访问 Archery。
4
4
 
5
5
  ## 前置条件
6
6
 
@@ -9,7 +9,8 @@ export interface QueryInput {
9
9
  tb_name?: string;
10
10
  }
11
11
  export interface ResourceInput {
12
- instance_id: number;
12
+ instance_id?: number;
13
+ instance_name?: string;
13
14
  resource_type: "database" | "schema" | "table" | "column";
14
15
  db_name?: string;
15
16
  schema_name?: string;
@@ -1 +1 @@
1
- import{CookieJar as h}from"./cookie-jar.js";import{ArcheryMcpError as n}from"./errors.js";export class ArcheryClient{config;jar=new h;authenticated=!1;constructor(t){this.config=t}async ping(){return await this.ensureAuthenticated(),{ok:!0,baseUrl:this.config.ARCHERY_BASE_URL,authenticated:this.authenticated}}async listInstances(){return this.requestJson("/api/v1/instance/",{method:"GET"})}async listResources(t){return this.requestJson("/api/v1/instance/resource/",{method:"POST",form:{instance_id:String(t.instance_id),resource_type:t.resource_type,db_name:t.db_name??"",schema_name:t.schema_name??"",tb_name:t.tb_name??""}})}async query(t){return this.requestJson("/query/",{method:"POST",form:{instance_name:t.instance_name,db_name:t.db_name,sql_content:t.sql_content,limit_num:String(t.limit_num),schema_name:t.schema_name??"",tb_name:t.tb_name??""}})}async queryLogs(t){const e=new URLSearchParams;for(const[s,a]of Object.entries(t))a!==void 0&&e.set(s,String(a));const r=e.size>0?`?${e.toString()}`:"";return this.requestJson(`/query/querylog/${r}`,{method:"GET"})}async ensureAuthenticated(){this.authenticated&&this.jar.hasSession()||await this.login()}async login(){this.authenticated=!1,this.jar.clearSession();const t=await fetch(this.url("/login/"),{method:"GET",redirect:"manual"});this.jar.setFromHeaders(t.headers);const e=this.jar.get("csrftoken");if(!e)throw new n("ARCHERY_AUTH_FAILED","\u65E0\u6CD5\u4ECE Archery \u767B\u5F55\u9875\u83B7\u53D6 csrftoken",t.status);const r=new URLSearchParams({username:this.config.ARCHERY_USERNAME,password:this.config.ARCHERY_PASSWORD}),s=await fetch(this.url("/authenticate/"),{method:"POST",headers:this.headers({"Content-Type":"application/x-www-form-urlencoded","X-CSRFToken":e}),body:r.toString(),redirect:"manual"});this.jar.setFromHeaders(s.headers);const a=await u(s);if(!s.ok||a.status!==0)throw new n("ARCHERY_AUTH_FAILED",typeof a.msg=="string"?a.msg:"Archery \u8D26\u53F7\u5BC6\u7801\u767B\u5F55\u5931\u8D25",s.status);if(a.data)throw new n("ARCHERY_AUTH_FAILED","Archery \u8FD4\u56DE 2FA \u4F1A\u8BDD\uFF0C\u4F46\u8BE5 MCP \u65B9\u6848\u8981\u6C42\u670D\u52A1\u8D26\u53F7\u5173\u95ED 2FA",s.status);if(!this.jar.hasSession())throw new n("ARCHERY_AUTH_FAILED","Archery \u767B\u5F55\u6210\u529F\u4F46\u672A\u8FD4\u56DE sessionid",s.status);this.authenticated=!0}async requestJson(t,e){await this.ensureAuthenticated();const r=await this.rawRequest(t,e);if(this.looksUnauthenticated(r.response,r.text)){await this.login();const s=await this.rawRequest(t,e);return this.parseSuccessfulJson(s.response,s.text)}return this.parseSuccessfulJson(r.response,r.text)}async rawRequest(t,e){const r=this.jar.get("csrftoken")??"",s={};let a;e.method==="POST"&&(s["Content-Type"]="application/x-www-form-urlencoded",s["X-CSRFToken"]=r,a=new URLSearchParams(e.form??{}).toString());const o=await fetch(this.url(t),{method:e.method,headers:this.headers(s),body:a,redirect:"manual"});this.jar.setFromHeaders(o.headers);const c=await o.text();return{response:o,text:c}}parseSuccessfulJson(t,e){if(t.status===403)throw new n("ARCHERY_PERMISSION_DENIED","Archery \u62D2\u7EDD\u8BBF\u95EE\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u8D26\u53F7\u6743\u9650",t.status);if(!t.ok)throw new n("ARCHERY_HTTP_ERROR",`Archery HTTP ${t.status}: ${e.slice(0,200)}`,t.status);try{return JSON.parse(e)}catch{throw new n("ARCHERY_RESPONSE_PARSE_ERROR",`Archery \u8FD4\u56DE\u975E JSON \u5185\u5BB9: ${e.slice(0,200)}`,t.status)}}looksUnauthenticated(t,e){return t.status===401?!0:t.status>=300&&t.status<400?(t.headers.get("location")??"").includes("/login"):t.status===200&&e.includes("Login To Archery")}headers(t){const e=new Headers(t),r=this.jar.header();return r&&e.set("Cookie",r),e.set("Accept","application/json, text/plain, */*"),e}url(t){if(t.startsWith("http://")||t.startsWith("https://"))return t;const e=t.startsWith("/")?t:`/${t}`;return`${this.config.ARCHERY_BASE_URL}${e}`}}async function u(i){const t=await i.text();try{return JSON.parse(t)}catch{throw new n("ARCHERY_RESPONSE_PARSE_ERROR",`Archery \u8FD4\u56DE\u975E JSON \u5185\u5BB9: ${t.slice(0,200)}`,i.status)}}
1
+ import{CookieJar as l}from"./cookie-jar.js";import{ArcheryMcpError as n}from"./errors.js";export class ArcheryClient{config;jar=new l;authenticated=!1;constructor(e){this.config=e}async ping(){return await this.ensureAuthenticated(),{ok:!0,baseUrl:this.config.ARCHERY_BASE_URL,authenticated:this.authenticated}}async listInstances(){return this.requestJson("/group/user_all_instances/",{method:"GET",form:{"tag_codes[]":"can_read"}})}async listResources(e){return this.requestJson("/instance/instance_resource/",{method:"GET",form:{instance_id:e.instance_id===void 0?"":String(e.instance_id),instance_name:e.instance_name??"",resource_type:e.resource_type,db_name:e.db_name??"",schema_name:e.schema_name??"",tb_name:e.tb_name??""}})}async query(e){return this.requestJson("/query/",{method:"POST",form:{instance_name:e.instance_name,db_name:e.db_name,sql_content:e.sql_content,limit_num:String(e.limit_num),schema_name:e.schema_name??"",tb_name:e.tb_name??""}})}async queryLogs(e){const t=new URLSearchParams;for(const[s,a]of Object.entries(e))a!==void 0&&t.set(s,String(a));const r=t.size>0?`?${t.toString()}`:"";return this.requestJson(`/query/querylog/${r}`,{method:"GET"})}async ensureAuthenticated(){this.authenticated&&this.jar.hasSession()||await this.login()}async login(){this.authenticated=!1,this.jar.clearSession();const e=await fetch(this.url("/login/"),{method:"GET",redirect:"manual"});this.jar.setFromHeaders(e.headers);const t=this.jar.get("csrftoken");if(!t)throw new n("ARCHERY_AUTH_FAILED","\u65E0\u6CD5\u4ECE Archery \u767B\u5F55\u9875\u83B7\u53D6 csrftoken",e.status);const r=new URLSearchParams({username:this.config.ARCHERY_USERNAME,password:this.config.ARCHERY_PASSWORD}),s=await fetch(this.url("/authenticate/"),{method:"POST",headers:this.headers({"Content-Type":"application/x-www-form-urlencoded","X-CSRFToken":t}),body:r.toString(),redirect:"manual"});this.jar.setFromHeaders(s.headers);const a=await R(s);if(!s.ok||a.status!==0)throw new n("ARCHERY_AUTH_FAILED",typeof a.msg=="string"?a.msg:"Archery \u8D26\u53F7\u5BC6\u7801\u767B\u5F55\u5931\u8D25",s.status);if(a.data)throw new n("ARCHERY_AUTH_FAILED","Archery \u8FD4\u56DE 2FA \u4F1A\u8BDD\uFF0C\u4F46\u8BE5 MCP \u65B9\u6848\u8981\u6C42\u670D\u52A1\u8D26\u53F7\u5173\u95ED 2FA",s.status);if(!this.jar.hasSession())throw new n("ARCHERY_AUTH_FAILED","Archery \u767B\u5F55\u6210\u529F\u4F46\u672A\u8FD4\u56DE sessionid",s.status);this.authenticated=!0}async requestJson(e,t){await this.ensureAuthenticated();const r=await this.rawRequest(e,t);if(this.looksUnauthenticated(r.response,r.text)){await this.login();const s=await this.rawRequest(e,t);return this.parseSuccessfulJson(s.response,s.text)}return this.parseSuccessfulJson(r.response,r.text)}async rawRequest(e,t){const r=this.jar.get("csrftoken")??"",s={};let a,i=e;if(t.method==="POST")s["Content-Type"]="application/x-www-form-urlencoded",s["X-CSRFToken"]=r,a=new URLSearchParams(t.form??{}).toString();else if(t.form&&Object.keys(t.form).length>0){const h=new URLSearchParams;for(const[f,u]of Object.entries(t.form))u!==""&&h.set(f,u);const m=i.includes("?")?"&":"?";i=h.size>0?`${i}${m}${h.toString()}`:i}const o=await fetch(this.url(i),{method:t.method,headers:this.headers(s),body:a,redirect:"manual"});this.jar.setFromHeaders(o.headers);const d=await o.text();return{response:o,text:d}}parseSuccessfulJson(e,t){if(e.status===403)throw new n("ARCHERY_PERMISSION_DENIED","Archery \u62D2\u7EDD\u8BBF\u95EE\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u8D26\u53F7\u6743\u9650",e.status);if(!e.ok)throw new n("ARCHERY_HTTP_ERROR",`Archery HTTP ${e.status}: ${t.slice(0,200)}`,e.status);try{return JSON.parse(t)}catch{throw new n("ARCHERY_RESPONSE_PARSE_ERROR",`Archery \u8FD4\u56DE\u975E JSON \u5185\u5BB9: ${t.slice(0,200)}`,e.status)}}looksUnauthenticated(e,t){return e.status===401?!0:e.status>=300&&e.status<400?(e.headers.get("location")??"").includes("/login"):e.status===200&&t.includes("Login To Archery")}headers(e){const t=new Headers(e),r=this.jar.header();return r&&t.set("Cookie",r),t.set("Accept","application/json, text/plain, */*"),t}url(e){if(e.startsWith("http://")||e.startsWith("https://"))return e;const t=e.startsWith("/")?e:`/${e}`;return`${this.config.ARCHERY_BASE_URL}${t}`}}async function R(c){const e=await c.text();try{return JSON.parse(e)}catch{throw new n("ARCHERY_RESPONSE_PARSE_ERROR",`Archery \u8FD4\u56DE\u975E JSON \u5185\u5BB9: ${e.slice(0,200)}`,c.status)}}
@@ -20,8 +20,8 @@ export declare class MetadataCache {
20
20
  getPath(): string;
21
21
  readInstances(): Promise<CacheReadResult | undefined>;
22
22
  writeInstances(data: unknown): Promise<CacheReadResult>;
23
- readDatabases(instanceId: number): Promise<CacheReadResult | undefined>;
24
- writeDatabases(instanceId: number, data: unknown): Promise<CacheReadResult>;
23
+ readDatabases(instanceKey: number | string): Promise<CacheReadResult | undefined>;
24
+ writeDatabases(instanceKey: number | string, data: unknown): Promise<CacheReadResult>;
25
25
  realtimeState(): CacheState;
26
26
  private toReadResult;
27
27
  private toCacheState;
@@ -7,6 +7,11 @@ export interface CacheOptions {
7
7
  export interface RefreshOptions {
8
8
  scope?: RefreshScope;
9
9
  instance_id?: number;
10
+ instance_name?: string;
11
+ }
12
+ export interface DatabaseTarget {
13
+ instance_id?: number;
14
+ instance_name?: string;
10
15
  }
11
16
  export interface CachedPayload {
12
17
  cache: CacheState;
@@ -19,8 +24,7 @@ export interface RefreshPayload {
19
24
  };
20
25
  refreshed: {
21
26
  instances: boolean;
22
- databases: Array<{
23
- instance_id: number;
27
+ databases: Array<DatabaseTarget & {
24
28
  ok: boolean;
25
29
  message?: string;
26
30
  }>;
@@ -31,7 +35,7 @@ export declare class MetadataService {
31
35
  private readonly cache;
32
36
  constructor(client: ArcheryClient, cache: MetadataCache);
33
37
  listInstances(options?: CacheOptions): Promise<CachedPayload>;
34
- listDatabases(instanceId: number, options?: CacheOptions): Promise<CachedPayload>;
38
+ listDatabases(target: DatabaseTarget, options?: CacheOptions): Promise<CachedPayload>;
35
39
  refreshMetadataCache(options?: RefreshOptions): Promise<RefreshPayload>;
36
40
  private wrapRealtime;
37
41
  }
@@ -1 +1 @@
1
- export class MetadataService{client;cache;constructor(t,a){this.client=t,this.cache=a}async listInstances(t={}){if(!this.cache.isEnabled())return this.wrapRealtime(await this.client.listInstances());if(!t.force_refresh){const e=await this.cache.readInstances();if(e)return e}const a=await this.client.listInstances();return this.cache.writeInstances(a)}async listDatabases(t,a={}){if(!this.cache.isEnabled())return this.wrapRealtime(await this.client.listResources({instance_id:t,resource_type:"database"}));if(!a.force_refresh){const s=await this.cache.readDatabases(t);if(s)return s}const e=await this.client.listResources({instance_id:t,resource_type:"database"});return this.cache.writeDatabases(t,e)}async refreshMetadataCache(t={}){const a=t.scope??"all",e={instances:!1,databases:[]};let s;if((a==="instances"||a==="all")&&(s=await this.client.listInstances(),this.cache.isEnabled()&&await this.cache.writeInstances(s),e.instances=!0),a==="databases"||a==="all"){const h=t.instance_id===void 0?d(s??await this.client.listInstances()):[t.instance_id];for(const n of h)try{const i=await this.client.listResources({instance_id:n,resource_type:"database"});this.cache.isEnabled()&&await this.cache.writeDatabases(n,i),e.databases.push({instance_id:n,ok:!0})}catch(i){e.databases.push({instance_id:n,ok:!1,message:i instanceof Error?i.message:String(i)})}}return{cache:{enabled:this.cache.isEnabled(),path:this.cache.getPath()},refreshed:e}}wrapRealtime(t){return{cache:this.cache.realtimeState(),data:t}}}function d(c){const t=new Set;return r(c,t),[...t].sort((a,e)=>a-e)}function r(c,t){if(Array.isArray(c)){for(const s of c)r(s,t);return}if(!c||typeof c!="object")return;const a=c,e=a.instance_id??a.instanceId??a.id;if(typeof e=="number"&&Number.isInteger(e)&&e>0&&t.add(e),typeof e=="string"&&/^\d+$/.test(e)){const s=Number(e);s>0&&t.add(s)}for(const s of["data","rows","result","list"])s in a&&r(a[s],t)}
1
+ export class MetadataService{client;cache;constructor(e,a){this.client=e,this.cache=a}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 a=await this.client.listInstances();return this.cache.writeInstances(a)}async listDatabases(e,a={}){const t=o(e);if(!this.cache.isEnabled())return this.wrapRealtime(await this.client.listResources({...e,resource_type:"database"}));if(t&&!a.force_refresh){const s=await this.cache.readDatabases(t);if(s)return s}const n=await this.client.listResources({...e,resource_type:"database"});return t?this.cache.writeDatabases(t,n):this.wrapRealtime(n)}async refreshMetadataCache(e={}){const a=e.scope??"all",t={instances:!1,databases:[]};let n;if((a==="instances"||a==="all")&&(n=await this.client.listInstances(),this.cache.isEnabled()&&await this.cache.writeInstances(n),t.instances=!0),a==="databases"||a==="all"){const s=e.instance_id!==void 0||e.instance_name!==void 0?[{instance_id:e.instance_id,instance_name:e.instance_name}]:f(n??await this.client.listInstances());for(const c of s)try{const r=await this.client.listResources({...c,resource_type:"database"}),d=o(c);this.cache.isEnabled()&&d&&await this.cache.writeDatabases(d,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}}}function o(i){return i.instance_name||i.instance_id}function f(i){const e=new Map;return h(i,e),[...e.values()].sort((a,t)=>{const n=a.instance_name??"",s=t.instance_name??"";return n.localeCompare(s,"zh-Hans-CN")})}function h(i,e){if(Array.isArray(i)){for(const c of i)h(c,e);return}if(!i||typeof i!="object")return;const a=i,t=a.instance_id??a.instanceId??a.id,n=a.instance_name??a.instanceName,s={};if(typeof t=="number"&&Number.isInteger(t)&&t>0&&(s.instance_id=t),typeof t=="string"&&/^\d+$/.test(t)){const c=Number(t);c>0&&(s.instance_id=c)}typeof n=="string"&&n.trim()&&(s.instance_name=n),(s.instance_id!==void 0||s.instance_name!==void 0)&&e.set(s.instance_name??String(s.instance_id),s);for(const c of["data","rows","result","list"])c in a&&h(a[c],e)}
package/dist/tools.js CHANGED
@@ -1 +1 @@
1
- import{CallToolRequestSchema as _,ListToolsRequestSchema as h}from"@modelcontextprotocol/sdk/types.js";import{z as e}from"zod";import{toErrorPayload as y}from"./errors.js";import{assertReadOnlySql as d,normalizeLimit as l}from"./sql-guard.js";const b=e.object({instance_id:e.number().int().positive(),resource_type:e.enum(["database","schema","table","column"]),db_name:e.string().optional(),schema_name:e.string().optional(),tb_name:e.string().optional(),force_refresh:e.boolean().optional()}),f=e.object({force_refresh:e.boolean().optional()}),g=e.object({scope:e.enum(["instances","databases","all"]).optional(),instance_id:e.number().int().positive().optional()}),R=e.object({instance_name:e.string().min(1),db_name:e.string().min(1),sql_content:e.string().min(1),limit_num:e.number().int().positive().optional(),schema_name:e.string().optional(),tb_name:e.string().optional()}),q=e.object({page:e.number().int().positive().optional(),limit:e.number().int().positive().optional(),instance_name:e.string().optional(),db_name:e.string().optional(),username:e.string().optional()});export function registerTools(i,a,o,m){i.setRequestHandler(h,async()=>({tools:[{name:"archery_ping",description:"Check Archery MCP configuration, reachability, and login state.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"archery_list_instances",description:"List Archery instances. Uses local metadata cache by default and refreshes when force_refresh is true or cache is expired.",inputSchema:{type:"object",properties:{force_refresh:{type:"boolean"}},additionalProperties:!1}},{name:"archery_list_resources",description:"List databases, schemas, tables, or columns for an Archery instance. Database discovery uses local metadata cache by default.",inputSchema:{type:"object",required:["instance_id","resource_type"],properties:{instance_id:{type:"number",minimum:1},resource_type:{type:"string",enum:["database","schema","table","column"]},db_name:{type:"string"},schema_name:{type:"string"},tb_name:{type:"string"},force_refresh:{type:"boolean"}},additionalProperties:!1}},{name:"archery_query",description:"Execute read-only SQL through Archery. Archery still enforces permissions, masking, limits, and audit logs.",inputSchema:{type:"object",required:["instance_name","db_name","sql_content"],properties:{instance_name:{type:"string",minLength:1},db_name:{type:"string",minLength:1},sql_content:{type:"string",minLength:1},limit_num:{type:"number",minimum:1},schema_name:{type:"string"},tb_name:{type:"string"}},additionalProperties:!1}},{name:"archery_query_logs",description:"Fetch Archery query logs visible to the service account.",inputSchema:{type:"object",properties:{page:{type:"number",minimum:1},limit:{type:"number",minimum:1},instance_name:{type:"string"},db_name:{type:"string"},username:{type:"string"}},additionalProperties:!1}},{name:"archery_refresh_metadata_cache",description:"Refresh local Archery metadata cache for instances and database lists.",inputSchema:{type:"object",properties:{scope:{type:"string",enum:["instances","databases","all"]},instance_id:{type:"number",minimum:1}},additionalProperties:!1}}]})),i.setRequestHandler(_,async p=>{const r=p.params.name,s=p.params.arguments??{};try{if(r==="archery_ping")return n(await a.ping());if(r==="archery_list_instances"){const t=f.parse(s);return n(await m.listInstances(t))}if(r==="archery_list_resources"){const t=b.parse(s);if(t.resource_type==="database")return n(await m.listDatabases(t.instance_id,{force_refresh:t.force_refresh}));const{force_refresh:c,...u}=t;return n(await a.listResources(u))}if(r==="archery_query"){const t=R.parse(s);d(t.sql_content);const c=l(t.limit_num,o.ARCHERY_DEFAULT_LIMIT,o.ARCHERY_MAX_LIMIT);return n(await a.query({...t,limit_num:c}))}if(r==="archery_query_logs"){const t=q.parse(s),c=t.limit===void 0?void 0:l(t.limit,o.ARCHERY_DEFAULT_LIMIT,o.ARCHERY_MAX_LIMIT);return n(await a.queryLogs({...t,limit:c}))}if(r==="archery_refresh_metadata_cache"){const t=g.parse(s);return n(await m.refreshMetadataCache(t))}return n({ok:!1,error:{code:"ARCHERY_HTTP_ERROR",message:`Unknown tool: ${r}`}},!0)}catch(t){return n(y(t),!0)}})}function n(i,a=!1){return{content:[{type:"text",text:JSON.stringify(i,null,2)}],...a?{isError:!0}:{}}}
1
+ import{CallToolRequestSchema as h,ListToolsRequestSchema as d}from"@modelcontextprotocol/sdk/types.js";import{z as e}from"zod";import{toErrorPayload as y}from"./errors.js";import{assertReadOnlySql as f,normalizeLimit as l}from"./sql-guard.js";const b=e.object({instance_id:e.number().int().positive().optional(),instance_name:e.string().min(1).optional(),resource_type:e.enum(["database","schema","table","column"]),db_name:e.string().optional(),schema_name:e.string().optional(),tb_name:e.string().optional(),force_refresh:e.boolean().optional()}).refine(a=>a.instance_id!==void 0||a.instance_name!==void 0,{message:"instance_id or instance_name is required"}),g=e.object({force_refresh:e.boolean().optional()}),R=e.object({scope:e.enum(["instances","databases","all"]).optional(),instance_id:e.number().int().positive().optional(),instance_name:e.string().min(1).optional()}),q=e.object({instance_name:e.string().min(1),db_name:e.string().min(1),sql_content:e.string().min(1),limit_num:e.number().int().positive().optional(),schema_name:e.string().optional(),tb_name:e.string().optional()}),L=e.object({page:e.number().int().positive().optional(),limit:e.number().int().positive().optional(),instance_name:e.string().optional(),db_name:e.string().optional(),username:e.string().optional()});export function registerTools(a,i,o,m){a.setRequestHandler(d,async()=>({tools:[{name:"archery_ping",description:"Check Archery MCP configuration, reachability, and login state.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"archery_list_instances",description:"List Archery readable instances. Uses local metadata cache by default and refreshes when force_refresh is true or cache is expired.",inputSchema:{type:"object",properties:{force_refresh:{type:"boolean"}},additionalProperties:!1}},{name:"archery_list_resources",description:"List databases, schemas, tables, or columns for an Archery instance. Database discovery uses local metadata cache by default.",inputSchema:{type:"object",required:["resource_type"],properties:{instance_id:{type:"number",minimum:1},instance_name:{type:"string",minLength:1},resource_type:{type:"string",enum:["database","schema","table","column"]},db_name:{type:"string"},schema_name:{type:"string"},tb_name:{type:"string"},force_refresh:{type:"boolean"}},additionalProperties:!1}},{name:"archery_query",description:"Execute read-only SQL through Archery. Archery still enforces permissions, masking, limits, and audit logs.",inputSchema:{type:"object",required:["instance_name","db_name","sql_content"],properties:{instance_name:{type:"string",minLength:1},db_name:{type:"string",minLength:1},sql_content:{type:"string",minLength:1},limit_num:{type:"number",minimum:1},schema_name:{type:"string"},tb_name:{type:"string"}},additionalProperties:!1}},{name:"archery_query_logs",description:"Fetch Archery query logs visible to the service account.",inputSchema:{type:"object",properties:{page:{type:"number",minimum:1},limit:{type:"number",minimum:1},instance_name:{type:"string"},db_name:{type:"string"},username:{type:"string"}},additionalProperties:!1}},{name:"archery_refresh_metadata_cache",description:"Refresh local Archery metadata cache for instances and database lists.",inputSchema:{type:"object",properties:{scope:{type:"string",enum:["instances","databases","all"]},instance_id:{type:"number",minimum:1},instance_name:{type:"string",minLength:1}},additionalProperties:!1}}]})),a.setRequestHandler(h,async p=>{const r=p.params.name,s=p.params.arguments??{};try{if(r==="archery_ping")return n(await i.ping());if(r==="archery_list_instances"){const t=g.parse(s);return n(await m.listInstances(t))}if(r==="archery_list_resources"){const t=b.parse(s);if(t.resource_type==="database"){const _={instance_id:t.instance_id,instance_name:t.instance_name};return n(await m.listDatabases(_,{force_refresh:t.force_refresh}))}const{force_refresh:c,...u}=t;return n(await i.listResources(u))}if(r==="archery_query"){const t=q.parse(s);f(t.sql_content);const c=l(t.limit_num,o.ARCHERY_DEFAULT_LIMIT,o.ARCHERY_MAX_LIMIT);return n(await i.query({...t,limit_num:c}))}if(r==="archery_query_logs"){const t=L.parse(s),c=t.limit===void 0?void 0:l(t.limit,o.ARCHERY_DEFAULT_LIMIT,o.ARCHERY_MAX_LIMIT);return n(await i.queryLogs({...t,limit:c}))}if(r==="archery_refresh_metadata_cache"){const t=R.parse(s);return n(await m.refreshMetadataCache(t))}return n({ok:!1,error:{code:"ARCHERY_HTTP_ERROR",message:`Unknown tool: ${r}`}},!0)}catch(t){return n(y(t),!0)}})}function n(a,i=!1){return{content:[{type:"text",text:JSON.stringify(a,null,2)}],...i?{isError:!0}:{}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seaxlab/archery-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Archery MCP server (Node.js, stdio)",
6
6
  "main": "dist/index.js",