mcp-server-opencitations 0.1.1 → 0.1.3

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 (3) hide show
  1. package/dist/index.js +107 -30
  2. package/dist/lib.js +146 -46
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,63 +5,140 @@
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
7
  import { z } from "zod";
8
+ // Import API client functions from lib.ts
9
+ import { getCitation, getCitationCount, getCitations, getReferenceCount, getReferences, getVenueCitationCount, formatDoi, formatIssn, formatCitationAsText, formatCitationsAsText, } from "./lib.js";
8
10
  // -----------------------------------------------------------------------------
9
- // CONFIGURATION: Environment Variables + Command Line Args
11
+ // CONFIGURATION
10
12
  // -----------------------------------------------------------------------------
11
- // Priority: Command line args > Environment variables
12
- // process.argv is an array of command-line arguments:
13
- // [0] = path to node
14
- // [1] = path to script
15
- // [2+] = user arguments
16
- //
17
- // Example: node dist/index.js --token=abc123
18
- // process.argv[2] would be "--token=abc123"
19
13
  function getAccessToken() {
20
- // Check command line args first
21
14
  const tokenArg = process.argv.find(arg => arg.startsWith('--token='));
22
15
  if (tokenArg) {
23
16
  return tokenArg.split('=')[1];
24
17
  }
25
- // Fall back to environment variable
26
18
  return process.env.OPENCITATIONS_ACCESS_TOKEN;
27
19
  }
28
20
  const ACCESS_TOKEN = getAccessToken();
29
21
  if (!ACCESS_TOKEN) {
30
- console.error("Warning: No access token set.");
22
+ console.error("Warning: No access token set (optional but recommended)");
31
23
  console.error(" Use: --token=YOUR_TOKEN");
32
24
  console.error(" Or set OPENCITATIONS_ACCESS_TOKEN environment variable");
33
25
  }
34
- // Server setup
26
+ // -----------------------------------------------------------------------------
27
+ // SERVER SETUP
28
+ // -----------------------------------------------------------------------------
35
29
  const server = new McpServer({
36
30
  name: "opencitations-server",
37
- version: "0.1.0",
31
+ version: "0.2.0",
38
32
  });
39
33
  // -----------------------------------------------------------------------------
40
34
  // TOOLS
41
35
  // -----------------------------------------------------------------------------
