Skip to content

Commit fcc5ff4

Browse files
author
Jurij Skornik
committed
Added a DKG SPARQL query tool, solved the concurrency issue when running in dev mode, and made some general code cleanup in the plugin-dkg-essentials.
1 parent 8896fcf commit fcc5ff4

File tree

4 files changed

+131
-23
lines changed

4 files changed

+131
-23
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"build": "turbo run build",
66
"build:check": "turbo format && turbo run lint check-types build",
77
"check": "turbo run check-format check-types lint",
8-
"dev": "turbo run dev --parallel",
8+
"dev": "turbo run dev --parallel --concurrency=100",
99
"lint": "turbo run lint",
1010
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,mjs,json,md,yml}\"",
1111
"check-format": "prettier --check \"**/*.{ts,tsx,js,jsx,mjs,json,md,yml}\"",

packages/plugin-dkg-essentials/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@
2929
"dependencies": {
3030
"@dkg/plugin-swagger": "^0.0.2",
3131
"@dkg/plugins": "^0.0.2",
32-
"busboy": "^1.6.0"
32+
"busboy": "^1.6.0",
33+
"sparqljs": "^3.7.3"
3334
},
3435
"devDependencies": {
3536
"@dkg/eslint-config": "*",
3637
"@dkg/typescript-config": "*",
3738
"@types/busboy": "^1.5.4",
39+
"@types/sparqljs": "^3.1.12",
3840
"tsup": "^8.5.0"
3941
}
4042
}

packages/plugin-dkg-essentials/src/plugins/dkg-tools.ts

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import {
77
} from "@modelcontextprotocol/sdk/server/mcp.js";
88
// @ts-expect-error dkg.js
99
import { BLOCKCHAIN_IDS } from "dkg.js/constants";
10-
import { getExplorerUrl } from "../utils";
10+
import { getExplorerUrl, validateSparqlQuery } from "../utils";
1111

