Skip to content

Commit 7b0898c

Browse files
committed
chore(mcp): split ooxml dispatch + add local e2e harness
The deployed Worker uses @neondatabase/serverless (HTTP-only) which can't talk to local Postgres, so callOoxmlTool is now a thin Neon-creating wrapper around runOoxmlTool, which takes any tagged-template sql function. Tests and the new CLI use postgres.js against local Docker; the Worker keeps Neon. scripts/ooxml-call.ts dispatches the same code path the Worker uses. Five PLAN.md acceptance queries verified against the real WML closure: ooxml_children("w:tbl") -> EG_RangeMarkupElements (group, 0..unbounded), tblPr (1..1), tblGrid (1..1), EG_ContentRowContent (group, 0..unbounded) ooxml_lookup_element("w:tblGrid") -> type_ref={...wml-main}CT_TblGridBase; in CT_Tbl context min/max=1 (required, per Q1) ooxml_attributes("w:jc") -> single attr 'val' (required), type_ref to ST_Jc ooxml_enum("w:ST_Jc") -> 12 values incl. start/end (Strict) and left/right (Transitional) ooxml_lookup_element("w:notARealElement") -> 'Not found' card with profile and recovery hints
1 parent e1c5cb0 commit 7b0898c

3 files changed

Lines changed: 88 additions & 6 deletions

File tree

apps/mcp-server/src/ooxml-tools.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ import {
1616
type AttrEntry,
1717
type ChildEdge,
1818
type EnumEntry,
19-
type NamespaceInfo,
20-
type SymbolHit,
2119
getAttributes,
2220
getChildren,
2321
getEnums,
2422
getNamespaceInfo,
2523
lookupElement,
2624
lookupSymbolByTypeRef,
2725
lookupType,
26+
type NamespaceInfo,
2827
parseQName,
28+
type SymbolHit,
2929
} from "./ooxml-queries";
3030

