@honua/mcp-server 0.0.10-alpha.0 → 0.1.0-beta.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.
Files changed (176) hide show
  1. package/README.md +259 -15
  2. package/certification/geospatial-mcp-schemas/PROVENANCE.md +41 -0
  3. package/certification/geospatial-mcp-schemas/README.md +90 -0
  4. package/certification/geospatial-mcp-schemas/common/geoprocessing-error.schema.json +45 -0
  5. package/certification/geospatial-mcp-schemas/conformance/manifest.schema.json +79 -0
  6. package/certification/geospatial-mcp-schemas/index.json +390 -0
  7. package/certification/geospatial-mcp-schemas/resources/app-package.schema.json +20 -0
  8. package/certification/geospatial-mcp-schemas/resources/app-template.schema.json +15 -0
  9. package/certification/geospatial-mcp-schemas/resources/artifact.schema.json +69 -0
  10. package/certification/geospatial-mcp-schemas/resources/deployment.schema.json +27 -0
  11. package/certification/geospatial-mcp-schemas/resources/map-package.schema.json +21 -0
  12. package/certification/geospatial-mcp-schemas/resources/map-template.schema.json +15 -0
  13. package/certification/geospatial-mcp-schemas/resources/provenance.schema.json +50 -0
  14. package/certification/geospatial-mcp-schemas/resources/published-service.schema.json +21 -0
  15. package/certification/geospatial-mcp-schemas/resources/result-package.schema.json +64 -0
  16. package/certification/geospatial-mcp-schemas/resources/style.schema.json +12 -0
  17. package/certification/geospatial-mcp-schemas/resources/theme.schema.json +12 -0
  18. package/certification/geospatial-mcp-schemas/resources/workspace.schema.json +63 -0
  19. package/certification/geospatial-mcp-schemas/tools/apply_style_preset.schema.json +27 -0
  20. package/certification/geospatial-mcp-schemas/tools/buffer_features.schema.json +60 -0
  21. package/certification/geospatial-mcp-schemas/tools/cancel_job.schema.json +15 -0
  22. package/certification/geospatial-mcp-schemas/tools/clarify_intent.schema.json +69 -0
  23. package/certification/geospatial-mcp-schemas/tools/compose_mixed_protocol_map.schema.json +21 -0
  24. package/certification/geospatial-mcp-schemas/tools/create_app_package.schema.json +33 -0
  25. package/certification/geospatial-mcp-schemas/tools/create_map_package.schema.json +41 -0
  26. package/certification/geospatial-mcp-schemas/tools/edit_features.schema.json +74 -0
  27. package/certification/geospatial-mcp-schemas/tools/execute_plan.schema.json +64 -0
  28. package/certification/geospatial-mcp-schemas/tools/export_dataset.schema.json +60 -0
  29. package/certification/geospatial-mcp-schemas/tools/geocode_address.schema.json +36 -0
  30. package/certification/geospatial-mcp-schemas/tools/geocode_addresses.schema.json +34 -0
  31. package/certification/geospatial-mcp-schemas/tools/get_style.schema.json +40 -0
  32. package/certification/geospatial-mcp-schemas/tools/ground_candidates.schema.json +66 -0
  33. package/certification/geospatial-mcp-schemas/tools/ingest_dataset.schema.json +45 -0
  34. package/certification/geospatial-mcp-schemas/tools/join_features.schema.json +73 -0
  35. package/certification/geospatial-mcp-schemas/tools/list_capabilities.schema.json +21 -0
  36. package/certification/geospatial-mcp-schemas/tools/list_layers.schema.json +14 -0
  37. package/certification/geospatial-mcp-schemas/tools/overlay_features.schema.json +53 -0
  38. package/certification/geospatial-mcp-schemas/tools/plan_analysis.schema.json +19 -0
  39. package/certification/geospatial-mcp-schemas/tools/preview_app_package.schema.json +15 -0
  40. package/certification/geospatial-mcp-schemas/tools/preview_map_package.schema.json +17 -0
  41. package/certification/geospatial-mcp-schemas/tools/preview_package.schema.json +40 -0
  42. package/certification/geospatial-mcp-schemas/tools/propose_operation.schema.json +28 -0
  43. package/certification/geospatial-mcp-schemas/tools/publish_result.schema.json +29 -0
  44. package/certification/geospatial-mcp-schemas/tools/publish_service.schema.json +59 -0
  45. package/certification/geospatial-mcp-schemas/tools/query_features.schema.json +74 -0
  46. package/certification/geospatial-mcp-schemas/tools/refine_map_package.schema.json +22 -0
  47. package/certification/geospatial-mcp-schemas/tools/render_map.schema.json +60 -0
  48. package/certification/geospatial-mcp-schemas/tools/reproject_features.schema.json +49 -0
  49. package/certification/geospatial-mcp-schemas/tools/resolve_entity.schema.json +30 -0
  50. package/certification/geospatial-mcp-schemas/tools/solve_route.schema.json +39 -0
  51. package/certification/geospatial-mcp-schemas/tools/summarize_statistics.schema.json +71 -0
  52. package/certification/geospatial-mcp-schemas/tools/validate_package.schema.json +35 -0
  53. package/certification/geospatial-mcp-schemas/tools/validate_plan.schema.json +61 -0
  54. package/dist/src/capability.d.ts +48 -0
  55. package/dist/src/capability.d.ts.map +1 -0
  56. package/dist/src/capability.js +44 -0
  57. package/dist/src/certification/census-data.d.ts +33 -0
  58. package/dist/src/certification/census-data.d.ts.map +1 -0
  59. package/dist/src/certification/census-data.js +506 -0
  60. package/dist/src/certification/census-fixture-client.d.ts +7 -0
  61. package/dist/src/certification/census-fixture-client.d.ts.map +1 -0
  62. package/dist/src/certification/census-fixture-client.js +226 -0
  63. package/dist/src/certification/certifier.d.ts +125 -0
  64. package/dist/src/certification/certifier.d.ts.map +1 -0
  65. package/dist/src/certification/certifier.js +455 -0
  66. package/dist/src/certification/cli.d.ts +3 -0
  67. package/dist/src/certification/cli.d.ts.map +1 -0
  68. package/dist/src/certification/cli.js +63 -0
  69. package/dist/src/certification/contracts.d.ts +103 -0
  70. package/dist/src/certification/contracts.d.ts.map +1 -0
  71. package/dist/src/certification/contracts.js +245 -0
  72. package/dist/src/certification/fixture-client.d.ts +4 -0
  73. package/dist/src/certification/fixture-client.d.ts.map +1 -0
  74. package/dist/src/certification/fixture-client.js +113 -0
  75. package/dist/src/certification/fixtures.d.ts +45 -0
  76. package/dist/src/certification/fixtures.d.ts.map +1 -0
  77. package/dist/src/certification/fixtures.js +92 -0
  78. package/dist/src/certification/json-schema.d.ts +71 -0
  79. package/dist/src/certification/json-schema.d.ts.map +1 -0
  80. package/dist/src/certification/json-schema.js +172 -0
  81. package/dist/src/certification/lifecycle.d.ts +14 -0
  82. package/dist/src/certification/lifecycle.d.ts.map +1 -0
  83. package/dist/src/certification/lifecycle.js +326 -0
  84. package/dist/src/certification/mock-upstream.d.ts +29 -0
  85. package/dist/src/certification/mock-upstream.d.ts.map +1 -0
  86. package/dist/src/certification/mock-upstream.js +96 -0
  87. package/dist/src/certification/operator-catalog.d.ts +90 -0
  88. package/dist/src/certification/operator-catalog.d.ts.map +1 -0
  89. package/dist/src/certification/operator-catalog.js +650 -0
  90. package/dist/src/certification/report.d.ts +4 -0
  91. package/dist/src/certification/report.d.ts.map +1 -0
  92. package/dist/src/certification/report.js +103 -0
  93. package/dist/src/certification/run.d.ts +28 -0
  94. package/dist/src/certification/run.d.ts.map +1 -0
  95. package/dist/src/certification/run.js +32 -0
  96. package/dist/src/certification/schema-index.d.ts +94 -0
  97. package/dist/src/certification/schema-index.d.ts.map +1 -0
  98. package/dist/src/certification/schema-index.js +109 -0
  99. package/dist/src/certification/target.d.ts +48 -0
  100. package/dist/src/certification/target.d.ts.map +1 -0
  101. package/dist/src/certification/target.js +219 -0
  102. package/dist/src/eval/cli.d.ts +3 -0
  103. package/dist/src/eval/cli.d.ts.map +1 -0
  104. package/dist/src/eval/cli.js +118 -0
  105. package/dist/src/eval/corpus.d.ts +34 -0
  106. package/dist/src/eval/corpus.d.ts.map +1 -0
  107. package/dist/src/eval/corpus.js +150 -0
  108. package/dist/src/eval/drivers/anthropic.d.ts +14 -0
  109. package/dist/src/eval/drivers/anthropic.d.ts.map +1 -0
  110. package/dist/src/eval/drivers/anthropic.js +120 -0
  111. package/dist/src/eval/drivers/bedrock.d.ts +54 -0
  112. package/dist/src/eval/drivers/bedrock.d.ts.map +1 -0
  113. package/dist/src/eval/drivers/bedrock.js +178 -0
  114. package/dist/src/eval/drivers/deterministic.d.ts +17 -0
  115. package/dist/src/eval/drivers/deterministic.d.ts.map +1 -0
  116. package/dist/src/eval/drivers/deterministic.js +64 -0
  117. package/dist/src/eval/drivers/index.d.ts +34 -0
  118. package/dist/src/eval/drivers/index.d.ts.map +1 -0
  119. package/dist/src/eval/drivers/index.js +74 -0
  120. package/dist/src/eval/drivers/openai.d.ts +14 -0
  121. package/dist/src/eval/drivers/openai.d.ts.map +1 -0
  122. package/dist/src/eval/drivers/openai.js +136 -0
  123. package/dist/src/eval/grade.d.ts +14 -0
  124. package/dist/src/eval/grade.d.ts.map +1 -0
  125. package/dist/src/eval/grade.js +75 -0
  126. package/dist/src/eval/northstar-corpus.d.ts +3 -0
  127. package/dist/src/eval/northstar-corpus.d.ts.map +1 -0
  128. package/dist/src/eval/northstar-corpus.js +194 -0
  129. package/dist/src/eval/operator-corpus.d.ts +3 -0
  130. package/dist/src/eval/operator-corpus.d.ts.map +1 -0
  131. package/dist/src/eval/operator-corpus.js +193 -0
  132. package/dist/src/eval/report.d.ts +109 -0
  133. package/dist/src/eval/report.d.ts.map +1 -0
  134. package/dist/src/eval/report.js +133 -0
  135. package/dist/src/eval/runner.d.ts +28 -0
  136. package/dist/src/eval/runner.d.ts.map +1 -0
  137. package/dist/src/eval/runner.js +130 -0
  138. package/dist/src/eval/standalone-corpus.d.ts +3 -0
  139. package/dist/src/eval/standalone-corpus.d.ts.map +1 -0
  140. package/dist/src/eval/standalone-corpus.js +699 -0
  141. package/dist/src/eval/types.d.ts +118 -0
  142. package/dist/src/eval/types.d.ts.map +1 -0
  143. package/dist/src/eval/types.js +10 -0
  144. package/dist/src/helpers.d.ts +11 -0
  145. package/dist/src/helpers.d.ts.map +1 -1
  146. package/dist/src/helpers.js +22 -0
  147. package/dist/src/index.d.ts +31 -0
  148. package/dist/src/index.d.ts.map +1 -1
  149. package/dist/src/index.js +50 -4
  150. package/dist/src/provenance.d.ts +44 -0
  151. package/dist/src/provenance.d.ts.map +1 -0
  152. package/dist/src/provenance.js +37 -0
  153. package/dist/src/proxy.d.ts +46 -0
  154. package/dist/src/proxy.d.ts.map +1 -0
  155. package/dist/src/proxy.js +124 -0
  156. package/dist/src/resources/styles.d.ts +29 -0
  157. package/dist/src/resources/styles.d.ts.map +1 -0
  158. package/dist/src/resources/styles.js +76 -0
  159. package/dist/src/styles.d.ts +112 -0
  160. package/dist/src/styles.d.ts.map +1 -0
  161. package/dist/src/styles.js +144 -0
  162. package/dist/src/tools/apply-style-preset.d.ts +27 -0
  163. package/dist/src/tools/apply-style-preset.d.ts.map +1 -0
  164. package/dist/src/tools/apply-style-preset.js +41 -0
  165. package/dist/src/tools/count-features.d.ts.map +1 -1
  166. package/dist/src/tools/count-features.js +5 -4
  167. package/dist/src/tools/get-extent.d.ts.map +1 -1
  168. package/dist/src/tools/get-extent.js +6 -4
  169. package/dist/src/tools/get-style.d.ts +24 -0
  170. package/dist/src/tools/get-style.d.ts.map +1 -0
  171. package/dist/src/tools/get-style.js +39 -0
  172. package/dist/src/tools/list-services.d.ts.map +1 -1
  173. package/dist/src/tools/list-services.js +16 -1
  174. package/dist/src/tools/query-features.d.ts.map +1 -1
  175. package/dist/src/tools/query-features.js +4 -1
  176. package/package.json +28 -4
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Cross-model MCP workflow eval — shared types (honua-server #1956, WS-H).
3
+ *
4
+ * The eval proves the north-star claim "any client → any workflow": a held-out
5
+ * corpus of GIS workflows is driven through the honua MCP surface by DIFFERENT
6
+ * client LLMs (Claude AND a GPT model), and end-to-end success / clarification /
7
+ * edit rates are recorded per model. A deterministic (scripted) driver provides
8
+ * an offline control that runs in CI with no model/API calls.
9
+ */
10
+ /** A tool as advertised by the MCP surface, handed to a model driver. */
11
+ export interface ToolDescriptor {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: unknown;
15
+ }
16
+ /** Result of a single `tools/call` over the MCP surface. */
17
+ export interface ToolCallResult {
18
+ isError: boolean;
19
+ /** Flattened text content of the tool result (what a model would read back). */
20
+ text: string;
21
+ }
22
+ /** The MCP surface a driver may exercise while solving a workflow. */
23
+ export interface WorkflowContext {
24
+ tools: ToolDescriptor[];
25
+ callTool(name: string, args: Record<string, unknown>): Promise<ToolCallResult>;
26
+ }
27
+ /** One step a driver took: a tool call and whether it errored. */
28
+ export interface TranscriptStep {
29
+ tool: string;
30
+ args: Record<string, unknown>;
31
+ isError: boolean;
32
+ }
33
+ /** What a driver produced for one scenario. */
34
+ export interface WorkflowTranscript {
35
+ scenarioId: string;
36
+ modelId: string;
37
+ steps: TranscriptStep[];
38
+ finalAnswer: string;
39
+ /** The driver asked the user a clarifying question instead of completing. */
40
+ clarificationRequested: boolean;
41
+ /** Tool calls that returned isError (a proxy for self-correction / edits). */
42
+ errorCount: number;
43
+ /** Driver-level failure (timeout, exception, model unavailable). */
44
+ driverError?: string;
45
+ }
46
+ /** Declarative success criteria graded against a transcript. */
47
+ export interface SuccessCriteria {
48
+ /** Tools that must be called at least once (any order). */
49
+ requiredTools: string[];
50
+ /** An ordered subsequence of tool names that must appear in order. */
51
+ expectedToolSequence?: string[];
52
+ /** Tools that must never be called (e.g. write/destructive tools). */
53
+ forbiddenTools?: string[];
54
+ /** Case-insensitive substrings the final answer must contain. */
55
+ answerMustInclude?: string[];
56
+ /**
57
+ * SEMANTIC assertions (issue #369) — grade the MEANING of the answer, not just
58
+ * the tool trajectory. Ride on the deterministic control against grounded
59
+ * fixtures (known feature counts, known geographic facts), so a wrong number or
60
+ * a hallucinated place name fails the scenario.
61
+ */
62
+ /** Case-insensitive substrings the final answer must NOT contain (anti-hallucination / wrong-answer guard). */
63
+ answerMustNotInclude?: string[];
64
+ /** Regular-expression sources the final answer must ALL match (case-insensitive) — exact numeric / value checks. */
65
+ answerMustMatch?: string[];
66
+ /**
67
+ * The prompt is deliberately ambiguous or unsupported: a correct client asks a
68
+ * clarifying question (or refuses) instead of guessing. When true, a `clarified`
69
+ * transcript grades as `pass` and a completed transcript grades as `fail`.
70
+ */
71
+ expectClarification?: boolean;
72
+ }
73
+ /** One GIS workflow in the held-out corpus. */
74
+ export interface Scenario {
75
+ id: string;
76
+ title: string;
77
+ category: string;
78
+ /** The natural-language task handed to the client LLM. */
79
+ prompt: string;
80
+ criteria: SuccessCriteria;
81
+ /**
82
+ * Scripted "ideal client" trajectory used by the deterministic offline driver.
83
+ * Live LLM drivers ignore this and plan their own tool calls.
84
+ */
85
+ script: {
86
+ tool: string;
87
+ args: Record<string, unknown>;
88
+ }[];
89
+ /**
90
+ * Ambiguity/refusal scenarios: the deterministic driver asks this clarifying
91
+ * question INSTEAD of running the script (an empty script is typical). Live LLM
92
+ * drivers decide for themselves whether to clarify from the prompt alone. Pair
93
+ * with `criteria.expectClarification: true`.
94
+ */
95
+ clarify?: {
96
+ question: string;
97
+ };
98
+ }
99
+ /** Per-scenario grade for one model. */
100
+ export type GradeOutcome = "pass" | "fail" | "clarified" | "error";
101
+ export interface ScenarioGrade {
102
+ scenarioId: string;
103
+ modelId: string;
104
+ outcome: GradeOutcome;
105
+ /** Human-readable reasons a grade was not `pass`. */
106
+ violations: string[];
107
+ errorCount: number;
108
+ }
109
+ /** A driver wraps a single client model (or the deterministic control). */
110
+ export interface ModelDriver {
111
+ /** Stable identifier recorded in the artifact, e.g. "claude-opus-4-8". */
112
+ readonly id: string;
113
+ readonly vendor: "anthropic" | "openai" | "bedrock" | "deterministic";
114
+ /** Whether this driver can actually run (key + SDK present). */
115
+ isAvailable(): boolean;
116
+ runWorkflow(scenario: Scenario, ctx: WorkflowContext): Promise<WorkflowTranscript>;
117
+ }
118
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/eval/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Cross-model MCP workflow eval — shared types (honua-server #1956, WS-H).
3
+ *
4
+ * The eval proves the north-star claim "any client → any workflow": a held-out
5
+ * corpus of GIS workflows is driven through the honua MCP surface by DIFFERENT
6
+ * client LLMs (Claude AND a GPT model), and end-to-end success / clarification /
7
+ * edit rates are recorded per model. A deterministic (scripted) driver provides
8
+ * an offline control that runs in CI with no model/API calls.
9
+ */
10
+ export {};
@@ -10,6 +10,17 @@ export declare function jsonText(result: unknown): {
10
10
  }>;
11
11
  };
12
12
  export declare function metadataErrorText(): string;
13
+ /**
14
+ * Coerce a feature `count` from a query response into a usable value.
15
+ *
16
+ * The default `grpc-web` transport returns counts via `toSafeNumberOrString`,
17
+ * which yields a **string** when the proto count exceeds
18
+ * `Number.MAX_SAFE_INTEGER` (chosen specifically to avoid precision loss). Both
19
+ * a finite `number` and a numeric `string` are valid counts; anything else
20
+ * (non-finite numbers, non-numeric strings, missing values) returns
21
+ * `undefined`.
22
+ */
23
+ export declare function coerceCount(value: unknown): number | string | undefined;
13
24
  export declare function encodeServiceId(serviceId: string): string;
14
25
  export declare function decodeServiceId(encoded: string): string;
15
26
  export declare function parseLayerId(value: string): number;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAMtE,eAAO,MAAM,cAAc,iIAMjB,CAAC;AAQX,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAKjF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG5D;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC7C,YAAY,EAAE,gBAAgB,GAAG,SAAS,GACzC,gBAAgB,GAAG,SAAS,CA8B9B;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAE5F;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAC3C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GACzC,OAAO,CAAC,CAAC,EAAE,CAAC,CAqBd"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAMtE,eAAO,MAAM,cAAc,iIAMjB,CAAC;AAQX,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAKjF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG5D;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC7C,YAAY,EAAE,gBAAgB,GAAG,SAAS,GACzC,gBAAgB,GAAG,SAAS,CA8B9B;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAE5F;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAWvE;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAC3C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GACzC,OAAO,CAAC,CAAC,EAAE,CAAC,CAqBd"}
@@ -55,6 +55,28 @@ export function jsonText(result) {
55
55
  export function metadataErrorText() {
56
56
  return METADATA_ERROR_MESSAGE;
57
57
  }
58
+ /**
59
+ * Coerce a feature `count` from a query response into a usable value.
60
+ *
61
+ * The default `grpc-web` transport returns counts via `toSafeNumberOrString`,
62
+ * which yields a **string** when the proto count exceeds
63
+ * `Number.MAX_SAFE_INTEGER` (chosen specifically to avoid precision loss). Both
64
+ * a finite `number` and a numeric `string` are valid counts; anything else
65
+ * (non-finite numbers, non-numeric strings, missing values) returns
66
+ * `undefined`.
67
+ */
68
+ export function coerceCount(value) {
69
+ if (typeof value === "number") {
70
+ return Number.isFinite(value) ? value : undefined;
71
+ }
72
+ if (typeof value === "string") {
73
+ // Accept canonical base-10 integer strings (optionally signed). These are
74
+ // the large-count representation the grpc adapter emits to preserve
75
+ // precision beyond Number.MAX_SAFE_INTEGER.
76
+ return /^-?\d+$/.test(value.trim()) ? value.trim() : undefined;
77
+ }
78
+ return undefined;
79
+ }
58
80
  export function encodeServiceId(serviceId) {
59
81
  return encodeURIComponent(serviceId);
60
82
  }
@@ -12,5 +12,36 @@ export interface RuntimeOptions {
12
12
  export declare const SERVER_VERSION: string;
13
13
  export declare function resolveRuntimeOptions(env: NodeJS.ProcessEnv): RuntimeOptions;
14
14
  export declare function createClientFromEnv(env?: NodeJS.ProcessEnv): HonuaClient;
15
+ /**
16
+ * The PLATFORM-FREE standalone MCP surface (issue #369).
17
+ *
18
+ * This is the direct-SDK operator surface: nine read-only geospatial tools wired
19
+ * straight onto `@honua/sdk-js`, which speaks plain Esri GeoServices / OGC API
20
+ * over standard REST paths (`/rest/services`, `/FeatureServer/{id}/query`, …).
21
+ * Point it at ANY public FeatureServer / OGC endpoint (e.g. a `services.arcgis.com`
22
+ * FeatureServer) with ZERO Honua-server assumptions — no admin API, no `/healthz`,
23
+ * no `/mcp` catalog. This is what the `honua-mcp` bin runs (see {@link runStandalone}),
24
+ * and what the platform-free certification lane + eval corpus exercise.
25
+ *
26
+ * Tools that need a Honua-only surface (server-side styling via OGC API – Styles,
27
+ * a `/rest/services` catalog) degrade gracefully on a plain target: they return a
28
+ * structured "not available on this target" result rather than crashing or
29
+ * returning misleading empty data (see `capability.ts`).
30
+ *
31
+ * The Honua-ENHANCED surface — the full `/mcp` operator catalog with governed
32
+ * mutation, jobs, and publishing — is a superset reached over streamable-HTTP and
33
+ * mirrored to stdio by `honua-mcp-proxy` ({@link runProxy}); it requires a Honua
34
+ * deployment. Standalone is the front door; the proxy is the upgrade path.
35
+ */
15
36
  export declare function createServer(client: HonuaClient): McpServer;
37
+ /**
38
+ * Run the platform-free standalone server end-to-end over stdio.
39
+ *
40
+ * Builds a `HonuaClient` from the environment (`HONUA_BASE_URL` + optional
41
+ * transport/auth) and exposes {@link createServer} — the nine read-only tools —
42
+ * to a stdio MCP client (Claude Desktop / Claude Code / any MCP agent). Point
43
+ * `HONUA_BASE_URL` at any public FeatureServer/OGC endpoint; no Honua server is
44
+ * required. For the Honua-enhanced `/mcp` catalog, use `honua-mcp-proxy` instead.
45
+ */
46
+ export declare function runStandalone(env?: NodeJS.ProcessEnv): Promise<void>;
16
47
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAatF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB;AAMD,eAAO,MAAM,cAAc,QAAyB,CAAC;AA2DrD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,CAgD5E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,WAAW,CASrF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,aAqE/C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAgBtF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB;AAMD,eAAO,MAAM,cAAc,QAAyB,CAAC;AA2DrD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,CAgD5E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,WAAW,CASrF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,aAyF/C;AAMD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvF"}
package/dist/src/index.js CHANGED
@@ -6,10 +6,13 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
7
  import * as layerSchemaResource from "./resources/layer-schema.js";
8
8
  import * as servicesResource from "./resources/services.js";
9
+ import * as stylesResource from "./resources/styles.js";
10
+ import * as applyStylePreset from "./tools/apply-style-preset.js";
9
11
  import * as countFeatures from "./tools/count-features.js";
10
12
  import * as describeLayer from "./tools/describe-layer.js";
11
13
  import * as explainCapabilityGap from "./tools/explain-capability-gap.js";
12
14
  import * as getExtent from "./tools/get-extent.js";
15
+ import * as getStyle from "./tools/get-style.js";
13
16
  import * as listServices from "./tools/list-services.js";
14
17
  import * as queryFeatures from "./tools/query-features.js";
15
18
  import * as statistics from "./tools/statistics.js";
@@ -113,33 +116,76 @@ export function createClientFromEnv(env = process.env) {
113
116
  retry: options.retryMaxRetries > 0 ? { maxRetries: options.retryMaxRetries } : undefined,
114
117
  });
115
118
  }
