aqc-mcp 1.0.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.
Files changed (48) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +422 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +182 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/tools/ads.d.ts +4 -0
  8. package/dist/tools/ads.d.ts.map +1 -0
  9. package/dist/tools/ads.js +31 -0
  10. package/dist/tools/ads.js.map +1 -0
  11. package/dist/tools/alma.d.ts +4 -0
  12. package/dist/tools/alma.d.ts.map +1 -0
  13. package/dist/tools/alma.js +40 -0
  14. package/dist/tools/alma.js.map +1 -0
  15. package/dist/tools/call.d.ts +15 -0
  16. package/dist/tools/call.d.ts.map +1 -0
  17. package/dist/tools/call.js +31 -0
  18. package/dist/tools/call.js.map +1 -0
  19. package/dist/tools/gaia.d.ts +4 -0
  20. package/dist/tools/gaia.d.ts.map +1 -0
  21. package/dist/tools/gaia.js +31 -0
  22. package/dist/tools/gaia.js.map +1 -0
  23. package/dist/tools/index.d.ts +3 -0
  24. package/dist/tools/index.d.ts.map +1 -0
  25. package/dist/tools/index.js +72 -0
  26. package/dist/tools/index.js.map +1 -0
  27. package/dist/tools/simbad.d.ts +4 -0
  28. package/dist/tools/simbad.d.ts.map +1 -0
  29. package/dist/tools/simbad.js +29 -0
  30. package/dist/tools/simbad.js.map +1 -0
  31. package/dist/tools/vizier.d.ts +4 -0
  32. package/dist/tools/vizier.d.ts.map +1 -0
  33. package/dist/tools/vizier.js +38 -0
  34. package/dist/tools/vizier.js.map +1 -0
  35. package/dist/utils/executor.d.ts +12 -0
  36. package/dist/utils/executor.d.ts.map +1 -0
  37. package/dist/utils/executor.js +48 -0
  38. package/dist/utils/executor.js.map +1 -0
  39. package/package.json +38 -0
  40. package/src/index.ts +199 -0
  41. package/src/tools/ads.ts +41 -0
  42. package/src/tools/alma.ts +47 -0
  43. package/src/tools/gaia.ts +42 -0
  44. package/src/tools/index.ts +95 -0
  45. package/src/tools/simbad.ts +40 -0
  46. package/src/tools/vizier.ts +46 -0
  47. package/src/utils/executor.ts +59 -0
  48. package/tsconfig.json +20 -0