3131
export const DEFAULT_PROFILE = "transitional";
@@ -79,7 +79,8 @@ export const OOXML_TOOL_DEFS = [
7979
properties: {
8080
qname: {
8181
type: "string",
82-
description: "Element, complexType, or group qname (e.g. 'w:tbl', 'CT_Tbl', 'EG_PContent').",
82+
description:
83+
"Element, complexType, or group qname (e.g. 'w:tbl', 'CT_Tbl', 'EG_PContent').",
8384
},
8485
profile: { type: "string", description: "Profile name (default: 'transitional')." },
8586
},
@@ -143,12 +144,30 @@ export function isOoxmlTool(name: string): name is OoxmlToolName {
143144
// biome-ignore lint/suspicious/noExplicitAny: neon's tagged template is loosely typed.
144145
type Sql = any;
145146

147+
/**
148+
* Worker-side entry point: constructs a Neon HTTP client from env and dispatches.
149+
* Local CLIs and tests should call `runOoxmlTool` directly with their own sql
150+
* (e.g. postgres.js against a local Postgres) to avoid the Neon HTTP path.
151+
*/
146152
export async function callOoxmlTool(
147153
name: OoxmlToolName,
148154
args: Record<string, unknown>,
149155
env: OoxmlEnv,
150156
): Promise<string> {
151-
const sql: Sql = neon(env.DATABASE_URL);
157+
const sql = neon(env.DATABASE_URL);
158+
return runOoxmlTool(name, args, sql);
159+
}
160+
161+
/**
162+
* Driver-agnostic dispatch. `sql` is any tagged-template SQL function whose
163+
* shape matches `(strings, ...values) => Promise<row[]>` (Neon and postgres.js
164+
* both qualify).
165+
*/
166+
export async function runOoxmlTool(
167+
name: OoxmlToolName,
168+
args: Record<string, unknown>,
169+
sql: Sql,
170+
): Promise<string> {
152171
const profile = (args.profile as string | undefined) ?? DEFAULT_PROFILE;
153172

154173
switch (name) {
@@ -175,7 +194,11 @@ export async function callOoxmlTool(
175194
profile,
176195
);
177196
}
178-
return formatSymbolReport(hit.kind === "simpleType" ? "SimpleType" : "ComplexType", hit, profile);
197+
return formatSymbolReport(
198+
hit.kind === "simpleType" ? "SimpleType" : "ComplexType",
199+
hit,
200+
profile,
201+
);
179202
}
180203

181204
case "ooxml_children": {
@@ -286,7 +309,9 @@ function formatSymbolReport(label: string, hit: SymbolHit, profile: string): str
286309
lines.push(`## ${label}: ${hit.localName}`);
287310
lines.push("");
288311
lines.push(`- profile: ${profile}`);
289-
lines.push(`- canonical: (vocabulary=${hit.vocabularyId}, kind=${hit.kind}, name=${hit.localName})`);
312+
lines.push(
313+
`- canonical: (vocabulary=${hit.vocabularyId}, kind=${hit.kind}, name=${hit.localName})`,
314+
);
290315
lines.push(`- namespace: ${hit.namespaceUri}`);
291316
if (hit.typeRef) lines.push(`- type_ref: ${hit.typeRef}`);
292317
if (hit.sourceName) lines.push(`- source: ${hit.sourceName}`);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"xsd:fetch": "bun scripts/fetch-xsd.ts",
2525
"xsd:smoke": "bun scripts/ingest-xsd/smoke.ts",
2626
"xsd:ingest": "bun scripts/ingest-xsd/ingest.ts",
27+
"ooxml:call": "bun scripts/ooxml-call.ts",
2728
"test": "bun test tests/",
2829
"ingest": "bun scripts/ingest/pipeline.ts",
2930
"ingest:chunk": "bun scripts/ingest/chunk.ts",

scripts/ooxml-call.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Local end-to-end harness for the Phase 4 OOXML tools.
3+
*
4+
* The deployed Worker uses @neondatabase/serverless (HTTP-only), which can't
5+
* talk to local Postgres. This CLI bypasses the Worker and dispatches through
6+
* `runOoxmlTool` directly with a postgres.js-backed sql function, so the same
7+
* code path that the Worker exercises runs end-to-end against the dev DB.
8+
*
9+
* Usage:
10+
* bun scripts/ooxml-call.ts <tool> <jsonArgs>
11+
* bun scripts/ooxml-call.ts ooxml_children '{"qname":"w:tbl"}'
12+
* bun scripts/ooxml-call.ts ooxml_attributes '{"qname":"w:pBdr"}'
13+
* bun scripts/ooxml-call.ts ooxml_enum '{"qname":"w:ST_Jc"}'
14+
*
15+
* Environment:
16+
* DATABASE_URL - postgres connection string (defaults to local docker)
17+
*/
18+
19+
import { createDbClient } from "../packages/shared/src/db/index.ts";
20+
import {
21+
type OoxmlToolName,
22+
isOoxmlTool,
23+
runOoxmlTool,
24+
} from "../apps/mcp-server/src/ooxml-tools.ts";
25+
26+
async function main() {
27+
const [, , toolArg, argsArg] = process.argv;
28+
if (!toolArg) {
29+
console.error("Usage: bun scripts/ooxml-call.ts <tool> [jsonArgs]");
30+
console.error("Tools: ooxml_lookup_element, ooxml_lookup_type, ooxml_children,");
31+
console.error(" ooxml_attributes, ooxml_enum, ooxml_namespace_info");
32+
process.exit(1);
33+
}
34+
if (!isOoxmlTool(toolArg)) {
35+
console.error(`Unknown tool: ${toolArg}`);
36+
process.exit(1);
37+
}
38+
39+
const args: Record<string, unknown> = argsArg ? JSON.parse(argsArg) : {};
40+
41+
const databaseUrl =
42+
process.env.DATABASE_URL ?? "postgresql://postgres:postgres@localhost:5432/ecma_spec";
43+
const db = createDbClient(databaseUrl);
44+
45+
try {
46+
const text = await runOoxmlTool(toolArg as OoxmlToolName, args, db.sql);
47+
console.log(text);
48+
} finally {
49+
await db.close();
50+
}
51+
}
52+
53+
main().catch((err) => {
54+
console.error(err);
55+
process.exit(1);
56+
});

0 commit comments

Comments
 (0)