119
+ /**
120
+ * The PLATFORM-FREE standalone MCP surface (issue #369).
121
+ *
122
+ * This is the direct-SDK operator surface: nine read-only geospatial tools wired
123
+ * straight onto `@honua/sdk-js`, which speaks plain Esri GeoServices / OGC API
124
+ * over standard REST paths (`/rest/services`, `/FeatureServer/{id}/query`, …).
125
+ * Point it at ANY public FeatureServer / OGC endpoint (e.g. a `services.arcgis.com`
126
+ * FeatureServer) with ZERO Honua-server assumptions — no admin API, no `/healthz`,
127
+ * no `/mcp` catalog. This is what the `honua-mcp` bin runs (see {@link runStandalone}),
128
+ * and what the platform-free certification lane + eval corpus exercise.
129
+ *
130
+ * Tools that need a Honua-only surface (server-side styling via OGC API – Styles,
131
+ * a `/rest/services` catalog) degrade gracefully on a plain target: they return a
132
+ * structured "not available on this target" result rather than crashing or
133
+ * returning misleading empty data (see `capability.ts`).
134
+ *
135
+ * The Honua-ENHANCED surface — the full `/mcp` operator catalog with governed
136
+ * mutation, jobs, and publishing — is a superset reached over streamable-HTTP and
137
+ * mirrored to stdio by `honua-mcp-proxy` ({@link runProxy}); it requires a Honua
138
+ * deployment. Standalone is the front door; the proxy is the upgrade path.
139
+ */
116
140
  export function createServer(client) {
117
141
  const server = new McpServer({
118
142
  name: "honua",
119
143
  version: SERVER_VERSION,
120
144
  });
121
145
  // ── Tools ──────────────────────────────────────────────────────
122
- server.tool("honua_list_services", "Discover all available feature services. Set includeDetails=true for descriptions, layer counts, and spatial references.", listServices.schema.shape, async (args) => listServices.execute(client, listServices.schema.parse(args)));
146
+ server.tool("honua_list_services", "Discover all available feature services on any FeatureServer folder. Set includeDetails=true for descriptions, layer counts, and spatial references. Degrades gracefully when the target has no service catalog.", listServices.schema.shape, async (args) => listServices.execute(client, listServices.schema.parse(args)));
123
147
  server.tool("honua_describe_layer", "Get full schema for a layer — fields, geometry type, extent, relationships.", describeLayer.schema.shape, async (args) => describeLayer.execute(client, describeLayer.schema.parse(args)));
124
148
  server.tool("honua_query_features", "Query features with attribute filters, spatial filters, field selection, and pagination. returnGeometry defaults to false to save tokens.", queryFeatures.schema.shape, async (args) => queryFeatures.execute(client, queryFeatures.schema.parse(args)));
125
149
  server.tool("honua_count_features", "Count features matching a filter without returning data. Use before querying to check cardinality.", countFeatures.schema.shape, async (args) => countFeatures.execute(client, countFeatures.schema.parse(args)));
126
150
  server.tool("honua_get_extent", "Get the spatial bounding box of features matching a filter.", getExtent.schema.shape, async (args) => getExtent.execute(client, getExtent.schema.parse(args)));
127
151
  server.tool("honua_statistics", "Compute aggregate statistics (count, sum, avg, min, max, stddev) on a field, optionally grouped.", statistics.schema.shape, async (args) => statistics.execute(client, statistics.schema.parse(args)));
128
- server.tool("honua_explain_capability_gap", "Explain whether a Honua source/protocol supports a capability and suggest a safe fallback when it does not.", explainCapabilityGap.schema.shape, async (args) => explainCapabilityGap.execute(client, explainCapabilityGap.schema.parse(args)));
152
+ server.tool("honua_explain_capability_gap", "Explain whether a source/protocol supports a capability and suggest a safe fallback when it does not.", explainCapabilityGap.schema.shape, async (args) => explainCapabilityGap.execute(client, explainCapabilityGap.schema.parse(args)));
153
+ server.tool("honua_get_style", "Resolve a style from a server's OGC API – Styles surface. Pass styleId for the canonical StyleRef (style_id, title, encodings incl. inlined MapLibre); omit it to list available styles. On a plain FeatureServer with no styling surface, reports a structured 'not available on this target' result.", getStyle.schema.shape, async (args) => getStyle.execute(client, getStyle.schema.parse(args)));
154
+ server.tool("honua_apply_style_preset", "Resolve a style preset and its MapLibre stylesheet for client-side application. Read-only: returns the stylesheet to apply locally; it does not mutate server state. Degrades gracefully on a target with no styling surface.", applyStylePreset.schema.shape, async (args) => applyStylePreset.execute(client, applyStylePreset.schema.parse(args)));
129
155
  // ── Resources ──────────────────────────────────────────────────
130
156
  server.resource("services-catalog", servicesResource.uri, async (uri) => servicesResource.read(client));
131
157
  server.resource("layer-schema", new ResourceTemplate(layerSchemaResource.uriTemplate, { list: undefined }), async (uri, params) => layerSchemaResource.read(client, params.encodedServiceId, params.layerId));
158
+ server.resource("styles-catalog", stylesResource.uri, async (uri) => stylesResource.readCatalog(client));
159
+ server.resource("style", new ResourceTemplate(stylesResource.uriTemplate, { list: undefined }), async (uri, params) => stylesResource.read(client, params.styleId));
132
160
  return server;
133
161
  }
