@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 +22 -0
- package/README.md +16 -0
- package/lib/BinHelpers.d.ts +3 -0
- package/lib/BinHelpers.js +49 -0
- package/lib/BinHelpers.js.map +1 -0
- package/lib/SparqlMcpServer.d.ts +82 -0
- package/lib/SparqlMcpServer.js +273 -0
- package/lib/SparqlMcpServer.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +19 -0
- package/lib/index.js.map +1 -0
- package/package.json +50 -0
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
|
+
[](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,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
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
|
package/lib/index.js.map
ADDED
|
@@ -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
|
+
}
|