@foundation0/api 1.1.11 → 1.1.13

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/mcp/AGENTS.md DELETED
@@ -1,130 +0,0 @@
1
- # MCP Endpoint Design (LLM-Friendly)
2
-
3
- This folder contains the Foundation0 MCP server (`api/mcp/server.ts`) and CLI (`api/mcp/cli.ts`).
4
-
5
- These notes exist because the most common “LLM friction” issues are predictable:
6
- - tool name/prefix confusion (duplicate names, “tool not found”),
7
- - payload-shape confusion (positional `args` vs object options, or proxy tools that require JSON strings),
8
- - oversized responses (truncation → extra tool calls),
9
- - weak errors (not actionable → retries and wasted calls).
10
-
11
- Use this doc when adding or modifying MCP endpoints so models can succeed in **one call**.
12
-
13
- ## 1) Avoid Tool Prefix Double-Wrapping
14
-
15
- If your agent uses `pi-mcp-adapter` (or any MCP proxy that already prefixes tools), do **not** also run the server with `--tools-prefix`.
16
-
17
- Bad (creates confusing duplicates like `f0_f0_net_curl` and `f0_net_curl`):
18
- - Agent tool prefixing: `toolPrefix: "server"` (adds `f0_...`)
19
- - Server tool prefixing: `--tools-prefix f0` (adds `f0....`)
20
-
21
- Good (single predictable name):
22
- - Agent: `toolPrefix: "server"`
23
- - Server: no `--tools-prefix`
24
-
25
- ## 2) Prefer Direct Tools for Hot Paths (Fewer Calls, Fewer Errors)
26
-
27
- Proxy-based MCP gateways often require passing `args` as a **JSON string** (not an object), which LLMs frequently get wrong.
28
-
29
- If a tool is used often (HTTP, search, file ops), expose it as a **direct tool** in the agent config:
30
-
31
- ```json
32
- {
33
- "mcp": {
34
- "mcpServers": {
35
- "f0": {
36
- "directTools": ["net_curl", "batch"]
37
- }
38
- }
39
- }
40
- }
41
- ```
42
-
43
- Note: Direct tool names must be OpenAI-tool-name-safe (no dots). Use the underscore aliases (e.g., `net_curl`) when exposing direct tools.
44
-
45
- Benefits:
46
- - The model calls `f0_net_curl` directly (no gateway wrapper).
47
- - The model passes a normal JSON object (no “args must be a JSON string” confusion).
48
- - Tool discoverability improves (tool appears in the system prompt).
49
-
50
- Guideline: keep direct tools to a focused set (5–20). Use proxy for large catalogs.
51
-
52
- ## 3) Always Support a “Structured Mode” (Don’t Force Curl-CLI Literacy)
53
-
54
- LLMs naturally try:
55
- ```json
56
- { "url": "https://…", "method": "GET", "headers": { "accept": "application/json" } }
57
- ```
58
-
59
- If your tool only accepts positional CLI-like args (e.g. `{ "args": ["-L", "…"] }`), the model will often:
60
- - guess the wrong shape,
61
- - call `describe`,
62
- - retry,
63
- - and burn multiple tool calls.
64
-
65
- Best practice:
66
- - support **both** shapes:
67
- - Curl-style: `{ "args": ["-L", "https://…"] }`
68
- - Structured: `{ "url": "https://…", "method": "GET", ... }`
69
-
70
- For `net.curl` (alias `net_curl`), structured mode should accept the common fields:
71
- - `url`/`urls`, `method`, `headers`, `body`/`data`/`json`, `followRedirects`, `includeHeaders`,
72
- - `fail`, `silent`, `verbose`, `writeOut`,
73
- - `timeoutMs`, `maxTimeSeconds`, `maxRedirects`,
74
- - `output`/`remoteName`, `user`.
75
-
76
- Also: keep field names intuitive (`url`, `method`, `headers`, `body`) even if internally you translate to an `args[]` array.
77
-
78
- ## 4) Write a Tight Schema + Examples (Make the First Call Succeed)
79
-
80
- Tool schemas are often the only thing an LLM sees.
81
-
82
- Do:
83
- - Provide a schema that shows **both** modes (positional `args[]` + structured keys).
84
- - Add short descriptions that include **an example payload**.
85
- - Prefer `additionalProperties: true` to allow forwards-compatible additions.
86
-
87
- Avoid:
88
- - schemas that only describe `args` but not recommended “structured mode”.
89
- - schema-required fields that don’t exist for the other mode.
90
-
91
- ## 5) Make Errors Actionable (Self-Correct in One Retry)
92
-
93
- When rejecting input:
94
- - Use clear “what you passed / what I expected” wording.
95
- - Include a copy/paste example.
96
- - Provide likely tool-name suggestions when tool lookup fails.
97
-
98
- If you control the gateway layer:
99
- - Treat argument-shape failures as `isError: true` so the LLM knows to correct (not continue).
100
-
101
- ## 6) Keep Outputs Small by Default (Prevent Truncation)
102
-
103
- Large outputs cause:
104
- - truncation (the model misses the answer),
105
- - follow-up calls,
106
- - and sometimes tool-call loops.
107
-
108
- Do:
109
- - omit base64-encoded blobs by default (make them opt-in),
110
- - provide `-o/--output` style file outputs for large bodies,
111
- - include lightweight metadata (status code, headers, elapsed time).
112
-
113
- ## 7) Add Tests that Simulate “LLM Mistakes”
114
-
115
- Every new endpoint should include tests that cover:
116
- - structured payload success (`{ url, method }`),
117
- - curl-style args success (`{ args: [...] }`),
118
- - invalid shape failure (missing URL),
119
- - “tool not found” suggestion behavior (server-side).
120
-
121
- Tests belong next to the server/tool code (Bun tests under `api/`).
122
-
123
- ## 8) Endpoint Implementation Checklist
124
-
125
- When adding `root.namespace.tool`:
126
- - Add it under an explicit namespace (`net.*`, `projects.*`, etc.), not the root.
127
- - Add/adjust `TOOL_INPUT_SCHEMA_OVERRIDES[toolName]` with examples.
128
- - Ensure tool access is correct (read vs write vs admin).
129
- - Ensure agent configs include the root endpoint in `--allowed-root-endpoints` when needed.
130
- - Consider `directTools` for high-frequency tools.
@@ -1,142 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
2
- import fs from 'node:fs/promises'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { ExampleMcpClient } from './client'
6
-
7
- type RawToolResponse = {
8
- isError?: boolean
9
- content: Array<{ type: 'text'; text: string }>
10
- }
11
-
12
- const toRawToolResponse = (data: unknown, isError = false): RawToolResponse => ({
13
- isError,
14
- content: [
15
- {
16
- type: 'text',
17
- text: JSON.stringify(data),
18
- },
19
- ],
20
- })
21
-
22
- const toGitFileToolData = (content: string) => ({
23
- ok: true,
24
- status: 200,
25
- body: {
26
- path: 'README.md',
27
- type: 'file',
28
- encoding: 'base64',
29
- size: Buffer.byteLength(content, 'utf8'),
30
- content: Buffer.from(content, 'utf8').toString('base64'),
31
- },
32
- })
33
-
34
- describe('ExampleMcpClient git file guard', () => {
35
- let cacheDir: string
36
- let client: ExampleMcpClient
37
- let capturedRequests: unknown[]
38
-
39
- beforeEach(async () => {
40
- cacheDir = await fs.mkdtemp(path.join(os.tmpdir(), 'f0-mcp-client-test-'))
41
- capturedRequests = []
42
- client = new ExampleMcpClient({
43
- maxInlineFileLines: 3,
44
- fileCacheDir: cacheDir,
45
- requestTimeoutMs: 1_000,
46
- })
47
- })
48
-
49
- afterEach(async () => {
50
- await fs.rm(cacheDir, { recursive: true, force: true })
51
- })
52
-
53
- const stubRequests = (handler: (request: any) => RawToolResponse): void => {
54
- const internalClient = (client as any).client
55
- internalClient.request = async (request: any) => {
56
- capturedRequests.push(request)
57
- return handler(request)
58
- }
59
- }
60
-
61
- it('blocks large file responses unless allowLargeSize is set', async () => {
62
- const content = ['line 1', 'line 2', 'line 3', 'line 4'].join('\n')
63
- stubRequests(() => toRawToolResponse(toGitFileToolData(content)))
64
-
65
- const response = await client.call('repo.contents.view', {
66
- args: ['owner', 'repo', 'README.md'],
67
- })
68
-
69
- expect(response.isError).toBe(true)
70
- expect((response.data as any).hint).toContain('allowLargeSize')
71
- expect((response.data as any).stats.lines).toBe(4)
72
- })
73
-
74
- it('allows large file responses when allowLargeSize=true and strips client-only options', async () => {
75
- const content = ['line 1', 'line 2', 'line 3', 'line 4'].join('\n')
76
- stubRequests(() => toRawToolResponse(toGitFileToolData(content)))
77
-
78
- const response = await client.call('repo.contents.view', {
79
- args: ['owner', 'repo', 'README.md'],
80
- options: {
81
- allowLargeSize: true,
82
- },
83
- })
84
-
85
- expect(response.isError).toBe(false)
86
- expect((response.data as any).body.path).toBe('README.md')
87
- expect((capturedRequests[0] as any).params.arguments.options).toBeUndefined()
88
- })
89
-
90
- it('downloads once and serves line-by-line reads from local cache', async () => {
91
- const content = ['line 1', 'line 2', 'line 3', 'line 4'].join('\n')
92
- stubRequests(() => toRawToolResponse(toGitFileToolData(content)))
93
-
94
- const first = await client.call('repo.contents.view', {
95
- args: ['owner', 'repo', 'README.md'],
96
- options: {
97
- line: 2,
98
- },
99
- })
100
-
101
- expect(first.isError).toBe(false)
102
- expect((first.data as any).lines).toEqual([{ line: 2, text: 'line 2' }])
103
- expect(capturedRequests.length).toBe(1)
104
-
105
- const second = await client.call('repo.contents.view', {
106
- args: ['owner', 'repo', 'README.md'],
107
- options: {
108
- line: 3,
109
- },
110
- })
111
-
112
- expect(second.isError).toBe(false)
113
- expect((second.data as any).lines).toEqual([{ line: 3, text: 'line 3' }])
114
- expect(capturedRequests.length).toBe(1)
115
-
116
- const cacheEntries = await fs.readdir(cacheDir)
117
- expect(cacheEntries.some((entry) => entry.endsWith('.txt'))).toBe(true)
118
- expect(cacheEntries.some((entry) => entry.endsWith('.json'))).toBe(true)
119
- })
120
-
121
- it('keeps non-file tool calls unchanged', async () => {
122
- stubRequests(() => toRawToolResponse({ ok: true, body: { result: 'pass-through' } }))
123
-
124
- const response = await client.call('projects.listProjects')
125
-
126
- expect(response.isError).toBe(false)
127
- expect(response.data).toEqual({ ok: true, body: { result: 'pass-through' } })
128
- })
129
-
130
- it('keeps plain-text tool responses as data when JSON parsing fails', async () => {
131
- stubRequests(() => ({
132
- isError: false,
133
- content: [{ type: 'text', text: 'not-json' }],
134
- }))
135
-
136
- const response = await client.call('projects.listProjects')
137
-
138
- expect(response.isError).toBe(false)
139
- expect(response.text).toBe('not-json')
140
- expect(response.data).toBe('not-json')
141
- })
142
- })
package/mcp/manual.md DELETED
@@ -1,161 +0,0 @@
1
- # Foundation0 MCP Tool-Calling Manual (for LLMs)
2
-
3
- This is a **tool-calling guide** for the Foundation0 MCP server in `api/mcp/*`.
4
- Assume the server is already configured in your MCP host and you can call its tools.
5
-
6
- ## 0) Golden rules
7
-
8
- 1. If you are unsure about a tool name, call `mcp.listTools` first and use the exact name it returns (prefixes vary).
9
- 2. Use `mcp.describeTool` before your first call to a tool to get the input schema + an example payload.
10
- 3. Prefer named keys when possible (e.g. `projectName`, `repoName`) rather than relying on positional `args`.
11
- 4. `repoName` selects the active Git workspace identity. Use `adl` or `F0/adl`, or omit it to use the server default.
12
- 5. Tool results are returned as a **JSON text envelope**: parse the text as JSON and check `ok`.
13
-
14
- Note: If you're unsure what the server can see (cwd/workspaceRoot/available repoName values), call `mcp.workspace`.
15
-
16
- ## 1) Tool naming (prefixes + aliases)
17
-
18
- Depending on how the server was started, tool names may be:
19
-
20
- - unprefixed: `projects.listProjects`
21
- - prefixed: `api.projects.listProjects`
22
-
23
- The server usually exposes both when a prefix is set, but do not assume: **discover at runtime** via `mcp.listTools`.
24
-
25
- Some tools also have OpenAI-safe underscore aliases (no dots). Example:
26
-
27
- - `net.curl` may also be available as `net_curl`
28
-
29
- ## 2) The 4 discovery calls
30
-
31
- ### A) List all tools
32
-
33
- Tool call:
34
-
35
- ```json
36
- { "name": "mcp.listTools", "arguments": {} }
37
- ```
38
-
39
- ### B) Describe one tool (schema + example)
40
-
41
- Tool call (prefixed or unprefixed names both work here):
42
-
43
- ```json
44
- { "name": "mcp.describeTool", "arguments": { "args": ["projects.listProjects"] } }
45
- ```
46
-
47
- ### C) `mcp.search` for "search docs/spec" requests
48
-
49
- Tool call:
50
-
51
- ```json
52
- {
53
- "name": "mcp.search",
54
- "arguments": {
55
- "projectName": "<project-name>",
56
- "section": "spec",
57
- "pattern": "authentication",
58
- "repoName": "<repo-name>",
59
- "ignoreCase": true,
60
- "maxCount": 50
61
- }
62
- }
63
- ```
64
-
65
- ### D) `mcp.workspace` to debug repoName/workspaceRoot/cwd issues
66
-
67
- Tool call:
68
-
69
- ```json
70
- { "name": "mcp.workspace", "arguments": {} }
71
- ```
72
-
73
- ## 3) Payload shapes (important)
74
-
75
- Most tools accept either:
76
-
77
- ```json
78
- { "args": ["<project-name>"], "options": { "repoName": "<repo-name>" } }
79
- ```
80
-
81
- or named keys (recommended). The server merges top-level keys into `options`:
82
-
83
- ```json
84
- { "projectName": "<project-name>", "repoName": "<repo-name>" }
85
- ```
86
-
87
- Guideline: if a tool has a natural named parameter (`projectName`, `agentName`, `target`, `taskRef`), pass it explicitly.
88
-
89
- ## 4) Common calls (examples)
90
-
91
- ### A) List projects
92
-
93
- ```json
94
- {
95
- "name": "projects.listProjects",
96
- "arguments": { "repoName": "<repo-name>" }
97
- }
98
- ```
99
-
100
- ### B) List agents
101
-
102
- ```json
103
- {
104
- "name": "agents.listAgents",
105
- "arguments": { "repoName": "<repo-name>" }
106
- }
107
- ```
108
-
109
- ### C) Set an active file (projects)
110
-
111
- ```json
112
- {
113
- "name": "projects.setActive",
114
- "arguments": {
115
- "args": ["<project-name>", "/implementation-plan.v0.0.1"],
116
- "options": { "repoName": "<repo-name>", "latest": true }
117
- }
118
- }
119
- ```
120
-
121
- ### D) Batch multiple calls
122
-
123
- Call `batch` (or `<prefix>.batch`) to run multiple tool calls:
124
-
125
- ```json
126
- {
127
- "name": "batch",
128
- "arguments": {
129
- "calls": [
130
- { "tool": "projects.usage" },
131
- { "tool": "projects.listProjects", "options": { "repoName": "<repo-name>" } }
132
- ],
133
- "continueOnError": true,
134
- "maxConcurrency": 4
135
- }
136
- }
137
- ```
138
-
139
- ## 5) Reading responses (envelopes + errors)
140
-
141
- Tool results are returned as text containing JSON like:
142
-
143
- - success: `{ "ok": true, "result": ... }`
144
- - error: `{ "ok": false, "error": { "message": "...", "details": { ... } } }`
145
-
146
- If you get:
147
-
148
- - **Unknown tool**: use the `suggestions` from the error (when present), or call `mcp.listTools` again and retry.
149
- - **Missing project name**: pass `projectName` (or set `args[0]`).
150
- - **Project folder not found**: call `mcp.workspace` and verify `workspaceRoot` plus `availableRepoNames` (use `adl` or `F0/adl`).
151
- - **`projects.listProjects()` returns `[]` unexpectedly**: call `mcp.workspace` to confirm the server's `cwd`, `workspaceRoot`, and whether `/projects` exists.
152
-
153
- ## 6) Tool availability (read/write/admin)
154
-
155
- The server can be started in modes that hide tools:
156
-
157
- - read-only mode removes write-capable tools
158
- - admin-only tools are hidden unless the server is started in admin mode
159
- - root namespaces can be whitelisted (so entire namespaces may be missing)
160
-
161
- If a tool is not listed by `mcp.listTools`, you cannot call it in the current server configuration.