42
- server.registerTool("check_token", {
43
- description: "Check if the OpenCitations API token is configured",
44
- inputSchema: {},
45
- }, async () => {
46
- // Never expose the actual token! Just confirm if it's set
47
- const status = ACCESS_TOKEN
48
- ? `Token is set (${ACCESS_TOKEN.length} characters)`
49
- : "Token is NOT set. Set OPENCITATIONS_ACCESS_TOKEN environment variable.";
36
+ // Tool: Get citation count
37
+ server.registerTool("citation_count", {
38
+ description: "Get the number of citations for a paper (how many papers cite it). " +
39
+ "Provide a DOI like '10.1108/jd-12-2013-0166' or 'doi:10.1108/jd-12-2013-0166'.",
40
+ inputSchema: {
41
+ doi: z.string().describe("DOI of the paper (e.g., '10.1108/jd-12-2013-0166')"),
42
+ },
43
+ }, async (args) => {
44
+ const id = formatDoi(args.doi);
45
+ const count = await getCitationCount(id, ACCESS_TOKEN);
46
+ return {
47
+ content: [{
48
+ type: "text",
49
+ text: `Citation count for ${args.doi}: ${count}`,
50
+ }],
51
+ };
52
+ });
53
+ // Tool: Get citations (papers that cite this paper)
54
+ server.registerTool("get_citations", {
55
+ description: "Get all papers that cite a given paper. " +
56
+ "Returns a list of citing papers with their DOIs and metadata. " +
57
+ "Provide a DOI like '10.1108/jd-12-2013-0166'.",
58
+ inputSchema: {
59
+ doi: z.string().describe("DOI of the paper to find citations for"),
60
+ },
61
+ }, async (args) => {
62
+ const id = formatDoi(args.doi);
63
+ const citations = await getCitations(id, ACCESS_TOKEN);
64
+ const text = citations.length > 0
65
+ ? `Found ${citations.length} citations for ${args.doi}:\n\n${formatCitationsAsText(citations)}`
66
+ : `No citations found for ${args.doi}`;
67
+ return {
68
+ content: [{ type: "text", text }],
69
+ };
70
+ });
71
+ // Tool: Get reference count
72
+ server.registerTool("reference_count", {
73
+ description: "Get the number of references in a paper (how many papers it cites). " +
74
+ "Provide a DOI like '10.7717/peerj-cs.421'.",
75
+ inputSchema: {
76
+ doi: z.string().describe("DOI of the paper (e.g., '10.7717/peerj-cs.421')"),
77
+ },
78
+ }, async (args) => {
79
+ const id = formatDoi(args.doi);
80
+ const count = await getReferenceCount(id, ACCESS_TOKEN);
81
+ return {
82
+ content: [{
83
+ type: "text",
84
+ text: `Reference count for ${args.doi}: ${count}`,
85
+ }],
86
+ };
87
+ });
88
+ // Tool: Get references (papers this paper cites)
89
+ server.registerTool("get_references", {
90
+ description: "Get all papers referenced by a given paper. " +
91
+ "Returns a list of referenced papers with their DOIs and metadata. " +
92
+ "Provide a DOI like '10.7717/peerj-cs.421'.",
93
+ inputSchema: {
94
+ doi: z.string().describe("DOI of the paper to find references for"),
95
+ },
96
+ }, async (args) => {
97
+ const id = formatDoi(args.doi);
98
+ const references = await getReferences(id, ACCESS_TOKEN);
99
+ const text = references.length > 0
100
+ ? `Found ${references.length} references in ${args.doi}:\n\n${formatCitationsAsText(references)}`
101
+ : `No references found for ${args.doi}`;
102
+ return {
103
+ content: [{ type: "text", text }],
104
+ };
105
+ });
106
+ // Tool: Get single citation by OCI
107
+ server.registerTool("get_citation", {
108
+ description: "Get metadata for a specific citation using its Open Citation Identifier (OCI). " +
109
+ "An OCI is a unique identifier for a citation link between two papers. " +
110
+ "Example OCI: '06101801781-06180334099'.",
111
+ inputSchema: {
112
+ oci: z.string().describe("Open Citation Identifier (e.g., '06101801781-06180334099')"),
113
+ },
114
+ }, async (args) => {
115
+ // Remove 'oci:' prefix if present
116
+ const oci = args.oci.replace(/^oci:/, "");
117
+ const citation = await getCitation(oci, ACCESS_TOKEN);
118
+ if (!citation) {
119
+ return {
120
+ content: [{ type: "text", text: `No citation found for OCI: ${args.oci}` }],
121
+ };
122
+ }
50
123
  return {
51
- content: [{ type: "text", text: status }],
124
+ content: [{ type: "text", text: formatCitationAsText(citation) }],
52
125
  };
53
126
  });
54
- server.registerTool("hello", {
55
- description: "A simple hello world tool",
127
+ // Tool: Get venue citation count
128
+ server.registerTool("venue_citation_count", {
129
+ description: "Get the total number of citations for all papers published in a journal/venue. " +
130
+ "Provide an ISSN like '0138-9130' or 'issn:0138-9130'.",
56
131
  inputSchema: {
57
- name: z.string().optional().describe("Name to greet"),
132
+ issn: z.string().describe("ISSN of the journal (e.g., '0138-9130')"),
58
133
  },
59
134
  }, async (args) => {
60
- const greeting = args.name
61
- ? `Hello, ${args.name}!`
62
- : "Hello!";
135
+ const id = formatIssn(args.issn);
136
+ const count = await getVenueCitationCount(id, ACCESS_TOKEN);
63
137
  return {
64
- content: [{ type: "text", text: greeting }],
138
+ content: [{
139
+ type: "text",
140
+ text: `Total citations for venue ${args.issn}: ${count}`,
141
+ }],
65
142
  };
66
143
  });
67
144
  // -----------------------------------------------------------------------------
package/dist/lib.js CHANGED
@@ -1,67 +1,167 @@
1
1
  // =============================================================================
2
- // lib.ts - Helper functions for OpenCitations MCP Server
2
+ // OpenCitations API Client
3
3
  // =============================================================================
4
- // This file contains reusable functions that your tools can call.
5
- // Separating logic into lib.ts makes code easier to test and maintain.
4
+ const BASE_URL = "https://api.opencitations.net/index/v2";
6
5
  // -----------------------------------------------------------------------------
7
- // ASYNC FUNCTION EXAMPLE
6
+ // API Client
8
7
  // -----------------------------------------------------------------------------
9
8
  /**
10
- * Simulates fetching data (like from an API)
9
+ * Makes a request to the OpenCitations API
11
10
  *
12
- * In TypeScript:
13
- * - 'async' keyword makes this function return a Promise automatically
14
- * - Promise<string> is the return type annotation
15
- * - Whatever you 'return' becomes the resolved value of the Promise
11
+ * @param endpoint - API endpoint (e.g., "/citations/doi:10.1234/example")
12
+ * @param accessToken - Optional access token for authentication
13
+ * @returns Promise with the JSON response
16
14
  */
17
- export async function fetchData() {
18
- // Simulate network delay (like a real API call would have)
19
- delay(100);
20
- return 'data from fetchData()';
15
+ async function apiRequest(endpoint, accessToken) {
16
+ const url = `${BASE_URL}${endpoint}`;
17
+ const headers = {
18
+ "Accept": "application/json",
19
+ };
20
+ // Add authorization header if token is provided
21
+ if (accessToken) {
22
+ headers["Authorization"] = accessToken;
23
+ }
24
+ const response = await fetch(url, { headers });
25
+ if (!response.ok) {
26
+ throw new Error(`API request failed: ${response.status} ${response.statusText}`);
27
+ }
28
+ return response.json();
29
+ }
30
+ // -----------------------------------------------------------------------------
31
+ // Public Functions
32
+ // -----------------------------------------------------------------------------
33
+ /**
34
+ * Get the count of citations for a given identifier
35
+ *
36
+ * @param id - Identifier with prefix (e.g., "doi:10.1108/jd-12-2013-0166")
37
+ * @param accessToken - Optional access token
38
+ * @returns Number of citations
39
+ */
40
+ export async function getCitationCount(id, accessToken) {
41
+ const data = await apiRequest(`/citation-count/${id}`, accessToken);
42
+ if (data.length === 0) {
43
+ return 0;
44
+ }
45
+ return parseInt(data[0].count, 10);
21
46
  }
22
47
  /**
23
- * A utility function to create a delay (useful for simulating async operations)
48
+ * Get all citations (papers that cite the given identifier)
24
49
  *
25
- * @param ms - milliseconds to wait
26
- * @returns Promise that resolves after the delay
50
+ * @param id - Identifier with prefix (e.g., "doi:10.1108/jd-12-2013-0166")
51
+ * @param accessToken - Optional access token
52
+ * @returns Array of Citation objects
27
53
  */
28
- export function delay(ms) {
29
- // This is how you create a Promise manually:
30
- // - 'resolve' is called when the operation succeeds
31
- // - setTimeout calls resolve after 'ms' milliseconds
32
- return new Promise((resolve) => setTimeout(resolve, ms));
54
+ export async function getCitations(id, accessToken) {
55
+ return apiRequest(`/citations/${id}`, accessToken);
33
56
  }
34
57
  /**
35
- * Simulates looking up a citation by DOI
36
- * Later, this will call the real OpenCitations API
58
+ * Get the count of references for a given identifier
37
59
  *
38
- * @param doi - The DOI to look up (e.g., "10.1234/example")
39
- * @returns Promise<Citation> - resolves to a Citation object
40
- * @throws Error if DOI is not found
60
+ * @param id - Identifier with prefix (e.g., "doi:10.7717/peerj-cs.421")
61
+ * @param accessToken - Optional access token
62
+ * @returns Number of references
41
63
  */
42
- export async function lookupCitation(doi) {
43
- // Simulate API delay
44
- await delay(50);
45
- // For now, return dummy data
46
- // Later, this will make a real HTTP request to OpenCitations API
47
- if (!doi || doi.trim() === '') {
48
- throw new Error('DOI cannot be empty');
64
+ export async function getReferenceCount(id, accessToken) {
65
+ const data = await apiRequest(`/reference-count/${id}`, accessToken);
66
+ if (data.length === 0) {
67
+ return 0;
49
68
  }
50
- // Simulated response
51
- return {
52
- doi: doi,
53
- title: `Sample Paper for ${doi}`,
54
- authors: ['Alice Smith', 'Bob Jones'],
55
- year: 2024,
56
- };
69
+ return parseInt(data[0].count, 10);
70
+ }
71
+ /**
72
+ * Get all references (papers cited by the given identifier)
73
+ *
74
+ * @param id - Identifier with prefix (e.g., "doi:10.7717/peerj-cs.421")
75
+ * @param accessToken - Optional access token
76
+ * @returns Array of Citation objects
77
+ */
78
+ export async function getReferences(id, accessToken) {
79
+ return apiRequest(`/references/${id}`, accessToken);
80
+ }
81
+ /**
82
+ * Get metadata for a specific citation by OCI
83
+ *
84
+ * @param oci - Open Citation Identifier (e.g., "06101801781-06180334099")
85
+ * @param accessToken - Optional access token
86
+ * @returns Citation metadata
87
+ */
88
+ export async function getCitation(oci, accessToken) {
89
+ const data = await apiRequest(`/citation/${oci}`, accessToken);
90
+ return data.length > 0 ? data[0] : null;
57
91
  }
58
92
  /**
59
- * Formats a Citation object into a readable string
93
+ * Get the total citation count for all papers in a venue (journal)
60
94
  *
61
- * @param citation - The citation to format
62
- * @returns Formatted citation string
95
+ * @param issn - ISSN of the venue (e.g., "issn:0138-9130")
96
+ * @param accessToken - Optional access token
97
+ * @returns Total citation count for the venue
63
98
  */
64
- export function formatCitation(citation) {
65
- const authorList = citation.authors.join(', ');
66
- return `${authorList} (${citation.year}). "${citation.title}". DOI: ${citation.doi}`;
99
+ export async function getVenueCitationCount(issn, accessToken) {
100
+ const data = await apiRequest(`/venue-citation-count/${issn}`, accessToken);
101
+ if (data.length === 0) {
102
+ return 0;
103
+ }
104
+ return parseInt(data[0].count, 10);
105
+ }
106
+ // -----------------------------------------------------------------------------
107
+ // Utility Functions
108
+ // -----------------------------------------------------------------------------
109
+ /**
110
+ * Format a DOI into the API's expected format
111
+ * Handles both raw DOIs and prefixed DOIs
112
+ */
113
+ export function formatDoi(doi) {
114
+ // Remove URL prefix if present
115
+ doi = doi.replace(/^https?:\/\/doi\.org\//, "");
116
+ // Add "doi:" prefix if not present
117
+ if (!doi.startsWith("doi:")) {
118
+ return `doi:${doi}`;
119
+ }
120
+ return doi;
121
+ }
122
+ /**
123
+ * Format an ISSN into the API's expected format
124
+ */
125
+ export function formatIssn(issn) {
126
+ // Add "issn:" prefix if not present
127
+ if (!issn.startsWith("issn:")) {
128
+ return `issn:${issn}`;
129
+ }
130
+ return issn;
131
+ }
132
+ /**
133
+ * Format a single Citation into a readable string
134
+ */
135
+ export function formatCitationAsText(citation) {
136
+ const lines = [
137
+ `OCI: ${citation.oci}`,
138
+ `Citing: ${citation.citing}`,
139
+ `Cited: ${citation.cited}`,
140
+ `Date: ${citation.creation || "N/A"}`,
141
+ `Timespan: ${citation.timespan || "N/A"}`,
142
+ `Journal self-citation: ${citation.journal_sc || "no"}`,
143
+ `Author self-citation: ${citation.author_sc || "no"}`,
144
+ ];
145
+ return lines.join("\n");
146
+ }
147
+ /**
148
+ * Format citations into a readable string
149
+ */
150
+ export function formatCitationsAsText(citations) {
151
+ if (citations.length === 0) {
152
+ return "No citations found.";
153
+ }
154
+ return citations.map((c, i) => {
155
+ const lines = [
156
+ `[${i + 1}] OCI: ${c.oci}`,
157
+ ` Citing: ${c.citing}`,
158
+ ` Cited: ${c.cited}`,
159
+ ` Date: ${c.creation || "N/A"}`,
160
+ ];
161
+ if (c.journal_sc === "yes")
162
+ lines.push(" (Journal self-citation)");
163
+ if (c.author_sc === "yes")
164
+ lines.push(" (Author self-citation)");
165
+ return lines.join("\n");
166
+ }).join("\n\n");
67
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-opencitations",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server for OpenCitations API",
5
5
  "license": "MIT",
6
6
  "author": "ahmeshaf",