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.
- package/dist/index.js +69 -30
- package/dist/lib.js +107 -46
- 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
|
|
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
|
-
//
|
|
26
|
+
// -----------------------------------------------------------------------------
|
|
27
|
+
// SERVER SETUP
|
|
28
|
+
// -----------------------------------------------------------------------------
|
|
35
29
|
const server = new McpServer({
|
|
36
30
|
name: "opencitations-server",
|
|
37
|
-
version: "0.
|
|
31
|
+
version: "0.2.0",
|
|
38
32
|
});
|
|
39
33
|
// -----------------------------------------------------------------------------
|
|
40
34
|
// TOOLS
|
|
41
35
|
// -----------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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: [{
|
|
82
|
+
content: [{
|
|
83
|
+
type: "text",
|
|
84
|
+
text: `Reference count for ${args.doi}: ${count}`,
|
|
85
|
+
}],
|
|
52
86
|
};
|
|
53
87
|
});
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
94
|
+
doi: z.string().describe("DOI of the paper to find references for"),
|
|
58
95
|
},
|
|
59
96
|
}, async (args) => {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
103
|
+
content: [{ type: "text", text }],
|
|
65
104
|
};
|
|
66
105
|
});
|
|
67
106
|
// -----------------------------------------------------------------------------
|
package/dist/lib.js
CHANGED
|
@@ -1,67 +1,128 @@
|
|
|
1
1
|
// =============================================================================
|
|
2
|
-
//
|
|
2
|
+
// OpenCitations API Client
|
|
3
3
|
// =============================================================================
|
|
4
|
-
|
|
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
|
-
//
|
|
6
|
+
// API Client
|
|
8
7
|
// -----------------------------------------------------------------------------
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
9
|
+
* Makes a request to the OpenCitations API
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
*
|
|
34
|
+
* Get the count of citations for a given identifier
|
|
24
35
|
*
|
|
25
|
-
* @param
|
|
26
|
-
* @
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
*
|
|
36
|
-
* Later, this will call the real OpenCitations API
|
|
48
|
+
* Get all citations (papers that cite the given identifier)
|
|
37
49
|
*
|
|
38
|
-
* @param
|
|
39
|
-
* @
|
|
40
|
-
* @
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
72
|
+
* Get all references (papers cited by the given identifier)
|
|
60
73
|
*
|
|
61
|
-
* @param
|
|
62
|
-
* @
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
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
|
}
|