ggaction 0.0.8 → 0.0.9
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/CHANGELOG.md +22 -0
- package/README.md +33 -5
- package/knowledge/action-cards.json +10941 -0
- package/knowledge/intent-taxonomy.json +183 -0
- package/knowledge/mcp-resources.json +95 -0
- package/knowledge/task-packet.schema.json +159 -0
- package/knowledge/task-resolver.js +1230 -0
- package/package.json +10 -1
- package/src/actions/basic.js +3 -3
- package/src/actions/coordinates/actions.js +4 -0
- package/src/actions/encodings/position/policies/line.js +5 -1
- package/src/actions/guides/legends/categorical/index.js +85 -2
- package/src/actions/guides/legends/categorical/symbols.js +2 -76
- package/src/actions/index.js +1 -1
- package/src/actions/primitives/index.js +27 -0
- package/src/actions/primitives/semantic.js +22 -185
- package/src/actions/primitives/semanticAction.js +188 -0
- package/src/actions/primitives/semanticValidation/dataset.js +7 -3
- package/src/actions/primitives/semanticValidation/index.js +28 -15
- package/src/actions/primitives/semanticValidation/layer.js +20 -16
- package/src/grammar/transformTopology.js +18 -0
- package/src/grammar/transforms.js +21 -20
- package/src/materialization/dataProvenance.js +2 -2
- package/src/materialization/marks/pathOrder.js +2 -2
- package/src/mcp/adapter.js +206 -0
- package/src/mcp/cli.js +11 -0
- package/src/mcp/server.js +101 -0
- package/src/actions/coordinates/index.js +0 -5
- package/src/actions/primitives/semanticValue.js +0 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { searchGgaction } from "../../knowledge/task-resolver.js";
|
|
6
|
+
|
|
7
|
+
const packageRoot = fileURLToPath(new URL("../../", import.meta.url));
|
|
8
|
+
const cardsArtifact = JSON.parse(readFileSync(
|
|
9
|
+
path.join(packageRoot, "knowledge/action-cards.json"),
|
|
10
|
+
"utf8"
|
|
11
|
+
));
|
|
12
|
+
const resourcesArtifact = JSON.parse(readFileSync(
|
|
13
|
+
path.join(packageRoot, "knowledge/mcp-resources.json"),
|
|
14
|
+
"utf8"
|
|
15
|
+
));
|
|
16
|
+
|
|
17
|
+
const cards = new Map(cardsArtifact.cards.map(card => [card.name, card]));
|
|
18
|
+
const recipes = new Map(resourcesArtifact.recipes.map(recipe => [recipe.id, recipe]));
|
|
19
|
+
const docs = new Map(resourcesArtifact.docs.map(section => [section.id, section]));
|
|
20
|
+
|
|
21
|
+
function unique(values) {
|
|
22
|
+
return [...new Set(values)];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const SEARCH_TOOL_NAME = "search_ggaction";
|
|
26
|
+
export const OVERVIEW_URI = "ggaction://overview";
|
|
27
|
+
|
|
28
|
+
export const SEARCH_TOOL = Object.freeze({
|
|
29
|
+
name: SEARCH_TOOL_NAME,
|
|
30
|
+
title: "Search ggaction authoring knowledge",
|
|
31
|
+
description: "Resolve one complete ggaction chart request into a bounded ordered task packet with exact imports, authoring prerequisites, executable immutable steps, terminal unsupported constraints, and explicit unresolved decisions.",
|
|
32
|
+
inputSchema: Object.freeze({
|
|
33
|
+
type: "object",
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
required: ["query"],
|
|
36
|
+
properties: Object.freeze({
|
|
37
|
+
query: Object.freeze({
|
|
38
|
+
type: "string",
|
|
39
|
+
minLength: 1,
|
|
40
|
+
maxLength: 500,
|
|
41
|
+
description: "The exact user chart-authoring request, including chart, encodings, guides, layout, and output format. Do not append dataset contents, code scaffolding, or evaluator instructions."
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
}),
|
|
45
|
+
annotations: Object.freeze({
|
|
46
|
+
readOnlyHint: true,
|
|
47
|
+
destructiveHint: false,
|
|
48
|
+
idempotentHint: true,
|
|
49
|
+
openWorldHint: false
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export class KnowledgeResourceError extends Error {
|
|
54
|
+
constructor(message) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.name = "KnowledgeResourceError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function jsonResource(uri, value) {
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
uri,
|
|
63
|
+
mimeType: "application/json",
|
|
64
|
+
text: JSON.stringify(value)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function markdownResource(uri, value) {
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
uri,
|
|
71
|
+
mimeType: "text/markdown",
|
|
72
|
+
text: `${value.text}\n\nCanonical public route: ${value.route}`
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function encodedUri(owner, id) {
|
|
77
|
+
return `ggaction://${owner}/${encodeURIComponent(id)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseResourceUri(uri) {
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = new URL(uri);
|
|
84
|
+
} catch {
|
|
85
|
+
throw new KnowledgeResourceError(`Invalid knowledge resource URI: ${uri}`);
|
|
86
|
+
}
|
|
87
|
+
if (parsed.protocol !== "ggaction:" || parsed.search || parsed.hash) {
|
|
88
|
+
throw new KnowledgeResourceError(`Unsupported knowledge resource URI: ${uri}`);
|
|
89
|
+
}
|
|
90
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
91
|
+
return { owner: parsed.hostname, segments };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function searchGgactionText(query) {
|
|
95
|
+
return JSON.stringify(searchGgaction(query));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function docsFallbackResources(packet) {
|
|
99
|
+
if (!packet || !Array.isArray(packet.unresolved) || packet.unresolved.length === 0) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
for (const entry of packet.unresolved) {
|
|
103
|
+
if (!Array.isArray(entry.resources) || entry.resources.length === 0) {
|
|
104
|
+
throw new KnowledgeResourceError(
|
|
105
|
+
`Unresolved constraint ${entry.constraint ?? "unknown"} has no explicit documentation resource.`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const resources = unique(packet.unresolved.flatMap(entry => entry.resources));
|
|
110
|
+
return resources.map(uri => {
|
|
111
|
+
const { owner, segments } = parseResourceUri(uri);
|
|
112
|
+
if (owner !== "docs" || segments.length !== 1) {
|
|
113
|
+
throw new KnowledgeResourceError(`Invalid unresolved documentation resource: ${uri}`);
|
|
114
|
+
}
|
|
115
|
+
const id = decodeURIComponent(segments[0]);
|
|
116
|
+
const section = docs.get(id);
|
|
117
|
+
if (!section) {
|
|
118
|
+
throw new KnowledgeResourceError(`Unknown unresolved documentation resource: ${uri}`);
|
|
119
|
+
}
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
uri,
|
|
122
|
+
name: section.title,
|
|
123
|
+
description: `Read only for the unresolved constraint; public route ${section.route}.`,
|
|
124
|
+
mimeType: "text/markdown"
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function listKnowledgeResources() {
|
|
130
|
+
return [
|
|
131
|
+
Object.freeze({
|
|
132
|
+
uri: OVERVIEW_URI,
|
|
133
|
+
name: resourcesArtifact.overview.title,
|
|
134
|
+
description: "Small MCP-first routing instructions; no complete documentation preload.",
|
|
135
|
+
mimeType: "application/json"
|
|
136
|
+
}),
|
|
137
|
+
...resourcesArtifact.recipes.map(recipe => Object.freeze({
|
|
138
|
+
uri: encodedUri("recipes", recipe.id),
|
|
139
|
+
name: recipe.title,
|
|
140
|
+
description: recipe.summary,
|
|
141
|
+
mimeType: "application/json"
|
|
142
|
+
}))
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function listKnowledgeResourceTemplates() {
|
|
147
|
+
return [
|
|
148
|
+
Object.freeze({
|
|
149
|
+
uriTemplate: "ggaction://actions/{name}",
|
|
150
|
+
name: "Exact compact action card",
|
|
151
|
+
description: "Read one exact action card by current public action name.",
|
|
152
|
+
mimeType: "application/json"
|
|
153
|
+
}),
|
|
154
|
+
Object.freeze({
|
|
155
|
+
uriTemplate: "ggaction://docs/{section}",
|
|
156
|
+
name: "Unresolved-only bounded documentation section",
|
|
157
|
+
description: "Read only a section recommended by the latest search_ggaction unresolved result.",
|
|
158
|
+
mimeType: "text/markdown"
|
|
159
|
+
})
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function readKnowledgeResource(uri, { allowedDocs = [] } = {}) {
|
|
164
|
+
if (uri === OVERVIEW_URI) {
|
|
165
|
+
return jsonResource(uri, {
|
|
166
|
+
schemaVersion: resourcesArtifact.schemaVersion,
|
|
167
|
+
title: resourcesArtifact.overview.title,
|
|
168
|
+
text: resourcesArtifact.overview.text
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const { owner, segments } = parseResourceUri(uri);
|
|
173
|
+
if (segments.length !== 1) {
|
|
174
|
+
throw new KnowledgeResourceError(`Unknown knowledge resource URI: ${uri}`);
|
|
175
|
+
}
|
|
176
|
+
const id = decodeURIComponent(segments[0]);
|
|
177
|
+
if (owner === "actions") {
|
|
178
|
+
const card = cards.get(id);
|
|
179
|
+
if (!card) throw new KnowledgeResourceError(`Unknown ggaction action: ${id}`);
|
|
180
|
+
return jsonResource(uri, card);
|
|
181
|
+
}
|
|
182
|
+
if (owner === "recipes") {
|
|
183
|
+
const recipe = recipes.get(id);
|
|
184
|
+
if (!recipe) throw new KnowledgeResourceError(`Unknown ggaction recipe: ${id}`);
|
|
185
|
+
return jsonResource(uri, {
|
|
186
|
+
schemaVersion: resourcesArtifact.schemaVersion,
|
|
187
|
+
...recipe,
|
|
188
|
+
packet: searchGgaction(recipe.query)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (owner === "docs") {
|
|
192
|
+
if (!new Set(allowedDocs).has(uri)) {
|
|
193
|
+
throw new KnowledgeResourceError(
|
|
194
|
+
"Documentation resources are available only when recommended by the latest unresolved search result."
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
const section = docs.get(id);
|
|
198
|
+
if (!section) throw new KnowledgeResourceError(`Unknown docs fallback section: ${id}`);
|
|
199
|
+
return markdownResource(uri, section);
|
|
200
|
+
}
|
|
201
|
+
throw new KnowledgeResourceError(`Unknown knowledge resource URI: ${uri}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (cardsArtifact.schemaVersion !== 1 || resourcesArtifact.schemaVersion !== 2) {
|
|
205
|
+
throw new Error("MCP action cards must use schemaVersion 1 and resources schemaVersion 2.");
|
|
206
|
+
}
|
package/src/mcp/cli.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { runGgactionMcpServer } from "./server.js";
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
await runGgactionMcpServer();
|
|
7
|
+
} catch (error) {
|
|
8
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9
|
+
process.stderr.write(`ggaction MCP failed: ${message}\n`);
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ErrorCode,
|
|
6
|
+
ListResourcesRequestSchema,
|
|
7
|
+
ListResourceTemplatesRequestSchema,
|
|
8
|
+
ListToolsRequestSchema,
|
|
9
|
+
McpError,
|
|
10
|
+
ReadResourceRequestSchema
|
|
11
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
docsFallbackResources,
|
|
15
|
+
KnowledgeResourceError,
|
|
16
|
+
listKnowledgeResources,
|
|
17
|
+
listKnowledgeResourceTemplates,
|
|
18
|
+
readKnowledgeResource,
|
|
19
|
+
SEARCH_TOOL,
|
|
20
|
+
SEARCH_TOOL_NAME,
|
|
21
|
+
searchGgactionText
|
|
22
|
+
} from "./adapter.js";
|
|
23
|
+
|
|
24
|
+
const SERVER_INFO = Object.freeze({ name: "ggaction", version: "1.0.0" });
|
|
25
|
+
|
|
26
|
+
function toolError(error) {
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
30
|
+
isError: true
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function validateToolArguments(args) {
|
|
35
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
36
|
+
throw new TypeError("search_ggaction arguments must be an object with one query string.");
|
|
37
|
+
}
|
|
38
|
+
const names = Object.keys(args);
|
|
39
|
+
if (names.length !== 1 || names[0] !== "query") {
|
|
40
|
+
throw new TypeError("search_ggaction accepts exactly one argument: query.");
|
|
41
|
+
}
|
|
42
|
+
return args.query;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createGgactionMcpServer() {
|
|
46
|
+
const server = new Server(SERVER_INFO, {
|
|
47
|
+
capabilities: { tools: {}, resources: {} },
|
|
48
|
+
instructions: "Call search_ggaction once with only the exact user task; do not append dataset contents, code scaffolding, or evaluator instructions. Use authoring imports, initialization, prerequisites, and immutable steps in order. Treat unsupported entries as terminal limitations. Read only the resource URIs attached to unresolved entries. The server is read-only and does not execute chart code."
|
|
49
|
+
});
|
|
50
|
+
let allowedDocs = new Set();
|
|
51
|
+
|
|
52
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
53
|
+
tools: [SEARCH_TOOL]
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
57
|
+
if (request.params.name !== SEARCH_TOOL_NAME) {
|
|
58
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const query = validateToolArguments(request.params.arguments);
|
|
62
|
+
const text = searchGgactionText(query);
|
|
63
|
+
const packet = JSON.parse(text);
|
|
64
|
+
allowedDocs = new Set(docsFallbackResources(packet).map(resource => resource.uri));
|
|
65
|
+
return { content: [{ type: "text", text }] };
|
|
66
|
+
} catch (error) {
|
|
67
|
+
allowedDocs = new Set();
|
|
68
|
+
return toolError(error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
73
|
+
resources: listKnowledgeResources()
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
77
|
+
resourceTemplates: listKnowledgeResourceTemplates()
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
|
81
|
+
try {
|
|
82
|
+
const resource = readKnowledgeResource(request.params.uri, {
|
|
83
|
+
allowedDocs: [...allowedDocs]
|
|
84
|
+
});
|
|
85
|
+
return { contents: [resource] };
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof KnowledgeResourceError) {
|
|
88
|
+
throw new McpError(ErrorCode.InvalidParams, error.message);
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return server;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function runGgactionMcpServer() {
|
|
98
|
+
const server = createGgactionMcpServer();
|
|
99
|
+
await server.connect(new StdioServerTransport());
|
|
100
|
+
return server;
|
|
101
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { validateSemanticValue } from "./semanticValidation/index.js";
|