@apps-in-toss/ax 0.7.1
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 +149 -0
- package/package.json +51 -0
- package/scripts/postinstall.js +185 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# AX (AppsInToss eXperience)
|
|
2
|
+
|
|
3
|
+
AI 어시스턴트가 AppsInToss 미니앱 개발을 도울 수 있도록 설계된 MCP(Model Context Protocol) 서버입니다.
|
|
4
|
+
|
|
5
|
+
## 주요 기능
|
|
6
|
+
|
|
7
|
+
AX는 AI 어시스턴트에게 AppsInToss Developer Center의 문서를 제공하여, 토스 미니앱 개발에 대한 정확하고 컨텍스트 기반의 지원을 가능하게 합니다.
|
|
8
|
+
|
|
9
|
+
### MCP Tools
|
|
10
|
+
|
|
11
|
+
| 도구 | 설명 |
|
|
12
|
+
|------|------|
|
|
13
|
+
| `search_docs` | AppsInToss 문서 검색 |
|
|
14
|
+
| `get_doc` | 검색 결과의 문서 전체 내용 조회 |
|
|
15
|
+
| `search_tds_rn_docs` | TDS React Native 문서 검색 |
|
|
16
|
+
| `get_tds_rn_doc` | TDS React Native 문서 전체 내용 조회 |
|
|
17
|
+
| `search_tds_web_docs` | TDS Web 문서 검색 |
|
|
18
|
+
| `get_tds_web_doc` | TDS Web 문서 전체 내용 조회 |
|
|
19
|
+
|
|
20
|
+
### 지원 문서
|
|
21
|
+
|
|
22
|
+
- **AppsInToss Developer Center** - 미니앱 개발 가이드
|
|
23
|
+
- **TDS React Native** - 토스 디자인 시스템 (React Native)
|
|
24
|
+
- **TDS Web** - 토스 디자인 시스템 (WebView)
|
|
25
|
+
|
|
26
|
+
## 설치
|
|
27
|
+
|
|
28
|
+
### Homebrew (macOS/Linux)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
brew tap toss/tap
|
|
32
|
+
brew install ax
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Scoop (Windows)
|
|
36
|
+
|
|
37
|
+
```powershell
|
|
38
|
+
scoop bucket add toss https://github.com/toss/scoop-bucket
|
|
39
|
+
scoop install ax
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### npm
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g @apps-in-toss/ax
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 사용법
|
|
49
|
+
|
|
50
|
+
### MCP 서버 시작
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
ax mcp
|
|
54
|
+
ax mcp --disable-usage-stats
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
AX는 제품 개선과 안정적인 운영을 위해 명령 실행, MCP 도구 호출, 성공 여부, 실행 시간 같은 사용 통계를 수집합니다.
|
|
58
|
+
사용 통계 수집을 원하지 않으면 `--disable-usage-stats` 옵션을 추가하세요.
|
|
59
|
+
|
|
60
|
+
### Cursor/Claude에서 사용
|
|
61
|
+
|
|
62
|
+
[](https://cursor.com/en-US/install-mcp?name=apps-in-toss&config=eyJjb21tYW5kIjoiYXgiLCJhcmdzIjpbIm1jcCJdfQ==)
|
|
63
|
+
|
|
64
|
+
또는 `.cursor/mcp.json` / `claude_desktop_config.json`에 다음을 추가하세요:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"apps-in-toss": {
|
|
70
|
+
"command": "ax",
|
|
71
|
+
"args": [
|
|
72
|
+
"mcp"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 프로젝트 구조
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
apps-in-toss-ax/
|
|
83
|
+
├── cmd/ # CLI 명령어 정의
|
|
84
|
+
├── internal/ # 내부 패키지
|
|
85
|
+
│ ├── httputil/ # HTTP 유틸리티
|
|
86
|
+
│ └── utils/ # 공통 유틸리티
|
|
87
|
+
├── pkg/ # 핵심 패키지
|
|
88
|
+
│ ├── app/ # 애플리케이션 진입점
|
|
89
|
+
│ ├── docs/ # 문서 관리
|
|
90
|
+
│ ├── docid/ # 문서 ID 생성
|
|
91
|
+
│ ├── features/ # 기능 구성
|
|
92
|
+
│ ├── fetcher/ # HTTP 클라이언트
|
|
93
|
+
│ ├── instrumentation/ # 사용 통계 수집
|
|
94
|
+
│ ├── llms/ # llms.txt 파서
|
|
95
|
+
│ ├── mcp/ # MCP 서버 구현
|
|
96
|
+
│ └── search/ # 문서 검색 엔진
|
|
97
|
+
├── tools/ # 배포 도구
|
|
98
|
+
│ ├── publish/ # 패키지 매니저 배포
|
|
99
|
+
│ └── shared/ # 공유 유틸리티
|
|
100
|
+
├── scripts/ # 스크립트
|
|
101
|
+
├── main.go # 메인 진입점
|
|
102
|
+
├── go.mod # Go 모듈 정의
|
|
103
|
+
├── Makefile # 빌드 명령어
|
|
104
|
+
└── package.json # npm 패키지 정의
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 개발
|
|
108
|
+
|
|
109
|
+
### 빌드
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
make build
|
|
113
|
+
# 또는
|
|
114
|
+
go build -o ax
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 테스트
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
go test ./...
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 로컬 실행
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
./ax mcp
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## AppsInToss란?
|
|
130
|
+
|
|
131
|
+
**AppsInToss**는 토스 앱 내에서 미니앱을 제공할 수 있는 플랫폼입니다. 3,000만 토스 사용자에게 서비스를 노출하고, SDK와 API를 활용해 빠르게 개발할 수 있습니다.
|
|
132
|
+
|
|
133
|
+
### 주요 개념
|
|
134
|
+
|
|
135
|
+
| 용어 | 설명 |
|
|
136
|
+
|------|------|
|
|
137
|
+
| **Granite** | 미니앱 개발 프레임워크 (구 Bedrock, v1.0+) |
|
|
138
|
+
| **TDS** | Toss Design System - 비게임 미니앱 필수 |
|
|
139
|
+
|
|
140
|
+
### 개발 방식
|
|
141
|
+
|
|
142
|
+
- **React Native**: 네이티브에 가까운 성능
|
|
143
|
+
- **WebView**: 웹 기술 활용
|
|
144
|
+
|
|
145
|
+
## 관련 링크
|
|
146
|
+
|
|
147
|
+
- [AppsInToss Developer Center](https://developers-apps-in-toss.toss.im)
|
|
148
|
+
- [TDS React Native](https://tossmini-docs.toss.im/tds-react-native)
|
|
149
|
+
- [TDS Web](https://tossmini-docs.toss.im/tds-mobile)
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@apps-in-toss/ax",
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"description": "MCP server that provides AppsInToss documentation to AI assistants",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.9.0",
|
|
8
|
+
"npm": ">=8"
|
|
9
|
+
},
|
|
10
|
+
"repository": "toss/apps-in-toss-ax",
|
|
11
|
+
"homepage": "https://github.com/toss/apps-in-toss-ax#readme",
|
|
12
|
+
"bugs": "https://github.com/toss/apps-in-toss-ax/issues",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"prepack": "node -e \"require('fs').rmSync('bin', { recursive: true, force: true })\"",
|
|
15
|
+
"postinstall": "node scripts/postinstall.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"bin-links": "^6.0.0",
|
|
19
|
+
"https-proxy-agent": "^7.0.2",
|
|
20
|
+
"node-fetch": "^3.3.2",
|
|
21
|
+
"tar": "7.5.16"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"ax": "bin/ax"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"scripts/postinstall.js"
|
|
28
|
+
],
|
|
29
|
+
"os": [
|
|
30
|
+
"darwin",
|
|
31
|
+
"linux",
|
|
32
|
+
"win32"
|
|
33
|
+
],
|
|
34
|
+
"cpu": [
|
|
35
|
+
"x64",
|
|
36
|
+
"arm64"
|
|
37
|
+
],
|
|
38
|
+
"keywords": [
|
|
39
|
+
"apps-in-toss",
|
|
40
|
+
"toss",
|
|
41
|
+
"mcp",
|
|
42
|
+
"mcp-server",
|
|
43
|
+
"mini-app",
|
|
44
|
+
"cli"
|
|
45
|
+
],
|
|
46
|
+
"author": "Viva Republica, Inc.",
|
|
47
|
+
"license": "ISC",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Ref 1: https://github.com/sanathkr/go-npm
|
|
4
|
+
// Ref 2: https://medium.com/xendit-engineering/how-we-repurposed-npm-to-publish-and-distribute-our-go-binaries-for-internal-cli-23981b80911b
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
import binLinks from "bin-links";
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import fetch from "node-fetch";
|
|
11
|
+
import { Agent } from "https";
|
|
12
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { extract } from "tar";
|
|
15
|
+
import zlib from "zlib";
|
|
16
|
+
|
|
17
|
+
// Mapping from Node's `process.arch` to Golang's `$GOARCH`
|
|
18
|
+
const ARCH_MAPPING = {
|
|
19
|
+
x64: "amd64",
|
|
20
|
+
arm64: "arm64",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Mapping between Node's `process.platform` to Golang's
|
|
24
|
+
const PLATFORM_MAPPING = {
|
|
25
|
+
darwin: "darwin",
|
|
26
|
+
linux: "linux",
|
|
27
|
+
win32: "windows",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const arch = ARCH_MAPPING[process.arch];
|
|
31
|
+
const platform = PLATFORM_MAPPING[process.platform];
|
|
32
|
+
|
|
33
|
+
// TODO: import pkg from "../package.json" assert { type: "json" };
|
|
34
|
+
const readPackageJson = async () => {
|
|
35
|
+
const contents = await fs.promises.readFile("package.json");
|
|
36
|
+
return JSON.parse(contents);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Release artifacts are named after the binary (GoReleaser project name),
|
|
40
|
+
// not the scoped npm package name.
|
|
41
|
+
const getBinaryName = (packageJson) => Object.keys(packageJson.bin)[0];
|
|
42
|
+
|
|
43
|
+
// Build the download url from package.json
|
|
44
|
+
const getDownloadUrl = (packageJson) => {
|
|
45
|
+
const binName = getBinaryName(packageJson);
|
|
46
|
+
const version = packageJson.version;
|
|
47
|
+
const repo = packageJson.repository;
|
|
48
|
+
const url = `https://github.com/${repo}/releases/download/v${version}/${binName}_${platform}_${arch}.tar.gz`;
|
|
49
|
+
return url;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const fetchAndParseCheckSumFile = async (packageJson, agent) => {
|
|
53
|
+
const version = packageJson.version;
|
|
54
|
+
const binName = getBinaryName(packageJson);
|
|
55
|
+
const repo = packageJson.repository;
|
|
56
|
+
const checksumFileUrl = `https://github.com/${repo}/releases/download/v${version}/${binName}_${version}_checksums.txt`;
|
|
57
|
+
|
|
58
|
+
// Fetch the checksum file
|
|
59
|
+
console.info("Downloading", checksumFileUrl);
|
|
60
|
+
const response = await fetch(checksumFileUrl, { agent });
|
|
61
|
+
if (response.ok) {
|
|
62
|
+
const checkSumContent = await response.text();
|
|
63
|
+
const lines = checkSumContent.split("\n");
|
|
64
|
+
|
|
65
|
+
const checksums = {};
|
|
66
|
+
for (const line of lines) {
|
|
67
|
+
const [checksum, packageName] = line.split(/\s+/);
|
|
68
|
+
checksums[packageName] = checksum;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return checksums;
|
|
72
|
+
} else {
|
|
73
|
+
console.error(
|
|
74
|
+
"Could not fetch checksum file",
|
|
75
|
+
response.status,
|
|
76
|
+
response.statusText
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const errChecksum = "Checksum mismatch. Downloaded data might be corrupted.";
|
|
82
|
+
const errUnsupported = `Installation is not supported for ${process.platform} ${process.arch}`;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Reads the configuration from application's package.json,
|
|
86
|
+
* downloads the binary from package url and stores at
|
|
87
|
+
* ./bin in the package's root.
|
|
88
|
+
*
|
|
89
|
+
* See: https://docs.npmjs.com/files/package.json#bin
|
|
90
|
+
*/
|
|
91
|
+
async function main() {
|
|
92
|
+
if (!arch || !platform) {
|
|
93
|
+
throw errUnsupported;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Read from package.json and prepare for the installation.
|
|
97
|
+
const pkg = await readPackageJson();
|
|
98
|
+
const binCmd = getBinaryName(pkg);
|
|
99
|
+
if (platform === "windows") {
|
|
100
|
+
// Update bin path in package.json
|
|
101
|
+
pkg.bin[binCmd] += ".exe";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Prepare the installation path by creating the directory if it doesn't exist.
|
|
105
|
+
const binPath = pkg.bin[binCmd];
|
|
106
|
+
const binDir = path.dirname(binPath);
|
|
107
|
+
await fs.promises.mkdir(binDir, { recursive: true });
|
|
108
|
+
|
|
109
|
+
// Create the agent that will be used for all the fetch requests later.
|
|
110
|
+
const proxyUrl =
|
|
111
|
+
process.env.npm_config_https_proxy ||
|
|
112
|
+
process.env.npm_config_http_proxy ||
|
|
113
|
+
process.env.npm_config_proxy;
|
|
114
|
+
// Keeps the TCP connection alive when sending multiple requests
|
|
115
|
+
// Ref: https://github.com/node-fetch/node-fetch/issues/1735
|
|
116
|
+
const agent = proxyUrl
|
|
117
|
+
? new HttpsProxyAgent(proxyUrl, { keepAlive: true })
|
|
118
|
+
: new Agent({ keepAlive: true });
|
|
119
|
+
|
|
120
|
+
// First, fetch the checksum map.
|
|
121
|
+
const checksumMap = await fetchAndParseCheckSumFile(pkg, agent);
|
|
122
|
+
|
|
123
|
+
// Then, download the binary.
|
|
124
|
+
const url = getDownloadUrl(pkg);
|
|
125
|
+
console.info("Downloading", url);
|
|
126
|
+
const resp = await fetch(url, { agent });
|
|
127
|
+
if (!resp.ok) {
|
|
128
|
+
throw `Failed to download ${url}: ${resp.status} ${resp.statusText}`;
|
|
129
|
+
}
|
|
130
|
+
const hash = createHash("sha256");
|
|
131
|
+
const pkgNameWithPlatform = `${binCmd}_${platform}_${arch}.tar.gz`;
|
|
132
|
+
|
|
133
|
+
// Then, decompress the binary -- we will first Un-GZip, then we will untar.
|
|
134
|
+
const ungz = zlib.createGunzip();
|
|
135
|
+
const binName = path.basename(binPath);
|
|
136
|
+
const untar = extract({ cwd: binDir }, [binName]);
|
|
137
|
+
|
|
138
|
+
// Update the hash with the binary data as it's being downloaded.
|
|
139
|
+
resp.body
|
|
140
|
+
.on("data", (chunk) => {
|
|
141
|
+
hash.update(chunk);
|
|
142
|
+
})
|
|
143
|
+
// Pipe the data to the ungz stream.
|
|
144
|
+
.pipe(ungz);
|
|
145
|
+
|
|
146
|
+
// After the ungz stream has ended, verify the checksum.
|
|
147
|
+
ungz
|
|
148
|
+
.on("end", () => {
|
|
149
|
+
const expectedChecksum = checksumMap?.[pkgNameWithPlatform];
|
|
150
|
+
// Skip verification if we can't find the file checksum
|
|
151
|
+
if (!expectedChecksum) {
|
|
152
|
+
console.warn("Skipping checksum verification");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const calculatedChecksum = hash.digest("hex");
|
|
156
|
+
if (calculatedChecksum !== expectedChecksum) {
|
|
157
|
+
throw errChecksum;
|
|
158
|
+
}
|
|
159
|
+
console.info("Checksum verified.");
|
|
160
|
+
})
|
|
161
|
+
// Pipe the data to the untar stream.
|
|
162
|
+
.pipe(untar);
|
|
163
|
+
|
|
164
|
+
// Wait for the untar stream to finish.
|
|
165
|
+
await new Promise((resolve, reject) => {
|
|
166
|
+
untar.on("error", reject);
|
|
167
|
+
untar.on("end", () => resolve());
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// npm links bins before postinstall runs and silently skips the
|
|
171
|
+
// then-missing binary, so re-link here. global/top make the link land in
|
|
172
|
+
// the global bin dir on `npm install -g`.
|
|
173
|
+
const isGlobalInstall = process.env.npm_config_global === "true";
|
|
174
|
+
await binLinks({
|
|
175
|
+
path: path.resolve("."),
|
|
176
|
+
pkg: { ...pkg, bin: { [binCmd]: binPath } },
|
|
177
|
+
global: isGlobalInstall,
|
|
178
|
+
top: isGlobalInstall,
|
|
179
|
+
force: true,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
console.info("Installed AppsInToss CLI successfully");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await main();
|