package/src/index.ts ADDED
@@ -0,0 +1,199 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { executeAqc } from './utils/executor.js';
4
+ import { listTools } from './tools/index.js';
5
+
6
+ const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000;
7
+
8
+ async function createMcpServer() {
9
+ const server = new McpServer(
10
+ {
11
+ name: 'astroquery-mcp',
12
+ version: '1.0.0',
13
+ },
14
+ {
15
+ capabilities: {
16
+ tools: {},
17
+ },
18
+ }
19
+ );
20
+
21
+ await listTools(server);
22
+
23
+ return server;
24
+ }
25
+
26
+ // Helper function to build command arguments
27
+ function buildArgs(module: string, command: string, mainArg: string, extraArgs: (string | null | undefined)[]): string[] {
28
+ const args: string[] = [module, command, mainArg];
29
+ for (const arg of extraArgs) {
30
+ if (arg) args.push(arg);
31
+ }
32
+ return args;
33
+ }
34
+
35
+ async function startHttpServer() {
36
+ const express = (await import('express')).default;
37
+ const cors = (await import('cors')).default;
38
+
39
+ const app = express();
40
+ app.use(cors());
41
+ app.use(express.json());
42
+
43
+ const mcpServer = await createMcpServer();
44
+
45
+ // GET /health - Health check
46
+ app.get('/health', (req: any, res: any) => {
47
+ res.json({ status: 'ok' });
48
+ });
49
+
50
+ // GET / - Server info
51
+ app.get('/', (req: any, res: any) => {
52
+ res.json({
53
+ name: 'astroquery-mcp',
54
+ version: '1.0.0',
55
+ protocol: 'MCP',
56
+ transport: 'HTTP',
57
+ endpoints: {
58
+ health: '/health',
59
+ sse: '/sse',
60
+ tools: '/tools'
61
+ }
62
+ });
63
+ });
64
+
65
+ // GET /sse - SSE endpoint
66
+ app.get('/sse', async (req: any, res: any) => {
67
+ res.setHeader('Content-Type', 'text/event-stream');
68
+ res.setHeader('Cache-Control', 'no-cache');
69
+ res.setHeader('Connection', 'keep-alive');
70
+
71
+ res.write(`data: ${JSON.stringify({ type: 'connected' })}\n\n`);
72
+
73
+ req.on('close', () => {
74
+ res.end();
75
+ });
76
+ });
77
+
78
+ // POST /tools/list - List available tools
79
+ app.get('/tools/list', (req: any, res: any) => {
80
+ res.json({ message: 'Use MCP protocol to list tools' });
81
+ });
82
+
83
+ // POST /tools/call - Call a tool directly (for HTTP clients)
84
+ app.post('/tools/call', async (req: any, res: any) => {
85
+ try {
86
+ const { name, arguments: args } = req.body;
87
+
88
+ if (!name) {
89
+ return res.status(400).json({ error: 'Tool name is required' });
90
+ }
91
+
92
+ const cmdArgs: string[] = [];
93
+ switch (name) {
94
+ case 'simbad_query':
95
+ cmdArgs.push('simbad', 'object', args.object_name);
96
+ break;
97
+ case 'vizier_query':
98
+ cmdArgs.push('vizier', 'object', args.target, args.radius);
99
+ if (args.catalog) cmdArgs.push('--catalog', args.catalog);
100
+ break;
101
+ case 'alma_query':
102
+ cmdArgs.push('alma', 'object', args.object_name);
103
+ break;
104
+ case 'ads_query':
105
+ cmdArgs.push('ads', 'query');
106
+ if (args.latest) cmdArgs.push('--latest');
107
+ if (args.review) cmdArgs.push('--review');
108
+ if (args.query) cmdArgs.push(args.query);
109
+ break;
110
+ case 'gaia_cone_search':
111
+ cmdArgs.push('gaia', 'cone-search', args.target);
112
+ if (args.radius) cmdArgs.push('--radius', args.radius);
113
+ break;
114
+ default:
115
+ throw new Error(`Tool '${name}' not found`);
116
+ }
117
+
118
+ const result = await executeAqc(cmdArgs);
119
+ res.json({ content: [{ type: 'text', text: result.output || '' }] });
120
+ } catch (error: any) {
121
+ res.status(500).json({ error: error.message });
122
+ }
123
+ });
124
+
125
+ // POST /tools/batch - Call multiple tools in parallel
126
+ app.post('/tools/batch', async (req: any, res: any) => {
127
+ try {
128
+ const { tools } = req.body;
129
+
130
+ if (!Array.isArray(tools)) {
131
+ return res.status(400).json({ error: 'Tools must be an array' });
132
+ }
133
+
134
+ const results = await Promise.all(
135
+ tools.map(async ({ name, arguments: args }: any) => {
136
+ try {
137
+ const cmdArgs: string[] = [];
138
+ switch (name) {
139
+ case 'simbad_query':
140
+ cmdArgs.push('simbad', 'object', args.object_name);
141
+ break;
142
+ case 'vizier_query':
143
+ cmdArgs.push('vizier', 'object', args.target, args.radius);
144
+ if (args.catalog) cmdArgs.push('--catalog', args.catalog);
145
+ break;
146
+ case 'alma_query':
147
+ cmdArgs.push('alma', 'object', args.object_name);
148
+ break;
149
+ case 'ads_query':
150
+ cmdArgs.push('ads', 'query');
151
+ if (args.latest) cmdArgs.push('--latest');
152
+ if (args.review) cmdArgs.push('--review');
153
+ if (args.query) cmdArgs.push(args.query);
154
+ break;
155
+ case 'gaia_cone_search':
156
+ cmdArgs.push('gaia', 'cone-search', args.target);
157
+ if (args.radius) cmdArgs.push('--radius', args.radius);
158
+ break;
159
+ default:
160
+ throw new Error(`Tool '${name}' not found`);
161
+ }
162
+
163
+ const result = await executeAqc(cmdArgs);
164
+ return { name, success: true, result: { content: [{ type: 'text', text: result.output || '' }] } };
165
+ } catch (error: any) {
166
+ return { name, success: false, error: error.message };
167
+ }
168
+ })
169
+ );
170
+
171
+ res.json(results);
172
+ } catch (error: any) {
173
+ res.status(500).json({ error: error.message });
174
+ }
175
+ });
176
+
177
+ app.listen(PORT, () => {
178
+ console.log(`Astroquery MCP Server running on http://localhost:${PORT}`);
179
+ console.log(`Health check: http://localhost:${PORT}/health`);
180
+ console.log(`Tools list: http://localhost:${PORT}/tools/list`);
181
+ console.log(`SSE endpoint: http://localhost:${PORT}/sse`);
182
+ });
183
+ }
184
+
185
+ async function startStdioServer() {
186
+ const server = await createMcpServer();
187
+ const transport = new StdioServerTransport();
188
+
189
+ await server.connect(transport);
190
+ }
191
+
192
+ // Start server based on transport mode
193
+ const mode = process.env.MCP_TRANSPORT || 'stdio';
194
+
195
+ if (mode === 'http') {
196
+ startHttpServer();
197
+ } else {
198
+ startStdioServer();
199
+ }
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { executeAqc } from '../utils/executor.js';
3
+
4
+ let executeAdsQuery: (query?: string, lang?: string) => Promise<string>;
5
+
6
+ export function registerAdsTools(server: any) {
7
+ executeAdsQuery = async (query?: string, lang?: string) => {
8
+ const args = ['ads', 'query'];
9
+ if (query) args.push('--query', query);
10
+ if (lang) args.push('--lang', lang);
11
+
12
+ const result = await executeAqc(args);
13
+
14
+ if (!result.success) {
15
+ throw new Error(result.error);
16
+ }
17
+
18
+ return result.output || '';
19
+ };
20
+
21
+ server.tool(
22
+ 'ads_query',
23
+ 'Query NASA Astrophysics Data System for papers and bibliographic information',
24
+ {
25
+ query: z.string().optional().describe('Search query (e.g., "latest papers", "highly cited reviews")'),
26
+ lang: z.enum(['en', 'zh']).optional().describe('Output language'),
27
+ },
28
+ async ({ query, lang }: any) => {
29
+ const output = await executeAdsQuery(query, lang);
30
+
31
+ return {
32
+ content: [{
33
+ type: 'text',
34
+ text: output
35
+ }]
36
+ };
37
+ }
38
+ );
39
+ }
40
+
41
+ export { executeAdsQuery };
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ import { executeAqc } from '../utils/executor.js';
3
+
4
+ let executeAlmaQuery: (ra?: number, dec?: number, radius?: number, catalog?: string, lang?: string) => Promise<string>;
5
+
6
+ export function registerAlmaTools(server: any) {
7
+ executeAlmaQuery = async (ra?: number, dec?: number, radius?: number, catalog?: string, lang?: string) => {
8
+ const args = ['alma', 'query'];
9
+ if (ra !== undefined) args.push('--ra', ra.toString());
10
+ if (dec !== undefined) args.push('--dec', dec.toString());
11
+ if (radius !== undefined) args.push('--radius', radius.toString());
12
+ if (catalog) args.push('--catalog', catalog);
13
+ if (lang) args.push('--lang', lang);
14
+
15
+ const result = await executeAqc(args);
16
+
17
+ if (!result.success) {
18
+ throw new Error(result.error);
19
+ }
20
+
21
+ return result.output || '';
22
+ };
23
+
24
+ server.tool(
25
+ 'alma_query',
26
+ 'Query ALMA archive for observations',
27
+ {
28
+ ra: z.number().optional().describe('Right Ascension (degrees)'),
29
+ dec: z.number().optional().describe('Declination (degrees)'),
30
+ radius: z.number().optional().describe('Search radius (degrees)'),
31
+ catalog: z.string().optional().describe('Specific catalog to search'),
32
+ lang: z.enum(['en', 'zh']).optional().describe('Output language'),
33
+ },
34
+ async ({ ra, dec, radius, catalog, lang }: any) => {
35
+ const output = await executeAlmaQuery(ra, dec, radius, catalog, lang);
36
+
37
+ return {
38
+ content: [{
39
+ type: 'text',
40
+ text: output
41
+ }]
42
+ };
43
+ }
44
+ );
45
+ }
46
+
47
+ export { executeAlmaQuery };
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+ import { executeAqc } from '../utils/executor.js';
3
+
4
+ let executeGaiaConeSearch: (ra: number, dec: number, radius?: number, lang?: string) => Promise<string>;
5
+
6
+ export function registerGaiaTools(server: any) {
7
+ executeGaiaConeSearch = async (ra: number, dec: number, radius?: number, lang?: string) => {
8
+ const args = ['gaia', 'cone-search', '--ra', ra.toString(), '--dec', dec.toString(), '--radius', (radius || 1).toString()];
9
+ if (lang) args.push('--lang', lang);
10
+
11
+ const result = await executeAqc(args);
12
+
13
+ if (!result.success) {
14
+ throw new Error(result.error);
15
+ }
16
+
17
+ return result.output || '';
18
+ };
19
+
20
+ server.tool(
21
+ 'gaia_cone_search',
22
+ 'Query Gaia archive via cone search',
23
+ {
24
+ ra: z.number().describe('Right Ascension (degrees)'),
25
+ dec: z.number().describe('Declination (degrees)'),
26
+ radius: z.number().optional().default(1).describe('Search radius (degrees)'),
27
+ lang: z.enum(['en', 'zh']).optional().describe('Output language'),
28
+ },
29
+ async ({ ra, dec, radius, lang }: any) => {
30
+ const output = await executeGaiaConeSearch(ra, dec, radius, lang);
31
+
32
+ return {
33
+ content: [{
34
+ type: 'text',
35
+ text: output
36
+ }]
37
+ };
38
+ }
39
+ );
40
+ }
41
+
42
+ export { executeGaiaConeSearch };
@@ -0,0 +1,95 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { executeAqc } from '../utils/executor.js';
4
+
5
+ export async function listTools(mcpServer: McpServer) {
6
+ // SIMBAD - simbad object NAME
7
+ mcpServer.registerTool(
8
+ 'simbad_query',
9
+ {
10
+ description: 'Query the SIMBAD astronomical database for an object',
11
+ },
12
+ async ({ object_name, lang }: any) => {
13
+ const args = ['simbad', 'object', object_name];
14
+ if (lang) args.push('--lang', lang);
15
+
16
+ const result = await executeAqc(args);
17
+ if (!result.success) throw new Error(result.error);
18
+
19
+ return { content: [{ type: 'text', text: result.output || '' }] };
20
+ }
21
+ );
22
+
23
+ // VizieR - vizier object TARGET RADIUS
24
+ mcpServer.registerTool(
25
+ 'vizier_query',
26
+ {
27
+ description: 'Query the VizieR catalog database for an object or coordinates',
28
+ },
29
+ async ({ target, radius, catalog, lang }: any) => {
30
+ const args = ['vizier', 'object', target, radius];
31
+ if (catalog) args.push('--catalog', catalog);
32
+ if (lang) args.push('--lang', lang);
33
+
34
+ const result = await executeAqc(args);
35
+ if (!result.success) throw new Error(result.error);
36
+
37
+ return { content: [{ type: 'text', text: result.output || '' }] };
38
+ }
39
+ );
40
+
41
+ // ALMA - alma object OBJECT_NAME
42
+ mcpServer.registerTool(
43
+ 'alma_query',
44
+ {
45
+ description: 'Query the ALMA archive for observations of an object',
46
+ },
47
+ async ({ object_name, lang }: any) => {
48
+ const args = ['alma', 'object', object_name];
49
+ if (lang) args.push('--lang', lang);
50
+
51
+ const result = await executeAqc(args);
52
+ if (!result.success) throw new Error(result.error);
53
+
54
+ return { content: [{ type: 'text', text: result.output || '' }] };
55
+ }
56
+ );
57
+
58
+ // ADS - ads query [QUERY_STRING]
59
+ mcpServer.registerTool(
60
+ 'ads_query',
61
+ {
62
+ description: 'Query NASA Astrophysics Data System for papers and bibliographic information',
63
+ },
64
+ async ({ query, latest, review, lang }: any) => {
65
+ const args = ['ads', 'query'];
66
+
67
+ if (latest) args.push('--latest');
68
+ if (review) args.push('--review');
69
+ if (query) args.push(query);
70
+ if (lang) args.push('--lang', lang);
71
+
72
+ const result = await executeAqc(args);
73
+ if (!result.success) throw new Error(result.error);
74
+
75
+ return { content: [{ type: 'text', text: result.output || '' }] };
76
+ }
77
+ );
78
+
79
+ // Gaia - gaia cone-search TARGET --radius RADIUS
80
+ mcpServer.registerTool(
81
+ 'gaia_cone_search',
82
+ {
83
+ description: 'Query Gaia archive via cone search around an object or coordinates',
84
+ },
85
+ async ({ target, radius, lang }: any) => {
86
+ const args = ['gaia', 'cone-search', target, '--radius', radius || '10arcsec'];
87
+ if (lang) args.push('--lang', lang);
88
+
89
+ const result = await executeAqc(args);
90
+ if (!result.success) throw new Error(result.error);
91
+
92
+ return { content: [{ type: 'text', text: result.output || '' }] };
93
+ }
94
+ );
95
+ }
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ import { executeAqc } from '../utils/executor.js';
3
+
4
+ let executeSimbadQuery: (identifier: string, lang?: string) => Promise<string>;
5
+
6
+ export function registerSimbadTools(server: any) {
7
+ executeSimbadQuery = async (identifier: string, lang?: string) => {
8
+ const args = ['simbad', 'query', '--identifier', identifier];
9
+ if (lang) args.push('--lang', lang);
10
+
11
+ const result = await executeAqc(args);
12
+
13
+ if (!result.success) {
14
+ throw new Error(result.error);
15
+ }
16
+
17
+ return result.output || '';
18
+ };
19
+
20
+ server.tool(
21
+ 'simbad_query',
22
+ 'Query the SIMBAD astronomical database for an object identifier',
23
+ {
24
+ identifier: z.string().describe('Object identifier (e.g., "M31", "NGC 1234")'),
25
+ lang: z.enum(['en', 'zh']).optional().describe('Output language'),
26
+ },
27
+ async ({ identifier, lang }: any) => {
28
+ const output = await executeSimbadQuery(identifier, lang);
29
+
30
+ return {
31
+ content: [{
32
+ type: 'text',
33
+ text: output
34
+ }]
35
+ };
36
+ }
37
+ );
38
+ }
39
+
40
+ export { executeSimbadQuery };
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ import { executeAqc } from '../utils/executor.js';
3
+
4
+ let executeVizierQuery: (catalog: string, ra?: number, dec?: number, radius?: number, lang?: string) => Promise<string>;
5
+
6
+ export function registerVizierTools(server: any) {
7
+ executeVizierQuery = async (catalog: string, ra?: number, dec?: number, radius?: number, lang?: string) => {
8
+ const args = ['vizier', 'query', '--catalog', catalog];
9
+ if (ra !== undefined) args.push('--ra', ra.toString());
10
+ if (dec !== undefined) args.push('--dec', dec.toString());
11
+ if (radius !== undefined) args.push('--radius', radius.toString());
12
+ if (lang) args.push('--lang', lang);
13
+
14
+ const result = await executeAqc(args);
15
+
16
+ if (!result.success) {
17
+ throw new Error(result.error);
18
+ }
19
+
20
+ return result.output || '';
21
+ };
22
+
23
+ server.tool(
24
+ 'vizier_query',
25
+ 'Query the VizieR catalog database for a specific catalog',
26
+ {
27
+ catalog: z.string().describe('Catalog identifier (e.g., "VII/118")'),
28
+ ra: z.number().optional().describe('Right Ascension (degrees)'),
29
+ dec: z.number().optional().describe('Declination (degrees)'),
30
+ radius: z.number().optional().describe('Search radius (degrees)'),
31
+ lang: z.enum(['en', 'zh']).optional().describe('Output language'),
32
+ },
33
+ async ({ catalog, ra, dec, radius, lang }: any) => {
34
+ const output = await executeVizierQuery(catalog, ra, dec, radius, lang);
35
+
36
+ return {
37
+ content: [{
38
+ type: 'text',
39
+ text: output
40
+ }]
41
+ };
42
+ }
43
+ );
44
+ }
45
+
46
+ export { executeVizierQuery };
@@ -0,0 +1,59 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ const execAsync = promisify(exec);
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ export interface AqcResult {
11
+ success: boolean;
12
+ output?: string;
13
+ error?: string;
14
+ }
15
+
16
+ /**
17
+ * Execute astroquery-cli command
18
+ * @param args Command arguments (e.g., ['simbad', 'object', 'M31'])
19
+ * @returns Execution result
20
+ */
21
+ export async function executeAqc(args: string[]): Promise<AqcResult> {
22
+ try {
23
+ // Determine the CLI project root (parent of astroquery-mcp)
24
+ const cliRoot = join(__dirname, '..', '..', '..');
25
+
26
+ // Build command - use poetry run from CLI root directory
27
+ const command = `cd ${cliRoot} && poetry run aqc ${args.join(' ')}`;
28
+
29
+ // Pass through ADS_API_KEY if set
30
+ const env = { ...process.env };
31
+ if (process.env.ADS_API_KEY) {
32
+ env.ADS_API_KEY = process.env.ADS_API_KEY;
33
+ }
34
+
35
+ const { stdout, stderr } = await execAsync(command, {
36
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
37
+ timeout: 60000, // 60s timeout
38
+ env,
39
+ shell: '/bin/bash'
40
+ });
41
+
42
+ if (stderr && !stdout) {
43
+ return {
44
+ success: false,
45
+ error: stderr
46
+ };
47
+ }
48
+
49
+ return {
50
+ success: true,
51
+ output: stdout || stderr
52
+ };
53
+ } catch (error: any) {
54
+ return {
55
+ success: false,
56
+ error: error.message || String(error)
57
+ };
58
+ }
59
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "node",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }