@hoststack.dev/mcp 0.9.1 → 0.10.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 +3 -3
- package/dist/hoststack-mcp.js +191 -8
- package/dist/hoststack-mcp.js.map +1 -1
- package/dist/index.js +191 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -85,12 +85,12 @@ If `HOSTSTACK_API_KEY` is set in your shell, it gets baked into the snippet; oth
|
|
|
85
85
|
|
|
86
86
|
## Tool inventory
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
60 tools, grouped by resource:
|
|
89
89
|
|
|
90
90
|
| Category | Read | Write |
|
|
91
91
|
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
92
92
|
| **projects** | `list_projects`, `get_project` | `create_project`, `update_project` |
|
|
93
|
-
| **services** | `list_services`, `get_service`, `get_service_metrics`, `get_service_metrics_history`, `get_service_logs`, `get_service_logs_bulk` | `update_service`, `update_service_config`, `suspend_service`, `resume_service` |
|
|
93
|
+
| **services** | `list_services`, `get_service`, `get_service_metrics`, `get_service_metrics_history`, `get_service_logs`, `get_service_logs_bulk` | `create_service`, `create_dev_environment`, `update_service`, `update_service_config`, `suspend_service`, `resume_service` |
|
|
94
94
|
| **deploys** | `list_deploys`, `get_deploy`, `get_deploy_logs`, `diagnose_deploy` | `trigger_deploy`, `cancel_deploy` |
|
|
95
95
|
| **environments** | `list_environments` | `create_environment`, `delete_environment`, `promote_deploy` |
|
|
96
96
|
| **databases** | `list_databases`, `get_database`, `get_database_cluster`, `query_database` | `update_database`, `upgrade_database_to_ha` (use the dashboard for create/delete/credentials) |
|
|
@@ -109,7 +109,7 @@ A few design notes worth knowing as a caller:
|
|
|
109
109
|
- **`set_env_var` / `delete_env_var` are key-based.** You don't need an env-var ID; the MCP looks up the existing var by key first, then patches or deletes by ID under the hood.
|
|
110
110
|
- **`list_env_vars` masks secret values.** Anything stored with `is_secret: true` comes back as `••••••`. The masking happens server-side, so you can't accidentally leak a secret to the agent's context window.
|
|
111
111
|
- **`get_service_logs` and `get_deploy_logs` are snapshots.** Streaming logs over MCP isn't supported — re-call the tool to get newer entries. Use the dashboard's `/dashboard/services/:id/logs` for live tails.
|
|
112
|
-
- **No `delete_project
|
|
112
|
+
- **No `delete_project` or `delete_service`.** Destructive cascades are dashboard-only — too risky for an agent to call. Creating is supported via `create_service` / `create_dev_environment`.
|
|
113
113
|
- **Telemetry, hosted only.** When you use `https://hoststack.dev/api/mcp`, we record one row per tool call (tool name, duration, ok/error, SHA-derived hash of input args — never the args themselves) for the analytics page and on-call alerts. Retained 30 days. The local stdio install records nothing.
|
|
114
114
|
|
|
115
115
|
## Environment variables
|
package/dist/hoststack-mcp.js
CHANGED
|
@@ -1154,20 +1154,23 @@ defineTool({
|
|
|
1154
1154
|
"",
|
|
1155
1155
|
"Inputs:",
|
|
1156
1156
|
' - type: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS" | "SRV" | "CAA" | "ALIAS".',
|
|
1157
|
-
' - name: "@" for the zone apex, or a subdomain label ("www", "api", "_acme-challenge").',
|
|
1158
|
-
" - content: the literal record value. For TXT pass the unquoted string \u2014 the PowerDNS layer handles quoting.",
|
|
1157
|
+
' - name: "@" for the zone apex, or a subdomain label ("www", "api", "_acme-challenge"). Do NOT include the zone suffix \u2014 the backend prepends it. "bounce" on zone "micci.dk" publishes "bounce.micci.dk"; passing "bounce.micci.dk" works (the backend strips the suffix) but is non-idiomatic.',
|
|
1158
|
+
" - content: the literal record value. For TXT pass the unquoted string \u2014 the PowerDNS layer handles quoting. For CNAME / MX / NS / SRV / ALIAS the target may be passed with or without a trailing dot; the adapter FQDN-izes it.",
|
|
1159
1159
|
" - ttl (optional): 60\u201386400 seconds, default 3600.",
|
|
1160
1160
|
" - priority (optional, required for MX and SRV): 0\u201365535.",
|
|
1161
1161
|
"",
|
|
1162
1162
|
"Returns: { record: Record } \u2014 see get_dns_record for the shape.",
|
|
1163
1163
|
"",
|
|
1164
|
+
"Notes:",
|
|
1165
|
+
' - CNAME at the apex is rejected (RFC 1034 forbids CNAME coexisting with NS/SOA). Use type="ALIAS" for apex aliasing.',
|
|
1166
|
+
"",
|
|
1164
1167
|
'Example: create_dns_record({ domain: "micci.dk", type: "TXT", name: "@", content: "google-site-verification=C0V0scK48g2\u2026", ttl: 300 })'
|
|
1165
1168
|
].join("\n"),
|
|
1166
1169
|
input: {
|
|
1167
1170
|
zone_id: z7.string().optional().describe("Zone publicId."),
|
|
1168
1171
|
domain: z7.string().optional().describe("Apex or subdomain to resolve a hosted zone."),
|
|
1169
1172
|
type: z7.enum(DNS_RECORD_TYPES).describe("DNS record type."),
|
|
1170
|
-
name: z7.string().min(1).max(253).describe('"@" for apex, or label
|
|
1173
|
+
name: z7.string().min(1).max(253).describe('"@" for apex, or a bare label. Do NOT include the zone suffix.'),
|
|
1171
1174
|
content: z7.string().min(1).max(2e3).describe("Record value; TXT values are unquoted."),
|
|
1172
1175
|
ttl: z7.number().int().min(60).max(86400).optional().describe("TTL in seconds (default 3600)."),
|
|
1173
1176
|
priority: z7.number().int().min(0).max(65535).optional().describe("Required for MX / SRV.")
|
|
@@ -1221,7 +1224,7 @@ defineTool({
|
|
|
1221
1224
|
input: {
|
|
1222
1225
|
record_id: z7.string().describe("Record publicId."),
|
|
1223
1226
|
type: z7.enum(DNS_RECORD_TYPES).describe("DNS record type."),
|
|
1224
|
-
name: z7.string().min(1).max(253).describe('"@" for apex, or label.'),
|
|
1227
|
+
name: z7.string().min(1).max(253).describe('"@" for apex, or a bare label. Do NOT include the zone suffix.'),
|
|
1225
1228
|
content: z7.string().min(1).max(2e3).describe("Record value; TXT unquoted."),
|
|
1226
1229
|
ttl: z7.number().int().min(60).max(86400).optional().describe("TTL in seconds (default 3600)."),
|
|
1227
1230
|
priority: z7.number().int().min(0).max(65535).optional().describe("Required for MX / SRV.")
|
|
@@ -1937,6 +1940,7 @@ defineTool({
|
|
|
1937
1940
|
|
|
1938
1941
|
// src/tools/projects.ts
|
|
1939
1942
|
import { z as z12 } from "zod";
|
|
1943
|
+
var REGION_IDS = ["eu-central-1", "eu-central-2", "eu-west-1", "us-east-1"];
|
|
1940
1944
|
defineTool({
|
|
1941
1945
|
name: "list_projects",
|
|
1942
1946
|
category: "projects",
|
|
@@ -1969,16 +1973,16 @@ defineTool({
|
|
|
1969
1973
|
"Inputs:",
|
|
1970
1974
|
" - name: human-readable project name (1\u201360 chars).",
|
|
1971
1975
|
" - description (optional): short blurb shown in the dashboard.",
|
|
1972
|
-
' - region (optional): "
|
|
1976
|
+
' - region (optional): "eu-central-1" (Falkenstein) | "eu-central-2" (Nuremberg) | "eu-west-1" (Helsinki) | "us-east-1" (Ashburn). Defaults to eu-central-2.',
|
|
1973
1977
|
"",
|
|
1974
1978
|
"Returns: { project: Project } \u2014 includes the new id and publicId.",
|
|
1975
1979
|
"",
|
|
1976
|
-
'Example: create_project({ name: "billing-api", description: "Stripe webhooks", region: "
|
|
1980
|
+
'Example: create_project({ name: "billing-api", description: "Stripe webhooks", region: "eu-central-1" }) \u2192 { project: { id: 12, publicId: "prj_\u2026", \u2026 } }'
|
|
1977
1981
|
].join("\n"),
|
|
1978
1982
|
input: {
|
|
1979
1983
|
name: z12.string().min(1).max(60).describe("Project name (1\u201360 chars)."),
|
|
1980
1984
|
description: z12.string().max(500).optional().describe("Short description (\u2264500 chars)."),
|
|
1981
|
-
region: z12.enum(
|
|
1985
|
+
region: z12.enum(REGION_IDS).optional().describe("Region: eu-central-1 | eu-central-2 | eu-west-1 | us-east-1.")
|
|
1982
1986
|
},
|
|
1983
1987
|
handler: async (args2, ctx) => {
|
|
1984
1988
|
const teamId = await ctx.resolveTeamId();
|
|
@@ -2058,6 +2062,24 @@ defineTool({
|
|
|
2058
2062
|
|
|
2059
2063
|
// src/tools/services.ts
|
|
2060
2064
|
import { z as z13 } from "zod";
|
|
2065
|
+
var DEV_ENV_IMAGE = "hoststack/dev-env:claude";
|
|
2066
|
+
var DEV_ENV_VOLUME = { name: "workspace", mountPath: "/workspace", sizeGb: 10 };
|
|
2067
|
+
var SERVICE_TYPES = [
|
|
2068
|
+
"web_service",
|
|
2069
|
+
"private_service",
|
|
2070
|
+
"worker",
|
|
2071
|
+
"cron_job",
|
|
2072
|
+
"static_site"
|
|
2073
|
+
];
|
|
2074
|
+
var SERVICE_PLANS = [
|
|
2075
|
+
"pico",
|
|
2076
|
+
"nano",
|
|
2077
|
+
"micro",
|
|
2078
|
+
"starter",
|
|
2079
|
+
"standard",
|
|
2080
|
+
"pro_standard",
|
|
2081
|
+
"pro_large"
|
|
2082
|
+
];
|
|
2061
2083
|
defineTool({
|
|
2062
2084
|
name: "list_services",
|
|
2063
2085
|
category: "services",
|
|
@@ -2109,10 +2131,171 @@ defineTool({
|
|
|
2109
2131
|
args2.status ? `status=${args2.status}` : null,
|
|
2110
2132
|
args2.type ? `type=${args2.type}` : null
|
|
2111
2133
|
].filter(Boolean).join(", ");
|
|
2112
|
-
const summary = data.items.length === 0 ? filterDesc ? `No services match the given filters (${filterDesc}).` : "No services yet \u2014 create one with create_service or via the dashboard." : `Found ${data.items.length} service${data.items.length === 1 ? "" : "s"}${filterDesc ? ` matching ${filterDesc}` : ""}.`;
|
|
2134
|
+
const summary = data.items.length === 0 ? filterDesc ? `No services match the given filters (${filterDesc}).` : "No services yet \u2014 create one with create_service (or create_dev_environment for an AI dev box), or via the dashboard." : `Found ${data.items.length} service${data.items.length === 1 ? "" : "s"}${filterDesc ? ` matching ${filterDesc}` : ""}.`;
|
|
2113
2135
|
return respond({ summary, data });
|
|
2114
2136
|
}
|
|
2115
2137
|
});
|
|
2138
|
+
defineTool({
|
|
2139
|
+
name: "create_service",
|
|
2140
|
+
category: "services",
|
|
2141
|
+
description: [
|
|
2142
|
+
"Create a new service in a project. Source is either a connected git repo (github_repo_id) OR a pre-built docker_image \u2014 never both.",
|
|
2143
|
+
"",
|
|
2144
|
+
"When to use: the user wants to deploy something new. For a one-command AI dev environment specifically, prefer create_dev_environment (it also attaches the /workspace volume and sets the MCP keys).",
|
|
2145
|
+
"",
|
|
2146
|
+
"Inputs:",
|
|
2147
|
+
' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
|
|
2148
|
+
" - name: service name (1\u2013100 chars).",
|
|
2149
|
+
' - type: "web_service" | "private_service" | "worker" | "cron_job" | "static_site".',
|
|
2150
|
+
" - docker_image (optional): pre-built image ref to deploy instead of building from source.",
|
|
2151
|
+
" - github_repo_id (optional): connect a previously-linked GitHub repo by numeric id.",
|
|
2152
|
+
' - branch (optional): git branch (default "main").',
|
|
2153
|
+
" - install_command / build_command / start_command (optional): build/run shell commands. start_command is required for web/private services without a docker_image.",
|
|
2154
|
+
" - cron_schedule (optional): cron expression \u2014 required for cron_job.",
|
|
2155
|
+
' - publish_path (optional): static-site output dir (e.g. "dist").',
|
|
2156
|
+
' - runtime (optional): "node" | "bun" | "python" | \u2026 (auto-detected from a repo when omitted).',
|
|
2157
|
+
' - plan (optional): service size (default "micro").',
|
|
2158
|
+
" - environment_id (optional): bind to a specific environment; defaults to the project Production env.",
|
|
2159
|
+
" - auto_deploy (optional, default true): trigger the first deploy immediately when a source is present.",
|
|
2160
|
+
"",
|
|
2161
|
+
"Returns: { service: Service, deployId: number | null }.",
|
|
2162
|
+
"",
|
|
2163
|
+
'Example: create_service({ project_id: "prj_abc", name: "api", type: "web_service", github_repo_id: 42 }) \u2192 { service: { publicId: "svc_\u2026" }, deployId: 1234 }'
|
|
2164
|
+
].join("\n"),
|
|
2165
|
+
input: {
|
|
2166
|
+
project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
|
|
2167
|
+
name: z13.string().min(1).max(100).describe("Service name (1\u2013100 chars)."),
|
|
2168
|
+
type: z13.enum(SERVICE_TYPES).describe("Service type."),
|
|
2169
|
+
docker_image: z13.string().max(500).optional().describe("Pre-built image ref. Mutually exclusive with github_repo_id."),
|
|
2170
|
+
github_repo_id: z13.number().int().positive().optional().describe("Linked GitHub repo numeric id. Mutually exclusive with docker_image."),
|
|
2171
|
+
branch: z13.string().max(200).optional().describe('Git branch (default "main").'),
|
|
2172
|
+
install_command: z13.string().max(1e3).optional().describe("Install shell command."),
|
|
2173
|
+
build_command: z13.string().max(1e3).optional().describe("Build shell command."),
|
|
2174
|
+
start_command: z13.string().max(1e3).optional().describe("Start shell command (required for web/private services without an image)."),
|
|
2175
|
+
cron_schedule: z13.string().max(100).optional().describe("Cron expression \u2014 required for cron_job."),
|
|
2176
|
+
publish_path: z13.string().max(500).optional().describe("Static-site output dir."),
|
|
2177
|
+
runtime: z13.string().max(50).optional().describe("Runtime hint (node/bun/python/\u2026)."),
|
|
2178
|
+
plan: z13.enum(SERVICE_PLANS).optional().describe('Service size (default "micro").'),
|
|
2179
|
+
environment_id: z13.union([z13.number().int().positive(), z13.string()]).optional().describe("Bind to a specific environment; defaults to Production."),
|
|
2180
|
+
auto_deploy: z13.boolean().optional().describe("Trigger the first deploy immediately (default true).")
|
|
2181
|
+
},
|
|
2182
|
+
handler: async (args2, ctx) => {
|
|
2183
|
+
const teamId = await ctx.resolveTeamId();
|
|
2184
|
+
const projectId = await ctx.hoststack.resolveId(args2.project_id, {
|
|
2185
|
+
kind: "project",
|
|
2186
|
+
teamId
|
|
2187
|
+
});
|
|
2188
|
+
const input = {
|
|
2189
|
+
name: args2.name,
|
|
2190
|
+
type: args2.type,
|
|
2191
|
+
projectId
|
|
2192
|
+
};
|
|
2193
|
+
if (args2.docker_image !== void 0) input.dockerImage = args2.docker_image;
|
|
2194
|
+
if (args2.github_repo_id !== void 0) input.githubRepoId = args2.github_repo_id;
|
|
2195
|
+
if (args2.branch !== void 0) input.branch = args2.branch;
|
|
2196
|
+
if (args2.install_command !== void 0) input.installCommand = args2.install_command;
|
|
2197
|
+
if (args2.build_command !== void 0) input.buildCommand = args2.build_command;
|
|
2198
|
+
if (args2.start_command !== void 0) input.startCommand = args2.start_command;
|
|
2199
|
+
if (args2.cron_schedule !== void 0) input.cronSchedule = args2.cron_schedule;
|
|
2200
|
+
if (args2.publish_path !== void 0) input.publishPath = args2.publish_path;
|
|
2201
|
+
if (args2.runtime !== void 0) input.runtime = args2.runtime;
|
|
2202
|
+
if (args2.plan !== void 0) input.plan = args2.plan;
|
|
2203
|
+
if (args2.auto_deploy !== void 0) input.autoDeploy = args2.auto_deploy;
|
|
2204
|
+
if (args2.environment_id !== void 0) {
|
|
2205
|
+
input.environmentId = await ctx.hoststack.resolveId(args2.environment_id, {
|
|
2206
|
+
kind: "environment",
|
|
2207
|
+
teamId
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
const response = await ctx.hoststack.services.create(teamId, input);
|
|
2211
|
+
const data = {
|
|
2212
|
+
service: shapeService(response.service),
|
|
2213
|
+
deployId: response.deployId ?? null
|
|
2214
|
+
};
|
|
2215
|
+
const publicId = data.service && "publicId" in data.service ? data.service.publicId : "unknown";
|
|
2216
|
+
return respond({ summary: `Created service "${args2.name}" (${publicId}).`, data });
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
2219
|
+
defineTool({
|
|
2220
|
+
name: "create_dev_environment",
|
|
2221
|
+
category: "services",
|
|
2222
|
+
description: [
|
|
2223
|
+
"Spin up an AI dev environment in one call: a private service from the agentic dev-env image (Claude Code + Codex + OpenCode + MCPs preinstalled), with a persistent /workspace volume and the MCP API keys set, then fire the first deploy.",
|
|
2224
|
+
"",
|
|
2225
|
+
`When to use: the user wants a cloud terminal / dev box they can drive coding agents in (from a desk or a phone). This mirrors the dashboard's "AI Dev Environment" wizard preset \u2014 create (deploy deferred) \u2192 set keys \u2192 attach /workspace \u2192 deploy, so the first container boots with its volume and keys already in place.`,
|
|
2226
|
+
"",
|
|
2227
|
+
"Inputs:",
|
|
2228
|
+
' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
|
|
2229
|
+
' - name (optional): service name (default "dev-environment").',
|
|
2230
|
+
' - plan (optional): service size (default "micro").',
|
|
2231
|
+
" - disk_gb (optional): /workspace volume size in GB (default 10, 1\u2013100).",
|
|
2232
|
+
" - hoststack_api_key (optional): sets HOSTSTACK_API_KEY so the hoststack MCP works inside the container.",
|
|
2233
|
+
" - poststack_api_key (optional): sets POSTSTACK_API_KEY so the poststack MCP works inside the container.",
|
|
2234
|
+
"",
|
|
2235
|
+
"Returns: { service: Service, volumeAttached: boolean, deployId: number | null }. Open the Terminal tab on the service (dashboard or phone) once it is running; log in once with `claude /login` inside the container.",
|
|
2236
|
+
"",
|
|
2237
|
+
'Example: create_dev_environment({ project_id: "prj_abc", name: "scratch", hoststack_api_key: "hs_live_\u2026" })'
|
|
2238
|
+
].join("\n"),
|
|
2239
|
+
input: {
|
|
2240
|
+
project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
|
|
2241
|
+
name: z13.string().min(1).max(100).optional().describe('Service name (default "dev-environment").'),
|
|
2242
|
+
plan: z13.enum(SERVICE_PLANS).optional().describe('Service size (default "micro").'),
|
|
2243
|
+
disk_gb: z13.number().int().min(1).max(100).optional().describe("/workspace volume size in GB (default 10)."),
|
|
2244
|
+
hoststack_api_key: z13.string().optional().describe("Value for HOSTSTACK_API_KEY (enables the hoststack MCP in-container)."),
|
|
2245
|
+
poststack_api_key: z13.string().optional().describe("Value for POSTSTACK_API_KEY (enables the poststack MCP in-container).")
|
|
2246
|
+
},
|
|
2247
|
+
handler: async (args2, ctx) => {
|
|
2248
|
+
const teamId = await ctx.resolveTeamId();
|
|
2249
|
+
const projectId = await ctx.hoststack.resolveId(args2.project_id, {
|
|
2250
|
+
kind: "project",
|
|
2251
|
+
teamId
|
|
2252
|
+
});
|
|
2253
|
+
const name = args2.name ?? "dev-environment";
|
|
2254
|
+
const sizeGb = args2.disk_gb ?? DEV_ENV_VOLUME.sizeGb;
|
|
2255
|
+
const createInput = {
|
|
2256
|
+
name,
|
|
2257
|
+
type: "private_service",
|
|
2258
|
+
projectId,
|
|
2259
|
+
dockerImage: DEV_ENV_IMAGE,
|
|
2260
|
+
autoDeploy: false
|
|
2261
|
+
};
|
|
2262
|
+
if (args2.plan !== void 0) createInput.plan = args2.plan;
|
|
2263
|
+
const created = await ctx.hoststack.services.create(teamId, createInput);
|
|
2264
|
+
const service = created.service;
|
|
2265
|
+
const envVars = [];
|
|
2266
|
+
if (args2.hoststack_api_key)
|
|
2267
|
+
envVars.push({
|
|
2268
|
+
key: "HOSTSTACK_API_KEY",
|
|
2269
|
+
value: args2.hoststack_api_key,
|
|
2270
|
+
isSecret: true
|
|
2271
|
+
});
|
|
2272
|
+
if (args2.poststack_api_key)
|
|
2273
|
+
envVars.push({
|
|
2274
|
+
key: "POSTSTACK_API_KEY",
|
|
2275
|
+
value: args2.poststack_api_key,
|
|
2276
|
+
isSecret: true
|
|
2277
|
+
});
|
|
2278
|
+
if (envVars.length > 0) {
|
|
2279
|
+
await ctx.hoststack.envVars.bulkSet(teamId, service.id, { vars: envVars });
|
|
2280
|
+
}
|
|
2281
|
+
let volumeAttached = false;
|
|
2282
|
+
try {
|
|
2283
|
+
await ctx.hoststack.volumes.create(teamId, service.id, {
|
|
2284
|
+
name: DEV_ENV_VOLUME.name,
|
|
2285
|
+
mountPath: DEV_ENV_VOLUME.mountPath,
|
|
2286
|
+
sizeGb
|
|
2287
|
+
});
|
|
2288
|
+
volumeAttached = true;
|
|
2289
|
+
} catch {
|
|
2290
|
+
}
|
|
2291
|
+
const deploy = await ctx.hoststack.deploys.trigger(teamId, service.id);
|
|
2292
|
+
const deployId = deploy.deploy?.id ?? null;
|
|
2293
|
+
return respond({
|
|
2294
|
+
summary: `Created AI dev environment "${name}" (${service.publicId})${volumeAttached ? " with a /workspace volume" : ""} \u2014 deploying. Open its Terminal tab once running.`,
|
|
2295
|
+
data: { service: shapeService(service), volumeAttached, deployId }
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2116
2299
|
defineTool({
|
|
2117
2300
|
name: "get_service",
|
|
2118
2301
|
category: "services",
|