@cyanheads/openalex-mcp-server 0.2.7

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 (42) hide show
  1. package/CLAUDE.md +282 -0
  2. package/Dockerfile +99 -0
  3. package/LICENSE +190 -0
  4. package/README.md +188 -0
  5. package/dist/config/server-config.d.ts +13 -0
  6. package/dist/config/server-config.d.ts.map +1 -0
  7. package/dist/config/server-config.js +18 -0
  8. package/dist/config/server-config.js.map +1 -0
  9. package/dist/index.d.ts +7 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +20 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/mcp-server/prompts/definitions/literature-review.prompt.d.ts +13 -0
  14. package/dist/mcp-server/prompts/definitions/literature-review.prompt.d.ts.map +1 -0
  15. package/dist/mcp-server/prompts/definitions/literature-review.prompt.js +53 -0
  16. package/dist/mcp-server/prompts/definitions/literature-review.prompt.js.map +1 -0
  17. package/dist/mcp-server/prompts/definitions/research-landscape.prompt.d.ts +9 -0
  18. package/dist/mcp-server/prompts/definitions/research-landscape.prompt.d.ts.map +1 -0
  19. package/dist/mcp-server/prompts/definitions/research-landscape.prompt.js +42 -0
  20. package/dist/mcp-server/prompts/definitions/research-landscape.prompt.js.map +1 -0
  21. package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +33 -0
  22. package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -0
  23. package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +82 -0
  24. package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -0
  25. package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts +30 -0
  26. package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts.map +1 -0
  27. package/dist/mcp-server/tools/definitions/resolve-name.tool.js +73 -0
  28. package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -0
  29. package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +40 -0
  30. package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -0
  31. package/dist/mcp-server/tools/definitions/search-entities.tool.js +113 -0
  32. package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -0
  33. package/dist/services/openalex/openalex-service.d.ts +23 -0
  34. package/dist/services/openalex/openalex-service.d.ts.map +1 -0
  35. package/dist/services/openalex/openalex-service.js +254 -0
  36. package/dist/services/openalex/openalex-service.js.map +1 -0
  37. package/dist/services/openalex/types.d.ts +68 -0
  38. package/dist/services/openalex/types.d.ts.map +1 -0
  39. package/dist/services/openalex/types.js +15 -0
  40. package/dist/services/openalex/types.js.map +1 -0
  41. package/package.json +73 -0
  42. package/server.json +125 -0
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ <div align="center">
2
+ <h1>@cyanheads/openalex-mcp-server</h1>
3
+ <p><b>Access the OpenAlex academic research catalog - 270M+ publications through MCP. STDIO & Streamable HTTP.</b></p>
4
+ <p><b>3 Tools &middot; 2 Prompts</b></p>
5
+ </div>
6
+
7
+ <div align="center">
8
+
9
+ [![Version](https://img.shields.io/badge/Version-0.2.6-blue.svg?style=flat-square)](./CHANGELOG.md) [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-259?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.27.1-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/)
10
+
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## Tools
16
+
17
+ Three tools for querying the [OpenAlex](https://openalex.org) academic research catalog:
18
+
19
+ | Tool Name | Description |
20
+ |:----------|:------------|
21
+ | `openalex_search_entities` | Search, filter, sort, or retrieve by ID across all 8 entity types. |
22
+ | `openalex_analyze_trends` | Group-by aggregation for trend and distribution analysis. |
23
+ | `openalex_resolve_name` | Resolve a name or partial name to an OpenAlex ID via autocomplete. |
24
+
25
+ ### `openalex_search_entities`
26
+
27
+ Primary discovery and lookup tool. Covers all OpenAlex entity types (works, authors, sources, institutions, topics, keywords, publishers, funders).
28
+
29
+ - Retrieve a single entity by ID (OpenAlex ID, DOI, ORCID, ROR, PMID, PMCID, ISSN)
30
+ - Keyword search with boolean operators, quoted phrases, wildcards, and fuzzy matching
31
+ - Exact and AI semantic search modes
32
+ - Rich filter syntax: AND across fields, OR within fields (`us|gb`), NOT (`!us`), ranges (`2020-2024`), comparisons (`>100`)
33
+ - Field selection via `select` to control payload size (full work records are ~70KB each)
34
+ - Cursor pagination, sorting, up to 100 results per page
35
+
36
+ ---
37
+
38
+ ### `openalex_analyze_trends`
39
+
40
+ Aggregate entities into groups and count them for trend, distribution, and comparative analysis.
41
+
42
+ - Group by any supported field (publication year, OA status, institution, country, topic, etc.)
43
+ - Combine with filters to scope the population before aggregation
44
+ - Up to 200 groups per page with cursor pagination
45
+ - Supports `include_unknown` to show entities with no value for the grouped field
46
+
47
+ ---
48
+
49
+ ### `openalex_resolve_name`
50
+
51
+ Name-to-ID resolution via autocomplete. **Always use this before filtering by entity** — names are ambiguous, IDs are not.
52
+
53
+ - Returns up to 10 matches with disambiguation hints
54
+ - Accepts partial names and DOIs for direct lookup
55
+ - Optional entity type filter and field-level filters
56
+ - ~200ms response time
57
+
58
+ ## Prompts
59
+
60
+ | Prompt | Description |
61
+ |:-------|:------------|
62
+ | `openalex_literature_review` | Guides a systematic literature search: formulate query, search, filter, analyze citation network, synthesize findings. |
63
+ | `openalex_research_landscape` | Analyzes the research landscape for a topic: volume trends, top authors/institutions, open access rates, funding sources. |
64
+
65
+ ## Features
66
+
67
+ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):
68
+
69
+ - Declarative tool definitions — single file per tool, framework handles registration and validation
70
+ - Unified error handling across all tools
71
+ - Pluggable auth (`none`, `jwt`, `oauth`)
72
+ - Swappable storage backends via the framework (not currently used by this server)
73
+ - Structured logging with optional OpenTelemetry tracing
74
+ - Runs locally (stdio/HTTP) or in Docker from the same codebase
75
+
76
+ OpenAlex-specific:
77
+
78
+ - Typed API client with automatic ID normalization (DOI, ORCID, ROR, PMID, PMCID, ISSN, OpenAlex URLs)
79
+ - Abstract reconstruction from inverted indices
80
+ - Cancellation support via `AbortSignal`
81
+
82
+ ## Getting Started
83
+
84
+ Add to your MCP client config (e.g., `claude_desktop_config.json`):
85
+
86
+ ```json
87
+ {
88
+ "mcpServers": {
89
+ "openalex": {
90
+ "type": "stdio",
91
+ "command": "npx",
92
+ "args": ["-y", "@cyanheads/openalex-mcp-server"],
93
+ "env": {
94
+ "OPENALEX_API_KEY": "your-email@example.com"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ No API key needed — just provide your email to access the [polite pool](https://docs.openalex.org/how-to-use-the-api/rate-limits-and-authentication#the-polite-pool) (10x faster rate limits).
102
+
103
+ ### Prerequisites
104
+
105
+ - [Bun v1.3.0](https://bun.sh/) or higher (for development)
106
+
107
+ ### Installation
108
+
109
+ 1. **Clone the repository:**
110
+ ```sh
111
+ git clone https://github.com/cyanheads/openalex-mcp-server.git
112
+ ```
113
+
114
+ 2. **Navigate into the directory:**
115
+ ```sh
116
+ cd openalex-mcp-server
117
+ ```
118
+
119
+ 3. **Install dependencies:**
120
+ ```sh
121
+ bun install
122
+ ```
123
+
124
+ ## Configuration
125
+
126
+ | Variable | Description | Default |
127
+ |:---------|:------------|:--------|
128
+ | `OPENALEX_API_KEY` | **Required.** Email address for the OpenAlex [polite pool](https://docs.openalex.org/how-to-use-the-api/rate-limits-and-authentication#the-polite-pool) (faster rate limits). | — |
129
+ | `OPENALEX_BASE_URL` | OpenAlex API base URL. | `https://api.openalex.org` |
130
+ | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
131
+ | `MCP_HTTP_PORT` | Port for HTTP server. | `3010` |
132
+ | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
133
+ | `MCP_LOG_LEVEL` | Log level (RFC 5424). | `debug` |
134
+ | `OTEL_ENABLED` | Enable OpenTelemetry. | `false` |
135
+
136
+ ## Running the Server
137
+
138
+ ### Local Development
139
+
140
+ - **Build and run the production version:**
141
+ ```sh
142
+ bun run build
143
+ bun run start:http # or start:stdio
144
+ ```
145
+
146
+ - **Run checks and tests:**
147
+ ```sh
148
+ bun run devcheck # Lints, formats, type-checks
149
+ bun run test # Runs test suite
150
+ ```
151
+
152
+ ### Docker
153
+
154
+ ```sh
155
+ docker build -t openalex-mcp-server .
156
+ docker run -e OPENALEX_API_KEY=your-key -p 3010:3010 openalex-mcp-server
157
+ ```
158
+
159
+ ## Project Structure
160
+
161
+ | Directory | Purpose |
162
+ |:----------|:--------|
163
+ | `src/mcp-server/tools/definitions/` | Tool definitions (`*.tool.ts`). |
164
+ | `src/mcp-server/prompts/definitions/` | Prompt definitions (`*.prompt.ts`). |
165
+ | `src/services/openalex/` | OpenAlex API client service and domain types. |
166
+ | `src/config/` | Environment variable parsing and validation with Zod. |
167
+ | `tests/` | Unit and integration tests, mirroring the `src/` structure. |
168
+
169
+ ## Development Guide
170
+
171
+ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
172
+
173
+ - Handlers throw, framework catches — no `try/catch` in tool logic
174
+ - Use `ctx.log` for logging, `ctx.state` for storage
175
+ - Always resolve names to IDs via `openalex_resolve_name` before using them in filters
176
+
177
+ ## Contributing
178
+
179
+ Issues and pull requests are welcome. Run checks before submitting:
180
+
181
+ ```sh
182
+ bun run devcheck
183
+ bun run test
184
+ ```
185
+
186
+ ## License
187
+
188
+ Apache-2.0 — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview Server-specific configuration for OpenAlex API access.
3
+ * @module config/server-config
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ declare const ServerConfigSchema: z.ZodObject<{
7
+ apiKey: z.ZodString;
8
+ baseUrl: z.ZodDefault<z.ZodString>;
9
+ }, z.core.$strip>;
10
+ export type ServerConfig = z.infer<typeof ServerConfigSchema>;
11
+ export declare function getServerConfig(): ServerConfig;
12
+ export {};
13
+ //# sourceMappingURL=server-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C,QAAA,MAAM,kBAAkB;;;iBAGtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAM9C"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Server-specific configuration for OpenAlex API access.
3
+ * @module config/server-config
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ const ServerConfigSchema = z.object({
7
+ apiKey: z.string().min(1).describe('OpenAlex API key'),
8
+ baseUrl: z.string().url().default('https://api.openalex.org').describe('OpenAlex API base URL'),
9
+ });
10
+ let _config;
11
+ export function getServerConfig() {
12
+ _config ??= ServerConfigSchema.parse({
13
+ apiKey: process.env.OPENALEX_API_KEY,
14
+ baseUrl: process.env.OPENALEX_BASE_URL,
15
+ });
16
+ return _config;
17
+ }
18
+ //# sourceMappingURL=server-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IACtD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC;CAChG,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,kBAAkB,CAAC,KAAK,CAAC;QACnC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB;QACpC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;KACvC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview openalex-mcp-server MCP server entry point.
4
+ * @module index
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview openalex-mcp-server MCP server entry point.
4
+ * @module index
5
+ */
6
+ import { createApp } from '@cyanheads/mcp-ts-core';
7
+ import { literatureReviewPrompt } from './mcp-server/prompts/definitions/literature-review.prompt.js';
8
+ import { researchLandscapePrompt } from './mcp-server/prompts/definitions/research-landscape.prompt.js';
9
+ import { analyzeTrendsTool } from './mcp-server/tools/definitions/analyze-trends.tool.js';
10
+ import { resolveNameTool } from './mcp-server/tools/definitions/resolve-name.tool.js';
11
+ import { searchEntitiesTool } from './mcp-server/tools/definitions/search-entities.tool.js';
12
+ import { initOpenAlexService } from './services/openalex/openalex-service.js';
13
+ await createApp({
14
+ tools: [resolveNameTool, searchEntitiesTool, analyzeTrendsTool],
15
+ prompts: [literatureReviewPrompt, researchLandscapePrompt],
16
+ setup() {
17
+ initOpenAlexService();
18
+ },
19
+ });
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAC;AACtG,OAAO,EAAE,uBAAuB,EAAE,MAAM,+DAA+D,CAAC;AACxG,OAAO,EAAE,iBAAiB,EAAE,MAAM,uDAAuD,CAAC;AAC1F,OAAO,EAAE,eAAe,EAAE,MAAM,qDAAqD,CAAC;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,wDAAwD,CAAC;AAC5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAE9E,MAAM,SAAS,CAAC;IACd,KAAK,EAAE,CAAC,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,CAAC;IAC/D,OAAO,EAAE,CAAC,sBAAsB,EAAE,uBAAuB,CAAC;IAC1D,KAAK;QACH,mBAAmB,EAAE,CAAC;IACxB,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview Prompt template for guided systematic literature review using OpenAlex tools.
3
+ * @module mcp-server/prompts/definitions/literature-review.prompt
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ export declare const literatureReviewPrompt: import("@cyanheads/mcp-ts-core").PromptDefinition<z.ZodObject<{
7
+ topic: z.ZodString;
8
+ scope: z.ZodDefault<z.ZodEnum<{
9
+ narrow: "narrow";
10
+ broad: "broad";
11
+ }>>;
12
+ }, z.core.$strip>>;
13
+ //# sourceMappingURL=literature-review.prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"literature-review.prompt.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/prompts/definitions/literature-review.prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEnD,eAAO,MAAM,sBAAsB;;;;;;kBAiDjC,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @fileoverview Prompt template for guided systematic literature review using OpenAlex tools.
3
+ * @module mcp-server/prompts/definitions/literature-review.prompt
4
+ */
5
+ import { prompt, z } from '@cyanheads/mcp-ts-core';
6
+ export const literatureReviewPrompt = prompt('openalex_literature_review', {
7
+ description: 'Guides a systematic literature search: formulate query, search, filter, analyze citation network, synthesize findings.',
8
+ args: z.object({
9
+ topic: z.string().describe('Research topic or question to review.'),
10
+ scope: z
11
+ .enum(['narrow', 'broad'])
12
+ .default('narrow')
13
+ .describe('"narrow": focused on specific question. "broad": survey of the field.'),
14
+ }),
15
+ generate: (args) => [
16
+ {
17
+ role: 'user',
18
+ content: {
19
+ type: 'text',
20
+ text: `Conduct a systematic literature review on: "${args.topic}"
21
+
22
+ Scope: ${args.scope}
23
+
24
+ Follow this workflow using the OpenAlex tools:
25
+
26
+ 1. **Resolve entities** — Use openalex_resolve_name to identify key authors, institutions, or topics related to "${args.topic}". Collect their OpenAlex IDs.
27
+
28
+ 2. **Search literature** — Use openalex_search_entities to find relevant works.${args.scope === 'narrow'
29
+ ? `
30
+ - Use exact search mode with specific phrases
31
+ - Filter tightly by resolved topic IDs
32
+ - Focus on a single topic cluster`
33
+ : `
34
+ - Try keyword search for the topic
35
+ - Use semantic search for conceptually related work across subfields
36
+ - Search with multiple related topic IDs to capture adjacent areas`}
37
+ Use select to keep payloads manageable.
38
+
39
+ 3. **Identify key papers** — Sort by cited_by_count to find landmark works. Use openalex_search_entities with id to get full details on the most important ones.
40
+
41
+ 4. **Trace citations** — For key papers, search with the cited_by filter to find subsequent work, and the cites filter to find foundational work.
42
+
43
+ 5. **Analyze the landscape** — Use openalex_analyze_trends to understand:
44
+ - Publication volume over time (group_by: publication_year)
45
+ - Top contributing institutions (group_by: authorships.institutions.id)
46
+ - Open access availability (group_by: oa_status)
47
+
48
+ 6. **Synthesize** — Summarize findings: key themes, seminal papers, active research fronts, gaps in the literature, and methodological trends.`,
49
+ },
50
+ },
51
+ ],
52
+ });
53
+ //# sourceMappingURL=literature-review.prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"literature-review.prompt.js","sourceRoot":"","sources":["../../../../src/mcp-server/prompts/definitions/literature-review.prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,4BAA4B,EAAE;IACzE,WAAW,EACT,wHAAwH;IAC1H,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;QACnE,KAAK,EAAE,CAAC;aACL,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;aACzB,OAAO,CAAC,QAAQ,CAAC;aACjB,QAAQ,CAAC,uEAAuE,CAAC;KACrF,CAAC;IACF,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QAClB;YACE,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE;gBACP,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,+CAA+C,IAAI,CAAC,KAAK;;SAE9D,IAAI,CAAC,KAAK;;;;mHAIgG,IAAI,CAAC,KAAK;;iFAGnH,IAAI,CAAC,KAAK,KAAK,QAAQ;oBACrB,CAAC,CAAC;;;qCAGuB;oBACzB,CAAC,CAAC;;;sEAIN;;;;;;;;;;;;+IAYuI;aACxI;SACF;KACF;CACF,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Prompt template for quantitative research landscape analysis using OpenAlex tools.
3
+ * @module mcp-server/prompts/definitions/research-landscape.prompt
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ export declare const researchLandscapePrompt: import("@cyanheads/mcp-ts-core").PromptDefinition<z.ZodObject<{
7
+ topic: z.ZodString;
8
+ }, z.core.$strip>>;
9
+ //# sourceMappingURL=research-landscape.prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"research-landscape.prompt.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/prompts/definitions/research-landscape.prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEnD,eAAO,MAAM,uBAAuB;;kBAoClC,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @fileoverview Prompt template for quantitative research landscape analysis using OpenAlex tools.
3
+ * @module mcp-server/prompts/definitions/research-landscape.prompt
4
+ */
5
+ import { prompt, z } from '@cyanheads/mcp-ts-core';
6
+ export const researchLandscapePrompt = prompt('openalex_research_landscape', {
7
+ description: 'Analyzes the research landscape for a topic: volume trends, top authors/institutions, open access rates, funding sources.',
8
+ args: z.object({
9
+ topic: z.string().describe('Research area to analyze.'),
10
+ }),
11
+ generate: (args) => [
12
+ {
13
+ role: 'user',
14
+ content: {
15
+ type: 'text',
16
+ text: `Analyze the research landscape for: "${args.topic}"
17
+
18
+ Use the OpenAlex tools to build a quantitative profile:
19
+
20
+ 1. **Resolve** — Use openalex_resolve_name to find the OpenAlex topic ID for "${args.topic}".
21
+
22
+ 2. **Volume & trends** — Use openalex_analyze_trends to group works by publication_year, filtered to the resolved topic. Is the field growing, stable, or declining?
23
+
24
+ 3. **Top contributors** — Analyze by:
25
+ - authorships.institutions.id (which institutions lead?)
26
+ - authorships.institutions.country_code (geographic distribution)
27
+ - primary_location.source.id (which journals publish most?)
28
+
29
+ 4. **Open access** — Group by oa_status. What fraction is freely available?
30
+
31
+ 5. **Funding** — Group by awards.funder_id to identify major funders. The group keys are OpenAlex IDs — resolve the top funder IDs with openalex_resolve_name to get human-readable names.
32
+
33
+ 6. **Impact** — Search for the most-cited works (sort by -cited_by_count). Get details on the top 5.
34
+
35
+ 7. **Emerging fronts** — Filter to the last 2 years, sort by -cited_by_count to find rising papers. Compare topics to the broader field.
36
+
37
+ Present findings as a structured report with data tables and key takeaways.`,
38
+ },
39
+ },
40
+ ],
41
+ });
42
+ //# sourceMappingURL=research-landscape.prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"research-landscape.prompt.js","sourceRoot":"","sources":["../../../../src/mcp-server/prompts/definitions/research-landscape.prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,6BAA6B,EAAE;IAC3E,WAAW,EACT,2HAA2H;IAC7H,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;KACxD,CAAC;IACF,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QAClB;YACE,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE;gBACP,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,wCAAwC,IAAI,CAAC,KAAK;;;;gFAIgB,IAAI,CAAC,KAAK;;;;;;;;;;;;;;;;;4EAiBd;aACrE;SACF;KACF;CACF,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview Aggregation tool for trend and distribution analysis via OpenAlex group_by.
3
+ * @module mcp-server/tools/definitions/analyze-trends.tool
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ export declare const analyzeTrendsTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
7
+ entity_type: z.ZodEnum<{
8
+ works: "works";
9
+ authors: "authors";
10
+ sources: "sources";
11
+ institutions: "institutions";
12
+ topics: "topics";
13
+ keywords: "keywords";
14
+ publishers: "publishers";
15
+ funders: "funders";
16
+ }>;
17
+ group_by: z.ZodString;
18
+ filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
19
+ include_unknown: z.ZodDefault<z.ZodBoolean>;
20
+ cursor: z.ZodOptional<z.ZodString>;
21
+ }, z.core.$strip>, z.ZodObject<{
22
+ meta: z.ZodObject<{
23
+ count: z.ZodNumber;
24
+ groups_count: z.ZodNullable<z.ZodNumber>;
25
+ next_cursor: z.ZodNullable<z.ZodString>;
26
+ }, z.core.$strip>;
27
+ groups: z.ZodArray<z.ZodObject<{
28
+ key: z.ZodString;
29
+ key_display_name: z.ZodString;
30
+ count: z.ZodNumber;
31
+ }, z.core.$strip>>;
32
+ }, z.core.$strip>>;
33
+ //# sourceMappingURL=analyze-trends.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-trends.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/analyze-trends.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAIjD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4F5B,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview Aggregation tool for trend and distribution analysis via OpenAlex group_by.
3
+ * @module mcp-server/tools/definitions/analyze-trends.tool
4
+ */
5
+ import { tool, z } from '@cyanheads/mcp-ts-core';
6
+ import { getOpenAlexService } from '../../../services/openalex/openalex-service.js';
7
+ import { ENTITY_TYPES } from '../../../services/openalex/types.js';
8
+ export const analyzeTrendsTool = tool('openalex_analyze_trends', {
9
+ description: 'Aggregate OpenAlex entities into groups and count them. Use for trend analysis (group works by publication_year), distribution analysis (group by oa_status, type, country), and comparative analysis (group by institution or topic). Combine with filters to scope the analysis. Returns up to 200 groups per page — use cursor pagination for fields with many distinct values.',
10
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
11
+ input: z.object({
12
+ entity_type: z.enum(ENTITY_TYPES).describe('Entity type to aggregate.'),
13
+ group_by: z
14
+ .string()
15
+ .describe('Field to group by. Works examples: "publication_year", "type", "oa_status", "primary_topic.field.id", "authorships.institutions.country_code", "is_retracted". Authors: "last_known_institutions.country_code", "has_orcid". Sources: "type", "is_oa", "country_code". Not all fields support group_by — check entity docs if unsure.'),
16
+ filters: z
17
+ .record(z.string(), z.string())
18
+ .optional()
19
+ .describe('Filter criteria (same syntax as openalex_search_entities filters). Narrows the population before aggregation. Example: group works by year, filtered to a specific topic.'),
20
+ include_unknown: z
21
+ .boolean()
22
+ .default(false)
23
+ .describe('Include a group for entities with no value for the grouped field. Hidden by default.'),
24
+ cursor: z
25
+ .string()
26
+ .optional()
27
+ .describe('Pagination cursor from a previous response. Group-by returns max 200 groups per page. Pass cursor to get the next page. Note: paginated groups are sorted by key, not by count.'),
28
+ }),
29
+ output: z.object({
30
+ meta: z
31
+ .object({
32
+ count: z.number().describe('Total entities matching the filters (before grouping).'),
33
+ groups_count: z.number().nullable().describe('Number of groups on this page (max 200).'),
34
+ next_cursor: z
35
+ .string()
36
+ .nullable()
37
+ .describe('Cursor for next page of groups. null if no more groups.'),
38
+ })
39
+ .describe('Aggregation metadata.'),
40
+ groups: z
41
+ .array(z.object({
42
+ key: z.string().describe('Group key (OpenAlex ID or raw value).'),
43
+ key_display_name: z.string().describe('Human-readable group label.'),
44
+ count: z.number().describe('Number of entities in this group.'),
45
+ }))
46
+ .describe('Aggregation groups with counts.'),
47
+ }),
48
+ async handler(input, ctx) {
49
+ const service = getOpenAlexService();
50
+ const result = await service.analyze({
51
+ entityType: input.entity_type,
52
+ groupBy: input.group_by,
53
+ filters: input.filters,
54
+ includeUnknown: input.include_unknown,
55
+ cursor: input.cursor,
56
+ }, ctx);
57
+ ctx.log.info('Trend analysis completed', {
58
+ entityType: input.entity_type,
59
+ groupBy: input.group_by,
60
+ totalCount: result.meta.count,
61
+ groupCount: result.groups.length,
62
+ });
63
+ return result;
64
+ },
65
+ format: (result) => {
66
+ if (result.groups.length === 0) {
67
+ return [{ type: 'text', text: 'No groups found.' }];
68
+ }
69
+ const sorted = [...result.groups].sort((a, b) => b.count - a.count);
70
+ const MAX_DISPLAY = 50;
71
+ const display = sorted.slice(0, MAX_DISPLAY);
72
+ const lines = display.map((g) => `${g.key_display_name}: ${g.count.toLocaleString()}`);
73
+ const truncated = sorted.length > MAX_DISPLAY ? `\n\n...and ${sorted.length - MAX_DISPLAY} more groups` : '';
74
+ return [
75
+ {
76
+ type: 'text',
77
+ text: `${result.meta.count.toLocaleString()} total entities across ${result.groups.length} groups:\n\n${lines.join('\n')}${truncated}`,
78
+ },
79
+ ];
80
+ },
81
+ });
82
+ //# sourceMappingURL=analyze-trends.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-trends.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/analyze-trends.tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @fileoverview Tool for resolving names to OpenAlex IDs via autocomplete.
3
+ * @module mcp-server/tools/definitions/resolve-name.tool
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ export declare const resolveNameTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
7
+ entity_type: z.ZodOptional<z.ZodEnum<{
8
+ works: "works";
9
+ authors: "authors";
10
+ sources: "sources";
11
+ institutions: "institutions";
12
+ topics: "topics";
13
+ keywords: "keywords";
14
+ publishers: "publishers";
15
+ funders: "funders";
16
+ }>>;
17
+ query: z.ZodString;
18
+ filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
19
+ }, z.core.$strip>, z.ZodObject<{
20
+ results: z.ZodArray<z.ZodObject<{
21
+ id: z.ZodString;
22
+ external_id: z.ZodNullable<z.ZodString>;
23
+ display_name: z.ZodString;
24
+ entity_type: z.ZodString;
25
+ cited_by_count: z.ZodNumber;
26
+ works_count: z.ZodNullable<z.ZodNumber>;
27
+ hint: z.ZodNullable<z.ZodString>;
28
+ }, z.core.$strip>>;
29
+ }, z.core.$strip>>;
30
+ //# sourceMappingURL=resolve-name.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-name.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/resolve-name.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAIjD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;kBAkF1B,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @fileoverview Tool for resolving names to OpenAlex IDs via autocomplete.
3
+ * @module mcp-server/tools/definitions/resolve-name.tool
4
+ */
5
+ import { tool, z } from '@cyanheads/mcp-ts-core';
6
+ import { getOpenAlexService } from '../../../services/openalex/openalex-service.js';
7
+ import { ENTITY_TYPES } from '../../../services/openalex/types.js';
8
+ export const resolveNameTool = tool('openalex_resolve_name', {
9
+ description: 'Resolve a name or partial name to an OpenAlex ID. Returns up to 10 matches with disambiguation hints. ALWAYS use this before filtering by entity — names are ambiguous, IDs are not. Also accepts DOIs directly for quick lookup. Response time ~200ms.',
10
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
11
+ input: z.object({
12
+ entity_type: z
13
+ .enum(ENTITY_TYPES)
14
+ .optional()
15
+ .describe('Entity type to search. Omit for cross-entity search (useful when entity type is unknown).'),
16
+ query: z
17
+ .string()
18
+ .min(1)
19
+ .describe('Name or partial name to resolve. Also accepts DOIs for quick lookup.'),
20
+ filters: z
21
+ .record(z.string(), z.string())
22
+ .optional()
23
+ .describe('Narrow autocomplete results with filters. Example: restrict to a specific country or publication year range.'),
24
+ }),
25
+ output: z.object({
26
+ results: z
27
+ .array(z.object({
28
+ id: z.string().describe('OpenAlex ID.'),
29
+ external_id: z
30
+ .string()
31
+ .nullable()
32
+ .describe('Canonical external ID (DOI, ORCID, ROR, ISSN).'),
33
+ display_name: z.string().describe('Human-readable name.'),
34
+ entity_type: z
35
+ .string()
36
+ .describe('Entity type (work, author, source, institution, etc.).'),
37
+ cited_by_count: z
38
+ .number()
39
+ .describe('Citation count (direct for works, aggregate for others).'),
40
+ works_count: z
41
+ .number()
42
+ .nullable()
43
+ .describe('Associated works. null for works themselves.'),
44
+ hint: z
45
+ .string()
46
+ .nullable()
47
+ .describe('Disambiguation context: author names (works), last institution (authors), host org (sources), location (institutions).'),
48
+ }))
49
+ .describe('Autocomplete matches, up to 10.'),
50
+ }),
51
+ async handler(input, ctx) {
52
+ const service = getOpenAlexService();
53
+ const result = await service.autocomplete({
54
+ entityType: input.entity_type,
55
+ query: input.query,
56
+ filters: input.filters,
57
+ }, ctx);
58
+ ctx.log.info('Name resolved', {
59
+ query: input.query,
60
+ entityType: input.entity_type ?? 'all',
61
+ matchCount: result.results.length,
62
+ });
63
+ return result;
64
+ },
65
+ format: (result) => {
66
+ if (result.results.length === 0) {
67
+ return [{ type: 'text', text: 'No matches found.' }];
68
+ }
69
+ const lines = result.results.map((r) => `${r.display_name} (${r.entity_type}) — ${r.id}${r.hint ? ` [${r.hint}]` : ''}`);
70
+ return [{ type: 'text', text: lines.join('\n') }];
71
+ },
72
+ });
73
+ //# sourceMappingURL=resolve-name.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-name.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/resolve-name.tool.ts"],"names":[],"mappings":"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"}