agent-config-mcp 1.0.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 (3) hide show
  1. package/README.md +41 -0
  2. package/index.js +150 -0
  3. package/package.json +11 -0
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # agent-config-mcp
2
+
3
+ MCP server for syncing agent config (`.kiro/`) via Git. Changes are submitted as PRs for review.
4
+
5
+ ## Tools
6
+
7
+ | Tool | Description |
8
+ |------|-------------|
9
+ | `ac_init(repo)` | Clone agent config repo into `.kiro/` |
10
+ | `ac_status` | Show sync status (changed files, ahead/behind) |
11
+ | `ac_push(message?)` | Create branch → commit → push → open PR |
12
+ | `ac_pull` | Pull latest from main |
13
+
14
+ ## Setup
15
+
16
+ Add to your MCP config (`mcp.json`):
17
+
18
+ ```json
19
+ {
20
+ "agent-config": {
21
+ "command": "npx",
22
+ "args": ["-y", "agent-config-mcp"]
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## First use
28
+
29
+ Ask your agent to run `ac_init` with your config repo URL:
30
+
31
+ ```
32
+ ac_init({ repo: "https://github.com/your-org/your-agent-config.git" })
33
+ ```
34
+
35
+ This clones the repo into `.kiro/` of your current project. After that, `ac_push` and `ac_pull` work automatically.
36
+
37
+ ## Requirements
38
+
39
+ - Node.js 18+
40
+ - Git
41
+ - GitHub CLI (`gh`) — for PR creation
package/index.js ADDED
@@ -0,0 +1,150 @@
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 { execFile } from "child_process";
6
+ import { promisify } from "util";
7
+ import { existsSync } from "fs";
8
+ import { join } from "path";
9
+
10
+ const exec = promisify(execFile);
11
+ const CONFIG_DIR = ".kiro";
12
+
13
+ function configPath() {
14
+ const cwd = process.cwd();
15
+ return join(cwd, CONFIG_DIR);
16
+ }
17
+
18
+ async function git(args, cwd) {
19
+ const { stdout, stderr } = await exec("git", args, { cwd });
20
+ return stdout.trim();
21
+ }
22
+
23
+ function isGitRepo(dir) {
24
+ return existsSync(join(dir, ".git"));
25
+ }
26
+
27
+ const server = new McpServer({
28
+ name: "agent-config-mcp",
29
+ version: "1.0.0",
30
+ });
31
+
32
+ // ac_status
33
+ server.tool(
34
+ "ac_status",
35
+ "Show sync status of .kiro/ config directory",
36
+ {},
37
+ async () => {
38
+ const dir = configPath();
39
+ if (!existsSync(dir)) {
40
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: ".kiro/ directory not found" }) }] };
41
+ }
42
+ if (!isGitRepo(dir)) {
43
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: ".kiro/ is not a git repository. Run ac_init first." }) }] };
44
+ }
45
+ try {
46
+ const status = await git(["status", "--porcelain"], dir);
47
+ const log = await git(["log", "-1", "--format=%h %s (%cr)"], dir);
48
+ const remote = await git(["remote", "get-url", "origin"], dir);
49
+ let ahead = 0, behind = 0;
50
+ try {
51
+ await git(["fetch", "--quiet"], dir);
52
+ const ab = await git(["rev-list", "--left-right", "--count", "HEAD...@{u}"], dir);
53
+ [ahead, behind] = ab.split("\t").map(Number);
54
+ } catch {}
55
+ const files = status ? status.split("\n").map(l => l.trim()) : [];
56
+ return { content: [{ type: "text", text: JSON.stringify({
57
+ success: true, remote, lastCommit: log,
58
+ changed: files.length, files, ahead, behind
59
+ }) }] };
60
+ } catch (e) {
61
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] };
62
+ }
63
+ }
64
+ );
65
+
66
+ // ac_pull
67
+ server.tool(
68
+ "ac_pull",
69
+ "Pull latest changes from remote",
70
+ {},
71
+ async () => {
72
+ const dir = configPath();
73
+ if (!isGitRepo(dir)) {
74
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: ".kiro/ is not a git repository" }) }] };
75
+ }
76
+ try {
77
+ const result = await git(["pull", "--rebase"], dir);
78
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, message: result || "Already up to date" }) }] };
79
+ } catch (e) {
80
+ if (e.message.includes("CONFLICT")) {
81
+ const conflicts = await git(["diff", "--name-only", "--diff-filter=U"], dir);
82
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Conflict", conflicts: conflicts.split("\n") }) }] };
83
+ }
84
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] };
85
+ }
86
+ }
87
+ );
88
+
89
+ // ac_push
90
+ server.tool(
91
+ "ac_push",
92
+ "Create a branch, commit changes, push, and open a PR",
93
+ { message: z.string().optional().describe("Commit/PR message (auto-generated if omitted)") },
94
+ async ({ message }) => {
95
+ const dir = configPath();
96
+ if (!isGitRepo(dir)) {
97
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: ".kiro/ is not a git repository" }) }] };
98
+ }
99
+ try {
100
+ await git(["add", "-A"], dir);
101
+ const status = await git(["status", "--porcelain"], dir);
102
+ if (!status) {
103
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, message: "No changes to push" }) }] };
104
+ }
105
+ const files = status.split("\n").map(l => l.trim());
106
+ const msg = message || `sync: ${files.map(f => f.slice(3)).join(", ")}`;
107
+ // Create branch, commit, push
108
+ const branch = `update/${Date.now()}`;
109
+ await git(["checkout", "-b", branch], dir);
110
+ await git(["commit", "-m", msg], dir);
111
+ await git(["push", "-u", "origin", branch], dir);
112
+ // Create PR via gh CLI
113
+ const { stdout: prUrl } = await exec("gh", ["pr", "create", "--title", msg, "--body", files.map(f => `- ${f.slice(3)}`).join("\n"), "--base", "main"], { cwd: dir });
114
+ // Return to main
115
+ await git(["checkout", "main"], dir);
116
+ return { content: [{ type: "text", text: JSON.stringify({
117
+ success: true, message: "PR created", pr: prUrl.trim(), branch, files: files.map(f => f.slice(3))
118
+ }) }] };
119
+ } catch (e) {
120
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] };
121
+ }
122
+ }
123
+ );
124
+
125
+ // ac_init
126
+ server.tool(
127
+ "ac_init",
128
+ "Clone agent config repo into .kiro/",
129
+ { repo: z.string().describe("Git repository URL") },
130
+ async ({ repo }) => {
131
+ const dir = configPath();
132
+ if (isGitRepo(dir)) {
133
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: ".kiro/ is already a git repository" }) }] };
134
+ }
135
+ try {
136
+ // If .kiro/ exists with files, back up first
137
+ if (existsSync(dir)) {
138
+ const backupDir = `${dir}.backup.${Date.now()}`;
139
+ await exec("mv", [dir, backupDir]);
140
+ }
141
+ await exec("git", ["clone", repo, CONFIG_DIR], { cwd: process.cwd() });
142
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, message: `Cloned ${repo} into ${CONFIG_DIR}/` }) }] };
143
+ } catch (e) {
144
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] };
145
+ }
146
+ }
147
+ );
148
+
149
+ const transport = new StdioServerTransport();
150
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "agent-config-mcp",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "agent-config-mcp": "./index.js"
7
+ },
8
+ "dependencies": {
9
+ "@modelcontextprotocol/sdk": "^1.12.1"
10
+ }
11
+ }