create-multicast 0.4.2 → 0.4.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-multicast",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Create a Multicast MCP gateway — one command to scaffold, configure, and deploy your parallel MCP server.",
5
5
  "type": "module",
6
6
  "bin": "./dist/cli.js",
@@ -919,66 +919,118 @@ Clears the cache and re-fetches tools/list from every registered server.`,
919
919
 
920
920
  this.server.tool(
921
921
  "fetch_result",
922
- `Retrieve the full output of a large result that was stored by reference.
923
- When multicast returns an output_ref instead of inline output, call this
924
- tool with the ref ID to get the complete data. Results expire after 1 hour.`,
922
+ `Retrieve the full output of large results stored by reference.
923
+ When multicast returns output_ref instead of inline output, call this tool
924
+ to get the complete data. Accepts a SINGLE ref or MULTIPLE refs at once.
925
+ Always pass ALL refs in one call — don't call this tool multiple times.
926
+ Results expire after 1 hour.
927
+
928
+ Example (single): { "ref": "ref_abc123" }
929
+ Example (batch): { "refs": ["ref_abc123", "ref_def456"] }`,
925
930
  {
926
931
  ref: z
927
932
  .string()
928
- .describe("The output_ref ID from a multicast result (e.g., ref_abc123def456)"),
933
+ .optional()
934
+ .describe("Single output_ref ID (use this OR refs, not both)"),
935
+ refs: z
936
+ .array(z.string())
937
+ .optional()
938
+ .describe("Multiple output_ref IDs to fetch at once (use this OR ref, not both)"),
929
939
  },
930
- async ({ ref }) => {
940
+ async ({ ref, refs }) => {
931
941
  const db = this.env.DB;
932
942
 
933
- const row = await db
934
- .prepare(
935
- "SELECT server_name, tool_name, output, created_at FROM result_cache WHERE ref_id = ?"
936
- )
937
- .bind(ref)
938
- .first<{
939
- server_name: string;
940
- tool_name: string;
941
- output: string;
942
- created_at: string;
943
- }>();
944
-
945
- if (!row) {
943
+ // Normalize: single ref or batch refs into one array
944
+ const refIds: string[] = [];
945
+ if (refs && refs.length > 0) {
946
+ refIds.push(...refs);
947
+ } else if (ref) {
948
+ refIds.push(ref);
949
+ } else {
946
950
  return {
947
- content: [
948
- {
949
- type: "text" as const,
950
- text: JSON.stringify({
951
- error: `Result not found for ref "${ref}". It may have expired (results are kept for 1 hour).`,
952
- }),
953
- },
954
- ],
951
+ content: [{
952
+ type: "text" as const,
953
+ text: JSON.stringify({ error: "Provide either 'ref' (single) or 'refs' (array)." }),
954
+ }],
955
955
  };
956
956
  }
957
957
 
958
- // Parse the stored output back
959
- let parsedOutput: unknown;
960
- try {
961
- parsedOutput = JSON.parse(row.output);
962
- } catch {
963
- parsedOutput = row.output;
958
+ // Fetch all refs in one D1 batch
959
+ const results: Array<{
960
+ ref_id: string;
961
+ server: string;
962
+ tool: string;
963
+ output: unknown;
964
+ cached_at: string;
965
+ error?: string;
966
+ }> = [];
967
+
968
+ for (const refId of refIds) {
969
+ const row = await db
970
+ .prepare(
971
+ "SELECT server_name, tool_name, output, created_at FROM result_cache WHERE ref_id = ?"
972
+ )
973
+ .bind(refId)
974
+ .first<{
975
+ server_name: string;
976
+ tool_name: string;
977
+ output: string;
978
+ created_at: string;
979
+ }>();
980
+
981
+ if (!row) {
982
+ results.push({
983
+ ref_id: refId,
984
+ server: "unknown",
985
+ tool: "unknown",
986
+ output: null,
987
+ cached_at: "",
988
+ error: `Not found — may have expired (1 hour TTL).`,
989
+ });
990
+ continue;
991
+ }
992
+
993
+ let parsedOutput: unknown;
994
+ try {
995
+ parsedOutput = JSON.parse(row.output);
996
+ } catch {
997
+ parsedOutput = row.output;
998
+ }
999
+
1000
+ results.push({
1001
+ ref_id: refId,
1002
+ server: row.server_name,
1003
+ tool: row.tool_name,
1004
+ output: parsedOutput,
1005
+ cached_at: row.created_at,
1006
+ });
964
1007
  }
965
1008
 
966
- return {
967
- content: [
968
- {
1009
+ // Single ref: return flat (backwards compatible)
1010
+ if (refIds.length === 1 && results.length === 1 && !results[0].error) {
1011
+ return {
1012
+ content: [{
969
1013
  type: "text" as const,
970
- text: JSON.stringify(
971
- {
972
- server: row.server_name,
973
- tool: row.tool_name,
974
- output: parsedOutput,
975
- cached_at: row.created_at,
976
- },
977
- null,
978
- 2
979
- ),
980
- },
981
- ],
1014
+ text: JSON.stringify({
1015
+ server: results[0].server,
1016
+ tool: results[0].tool,
1017
+ output: results[0].output,
1018
+ cached_at: results[0].cached_at,
1019
+ }, null, 2),
1020
+ }],
1021
+ };
1022
+ }
1023
+
1024
+ // Batch: return array of results
1025
+ return {
1026
+ content: [{
1027
+ type: "text" as const,
1028
+ text: JSON.stringify({
1029
+ results,
1030
+ fetched: results.filter((r) => !r.error).length,
1031
+ errors: results.filter((r) => r.error).length,
1032
+ }, null, 2),
1033
+ }],
982
1034
  };
983
1035
  }
984
1036
  );
@@ -1024,6 +1076,86 @@ export default {
1024
1076
  );
1025
1077
  }
1026
1078
 
1079
+ // Status endpoint — full server + tool listing
1080
+ // Use: curl https://your-multicast.workers.dev/status
1081
+ // Or: curl https://your-multicast.workers.dev/status | jq .
1082
+ if (url.pathname === "/status") {
1083
+ try {
1084
+ const registry = getRegisteredServers(env);
1085
+ const db = env.DB;
1086
+
1087
+ const servers = await db
1088
+ .prepare(
1089
+ "SELECT name, url, status, tool_count, last_error, last_discovered_at FROM servers ORDER BY name"
1090
+ )
1091
+ .all<{
1092
+ name: string;
1093
+ url: string;
1094
+ status: string;
1095
+ tool_count: number;
1096
+ last_error: string | null;
1097
+ last_discovered_at: string | null;
1098
+ }>();
1099
+
1100
+ const serverDetails = [];
1101
+ for (const server of servers.results) {
1102
+ // Only include servers that are in the current registry
1103
+ if (!registry.has(server.name)) continue;
1104
+
1105
+ const regServer = registry.get(server.name)!;
1106
+ const tools = await db
1107
+ .prepare(
1108
+ "SELECT tool_name, description FROM tools WHERE server_name = ? ORDER BY tool_name"
1109
+ )
1110
+ .bind(server.name)
1111
+ .all<{ tool_name: string; description: string }>();
1112
+
1113
+ serverDetails.push({
1114
+ name: server.name,
1115
+ url: server.url,
1116
+ status: server.status,
1117
+ auth: regServer.isOAuth ? "oauth" : regServer.auth ? "static" : "none",
1118
+ tool_count: server.tool_count,
1119
+ last_error: server.last_error,
1120
+ last_discovered: server.last_discovered_at,
1121
+ tools: tools.results.map((t) => ({
1122
+ name: t.tool_name,
1123
+ description: t.description.slice(0, 120) + (t.description.length > 120 ? "..." : ""),
1124
+ })),
1125
+ });
1126
+ }
1127
+
1128
+ const totalTools = serverDetails.reduce((sum, s) => sum + s.tool_count, 0);
1129
+
1130
+ return new Response(
1131
+ JSON.stringify(
1132
+ {
1133
+ name: "Multicast",
1134
+ version: "0.4.2",
1135
+ description: "MCP gateway — one integration, all your servers, parallel execution",
1136
+ total_servers: serverDetails.length,
1137
+ total_tools: totalTools,
1138
+ servers: serverDetails,
1139
+ },
1140
+ null,
1141
+ 2
1142
+ ),
1143
+ {
1144
+ headers: {
1145
+ "Content-Type": "application/json",
1146
+ "Access-Control-Allow-Origin": "*",
1147
+ },
1148
+ }
1149
+ );
1150
+ } catch (err: unknown) {
1151
+ const message = err instanceof Error ? err.message : "unknown error";
1152
+ return new Response(
1153
+ JSON.stringify({ error: message }),
1154
+ { status: 500, headers: { "Content-Type": "application/json" } }
1155
+ );
1156
+ }
1157
+ }
1158
+
1027
1159
  // Return clean 404 for OAuth discovery and other non-MCP paths
1028
1160
  // Claude Code's HTTP transport probes /.well-known/oauth-authorization-server
1029
1161
  // If this returns a JSON-RPC error, Claude Code's OAuth parser breaks.