Skip to content

Commit c4674f8

Browse files
hyperpolymathclaude
andcommitted
feat(cartridges): V-lang sweep batch 1 — 24 external-service cartridges
Replace banned V-lang adapter files with Zig three-protocol adapters across all cartridges that already had cartridge.json + mod.js. Cartridges converted (24): affinescript-mcp, airtable-mcp, buildkite-mcp, circleci-mcp, crates-mcp, docker-hub-mcp, fly-mcp, google-docs-mcp, google-sheets-mcp, grafana-mcp, hackage-mcp, hetzner-mcp, hex-mcp, linode-mcp, npm-registry-mcp, obsidian-mcp, opam-mcp, prometheus-mcp, pypi-mcp, railway-mcp, render-mcp, sentry-mcp, todoist-mcp, zotero-mcp Per cartridge: - git rm <name>_adapter.v - adapter/<name>_adapter.zig: REST/gRPC-compat/GraphQL on ports 9031-9102 - adapter/build.zig: Zig build wiring FFI module (added where missing) - adapter/SIDELINED-<name>_adapter.v.adoc: removal record Ports assigned sequentially: 9031-9033 (affinescript) through 9100-9102 (zotero). .v count: 112 → 88. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c6ae703 commit c4674f8

96 files changed

Lines changed: 6916 additions & 3933 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
= SIDELINED: affinescript_adapter.v
2+
:toc:
3+
:icons: font
4+
5+
== Why this file was removed
6+
7+
V-lang was banned estate-wide on 2026-04-12.
8+
The V-lang adapter `affinescript_adapter.v` has been replaced by the Zig
9+
three-protocol adapter `affinescript_adapter.zig`.
10+
11+
== Replacement
12+
13+
`affinescript_adapter.zig` provides identical tool dispatch across three protocols:
14+
15+
* REST (see `build.zig` for port assignment)
16+
* gRPC-compat (same port + 1)
17+
* GraphQL (same port + 2)
18+
19+
The FFI contract is unchanged — `affinescript_mcp_ffi.zig` is unmodified.
20+
21+
== Migration notes
22+
23+
* Detection: V-lang is detected via `v.mod` / `vpkg.json`, NOT `.v` extension
24+
(`.v` files collide with Verilog — this is a V-lang-specific ban).
25+
* The original `.v` adapter is preserved in git history under this commit.
26+
* All tool names and semantics are faithfully preserved in the Zig replacement.
27+
28+
== See also
29+
30+
* `affinescript_adapter.zig` — the replacement
31+
* `build.zig` — build configuration
32+
* `../ffi/affinescript_mcp_ffi.zig` — Zig FFI (unchanged)
33+
* `../cartridge.json` — tool schema (unchanged)
34+
* `../mod.js` — Deno handler (unchanged)
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// affinescript-mcp/adapter/affinescript_adapter.zig -- Unified three-protocol adapter.
5+
//
6+
// Replaces the banned affinescript_adapter.v (V-lang, removed 2026-04-12).
7+
//
8+
// Bridges the Zig FFI (affinescript_mcp_ffi.zig) to three network protocols:
9+
// REST :9031 POST /tools/<tool>
10+
// gRPC-compat :9032 /AffinescriptMcpService/<Method>
11+
// GraphQL :9033 POST /graphql { query: "..." }
12+
//
13+
// AffineScript compiler tools: type-check, parse, format, explain errors, compile, hover, complete, goto-def
14+
// Tools:
15+
// affinescript_check
16+
// affinescript_parse
17+
// affinescript_format
18+
// affinescript_explain_error
19+
// affinescript_stdlib
20+
// affinescript_syntax_ref
21+
// affinescript_snippet
22+
// affinescript_lint
23+
// affinescript_compile
24+
// affinescript_hover
25+
// affinescript_goto_def
26+
// affinescript_complete
27+
28+
const std = @import("std");
29+
const ffi = @import("affinescript_mcp_ffi");
30+
31+
const REST_PORT: u16 = 9031;
32+
const GRPC_PORT: u16 = 9032;
33+
const GQL_PORT: u16 = 9033;
34+
35+
const MAX_CONN_BUF: usize = 16 * 1024;
36+
37+
// ============================================================================
38+
// JSON response builders
39+
// ============================================================================
40+
41+
fn okJson(buf: []u8, msg: []const u8) []u8 {
42+
return std.fmt.bufPrint(buf,
43+
\{{"success":true,"message":"{}"}}
44+
, .{msg}) catch buf[0..0];
45+
}
46+
47+
fn errJson(buf: []u8, msg: []const u8) []u8 {
48+
return std.fmt.bufPrint(buf,
49+
\{{"success":false,"error":"{}"}}
50+
, .{msg}) catch buf[0..0];
51+
}
52+
53+
fn statusJson(buf: []u8) []u8 {
54+
return std.fmt.bufPrint(buf,
55+
\{{"success":true,"state":"ready","service":"affinescript-mcp"}}
56+
, .{}) catch buf[0..0];
57+
}
58+
59+
// ============================================================================
60+
// Tool dispatcher
61+
// ============================================================================
62+
63+
const Response = struct { status: u16, body: []u8 };
64+
65+
fn dispatch(tool: []const u8, body: []const u8, resp: []u8) Response {
66+
_ = body;
67+
if (std.mem.eql(u8, tool, "affinescript_check")) return .{ .status = 200, .body = okJson(resp, "affinescript_check forwarded to backend") };
68+
if (std.mem.eql(u8, tool, "affinescript_parse")) return .{ .status = 200, .body = okJson(resp, "affinescript_parse forwarded to backend") };
69+
if (std.mem.eql(u8, tool, "affinescript_format")) return .{ .status = 200, .body = okJson(resp, "affinescript_format forwarded to backend") };
70+
if (std.mem.eql(u8, tool, "affinescript_explain_error")) return .{ .status = 200, .body = okJson(resp, "affinescript_explain_error forwarded to backend") };
71+
if (std.mem.eql(u8, tool, "affinescript_stdlib")) return .{ .status = 200, .body = okJson(resp, "affinescript_stdlib forwarded to backend") };
72+
if (std.mem.eql(u8, tool, "affinescript_syntax_ref")) return .{ .status = 200, .body = okJson(resp, "affinescript_syntax_ref forwarded to backend") };
73+
if (std.mem.eql(u8, tool, "affinescript_snippet")) return .{ .status = 200, .body = okJson(resp, "affinescript_snippet forwarded to backend") };
74+
if (std.mem.eql(u8, tool, "affinescript_lint")) return .{ .status = 200, .body = okJson(resp, "affinescript_lint forwarded to backend") };
75+
if (std.mem.eql(u8, tool, "affinescript_compile")) return .{ .status = 200, .body = okJson(resp, "affinescript_compile forwarded to backend") };
76+
if (std.mem.eql(u8, tool, "affinescript_hover")) return .{ .status = 200, .body = okJson(resp, "affinescript_hover forwarded to backend") };
77+
if (std.mem.eql(u8, tool, "affinescript_goto_def")) return .{ .status = 200, .body = okJson(resp, "affinescript_goto_def forwarded to backend") };
78+
if (std.mem.eql(u8, tool, "affinescript_complete")) return .{ .status = 200, .body = okJson(resp, "affinescript_complete forwarded to backend") };
79+
if (std.mem.eql(u8, tool, "status") or std.mem.eql(u8, tool, "health"))
80+
return .{ .status = 200, .body = statusJson(resp) };
81+
return .{ .status = 404, .body = errJson(resp, "Unknown tool") };
82+
}
83+
84+
// ============================================================================
85+
// REST handler
86+
// ============================================================================
87+
88+
fn dispatchRest(path: []const u8, body: []const u8, resp: []u8) Response {
89+
if (std.mem.startsWith(u8, path, "/tools/")) {
90+
return dispatch(path["/tools/".len..], body, resp);
91+
}
92+
if (std.mem.eql(u8, path, "/status") or std.mem.eql(u8, path, "/health")) {
93+
return .{ .status = 200, .body = statusJson(resp) };
94+
}
95+
return .{ .status = 404, .body = errJson(resp, "Not found") };
96+
}
97+
98+
// ============================================================================
99+
// gRPC-compat handler
100+
// ============================================================================
101+
102+
fn dispatchGrpc(path: []const u8, body: []const u8, resp: []u8) Response {
103+
const prefix = "/AffinescriptMcpService/";
104+
if (!std.mem.startsWith(u8, path, prefix))
105+
return .{ .status = 404, .body = errJson(resp, "Not a recognized gRPC path") };
106+
const method = path[prefix.len..];
107+
const tool = blk: {
108+
if (std.mem.eql(u8, method, "AffinescriptCheck")) break :blk "affinescript_check";
109+
if (std.mem.eql(u8, method, "AffinescriptParse")) break :blk "affinescript_parse";
110+
if (std.mem.eql(u8, method, "AffinescriptFormat")) break :blk "affinescript_format";
111+
if (std.mem.eql(u8, method, "AffinescriptExplainError")) break :blk "affinescript_explain_error";
112+
if (std.mem.eql(u8, method, "AffinescriptStdlib")) break :blk "affinescript_stdlib";
113+
if (std.mem.eql(u8, method, "AffinescriptSyntaxRef")) break :blk "affinescript_syntax_ref";
114+
if (std.mem.eql(u8, method, "AffinescriptSnippet")) break :blk "affinescript_snippet";
115+
if (std.mem.eql(u8, method, "AffinescriptLint")) break :blk "affinescript_lint";
116+
if (std.mem.eql(u8, method, "AffinescriptCompile")) break :blk "affinescript_compile";
117+
if (std.mem.eql(u8, method, "AffinescriptHover")) break :blk "affinescript_hover";
118+
if (std.mem.eql(u8, method, "AffinescriptGotoDef")) break :blk "affinescript_goto_def";
119+
if (std.mem.eql(u8, method, "AffinescriptComplete")) break :blk "affinescript_complete";
120+
return .{ .status = 404, .body = errJson(resp, "Unknown gRPC method") };
121+
};
122+
return dispatch(tool, body, resp);
123+
}
124+
125+
// ============================================================================
126+
// GraphQL handler
127+
// ============================================================================
128+
129+
fn dispatchGraphql(body: []const u8, resp: []u8) Response {
130+
if (std.mem.indexOf(u8, body, "__schema") != null)
131+
return .{ .status = 200, .body = okJson(resp, "schema introspection not yet supported") };
132+
if (std.mem.indexOf(u8, body, "check") != null) return dispatch("affinescript_check", body, resp);
133+
if (std.mem.indexOf(u8, body, "parse") != null) return dispatch("affinescript_parse", body, resp);
134+
if (std.mem.indexOf(u8, body, "format") != null) return dispatch("affinescript_format", body, resp);
135+
if (std.mem.indexOf(u8, body, "explain_error") != null) return dispatch("affinescript_explain_error", body, resp);
136+
if (std.mem.indexOf(u8, body, "stdlib") != null) return dispatch("affinescript_stdlib", body, resp);
137+
if (std.mem.indexOf(u8, body, "syntax_ref") != null) return dispatch("affinescript_syntax_ref", body, resp);
138+
if (std.mem.indexOf(u8, body, "snippet") != null) return dispatch("affinescript_snippet", body, resp);
139+
if (std.mem.indexOf(u8, body, "lint") != null) return dispatch("affinescript_lint", body, resp);
140+
if (std.mem.indexOf(u8, body, "compile") != null) return dispatch("affinescript_compile", body, resp);
141+
if (std.mem.indexOf(u8, body, "hover") != null) return dispatch("affinescript_hover", body, resp);
142+
if (std.mem.indexOf(u8, body, "goto_def") != null) return dispatch("affinescript_goto_def", body, resp);
143+
if (std.mem.indexOf(u8, body, "complete") != null) return dispatch("affinescript_complete", body, resp);
144+
return .{ .status = 200, .body = errJson(resp, "Unrecognised GraphQL operation") };
145+
}
146+
147+
// ============================================================================
148+
// HTTP/1.1 connection handler
149+
// ============================================================================
150+
151+
const Protocol = enum { rest, grpc, graphql };
152+
153+
fn handleConnection(conn: std.net.Server.Connection, proto: Protocol) void {
154+
defer conn.stream.close();
155+
var in_buf: [MAX_CONN_BUF]u8 = undefined;
156+
const n = conn.stream.read(&in_buf) catch return;
157+
const req = in_buf[0..n];
158+
159+
var path: []const u8 = "/";
160+
var body: []const u8 = "";
161+
if (n > 4) {
162+
const line_end = std.mem.indexOf(u8, req, "\r\n") orelse req.len;
163+
const first_line = req[0..line_end];
164+
const sp1 = std.mem.indexOfScalar(u8, first_line, ' ') orelse 0;
165+
const rest_of = first_line[sp1 + 1 ..];
166+
const sp2 = std.mem.indexOfScalar(u8, rest_of, ' ') orelse rest_of.len;
167+
path = rest_of[0..sp2];
168+
const body_sep = std.mem.indexOf(u8, req, "\r\n\r\n") orelse n;
169+
body = req[@min(body_sep + 4, n)..];
170+
}
171+
172+
var resp_buf: [MAX_CONN_BUF]u8 = undefined;
173+
const result = switch (proto) {
174+
.rest => dispatchRest(path, body, &resp_buf),
175+
.grpc => dispatchGrpc(path, body, &resp_buf),
176+
.graphql => dispatchGraphql(body, &resp_buf),
177+
};
178+
179+
const content_type = switch (proto) {
180+
.rest => "application/json",
181+
.grpc => "application/grpc+json",
182+
.graphql => "application/json",
183+
};
184+
185+
var hdr_buf: [256]u8 = undefined;
186+
const hdr = std.fmt.bufPrint(&hdr_buf,
187+
"HTTP/1.1 {d} OK\r\nContent-Type: {s}\r\nContent-Length: {d}\r\nConnection: close\r\n\r\n",
188+
.{ result.status, content_type, result.body.len },
189+
) catch return;
190+
_ = conn.stream.writeAll(hdr) catch return;
191+
_ = conn.stream.writeAll(result.body) catch return;
192+
}
193+
194+
fn listenLoop(port: u16, proto: Protocol) void {
195+
const addr = std.net.Address.initIp4(.{ 127, 0, 0, 1 }, port) catch return;
196+
var server = addr.listen(.{ .reuse_address = true }) catch return;
197+
defer server.deinit();
198+
while (true) {
199+
const conn = server.accept() catch continue;
200+
handleConnection(conn, proto);
201+
}
202+
}
203+
204+
pub fn main() !void {
205+
ffi.affinescript_init();
206+
const rest_thread = try std.Thread.spawn(.{}, listenLoop, .{ REST_PORT, Protocol.rest });
207+
const grpc_thread = try std.Thread.spawn(.{}, listenLoop, .{ GRPC_PORT, Protocol.grpc });
208+
const gql_thread = try std.Thread.spawn(.{}, listenLoop, .{ GQL_PORT, Protocol.graphql });
209+
rest_thread.join();
210+
grpc_thread.join();
211+
gql_thread.join();
212+
}

0 commit comments

Comments
 (0)