@comunica/utils-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.
package/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2026-now Ruben Taelman
4
+ Ghent University – imec, Belgium
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # Comunica MPC Utils
2
+
3
+ [![npm version](https://badge.fury.io/js/%40comunica%2Futils-mcp.svg)](https://www.npmjs.com/package/@comunica/utils-mcp)
4
+
5
+ A collection of MCP-related helpers for Comunica.
6
+
7
+ This module is part of the [Comunica framework](https://github.com/comunica/comunica-feature-mcp),
8
+ and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/).
9
+
10
+ [Click here if you just want to query with Comunica](https://comunica.dev/docs/query/).
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ $ yarn add @comunica/utils-mcp
16
+ ```
@@ -0,0 +1,3 @@
1
+ import type { QueryEngineBase } from '@comunica/actor-init-query';
2
+ import type { QueryStringContext } from '@comunica/types';
3
+ export declare function runCli(queryEngine: QueryEngineBase, version: string, customContext?: Partial<QueryStringContext>): void;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCli = runCli;
4
+ const yargs_1 = require("yargs");
5
+ const helpers_1 = require("yargs/helpers");
6
+ const SparqlMcpServer_1 = require("./SparqlMcpServer");
7
+ function runCli(queryEngine, version, customContext) {
8
+ (async () => {
9
+ const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
10
+ .usage('Usage: $0 [options] [sources...]')
11
+ .option('mode', {
12
+ alias: 'm',
13
+ type: 'string',
14
+ choices: ['stdio', 'http'],
15
+ demandOption: true,
16
+ description: 'Transport mode for the MCP server',
17
+ })
18
+ .option('port', {
19
+ alias: 'p',
20
+ type: 'number',
21
+ default: 3123,
22
+ description: 'Port to run the MCP server on (only for http mode)',
23
+ })
24
+ .example([
25
+ ['$0 --mode stdio', 'Start MCP server in stdio mode without default sources'],
26
+ ['$0 --mode http --port 3000', 'Start MCP server in HTTP mode on port 3000'],
27
+ ['$0 --mode stdio https://dbpedia.org/sparql', 'Start with a default SPARQL endpoint'],
28
+ ['$0 --mode stdio https://example.org/data.ttl file@/path/to/local.ttl', 'Start with multiple default sources'],
29
+ ])
30
+ .parse();
31
+ // Extract positional arguments as default sources
32
+ const defaultSources = argv._.length > 0 ? argv._.map(String) : undefined;
33
+ const server = new SparqlMcpServer_1.SparqlMcpServer(argv.mode, argv.port, queryEngine, version, process.stderr, defaultSources, customContext);
34
+ server.start().catch((error) => {
35
+ process.stderr.write(`Server error: ${error.message}\n`);
36
+ if (error.stack) {
37
+ process.stderr.write(`${error.stack}\n`);
38
+ }
39
+ process.exit(1);
40
+ });
41
+ })().catch((error) => {
42
+ process.stderr.write(`Initialization error: ${error.message}\n`);
43
+ if (error.stack) {
44
+ process.stderr.write(`${error.stack}\n`);
45
+ }
46
+ process.exit(1);
47
+ });
48
+ }
49
+ //# sourceMappingURL=BinHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BinHelpers.js","sourceRoot":"","sources":["BinHelpers.ts"],"names":[],"mappings":";;AAOA,wBAuDC;AA3DD,iCAA0B;AAC1B,2CAAwC;AACxC,uDAAoD;AAEpD,SAAgB,MAAM,CACpB,WAA4B,EAC5B,OAAe,EACf,aAA2C;IAE3C,CAAC,KAAK,IAAG,EAAE;QACT,MAAM,IAAI,GAAG,MAAM,IAAA,eAAK,EAAC,IAAA,iBAAO,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC;aAC5C,KAAK,CAAC,kCAAkC,CAAC;aACzC,MAAM,CAAC,MAAM,EAAE;YACd,KAAK,EAAE,GAAG;YACV,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,CAAE,OAAO,EAAE,MAAM,CAAE;YAC5B,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,mCAAmC;SACjD,CAAC;aACD,MAAM,CAAC,MAAM,EAAE;YACd,KAAK,EAAE,GAAG;YACV,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,oDAAoD;SAClE,CAAC;aACD,OAAO,CAAC;YACP,CAAE,iBAAiB,EAAE,wDAAwD,CAAE;YAC/E,CAAE,4BAA4B,EAAE,4CAA4C,CAAE;YAC9E,CAAE,4CAA4C,EAAE,sCAAsC,CAAE;YACxF,CAAE,sEAAsE,EAAE,qCAAqC,CAAE;SAClH,CAAC;aACD,KAAK,EAAE,CAAC;QAEX,kDAAkD;QAClD,MAAM,cAAc,GAAyB,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEhG,MAAM,MAAM,GAAG,IAAI,iCAAe,CACb,IAAI,CAAC,IAAI,EAC5B,IAAI,CAAC,IAAI,EACT,WAAW,EACX,OAAO,EACP,OAAO,CAAC,MAAM,EACd,cAAc,EACd,aAAa,CACd,CAAC;QACF,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YACzD,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;YAC3C,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QACjE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/* eslint-disable unicorn/no-process-exit */\nimport type { QueryEngineBase } from '@comunica/actor-init-query';\nimport type { QueryStringContext } from '@comunica/types';\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { SparqlMcpServer } from './SparqlMcpServer';\n\nexport function runCli(\n queryEngine: QueryEngineBase,\n version: string,\n customContext?: Partial<QueryStringContext>,\n): void {\n (async() => {\n const argv = await yargs(hideBin(process.argv))\n .usage('Usage: $0 [options] [sources...]')\n .option('mode', {\n alias: 'm',\n type: 'string',\n choices: [ 'stdio', 'http' ],\n demandOption: true,\n description: 'Transport mode for the MCP server',\n })\n .option('port', {\n alias: 'p',\n type: 'number',\n default: 3123,\n description: 'Port to run the MCP server on (only for http mode)',\n })\n .example([\n [ '$0 --mode stdio', 'Start MCP server in stdio mode without default sources' ],\n [ '$0 --mode http --port 3000', 'Start MCP server in HTTP mode on port 3000' ],\n [ '$0 --mode stdio https://dbpedia.org/sparql', 'Start with a default SPARQL endpoint' ],\n [ '$0 --mode stdio https://example.org/data.ttl file@/path/to/local.ttl', 'Start with multiple default sources' ],\n ])\n .parse();\n\n // Extract positional arguments as default sources\n const defaultSources: string[] | undefined = argv._.length > 0 ? argv._.map(String) : undefined;\n\n const server = new SparqlMcpServer(\n <'stdio' | 'http'> argv.mode,\n argv.port,\n queryEngine,\n version,\n process.stderr,\n defaultSources,\n customContext,\n );\n server.start().catch((error) => {\n process.stderr.write(`Server error: ${error.message}\\n`);\n if (error.stack) {\n process.stderr.write(`${error.stack}\\n`);\n }\n process.exit(1);\n });\n })().catch((error) => {\n process.stderr.write(`Initialization error: ${error.message}\\n`);\n if (error.stack) {\n process.stderr.write(`${error.stack}\\n`);\n }\n process.exit(1);\n });\n}\n"]}
@@ -0,0 +1,82 @@
1
+ import type { Writable } from 'node:stream';
2
+ import type { QueryEngineBase } from '@comunica/actor-init-query';
3
+ import type { IQuerySourceUnidentifiedExpanded, QueryStringContext } from '@comunica/types';
4
+ import type { Context, FastMCPSessionAuth } from 'fastmcp';
5
+ /**
6
+ * An MCP server for querying over one or more Knowledge Graphs using SPARQL queries.
7
+ */
8
+ export declare class SparqlMcpServer {
9
+ private readonly mode;
10
+ private readonly port;
11
+ private readonly queryEngine;
12
+ private readonly server;
13
+ private readonly stderr;
14
+ private readonly defaultSources?;
15
+ private readonly customContext?;
16
+ private queryId;
17
+ constructor(mode: 'stdio' | 'http', port: number, queryEngine: QueryEngineBase, version: string, stderr: Writable, defaultSources?: string[], customContext?: Partial<QueryStringContext>, additionalSourcesDescription?: string);
18
+ /**
19
+ * Start the MCP server in the configured mode (stdio or HTTP stream).
20
+ */
21
+ start(): Promise<void>;
22
+ /**
23
+ * Parse a source string that may contain a type prefix (e.g., 'sparql@https://example.org/sparql').
24
+ * This follows the same syntax as the Comunica CLI for forcing source types.
25
+ * @param sourceString A source URL that may be prefixed with a type annotation.
26
+ * @returns An object with 'value' and optionally 'type' properties.
27
+ */
28
+ protected parseSourceString(sourceString: string): IQuerySourceUnidentifiedExpanded;
29
+ /**
30
+ * Build a query context from optional parameters.
31
+ * @param options Optional parameters for the query context
32
+ * @param options.queryFormatLanguage The query language (e.g., 'sparql')
33
+ * @param options.queryFormatVersion The query language version (e.g., '1.1')
34
+ * @param options.baseIRI Base IRI for resolving relative IRIs
35
+ * @param options.httpProxy HTTP proxy URL
36
+ * @param options.httpAuth HTTP basic authentication credentials
37
+ * @param options.httpTimeout HTTP request timeout in milliseconds
38
+ * @param options.httpRetryCount Number of HTTP request retries
39
+ * @returns A partial query context object
40
+ */
41
+ protected buildQueryContext(options: {
42
+ queryFormatLanguage?: string;
43
+ queryFormatVersion?: string;
44
+ baseIRI?: string;
45
+ httpProxy?: string;
46
+ httpAuth?: string;
47
+ httpTimeout?: number;
48
+ httpRetryCount?: number;
49
+ }): Partial<QueryStringContext>;
50
+ protected registerTools(additionalSourcesDescription?: string): void;
51
+ /**
52
+ * Execute a SPARQL query and stream the results back to the client.
53
+ * This method contains the common logic for executing queries and handling results.
54
+ * @param query The SPARQL query string
55
+ * @param sources Array of query sources
56
+ * @param queryId The query ID for logging
57
+ * @param context The MCP context for streaming results
58
+ * @param queryContext Optional query context parameters
59
+ * @returns The query results as a string or an error object
60
+ */
61
+ protected executeQuery(query: string, sources: IQuerySourceUnidentifiedExpanded[], queryId: number, context: Context<FastMCPSessionAuth>, queryContext?: Partial<QueryStringContext>): Promise<any>;
62
+ protected executeQuerySparql(args: {
63
+ query: string;
64
+ sources?: string[];
65
+ queryFormatLanguage?: string;
66
+ queryFormatVersion?: string;
67
+ baseIRI?: string;
68
+ httpProxy?: string;
69
+ httpAuth?: string;
70
+ httpTimeout?: number;
71
+ httpRetryCount?: number;
72
+ }, context: Context<FastMCPSessionAuth>): Promise<any>;
73
+ protected executeQuerySparqlRdf(args: {
74
+ query: string;
75
+ value: string;
76
+ mediaType: string;
77
+ fileBaseIRI?: string;
78
+ baseIRI?: string;
79
+ queryFormatLanguage?: string;
80
+ queryFormatVersion?: string;
81
+ }, context: Context<FastMCPSessionAuth>): Promise<any>;
82
+ }
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SparqlMcpServer = void 0;
4
+ const fastmcp_1 = require("fastmcp");
5
+ const zod_1 = require("zod");
6
+ /**
7
+ * An MCP server for querying over one or more Knowledge Graphs using SPARQL queries.
8
+ */
9
+ class SparqlMcpServer {
10
+ mode;
11
+ port;
12
+ queryEngine;
13
+ server;
14
+ stderr;
15
+ defaultSources;
16
+ customContext;
17
+ queryId = 0;
18
+ constructor(mode, port, queryEngine, version, stderr, defaultSources, customContext, additionalSourcesDescription) {
19
+ this.mode = mode;
20
+ this.port = port;
21
+ this.queryEngine = queryEngine;
22
+ this.stderr = stderr;
23
+ this.server = new fastmcp_1.FastMCP({
24
+ name: 'sparql-mcp',
25
+ version: version,
26
+ });
27
+ // Parse default sources if provided
28
+ if (defaultSources && defaultSources.length > 0) {
29
+ this.defaultSources = defaultSources.map(source => this.parseSourceString(source));
30
+ }
31
+ // Store custom context to be merged with query context
32
+ this.customContext = customContext;
33
+ this.registerTools(additionalSourcesDescription);
34
+ }
35
+ /**
36
+ * Start the MCP server in the configured mode (stdio or HTTP stream).
37
+ */
38
+ async start() {
39
+ if (this.mode === 'stdio') {
40
+ await this.server.start({
41
+ transportType: 'stdio',
42
+ });
43
+ this.stderr.write(`SPARQL MCP Server running in stdio mode\n`);
44
+ if (this.defaultSources) {
45
+ this.stderr.write(`Default sources: ${this.defaultSources.map(s => s.value).join(', ')}\n`);
46
+ }
47
+ }
48
+ else {
49
+ await this.server.start({
50
+ transportType: 'httpStream',
51
+ httpStream: {
52
+ port: this.port,
53
+ stateless: true,
54
+ },
55
+ });
56
+ this.stderr.write(`SPARQL MCP Server listening on port ${this.port}\n`);
57
+ if (this.defaultSources) {
58
+ this.stderr.write(`Default sources: ${this.defaultSources.map(s => s.value).join(', ')}\n`);
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * Parse a source string that may contain a type prefix (e.g., 'sparql@https://example.org/sparql').
64
+ * This follows the same syntax as the Comunica CLI for forcing source types.
65
+ * @param sourceString A source URL that may be prefixed with a type annotation.
66
+ * @returns An object with 'value' and optionally 'type' properties.
67
+ */
68
+ parseSourceString(sourceString) {
69
+ const source = { value: '' };
70
+ const typeRegex = /^([^:]*)@/u;
71
+ const typeMatches = typeRegex.exec(sourceString);
72
+ if (typeMatches) {
73
+ source.type = typeMatches[1];
74
+ sourceString = sourceString.slice((source.type.length) + 1);
75
+ }
76
+ source.value = sourceString;
77
+ return source;
78
+ }
79
+ /**
80
+ * Build a query context from optional parameters.
81
+ * @param options Optional parameters for the query context
82
+ * @param options.queryFormatLanguage The query language (e.g., 'sparql')
83
+ * @param options.queryFormatVersion The query language version (e.g., '1.1')
84
+ * @param options.baseIRI Base IRI for resolving relative IRIs
85
+ * @param options.httpProxy HTTP proxy URL
86
+ * @param options.httpAuth HTTP basic authentication credentials
87
+ * @param options.httpTimeout HTTP request timeout in milliseconds
88
+ * @param options.httpRetryCount Number of HTTP request retries
89
+ * @returns A partial query context object
90
+ */
91
+ buildQueryContext(options) {
92
+ const context = {};
93
+ if (options.queryFormatLanguage !== undefined || options.queryFormatVersion !== undefined) {
94
+ context.queryFormat = {
95
+ language: options.queryFormatLanguage ?? 'sparql',
96
+ version: options.queryFormatVersion ?? '1.1',
97
+ };
98
+ }
99
+ if (options.baseIRI) {
100
+ context.baseIRI = options.baseIRI;
101
+ }
102
+ if (options.httpProxy) {
103
+ const proxyUrl = options.httpProxy;
104
+ context.httpProxyHandler = {
105
+ getProxy: async (request) => ({
106
+ input: proxyUrl,
107
+ init: request.init,
108
+ }),
109
+ };
110
+ }
111
+ if (options.httpAuth) {
112
+ context.httpAuth = options.httpAuth;
113
+ }
114
+ if (options.httpTimeout !== undefined) {
115
+ context.httpTimeout = options.httpTimeout;
116
+ }
117
+ if (options.httpRetryCount !== undefined) {
118
+ context.httpRetryCount = options.httpRetryCount;
119
+ }
120
+ return context;
121
+ }
122
+ registerTools(additionalSourcesDescription) {
123
+ // Common query format parameters shared between tools
124
+ const queryFormatParams = {
125
+ queryFormatLanguage: zod_1.z.string().optional()
126
+ .describe('Query language (e.g., sparql, graphql). ' +
127
+ 'Allows you to specify alternative query languages supported by Comunica'),
128
+ queryFormatVersion: zod_1.z.string().optional()
129
+ .describe('Query language version (e.g., 1.0, 1.1, 1.2). ' +
130
+ 'Specifies the version of the query language to use'),
131
+ };
132
+ // Build description for query_sparql tool
133
+ let querySparqlDescription = `Execute a SPARQL query over one or more sources. When sending a SELECT query, results are serialized as 'application/sparql-results+json', CONSTRUCT and DESCRIBE results are in 'application/trig', and ASK queries return true or false. Update queries (INSERT/DELETE) can also be passed, which in most cases will only work on private Knowledge Graphs or by passing authentication.`;
134
+ if (this.defaultSources) {
135
+ // If default sources are provided, mention them in the description
136
+ const sourceList = this.defaultSources.map(s => s.value).join(', ');
137
+ querySparqlDescription += ` Default sources: ${sourceList}`;
138
+ }
139
+ // Build parameters for query_sparql tool
140
+ const querySparqlParams = {
141
+ query: zod_1.z.string().describe('SPARQL query string'),
142
+ };
143
+ // Only add sources parameter if no default sources are provided
144
+ if (!this.defaultSources) {
145
+ querySparqlParams.sources = zod_1.z.array(zod_1.z.string()).describe(`List of SPARQL endpoint URLs, TPF interface URLs, or Linked Data (RDF) file paths. You can optionally force a source type by prefixing the URL with a type annotation (e.g., 'sparql@https://example.org/sparql', 'file@/path/to/file.ttl', 'hypermedia@https://example.org/'). This is useful when the source type is already known to avoid auto-detection overhead.${additionalSourcesDescription ?? ''}`);
146
+ }
147
+ // Add common parameters
148
+ Object.assign(querySparqlParams, {
149
+ ...queryFormatParams,
150
+ baseIRI: zod_1.z.string().optional().describe('Base IRI for resolving relative IRIs in the query'),
151
+ httpProxy: zod_1.z.string().optional().describe('HTTP proxy URL (e.g., http://proxy.example.com:8080)'),
152
+ httpAuth: zod_1.z.string().optional().describe('HTTP basic authentication in the format username:password'),
153
+ httpTimeout: zod_1.z.number().optional().describe('HTTP request timeout in milliseconds'),
154
+ httpRetryCount: zod_1.z.number().optional().describe('Number of HTTP request retries on failure'),
155
+ });
156
+ this.server.addTool({
157
+ name: 'query_sparql',
158
+ description: querySparqlDescription,
159
+ parameters: zod_1.z.object(querySparqlParams),
160
+ annotations: {
161
+ // Signals this tool uses streaming
162
+ streamingHint: true,
163
+ readOnlyHint: true,
164
+ },
165
+ // Type assertion is needed because we dynamically construct the parameters object
166
+ // based on whether default sources are provided. The runtime behavior is type-safe.
167
+ execute: (args, context) => this.executeQuerySparql(args, context),
168
+ });
169
+ this.server.addTool({
170
+ name: 'query_sparql_rdf',
171
+ description: `Execute a SPARQL query over a serialized RDF dataset provided as a string. This is useful for querying RDF data that is already available as a string (e.g., Turtle, N-Triples, etc.). When sending a SELECT query, results are serialized as 'application/sparql-results+json', CONSTRUCT and DESCRIBE results are in 'application/trig', and ASK queries return true or false.`,
172
+ parameters: zod_1.z.object({
173
+ query: zod_1.z.string().describe('SPARQL query string'),
174
+ value: zod_1.z.string().describe('Serialized RDF dataset as a string'),
175
+ mediaType: zod_1.z.string().describe(`Media type of the serialized RDF dataset (e.g., 'text/turtle', 'application/n-triples', 'application/ld+json', 'application/rdf+xml', 'application/n-quads', 'application/trig')`),
176
+ fileBaseIRI: zod_1.z.string().optional().describe('Base IRI for resolving relative IRIs in the RDF dataset'),
177
+ baseIRI: zod_1.z.string().optional().describe('Base IRI for resolving relative IRIs in the query'),
178
+ ...queryFormatParams,
179
+ }),
180
+ annotations: {
181
+ // Signals this tool uses streaming
182
+ streamingHint: true,
183
+ readOnlyHint: true,
184
+ },
185
+ execute: (args, context) => this.executeQuerySparqlRdf(args, context),
186
+ });
187
+ }
188
+ /**
189
+ * Execute a SPARQL query and stream the results back to the client.
190
+ * This method contains the common logic for executing queries and handling results.
191
+ * @param query The SPARQL query string
192
+ * @param sources Array of query sources
193
+ * @param queryId The query ID for logging
194
+ * @param context The MCP context for streaming results
195
+ * @param queryContext Optional query context parameters
196
+ * @returns The query results as a string or an error object
197
+ */
198
+ async executeQuery(query, sources, queryId, context, queryContext = {}) {
199
+ await context.streamContent({ type: 'text', text: `Streaming SPARQL query results hereafter:` });
200
+ try {
201
+ const promises = [];
202
+ const chunks = [];
203
+ // Merge custom context with provided query context
204
+ const mergedContext = { sources, ...this.customContext, ...queryContext };
205
+ const queryResult = await this.queryEngine.query(query, mergedContext);
206
+ const { data } = await this.queryEngine.resultToString(queryResult);
207
+ data.on('data', (chunk) => {
208
+ chunks.push(chunk);
209
+ promises.push(context.streamContent({ type: 'text', text: chunk.toString() }));
210
+ });
211
+ await new Promise((resolve, reject) => {
212
+ data.on('error', reject);
213
+ data.on('end', resolve);
214
+ });
215
+ await Promise.all(promises);
216
+ // Log successful completion
217
+ this.stderr.write(`[Query ${queryId}] Successfully completed\n`);
218
+ return chunks.join('');
219
+ }
220
+ catch (error) {
221
+ // Log query failure
222
+ this.stderr.write(`[Query ${queryId}] Failed: ${error.stack}\n`);
223
+ return {
224
+ isError: true,
225
+ content: [
226
+ {
227
+ type: 'text',
228
+ text: `Query failed: ${error.message}`,
229
+ },
230
+ ],
231
+ };
232
+ }
233
+ }
234
+ async executeQuerySparql(args, context) {
235
+ const currentQueryId = this.queryId++;
236
+ // Use default sources if provided, otherwise use sources from args
237
+ let parsedSources;
238
+ if (this.defaultSources) {
239
+ parsedSources = this.defaultSources;
240
+ }
241
+ else if (args.sources) {
242
+ parsedSources = args.sources.map(sourceString => this.parseSourceString(sourceString));
243
+ }
244
+ else {
245
+ throw new Error('No sources provided and no default sources configured');
246
+ }
247
+ // Build query context from optional parameters
248
+ const queryContext = this.buildQueryContext(args);
249
+ // Log query start
250
+ this.stderr.write(`[Query ${currentQueryId}] Starting SPARQL query\n`);
251
+ this.stderr.write(`[Query ${currentQueryId}] Sources: ${parsedSources.map(s => s.value).join(', ')}\n`);
252
+ this.stderr.write(`[Query ${currentQueryId}] Query: ${args.query}\n`);
253
+ return this.executeQuery(args.query, parsedSources, currentQueryId, context, queryContext);
254
+ }
255
+ async executeQuerySparqlRdf(args, context) {
256
+ const currentQueryId = this.queryId++;
257
+ // Create a serialized source
258
+ const source = {
259
+ type: 'serialized',
260
+ value: args.value,
261
+ mediaType: args.mediaType,
262
+ ...(args.fileBaseIRI && { baseIRI: args.fileBaseIRI }),
263
+ };
264
+ // Build query context from optional parameters
265
+ const queryContext = this.buildQueryContext(args);
266
+ // Log query start
267
+ this.stderr.write(`[Query ${currentQueryId}] Starting SPARQL query on serialized RDF (media type: ${args.mediaType})\n`);
268
+ this.stderr.write(`[Query ${currentQueryId}] Query: ${args.query}\n`);
269
+ return this.executeQuery(args.query, [source], currentQueryId, context, queryContext);
270
+ }
271
+ }
272
+ exports.SparqlMcpServer = SparqlMcpServer;
273
+ //# sourceMappingURL=SparqlMcpServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SparqlMcpServer.js","sourceRoot":"","sources":["SparqlMcpServer.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable import/no-nodejs-modules */\nimport type { Writable } from 'node:stream';\nimport type { QueryEngineBase } from '@comunica/actor-init-query';\nimport type { IQuerySourceSerialized, IQuerySourceUnidentifiedExpanded, QueryStringContext } from '@comunica/types';\nimport type { Context, FastMCPSessionAuth } from 'fastmcp';\nimport { FastMCP } from 'fastmcp';\nimport { z } from 'zod';\n\n/**\n * An MCP server for querying over one or more Knowledge Graphs using SPARQL queries.\n */\nexport class SparqlMcpServer {\n private readonly server: FastMCP;\n private readonly stderr: Writable;\n private readonly defaultSources?: IQuerySourceUnidentifiedExpanded[];\n private readonly customContext?: Partial<QueryStringContext>;\n private queryId = 0;\n\n public constructor(\n private readonly mode: 'stdio' | 'http',\n private readonly port: number,\n private readonly queryEngine: QueryEngineBase,\n version: string,\n stderr: Writable,\n defaultSources?: string[],\n customContext?: Partial<QueryStringContext>,\n additionalSourcesDescription?: string,\n ) {\n this.stderr = stderr;\n this.server = new FastMCP({\n name: 'sparql-mcp',\n version: <any> version,\n });\n\n // Parse default sources if provided\n if (defaultSources && defaultSources.length > 0) {\n this.defaultSources = defaultSources.map(source => this.parseSourceString(source));\n }\n\n // Store custom context to be merged with query context\n this.customContext = customContext;\n\n this.registerTools(additionalSourcesDescription);\n }\n\n /**\n * Start the MCP server in the configured mode (stdio or HTTP stream).\n */\n public async start(): Promise<void> {\n if (this.mode === 'stdio') {\n await this.server.start({\n transportType: 'stdio',\n });\n this.stderr.write(`SPARQL MCP Server running in stdio mode\\n`);\n if (this.defaultSources) {\n this.stderr.write(`Default sources: ${this.defaultSources.map(s => s.value).join(', ')}\\n`);\n }\n } else {\n await this.server.start({\n transportType: 'httpStream',\n httpStream: {\n port: this.port,\n stateless: true,\n },\n });\n this.stderr.write(`SPARQL MCP Server listening on port ${this.port}\\n`);\n if (this.defaultSources) {\n this.stderr.write(`Default sources: ${this.defaultSources.map(s => s.value).join(', ')}\\n`);\n }\n }\n }\n\n /**\n * Parse a source string that may contain a type prefix (e.g., 'sparql@https://example.org/sparql').\n * This follows the same syntax as the Comunica CLI for forcing source types.\n * @param sourceString A source URL that may be prefixed with a type annotation.\n * @returns An object with 'value' and optionally 'type' properties.\n */\n protected parseSourceString(sourceString: string): IQuerySourceUnidentifiedExpanded {\n const source: IQuerySourceUnidentifiedExpanded = { value: '' };\n const typeRegex = /^([^:]*)@/u;\n const typeMatches = typeRegex.exec(sourceString);\n if (typeMatches) {\n source.type = typeMatches[1];\n sourceString = sourceString.slice((source.type.length) + 1);\n }\n source.value = sourceString;\n return source;\n }\n\n /**\n * Build a query context from optional parameters.\n * @param options Optional parameters for the query context\n * @param options.queryFormatLanguage The query language (e.g., 'sparql')\n * @param options.queryFormatVersion The query language version (e.g., '1.1')\n * @param options.baseIRI Base IRI for resolving relative IRIs\n * @param options.httpProxy HTTP proxy URL\n * @param options.httpAuth HTTP basic authentication credentials\n * @param options.httpTimeout HTTP request timeout in milliseconds\n * @param options.httpRetryCount Number of HTTP request retries\n * @returns A partial query context object\n */\n protected buildQueryContext(options: {\n queryFormatLanguage?: string;\n queryFormatVersion?: string;\n baseIRI?: string;\n httpProxy?: string;\n httpAuth?: string;\n httpTimeout?: number;\n httpRetryCount?: number;\n }): Partial<QueryStringContext> {\n const context: Partial<QueryStringContext> = {};\n\n if (options.queryFormatLanguage !== undefined || options.queryFormatVersion !== undefined) {\n context.queryFormat = {\n language: options.queryFormatLanguage ?? 'sparql',\n version: options.queryFormatVersion ?? '1.1',\n };\n }\n if (options.baseIRI) {\n context.baseIRI = options.baseIRI;\n }\n if (options.httpProxy) {\n const proxyUrl = options.httpProxy;\n context.httpProxyHandler = {\n getProxy: async(request): Promise<any> => ({\n input: proxyUrl,\n init: request.init,\n }),\n };\n }\n if (options.httpAuth) {\n context.httpAuth = options.httpAuth;\n }\n if (options.httpTimeout !== undefined) {\n context.httpTimeout = options.httpTimeout;\n }\n if (options.httpRetryCount !== undefined) {\n context.httpRetryCount = options.httpRetryCount;\n }\n\n return context;\n }\n\n protected registerTools(additionalSourcesDescription?: string): void {\n // Common query format parameters shared between tools\n const queryFormatParams = {\n queryFormatLanguage: z.string().optional()\n .describe(\n 'Query language (e.g., sparql, graphql). ' +\n 'Allows you to specify alternative query languages supported by Comunica',\n ),\n queryFormatVersion: z.string().optional()\n .describe(\n 'Query language version (e.g., 1.0, 1.1, 1.2). ' +\n 'Specifies the version of the query language to use',\n ),\n };\n\n // Build description for query_sparql tool\n let querySparqlDescription = `Execute a SPARQL query over one or more sources. When sending a SELECT query, results are serialized as 'application/sparql-results+json', CONSTRUCT and DESCRIBE results are in 'application/trig', and ASK queries return true or false. Update queries (INSERT/DELETE) can also be passed, which in most cases will only work on private Knowledge Graphs or by passing authentication.`;\n\n if (this.defaultSources) {\n // If default sources are provided, mention them in the description\n const sourceList = this.defaultSources.map(s => s.value).join(', ');\n querySparqlDescription += ` Default sources: ${sourceList}`;\n }\n\n // Build parameters for query_sparql tool\n const querySparqlParams: any = {\n query: z.string().describe('SPARQL query string'),\n };\n\n // Only add sources parameter if no default sources are provided\n if (!this.defaultSources) {\n querySparqlParams.sources = z.array(z.string()).describe(`List of SPARQL endpoint URLs, TPF interface URLs, or Linked Data (RDF) file paths. You can optionally force a source type by prefixing the URL with a type annotation (e.g., 'sparql@https://example.org/sparql', 'file@/path/to/file.ttl', 'hypermedia@https://example.org/'). This is useful when the source type is already known to avoid auto-detection overhead.${additionalSourcesDescription ?? ''}`);\n }\n\n // Add common parameters\n Object.assign(querySparqlParams, {\n ...queryFormatParams,\n baseIRI: z.string().optional().describe('Base IRI for resolving relative IRIs in the query'),\n httpProxy: z.string().optional().describe('HTTP proxy URL (e.g., http://proxy.example.com:8080)'),\n httpAuth: z.string().optional().describe('HTTP basic authentication in the format username:password'),\n httpTimeout: z.number().optional().describe('HTTP request timeout in milliseconds'),\n httpRetryCount: z.number().optional().describe('Number of HTTP request retries on failure'),\n });\n\n this.server.addTool({\n name: 'query_sparql',\n description: querySparqlDescription,\n parameters: z.object(querySparqlParams),\n annotations: {\n // Signals this tool uses streaming\n streamingHint: true,\n readOnlyHint: true,\n },\n // Type assertion is needed because we dynamically construct the parameters object\n // based on whether default sources are provided. The runtime behavior is type-safe.\n execute: (args, context) => this.executeQuerySparql(<any>args, context),\n });\n\n this.server.addTool({\n name: 'query_sparql_rdf',\n description: `Execute a SPARQL query over a serialized RDF dataset provided as a string. This is useful for querying RDF data that is already available as a string (e.g., Turtle, N-Triples, etc.). When sending a SELECT query, results are serialized as 'application/sparql-results+json', CONSTRUCT and DESCRIBE results are in 'application/trig', and ASK queries return true or false.`,\n parameters: z.object({\n query: z.string().describe('SPARQL query string'),\n value: z.string().describe('Serialized RDF dataset as a string'),\n mediaType: z.string().describe(`Media type of the serialized RDF dataset (e.g., 'text/turtle', 'application/n-triples', 'application/ld+json', 'application/rdf+xml', 'application/n-quads', 'application/trig')`),\n fileBaseIRI: z.string().optional().describe('Base IRI for resolving relative IRIs in the RDF dataset'),\n baseIRI: z.string().optional().describe('Base IRI for resolving relative IRIs in the query'),\n ...queryFormatParams,\n }),\n annotations: {\n // Signals this tool uses streaming\n streamingHint: true,\n readOnlyHint: true,\n },\n execute: (args, context) => this.executeQuerySparqlRdf(args, context),\n });\n }\n\n /**\n * Execute a SPARQL query and stream the results back to the client.\n * This method contains the common logic for executing queries and handling results.\n * @param query The SPARQL query string\n * @param sources Array of query sources\n * @param queryId The query ID for logging\n * @param context The MCP context for streaming results\n * @param queryContext Optional query context parameters\n * @returns The query results as a string or an error object\n */\n protected async executeQuery(\n query: string,\n sources: IQuerySourceUnidentifiedExpanded[],\n queryId: number,\n context: Context<FastMCPSessionAuth>,\n queryContext: Partial<QueryStringContext> = {},\n ): Promise<any> {\n await context.streamContent({ type: 'text', text: `Streaming SPARQL query results hereafter:` });\n\n try {\n const promises: Promise<any>[] = [];\n const chunks: string[] = [];\n // Merge custom context with provided query context\n const mergedContext = { sources, ...this.customContext, ...queryContext };\n const queryResult = await this.queryEngine.query(query, mergedContext);\n const { data } = await this.queryEngine.resultToString(queryResult);\n data.on('data', (chunk: string) => {\n chunks.push(chunk);\n promises.push(context.streamContent({ type: 'text', text: chunk.toString() }));\n });\n await new Promise((resolve, reject) => {\n data.on('error', reject);\n data.on('end', resolve);\n });\n await Promise.all(promises);\n\n // Log successful completion\n this.stderr.write(`[Query ${queryId}] Successfully completed\\n`);\n\n return chunks.join('');\n } catch (error: any) {\n // Log query failure\n this.stderr.write(`[Query ${queryId}] Failed: ${error.stack}\\n`);\n\n return {\n isError: true,\n content: [\n {\n type: 'text',\n text: `Query failed: ${error.message}`,\n },\n ],\n };\n }\n }\n\n protected async executeQuerySparql(\n args: {\n query: string;\n sources?: string[];\n queryFormatLanguage?: string;\n queryFormatVersion?: string;\n baseIRI?: string;\n httpProxy?: string;\n httpAuth?: string;\n httpTimeout?: number;\n httpRetryCount?: number;\n },\n context: Context<FastMCPSessionAuth>,\n ): Promise<any> {\n const currentQueryId = this.queryId++;\n\n // Use default sources if provided, otherwise use sources from args\n let parsedSources: IQuerySourceUnidentifiedExpanded[];\n if (this.defaultSources) {\n parsedSources = this.defaultSources;\n } else if (args.sources) {\n parsedSources = args.sources.map(sourceString => this.parseSourceString(sourceString));\n } else {\n throw new Error('No sources provided and no default sources configured');\n }\n\n // Build query context from optional parameters\n const queryContext = this.buildQueryContext(args);\n\n // Log query start\n this.stderr.write(`[Query ${currentQueryId}] Starting SPARQL query\\n`);\n this.stderr.write(`[Query ${currentQueryId}] Sources: ${parsedSources.map(s => s.value).join(', ')}\\n`);\n this.stderr.write(`[Query ${currentQueryId}] Query: ${args.query}\\n`);\n\n return this.executeQuery(args.query, parsedSources, currentQueryId, context, queryContext);\n }\n\n protected async executeQuerySparqlRdf(\n args: {\n query: string;\n value: string;\n mediaType: string;\n fileBaseIRI?: string;\n baseIRI?: string;\n queryFormatLanguage?: string;\n queryFormatVersion?: string;\n },\n context: Context<FastMCPSessionAuth>,\n ): Promise<any> {\n const currentQueryId = this.queryId++;\n\n // Create a serialized source\n const source: IQuerySourceSerialized = {\n type: 'serialized',\n value: args.value,\n mediaType: args.mediaType,\n ...(args.fileBaseIRI && { baseIRI: args.fileBaseIRI }),\n };\n\n // Build query context from optional parameters\n const queryContext = this.buildQueryContext(args);\n\n // Log query start\n this.stderr.write(`[Query ${currentQueryId}] Starting SPARQL query on serialized RDF (media type: ${args.mediaType})\\n`);\n this.stderr.write(`[Query ${currentQueryId}] Query: ${args.query}\\n`);\n\n return this.executeQuery(args.query, [ source ], currentQueryId, context, queryContext);\n }\n}\n"]}
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './SparqlMcpServer';
2
+ export * from './BinHelpers';
package/lib/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./SparqlMcpServer"), exports);
18
+ __exportStar(require("./BinHelpers"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,+CAA6B","sourcesContent":["export * from './SparqlMcpServer';\nexport * from './BinHelpers';\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@comunica/utils-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP helpers for Comunica",
5
+ "license": "MIT",
6
+ "funding": {
7
+ "type": "opencollective",
8
+ "url": "https://opencollective.com/comunica-association"
9
+ },
10
+ "homepage": "https://comunica.dev/",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/comunica/comunica-feature-mcp.git",
14
+ "directory": "packages/utils-mcp"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/comunica/comunica-feature-mcp/issues"
18
+ },
19
+ "keywords": [
20
+ "comunica",
21
+ "mcp"
22
+ ],
23
+ "sideEffects": false,
24
+ "main": "lib/index.js",
25
+ "typings": "lib/index",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "files": [
30
+ "bin/**/*.d.ts",
31
+ "bin/**/*.js",
32
+ "bin/**/*.js.map",
33
+ "lib/**/*.d.ts",
34
+ "lib/**/*.js",
35
+ "lib/**/*.js.map"
36
+ ],
37
+ "scripts": {
38
+ "build": "yarn run build:ts",
39
+ "build:ts": "node \"../../node_modules/typescript/bin/tsc\""
40
+ },
41
+ "dependencies": {
42
+ "@comunica/actor-init-query": "^5.1.3",
43
+ "@comunica/types": "^5.1.3",
44
+ "@types/yargs": "^17.0.24",
45
+ "fastmcp": "^3.31.0",
46
+ "yargs": "^17.7.2",
47
+ "zod": "^4.3.6"
48
+ },
49
+ "gitHead": "8a35a73d61cf1066bb1181761884d84d3b932932"
50
+ }