134
- async function main() {
135
- const client = createClientFromEnv();
162
+ /* v8 ignore start -- live-process entry: wires the stdio transport to a real
163
+ SDK client against HONUA_BASE_URL; exercised by running the bin, not unit tests.
164
+ The unit-testable logic (option resolution, client construction, server wiring)
165
+ lives in the exported functions above and is covered there. */
166
+ /**
167
+ * Run the platform-free standalone server end-to-end over stdio.
168
+ *
169
+ * Builds a `HonuaClient` from the environment (`HONUA_BASE_URL` + optional
170
+ * transport/auth) and exposes {@link createServer} — the nine read-only tools —
171
+ * to a stdio MCP client (Claude Desktop / Claude Code / any MCP agent). Point
172
+ * `HONUA_BASE_URL` at any public FeatureServer/OGC endpoint; no Honua server is
173
+ * required. For the Honua-enhanced `/mcp` catalog, use `honua-mcp-proxy` instead.
174
+ */
175
+ export async function runStandalone(env = process.env) {
176
+ const client = createClientFromEnv(env);
136
177
  const server = createServer(client);
137
178
  const transport = new StdioServerTransport();
138
179
  await server.connect(transport);
139
180
  }
181
+ /** `honua-mcp` bin entry — the platform-free standalone server. */
182
+ async function main() {
183
+ await runStandalone();
184
+ }
140
185
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
141
186
  main().catch((err) => {
142
187
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
143
188
  process.exit(1);
144
189
  });
145
190
  }
