mcp-server-opencitations 0.1.1 → 0.1.2

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 +69 -30
  2. package/dist/lib.js +107 -46
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,63 +5,102 @@
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 { getCitationCount, getCitations, getReferenceCount, getReferences, formatDoi, 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);
50
81
  return {
51
- content: [{ type: "text", text: status }],
82
+ content: [{
83
+ type: "text",
84
+ text: `Reference count for ${args.doi}: ${count}`,
85
+ }],
52
86
  };
53
87
  });
54
- server.registerTool("hello", {
55
- description: "A simple hello world tool",
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'.",
56
93
  inputSchema: {
57
- name: z.string().optional().describe("Name to greet"),
94
+ doi: z.string().describe("DOI of the paper to find references for"),
58
95
  },
59
96
  }, async (args) => {
60
- const greeting = args.name
61
- ? `Hello, ${args.name}!`
62
- : "Hello!";
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}`;
63
102
  return {
64
- content: [{ type: "text", text: greeting }],
103
+ content: [{ type: "text", text }],
65
104
  };
66
105
  });
67
106
  // -----------------------------------------------------------------------------
package/dist/lib.js CHANGED
@@ -1,67 +1,128 @@
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();
21
29
  }
30
+ // -----------------------------------------------------------------------------
31
+ // Public Functions
32
+ // -----------------------------------------------------------------------------
22
33
  /**
23
- * A utility function to create a delay (useful for simulating async operations)
34
+ * Get the count of citations for a given identifier
24
35
  *
25
- * @param ms - milliseconds to wait
26
- * @returns Promise that resolves after the delay
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
27
39
  */
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));
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);
33
46
  }
34
47
  /**
35
- * Simulates looking up a citation by DOI
36
- * Later, this will call the real OpenCitations API
48
+ * Get all citations (papers that cite the given identifier)
37
49
  *
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
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
41
53
  */
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');
54
+ export async function getCitations(id, accessToken) {
55
+ return apiRequest(`/citations/${id}`, accessToken);
56
+ }
57
+ /**
58
+ * Get the count of references for a given identifier
59
+ *
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
63
+ */
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);
57
70
  }
58
71
  /**
59
- * Formats a Citation object into a readable string
72
+ * Get all references (papers cited by the given identifier)
60
73
  *
61
- * @param citation - The citation to format
62
- * @returns Formatted citation string
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
63
77
  */
64
- export function formatCitation(citation) {
65
- const authorList = citation.authors.join(', ');
66
- return `${authorList} (${citation.year}). "${citation.title}". DOI: ${citation.doi}`;
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;
91
+ }
92
+ // -----------------------------------------------------------------------------
93
+ // Utility Functions
94
+ // -----------------------------------------------------------------------------
95
+ /**
96
+ * Format a DOI into the API's expected format
97
+ * Handles both raw DOIs and prefixed DOIs
98
+ */
99
+ export function formatDoi(doi) {
100
+ // Remove URL prefix if present
101
+ doi = doi.replace(/^https?:\/\/doi\.org\//, "");
102
+ // Add "doi:" prefix if not present
103
+ if (!doi.startsWith("doi:")) {
104
+ return `doi:${doi}`;
105
+ }
106
+ return doi;
107
+ }
108
+ /**
109
+ * Format citations into a readable string
110
+ */
111
+ export function formatCitationsAsText(citations) {
112
+ if (citations.length === 0) {
113
+ return "No citations found.";
114
+ }
115
+ return citations.map((c, i) => {
116
+ const lines = [
117
+ `[${i + 1}] OCI: ${c.oci}`,
118
+ ` Citing: ${c.citing}`,
119
+ ` Cited: ${c.cited}`,
120
+ ` Date: ${c.creation || "N/A"}`,
121
+ ];
122
+ if (c.journal_sc === "yes")
123
+ lines.push(" (Journal self-citation)");
124
+ if (c.author_sc === "yes")
125
+ lines.push(" (Author self-citation)");
126
+ return lines.join("\n");
127
+ }).join("\n\n");
67
128
  }
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.2",
4
4
  "description": "MCP server for OpenCitations API",
5
5
  "license": "MIT",
6
6
  "author": "ahmeshaf",