@skaleagents/swarm 0.1.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.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @skaleagents/swarm (legacy template)
2
+
3
+ Historical stdio MCP starter for SkaleAgents Phase 1 (`review_architecture`, `scan_iac_stub`).
4
+ The extracted [`mcp-server`](https://github.com/SkaleAgents/mcp-server) repo is canonical.
5
+
6
+ This template talks to the legacy Fastify API with `SKALEAGENTS_API_TOKEN` (personal access token from mock Google login).
7
+
8
+ The default points at the Fastify template on `:4000`. For the extracted Laravel API, use `PLATFORM_API_URL=http://localhost:8082` and `PLATFORM_API_PREFIX=/api`.
9
+
10
+ ## Local
11
+
12
+ ```bash
13
+ npm install
14
+ cp .env.example .env
15
+ # Get a token:
16
+ # curl -s -X POST http://localhost:4000/auth/google/callback \
17
+ # -H 'Content-Type: application/json' \
18
+ # -d '{"code":"mcp","displayName":"MCP","email":"mcp@example.com"}'
19
+ npm run build
20
+ npm test
21
+ npm run smoke # needs platform-api on PLATFORM_API_URL
22
+ # Extracted Laravel API:
23
+ # PLATFORM_API_URL=http://localhost:8082 PLATFORM_API_PREFIX=/api npm run smoke
24
+ ```
25
+
26
+ ## Cursor / Claude Code
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "skaleagents": {
32
+ "command": "npx",
33
+ "args": ["tsx", "/absolute/path/to/mcp-server/src/index.ts"],
34
+ "env": {
35
+ "SKALEAGENTS_API_TOKEN": "<token from Google callback>",
36
+ "PLATFORM_API_URL": "http://localhost:4000",
37
+ "PLATFORM_API_PREFIX": ""
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ Or after build: `"command": "node", "args": ["/absolute/path/to/mcp-server/dist/index.js"]`.
45
+
46
+ Hub contract: [docs/contracts/mcp/tools.md](https://github.com/SkaleAgents/workspace/blob/main/docs/contracts/mcp/tools.md)
package/dist/auth.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ export type AuthResult = {
2
+ ok: true;
3
+ userId?: string;
4
+ } | {
5
+ ok: false;
6
+ reason: "missing_token" | "unauthorized" | "api_unavailable";
7
+ };
8
+ /**
9
+ * Require the API to validate the bearer token before running any tool.
10
+ */
11
+ export declare function requireApiAuth(): Promise<AuthResult>;
12
+ export declare function unauthorizedContent(reason: AuthResult & {
13
+ ok: false;
14
+ }): {
15
+ isError: true;
16
+ content: {
17
+ type: "text";
18
+ text: string;
19
+ }[];
20
+ };
package/dist/auth.js ADDED
@@ -0,0 +1,40 @@
1
+ import { getApiEndpoint, getApiToken } from "./config.js";
2
+ /**
3
+ * Require the API to validate the bearer token before running any tool.
4
+ */
5
+ export async function requireApiAuth() {
6
+ const token = getApiToken();
7
+ if (!token) {
8
+ return { ok: false, reason: "missing_token" };
9
+ }
10
+ try {
11
+ const res = await fetch(getApiEndpoint("/user"), {
12
+ headers: {
13
+ Accept: "application/json",
14
+ Authorization: `Bearer ${token}`,
15
+ },
16
+ });
17
+ if (res.status === 401 || res.status === 403) {
18
+ return { ok: false, reason: "unauthorized" };
19
+ }
20
+ if (!res.ok) {
21
+ return { ok: false, reason: "unauthorized" };
22
+ }
23
+ const user = (await res.json());
24
+ return { ok: true, userId: user.id };
25
+ }
26
+ catch {
27
+ return { ok: false, reason: "api_unavailable" };
28
+ }
29
+ }
30
+ export function unauthorizedContent(reason) {
31
+ const text = reason.reason === "missing_token"
32
+ ? "unauthorized: set SKALEAGENTS_API_TOKEN"
33
+ : reason.reason === "unauthorized"
34
+ ? "unauthorized: invalid SKALEAGENTS_API_TOKEN"
35
+ : "api unavailable: could not validate SKALEAGENTS_API_TOKEN";
36
+ return {
37
+ isError: true,
38
+ content: [{ type: "text", text }],
39
+ };
40
+ }
@@ -0,0 +1,3 @@
1
+ export declare function getApiUrl(): string;
2
+ export declare function getApiEndpoint(path: string): string;
3
+ export declare function getApiToken(): string;
package/dist/config.js ADDED
@@ -0,0 +1,11 @@
1
+ export function getApiUrl() {
2
+ return (process.env.PLATFORM_API_URL?.replace(/\/$/, "") ?? "http://localhost:4000");
3
+ }
4
+ export function getApiEndpoint(path) {
5
+ const rawPrefix = (process.env.PLATFORM_API_PREFIX ?? "").replace(/\/$/, "");
6
+ const prefix = rawPrefix && !rawPrefix.startsWith("/") ? `/${rawPrefix}` : rawPrefix;
7
+ return `${getApiUrl()}${prefix}${path.startsWith("/") ? path : `/${path}`}`;
8
+ }
9
+ export function getApiToken() {
10
+ return process.env.SKALEAGENTS_API_TOKEN?.trim() ?? "";
11
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { requireApiAuth, unauthorizedContent } from "./auth.js";
6
+ import { cannedFindings, countIacResources, fetchPublicBotHints, } from "./review.js";
7
+ const server = new McpServer({
8
+ name: "skaleagents-swarm",
9
+ version: "0.1.0",
10
+ });
11
+ server.registerTool("review_architecture", {
12
+ title: "Review architecture",
13
+ description: "Request a high-level architecture / security review of a code or IaC snippet.",
14
+ inputSchema: {
15
+ content: z.string().describe("Source or IaC text to review"),
16
+ focus: z
17
+ .string()
18
+ .optional()
19
+ .describe("Review focus: security, reliability, cost, general"),
20
+ },
21
+ }, async ({ content, focus }) => {
22
+ const auth = await requireApiAuth();
23
+ if (!auth.ok)
24
+ return unauthorizedContent(auth);
25
+ const botHints = await fetchPublicBotHints();
26
+ const focusValue = focus === "security" ||
27
+ focus === "reliability" ||
28
+ focus === "cost" ||
29
+ focus === "general"
30
+ ? focus
31
+ : "general";
32
+ const output = {
33
+ summary: "Phase 1 stub architecture review from @skaleagents/swarm",
34
+ findings: cannedFindings(content, focusValue),
35
+ botHints,
36
+ };
37
+ return {
38
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
39
+ };
40
+ });
41
+ server.registerTool("scan_iac_stub", {
42
+ title: "Scan IaC (stub)",
43
+ description: "Stub seam for Phase 2 DevSecOps IaC scanning.",
44
+ inputSchema: {
45
+ content: z
46
+ .string()
47
+ .describe("Terraform / CloudFormation / Kubernetes YAML"),
48
+ format: z
49
+ .string()
50
+ .optional()
51
+ .describe("terraform, cloudformation, kubernetes, auto"),
52
+ },
53
+ }, async ({ content, format }) => {
54
+ const auth = await requireApiAuth();
55
+ if (!auth.ok)
56
+ return unauthorizedContent(auth);
57
+ const formatValue = format === "terraform" ||
58
+ format === "cloudformation" ||
59
+ format === "kubernetes" ||
60
+ format === "auto"
61
+ ? format
62
+ : "auto";
63
+ const output = {
64
+ status: "stub",
65
+ message: "Full IaC scanning lands in Phase 2 agent-swarm",
66
+ format: formatValue,
67
+ parsedResourceCount: countIacResources(content),
68
+ };
69
+ return {
70
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
71
+ };
72
+ });
73
+ async function main() {
74
+ const transport = new StdioServerTransport();
75
+ await server.connect(transport);
76
+ }
77
+ main().catch((err) => {
78
+ console.error(err);
79
+ process.exit(1);
80
+ });
@@ -0,0 +1,9 @@
1
+ export type FindingSeverity = "info" | "low" | "medium" | "high" | "critical";
2
+ export type Finding = {
3
+ severity: FindingSeverity;
4
+ title: string;
5
+ detail: string;
6
+ };
7
+ export declare function cannedFindings(content: string, focus: string): Finding[];
8
+ export declare function fetchPublicBotHints(): Promise<string[]>;
9
+ export declare function countIacResources(content: string): number;
package/dist/review.js ADDED
@@ -0,0 +1,48 @@
1
+ import { getApiEndpoint, getApiToken } from "./config.js";
2
+ export function cannedFindings(content, focus) {
3
+ const findings = [
4
+ {
5
+ severity: "info",
6
+ title: "Stub review",
7
+ detail: `Phase 1 canned review (focus=${focus}). Length=${content.length} chars.`,
8
+ },
9
+ ];
10
+ if (/0\.0\.0\.0(?:\/0)?/.test(content)) {
11
+ findings.push({
12
+ severity: "high",
13
+ title: "Broad network exposure",
14
+ detail: "Detected a possible wide-open CIDR or bind address.",
15
+ });
16
+ }
17
+ if (/AKIA[0-9A-Z]{16}/.test(content)) {
18
+ findings.push({
19
+ severity: "critical",
20
+ title: "Possible AWS access key",
21
+ detail: "Remove secrets from source; rotate credentials.",
22
+ });
23
+ }
24
+ return findings;
25
+ }
26
+ export async function fetchPublicBotHints() {
27
+ try {
28
+ const res = await fetch(getApiEndpoint("/bots"), {
29
+ headers: {
30
+ Accept: "application/json",
31
+ Authorization: `Bearer ${getApiToken()}`,
32
+ },
33
+ });
34
+ if (!res.ok)
35
+ return [];
36
+ const data = (await res.json());
37
+ return data.bots
38
+ .filter((b) => b.status === "published" && b.visibility === "public")
39
+ .map((b) => b.name)
40
+ .slice(0, 5);
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ }
46
+ export function countIacResources(content) {
47
+ return (content.match(/\bresource\b|\bkind:\s*\w+/gi) ?? []).length;
48
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@skaleagents/swarm",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "SkaleAgents MCP server — stdio tools against the Laravel api",
6
+ "bin": {
7
+ "skaleagents-swarm": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "start": "node dist/index.js",
15
+ "dev": "tsx src/index.ts",
16
+ "typecheck": "tsc --noEmit",
17
+ "test": "tsx --test src/**/*.test.ts",
18
+ "smoke": "tsx scripts/smoke-tools.mjs"
19
+ },
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.30.0",
25
+ "zod": "^4.4.3"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^22.20.1",
29
+ "tsx": "^4.23.12",
30
+ "typescript": "^5.7.3"
31
+ }
32
+ }