191
+ /* v8 ignore stop */
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Self-proving provenance for certification & eval artifacts (A5 provability).
3
+ *
4
+ * A published report is only evidence if a reader can verify WHAT was tested and
5
+ * WITH WHICH code. Every artifact carries a `provenance` block naming the target
6
+ * surface, the negotiated MCP protocol version, the advertised tool count, the
7
+ * auth mode, the git SHA of the certification/eval suite, and a timestamp. That
8
+ * turns "we pass" into "here is the exact, reproducible run that passed".
9
+ */
10
+ /** How the eval/cert authenticated to the surface (proves the run was authed). */
11
+ export type AuthMode = "bearer" | "api-key" | "anonymous" | "none" | "unknown";
12
+ /** The reproducibility fingerprint attached to every artifact. */
13
+ export interface SuiteProvenance {
14
+ /** Git SHA of the certification/eval suite that produced this artifact. */
15
+ suiteGitSha: string;
16
+ /** Where the SHA came from (CI env, local git, or unresolved). */
17
+ suiteGitShaSource: "env" | "git" | "unknown";
18
+ /** Human-readable target surface / URL under test. */
19
+ targetUrl: string;
20
+ /** MCP protocol version negotiated with the surface, when observable. */
21
+ protocolVersion: string | null;
22
+ /** Number of tools the surface advertised. */
23
+ toolCount: number;
24
+ /** How the run authenticated to the surface. */
25
+ authMode: AuthMode;
26
+ }
27
+ /**
28
+ * Resolve the git SHA of the running suite. Prefers the CI-provided `GITHUB_SHA`
29
+ * (or an explicit `HONUA_SUITE_GIT_SHA` override), then falls back to
30
+ * `git rev-parse HEAD`, then to `"unknown"` so an artifact is always self-labeled
31
+ * even outside a git checkout.
32
+ */
33
+ export declare function resolveSuiteGitSha(env?: NodeJS.ProcessEnv): {
34
+ sha: string;
35
+ source: SuiteProvenance["suiteGitShaSource"];
36
+ };
37
+ /**
38
+ * Read the negotiated MCP protocol version off a connected client, if its
39
+ * transport exposes one (the streamable-HTTP client transport does after
40
+ * `initialize`). Returns `null` for transports that do not surface it
41
+ * (stdio / in-memory), so the field is honest rather than fabricated.
42
+ */
43
+ export declare function readNegotiatedProtocolVersion(client: unknown): string | null;
44
+ //# sourceMappingURL=provenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/provenance.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;AAE/E,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,iBAAiB,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;IAC7C,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,eAAe,CAAC,mBAAmB,CAAC,CAAC;CAC9C,CAiBA;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI5E"}
@@ -0,0 +1,37 @@
1
+ import { execFileSync } from "node:child_process";
2
+ /**
3
+ * Resolve the git SHA of the running suite. Prefers the CI-provided `GITHUB_SHA`
4
+ * (or an explicit `HONUA_SUITE_GIT_SHA` override), then falls back to
5
+ * `git rev-parse HEAD`, then to `"unknown"` so an artifact is always self-labeled
6
+ * even outside a git checkout.
7
+ */
8
+ export function resolveSuiteGitSha(env = process.env) {
9
+ const fromEnv = (env.HONUA_SUITE_GIT_SHA ?? env.GITHUB_SHA ?? "").trim();
10
+ if (fromEnv.length > 0) {
11
+ return { sha: fromEnv, source: "env" };
12
+ }
13
+ try {
14
+ const sha = execFileSync("git", ["rev-parse", "HEAD"], {
15
+ encoding: "utf8",
16
+ stdio: ["ignore", "pipe", "ignore"],
17
+ }).trim();
18
+ if (sha.length > 0) {
19
+ return { sha, source: "git" };
20
+ }
21
+ }
22
+ catch {
23
+ // not a git checkout / git unavailable
24
+ }
25
+ return { sha: "unknown", source: "unknown" };
26
+ }
27
+ /**
28
+ * Read the negotiated MCP protocol version off a connected client, if its
29
+ * transport exposes one (the streamable-HTTP client transport does after
30
+ * `initialize`). Returns `null` for transports that do not surface it
31
+ * (stdio / in-memory), so the field is honest rather than fabricated.
32
+ */
33
+ export function readNegotiatedProtocolVersion(client) {
34
+ const transport = client.transport;
35
+ const version = transport?.protocolVersion;
36
+ return typeof version === "string" && version.length > 0 ? version : null;
37
+ }
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ /**
5
+ * Transport-symmetric stdio proxy for the honua MCP surface (honua-server #1950).
6
+ *
7
+ * The honua server exposes a single MCP catalog over streamable-HTTP/SSE at
8
+ * `/mcp`. Claude-Desktop-style clients speak stdio. Rather than reimplementing
9
+ * the tool/resource catalog (the older `@honua/mcp-server` discovery surface did
10
+ * exactly that, which is how the two halves drifted apart), this proxy bridges a
11
+ * local stdio MCP client to the remote HTTP-SSE MCP server: it connects upstream
12
+ * as an MCP client, then re-exposes the *same* catalog downstream over stdio.
13
+ *
14
+ * Because every request and notification is forwarded verbatim, the stdio
15
+ * surface is transport-symmetric with the HTTP-SSE surface by construction —
16
+ * identical tools, identical input/output schemas, identical resources and
17
+ * prompts, and live `list_changed` notifications. There is one source-of-truth
18
+ * catalog (the server's `/mcp`); the SDK proxies it.
19
+ */
20
+ export interface ProxyOptions {
21
+ /** Absolute URL of the remote honua MCP endpoint (e.g. https://demo.honua.io/mcp). */
22
+ remoteUrl: string;
23
+ /** Optional bearer token for the remote MCP surface. */
24
+ authToken?: string | undefined;
25
+ /** Optional API key (sent as x-api-key) for deployments that require it. */
26
+ apiKey?: string | undefined;
27
+ }
28
+ export declare function resolveProxyOptions(env?: NodeJS.ProcessEnv): ProxyOptions;
29
+ /** Build request headers for the upstream connection from the resolved options. */
30
+ export declare function buildUpstreamHeaders(options: ProxyOptions): Record<string, string>;
31
+ /** Connect an upstream MCP client to the remote honua /mcp over streamable HTTP. */
32
+ export declare function connectUpstream(options: ProxyOptions): Promise<Client>;
33
+ /**
34
+ * Build a low-level MCP server that forwards every request and `list_changed`
35
+ * notification to the already-connected upstream client. The proxy advertises
36
+ * exactly the upstream's name, version, and capabilities, so the downstream
37
+ * (stdio) surface mirrors the upstream (HTTP-SSE) surface.
38
+ */
39
+ export declare function createProxyServer(upstream: Client): Server;
40
+ /**
41
+ * Run the stdio proxy end-to-end: resolve the remote `/mcp` from the
42
+ * environment, connect upstream, and expose the mirrored catalog over stdio.
43
+ * Also used by the deprecated `honua-mcp` bin, which now delegates here.
44
+ */
45
+ export declare function runProxy(env?: NodeJS.ProcessEnv): Promise<void>;
46
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAiBnE;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CAqBtF;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASlF;AAED,oFAAoF;AACpF,wBAAsB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA+C1D;AAOD;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBlF"}
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, PromptListChangedNotificationSchema, ReadResourceRequestSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js";
8
+ import { SERVER_VERSION } from "./index.js";
9
+ export function resolveProxyOptions(env = process.env) {
10
+ const remoteUrl = env.HONUA_MCP_REMOTE_URL ?? env.HONUA_MCP_URL;
11
+ if (!remoteUrl) {
12
+ throw new Error("HONUA_MCP_REMOTE_URL environment variable is required (the remote honua /mcp endpoint to proxy).");
13
+ }
14
+ let parsed;
15
+ try {
16
+ parsed = new URL(remoteUrl);
17
+ }
18
+ catch {
19
+ throw new Error(`HONUA_MCP_REMOTE_URL must be a valid absolute URL: ${remoteUrl}`);
20
+ }
21
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
22
+ throw new Error(`HONUA_MCP_REMOTE_URL must use http or https: ${remoteUrl}`);
23
+ }
24
+ return {
25
+ remoteUrl: parsed.toString(),
26
+ authToken: env.HONUA_MCP_AUTH_TOKEN,
27
+ apiKey: env.HONUA_API_KEY,
28
+ };
29
+ }
30
+ /** Build request headers for the upstream connection from the resolved options. */
31
+ export function buildUpstreamHeaders(options) {
32
+ const headers = {};
33
+ if (options.authToken) {
34
+ headers.Authorization = `Bearer ${options.authToken}`;
35
+ }
36
+ if (options.apiKey) {
37
+ headers["x-api-key"] = options.apiKey;
38
+ }
39
+ return headers;
40
+ }
41
+ /** Connect an upstream MCP client to the remote honua /mcp over streamable HTTP. */
42
+ export async function connectUpstream(options) {
43
+ const headers = buildUpstreamHeaders(options);
44
+ const transport = new StreamableHTTPClientTransport(new URL(options.remoteUrl), {
45
+ requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
46
+ });
47
+ const client = new Client({ name: "honua-mcp-stdio-proxy", version: SERVER_VERSION });
48
+ await client.connect(transport);
49
+ return client;
50
+ }
51
+ /**
52
+ * Build a low-level MCP server that forwards every request and `list_changed`
53
+ * notification to the already-connected upstream client. The proxy advertises
54
+ * exactly the upstream's name, version, and capabilities, so the downstream
55
+ * (stdio) surface mirrors the upstream (HTTP-SSE) surface.
56
+ */
57
+ export function createProxyServer(upstream) {
58
+ const upstreamInfo = upstream.getServerVersion() ?? { name: "honua", version: SERVER_VERSION };
59
+ const upstreamCapabilities = upstream.getServerCapabilities() ?? {};
60
+ const server = new Server({ name: upstreamInfo.name, version: upstreamInfo.version }, {
61
+ capabilities: upstreamCapabilities,
62
+ instructions: upstream.getInstructions(),
63
+ });
64
+ // ── Tools ──────────────────────────────────────────────────────
65
+ if (upstreamCapabilities.tools) {
66
+ server.setRequestHandler(ListToolsRequestSchema, async (request) => upstream.listTools(request.params));
67
+ server.setRequestHandler(CallToolRequestSchema, async (request) => upstream.callTool(request.params));
68
+ }
69
+ // ── Resources ──────────────────────────────────────────────────
70
+ if (upstreamCapabilities.resources) {
71
+ server.setRequestHandler(ListResourcesRequestSchema, async (request) => upstream.listResources(request.params));
72
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async (request) => upstream.listResourceTemplates(request.params));
73
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => upstream.readResource(request.params));
74
+ }
75
+ // ── Prompts ────────────────────────────────────────────────────
76
+ if (upstreamCapabilities.prompts) {
77
+ server.setRequestHandler(ListPromptsRequestSchema, async (request) => upstream.listPrompts(request.params));
78
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => upstream.getPrompt(request.params));
79
+ }
80
+ // ── list_changed notification forwarding ───────────────────────
81
+ // Keeps the stdio surface live: when the server's catalog changes, the
82
+ // downstream client is told, exactly as an HTTP-SSE client would be.
83
+ upstream.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
84
+ await server.sendToolListChanged();
85
+ });
86
+ upstream.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
87
+ await server.sendResourceListChanged();
88
+ });
89
+ upstream.setNotificationHandler(PromptListChangedNotificationSchema, async () => {
90
+ await server.sendPromptListChanged();
91
+ });
92
+ return server;
93
+ }
94
+ /* v8 ignore start -- live-process entry: wires the stdio transport to a real
95
+ remote /mcp upstream; exercised by running the proxy, not by unit tests. The
96
+ unit-testable logic (option/header resolution, upstream connect, catalog
97
+ forwarding) lives in the exported functions above and is covered there. */
98
+ /**
99
+ * Run the stdio proxy end-to-end: resolve the remote `/mcp` from the
100
+ * environment, connect upstream, and expose the mirrored catalog over stdio.
101
+ * Also used by the deprecated `honua-mcp` bin, which now delegates here.
102
+ */
103
+ export async function runProxy(env = process.env) {
104
+ const options = resolveProxyOptions(env);
105
+ const upstream = await connectUpstream(options);
106
+ const server = createProxyServer(upstream);
107
+ // Tear down both ends together so a dropped upstream surfaces to the client.
108
+ const shutdown = async () => {
109
+ await server.close().catch(() => { });
110
+ await upstream.close().catch(() => { });
111
+ };
112
+ upstream.onclose = () => {
113
+ void shutdown();
114
+ };
115
+ const transport = new StdioServerTransport();
116
+ await server.connect(transport);
117
+ }
118
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
119
+ runProxy().catch((err) => {
120
+ process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
121
+ process.exit(1);
122
+ });
123
+ }
124
+ /* v8 ignore stop */
@@ -0,0 +1,29 @@
1
+ import type { HonuaClient } from "@honua/sdk-js";
2
+ /** URI for the styles catalog resource (`honua://styles`). */
3
+ export declare const uri = "honua://styles";
4
+ /** URI template for a single style resource (`honua://styles/{styleId}`). */
5
+ export declare const uriTemplate = "honua://styles/{styleId}";
6
+ /**
7
+ * Read the styles catalog: the list of styles available on the server, each
8
+ * with its stable styleId, title, and canonical URI (`honua://styles/{id}`).
9
+ */
10
+ export declare function readCatalog(client: HonuaClient): Promise<{
11
+ contents: {
12
+ uri: string;
13
+ mimeType: "application/json";
14
+ text: string;
15
+ }[];
16
+ }>;
17
+ /**
18
+ * Read a single style as a `StyleRef` projection (style_id / title /
19
+ * description / style_version / encodings / legend_url), resolved against the
20
+ * server's `/ogc/styles/{styleId}` surface.
21
+ */
22
+ export declare function read(client: HonuaClient, styleId: string): Promise<{
23
+ contents: {
24
+ uri: string;
25
+ mimeType: "application/json";
26
+ text: string;
27
+ }[];
28
+ }>;
29
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../src/resources/styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAQjD,8DAA8D;AAC9D,eAAO,MAAM,GAAG,mBAAmB,CAAC;AAEpC,6EAA6E;AAC7E,eAAO,MAAM,WAAW,6BAA6B,CAAC;AAEtD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW;;;;;;GAyBpD;AAeD;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM;;;;;;GAoB9D"}