1212
export default defineDkgPlugin((ctx, mcp) => {
13+
1314
async function publishJsonLdAsset(
1415
jsonldRaw: string,
1516
privacy: "private" | "public",
@@ -30,24 +31,6 @@ export default defineDkgPlugin((ctx, mcp) => {
3031
}
3132
}
3233

33-
mcp.registerTool(
34-
"dkg-get",
35-
{
36-
title: "DKG Knowledge Asset get tool",
37-
description:
38-
"A tool for running a GET operation on OriginTrail Decentralized Knowledge Graph (DKG) and retrieving a specific Knowledge Asset by its UAL (Unique Asset Locator), taking the UAL as input.",
39-
inputSchema: { ual: z.string() },
40-
},
41-
async ({ ual }) => {
42-
const getAssetResult = await ctx.dkg.asset.get(ual);
43-
return {
44-
content: [
45-
{ type: "text", text: JSON.stringify(getAssetResult, null, 2) },
46-
],
47-
};
48-
},
49-
);
50-
5134
const ualCompleteOptions: Record<string, CompleteResourceTemplateCallback> = {
5235
blockchainName: (val) =>
5336
(Object.values(BLOCKCHAIN_IDS) as string[]).reduce<string[]>(
@@ -77,8 +60,6 @@ export default defineDkgPlugin((ctx, mcp) => {
7760
},
7861
[],
7962
),
80-
// TODO: List possible blockchain contract addresses for v8 and v6
81-
// blockchainAddress: (val, ctx) =>...
8263
};
8364

8465
mcp.registerResource(
@@ -179,4 +160,86 @@ export default defineDkgPlugin((ctx, mcp) => {
179160
};
180161
},
181162
);
163+
164+
mcp.registerTool(
165+
"dkg-sparql-query",
166+
{
167+
title: "DKG SPARQL Query Tool",
168+
description:
169+
"Execute SPARQL queries on the OriginTrail Decentralized Knowledge Graph (DKG). " +
170+
"Takes a SPARQL query as input and returns the query results from the DKG. " +
171+
"Supports SELECT and CONSTRUCT queries.",
172+
inputSchema: {
173+
query: z
174+
.string()
175+
.describe("SPARQL query to execute on the DKG (SELECT or CONSTRUCT)"),
176+
},
177+
},
178+
async ({ query }) => {
179+
// Validate query syntax
180+
const validation = validateSparqlQuery(query);
181+
182+
if (!validation.valid) {
183+
console.error("Invalid SPARQL query:", validation.error);
184+
return {
185+
content: [
186+
{
187+
type: "text",
188+
text: `❌ Invalid SPARQL query: ${validation.error}\n\nPlease check your query syntax and try again.`,
189+
},
190+
],
191+
};
192+
}
193+
194+
// Use validated query type
195+
const queryType = validation.queryType || "SELECT";
196+
197+
try {
198+
console.log(`Executing SPARQL ${queryType} query...`);
199+
const queryResult = await ctx.dkg.graph.query(query, queryType);
200+
201+
const resultText = JSON.stringify(queryResult, null, 2);
202+
203+
return {
204+
content: [
205+
{
206+
type: "text",
207+
text: `✅ Query executed successfully\n\n**Results:**\n\`\`\`json\n${resultText}\n\`\`\``,
208+
},
209+
],
210+
};
211+
} catch (error) {
212+
const errorMessage = error instanceof Error ? error.message : String(error);
213+
console.error("Error executing SPARQL query:", errorMessage);
214+
215+
return {
216+
content: [
217+
{
218+
type: "text",
219+
text: `❌ Error executing SPARQL query:\n\n${errorMessage}\n\nPlease check your query and try again.`,
220+
},
221+
],
222+
};
223+
}
224+
},
225+
);
226+
227+
mcp.registerTool(
228+
"dkg-get",
229+
{
230+
title: "DKG Knowledge Asset get tool",
231+
description:
232+
"A tool for running a GET operation on OriginTrail Decentralized Knowledge Graph (DKG) and retrieving a specific Knowledge Asset by its UAL (Unique Asset Locator), taking the UAL as input.",
233+
inputSchema: { ual: z.string() },
234+
},
235+
async ({ ual }) => {
236+
const getAssetResult = await ctx.dkg.asset.get(ual);
237+
return {
238+
content: [
239+
{ type: "text", text: JSON.stringify(getAssetResult, null, 2) },
240+
],
241+
};
242+
},
243+
);
244+
182245
});

packages/plugin-dkg-essentials/src/utils.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2+
import { Parser } from "sparqljs";
23

34
export type SourceKA = {
45
title: string;
@@ -62,3 +63,45 @@ export const withSourceKnowledgeAssets = <T extends CallToolResult>(
6263
...data,
6364
content: [...data.content, serializeSourceKAContent(kas)],
6465
});
66+
67+
// SPARQL query validation
68+
// Reuse parser instance for better performance
69+
const sparqlParser = new Parser();
70+
71+
export function validateSparqlQuery(query: string): { valid: boolean; error?: string; queryType?: string } {
72+
try {
73+
const trimmedQuery = query.trim();
74+
75+
if (!trimmedQuery) {
76+
return { valid: false, error: "Query cannot be empty" };
77+
}
78+
79+
// Use sparqljs parser for proper syntax validation
80+
const parsed = sparqlParser.parse(trimmedQuery);
81+
82+
// Check if it's a query (not an update)
83+
if (parsed.type !== "query") {
84+
return {
85+
valid: false,
86+
error: "Only SPARQL queries are allowed, not updates (INSERT, DELETE, MODIFY)",
87+
};
88+
}
89+
90+
// Only allow query types supported by the DKG node
91+
const allowedQueryTypes = ["SELECT", "CONSTRUCT"];
92+
if (!allowedQueryTypes.includes(parsed.queryType)) {
93+
return {
94+
valid: false,
95+
error: `Only SELECT and CONSTRUCT queries are supported. Received: ${parsed.queryType}`,
96+
};
97+
}
98+
99+
return { valid: true, queryType: parsed.queryType };
100+
} catch (error: unknown) {
101+
const errorMessage = error instanceof Error ? error.message : String(error);
102+
return {
103+
valid: false,
104+
error: `Invalid SPARQL syntax: ${errorMessage}`,
105+
};
106+
}
107+
}

0 commit comments

Comments
 (0)