Fix MCP buyer flow: search, agent registration, Linq wiring - #2
Merged
Conversation
Fixes surfaced during a real end-to-end test through the MCP buyer
path (search -> rent -> execute_rental_task), not direct API calls:
- src/mcp/server.js: db.* calls used prepared statements as plain
async functions instead of .get()/.run()/.all() (register_agent,
get_listing_detail, my_purchases, etc. were all broken)
- src/mcp/server.js: search tool sent GET with query params, but the
backend route is POST-only expecting a JSON body
- src/mcp/server.js: search response unwrap read data.data instead of
data.data.results, so formatting crashed with "results.map is not
a function"
- src/routes/marketplace.js: FTS5 MATCH received the raw natural-
language query, and FTS5 barewords default to AND, so any sentence
with words not in the listing text (e.g. "I need to rent...")
returned zero rows before semantic re-ranking ever ran. Now strips
stopwords and OR-joins remaining terms.
- src/services/linq.js: called linqClient.messages.send(), which
doesn't exist on the installed @linqapp/sdk; real API is
linqClient.chats.create({from, to, message}).
Not fixed here (need input from you/the team, see PR description):
- Prava mandate creation 401s (AUTH_1001 Invalid API key) on the
configured sandbox key -- falls back to a mock approval_url.
- Linq send now reaches the real API but is rejected with
"'to' must not include the 'from' address" -- .env only has one
phone number (LINQ_PHONE_NUMBER), used as both sender and
recipient. Needs a distinct recipient number.
- chargeMandate/reportMandateCharge are implemented in
src/services/prava.js but never called from the rent/execute
route -- capture is currently just a local status flip.
Also: gitignore .mcp.json (had a machine-specific absolute node
path) and add .mcp.json.example instead; document the better-
sqlite3 native-module / Node-version gotcha in README since it
caused a silent MCP crash during this debugging session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes and hardens the end-to-end MCP buyer flow (agent registration → search → rent → execute task) by correcting DB statement usage, aligning MCP tool calls with REST semantics, and adding a real LLM-backed live-agent A2A server for rentals.
Changes:
- Fix MCP server DB usage + search request/response wiring; add
get_rental_statustool. - Improve marketplace search behavior for natural-language queries (FTS stopword stripping + optional semantic re-ranking).
- Introduce shared
llm-clientand a newlive-agentservice to provide real A2A code review responses.
Reviewed changes
Copilot reviewed 18 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/services/prava.js | Adds mandate-status polling and extra diagnostics for Prava flows. |
| src/services/openai.js | Switches OpenAI usage to shared LLM client abstraction. |
| src/services/llm-client.js | New shared LLM client provider selector (Groq/OpenAI). |
| src/services/linq.js | Updates Linq send API wiring to the SDK’s chats.create. |
| src/services/a2a-client.js | Extends parsed A2A task responses with output and clarification fields. |
| src/server.js | Simplifies dotenv initialization. |
| src/routes/marketplace.js | Fixes natural-language FTS behavior, adds semantic ranking, adds rental status endpoint, and tightens execute gating. |
| src/mcp/server.js | Fixes DB prepared statement usage, switches search to POST JSON, fixes response unwrapping, adds get_rental_status. |
| src/db/seed.js | Adds a seed path for a default live-agent listing pointing to localhost A2A server. |
| README.md | Updates MCP tool count and documents better-sqlite3 Node version mismatch gotcha + .mcp.json workflow. |
| package.json | Adds live-agent script entry. |
| package-lock.json | Updates locked zod version. |
| live-agent/server.js | New real A2A live agent implementation backed by configured LLM provider. |
| live-agent/README.md | Documents running and behavior of the new live agent. |
| live-agent/package.json | Adds standalone package metadata for the live agent. |
| live-agent/package-lock.json | Lockfile for live-agent dependencies. |
| docs/implementation_plan.md | Adds a detailed implementation plan document. |
| docs/gemini-code-1785578177707.md | Adds documentation/guide content about sellable agentic assets. |
| .mcp.json.example | Adds example MCP client config file. |
| .gitignore | Ignores machine-specific .mcp.json. |
| .env.example | Adds LLM provider config and live-agent port; tweaks sandbox expiry example. |
Files not reviewed (1)
- live-agent/package-lock.json: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+3
to
7
| if (!process.env.PRAVA_API_URL) { | ||
| console.warn('[PravaService] PRAVA_API_URL not set in .env — falling back to default, this will likely fail against real sandbox'); | ||
| } | ||
|
|
||
| const PRAVA_API_URL = process.env.PRAVA_API_URL || 'https://api.prava.com'; |
Comment on lines
+145
to
+147
| const data = await response.json(); | ||
| console.log('[PravaService] Raw createMandate response:', JSON.stringify(data, null, 2)); | ||
| return data; |
Comment on lines
48
to
52
| if (query) { | ||
| try { | ||
| results = searchListingsFTS.all(query, limit, offset); | ||
| const ftsQuery = buildFtsQuery(query); | ||
| results = ftsQuery ? searchListingsFTS.all(ftsQuery, limit, offset) : []; | ||
| } catch (e) { |
Comment on lines
+358
to
+360
| const tx = getTransactionById.get(req.params.txId); | ||
| if (!tx) throw new AppError('NOT_FOUND', 404, 'Transaction not found'); | ||
|
|
Comment on lines
+11
to
+12
| "demo-agent": "node demo-agent/server.js", | ||
| "live-agent": "node live-agent/server.js" |
Comment on lines
+13
to
+17
| await linqClient.chats.create({ | ||
| from: process.env.LINQ_PHONE_NUMBER, | ||
| to: [phoneNumber], | ||
| message: { parts: [{ type: 'text', value: text }] } | ||
| }); |
| }, toolHandler(async ({ transaction_id, agent_id }) => { | ||
| await validateAgentAndLogUsage(agent_id, 'get_rental_status', { transaction_id }); | ||
|
|
||
| const data = await fetchApi(`/rent/${transaction_id}/status`); |
Priyank911
approved these changes
Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Tested the actual MCP buyer path end-to-end (search → rent → execute_rental_task through real MCP tool calls, not direct API calls) and fixed everything that was a code bug. Two things remain blocked on external input — see below, please read before re-debugging them.
What's fixed
src/mcp/server.js:db.*calls used prepared statements as plain async functions instead of.get()/.run()/.all()— brokeregister_agent,get_listing_detail,my_purchases, usage logging, etc.src/mcp/server.js:searchtool sent aGETwith query params; the backend route isPOST-only expecting a JSON body.src/mcp/server.js:searchresponse unwrap readdata.datainstead ofdata.data.results, crashing withresults.map is not a function.src/routes/marketplace.js: FTS5MATCHgot the raw natural-language query. FTS5 barewords default to AND, so a sentence like "I need to rent a remote agent for code review" required every word to appear in the listing text and returned zero rows — before semantic re-ranking ever got a chance to run. Now strips stopwords and OR-joins the remaining terms.src/services/linq.js: calledlinqClient.messages.send(), which doesn't exist on the installed@linqapp/sdk. Real API islinqClient.chats.create({from, to, message})..mcp.jsonhad a machine-specific absolute Node path baked in — gitignored it and added.mcp.json.exampleinstead, plus a README note about a real, confirmed gotcha:better-sqlite3is a native module compiled against a specific Node version; running the server with a differentnodeon PATH causes a silentNODE_MODULE_VERSIONmismatch crash. Cost real debugging time this session — worth reading before assuming a fresh clone "just works."Verified working
search→ real semantic re-ranking via Groq (confirmed by absence of the fallback/error log, not just by there being one listing).rent→ Prava mandate flow reaches the real API (see blocker below on the key itself).execute_rental_task: confirmed it correctly blocks unapproved transactions (402) and allows approved ones — tested both paths, including manually flipping a transaction toapprovedin SQLite to isolate the A2A handoff.execute_rental_taskreaches the live-agent process overlocalhost:9001and returns a real LLM-backed code review (issues, severities, recommendations) through the MCP tool result.Blocked — not code bugs, need external input
createMandategets a real401 AUTH_1001 Invalid API keyfrom two different valid-looking sandbox keys. Falls back to a mockapproval_url. Needs a response from Prava/hackathon support on whether the team's sandbox is actually activated — not a config typo, already checked the key format and API URL.'to' must not include the 'from' address—.envonly defines one phone number (LINQ_PHONE_NUMBER), used as both sender and recipient. Needs aUSER_PHONE_NUMBER(or similar) env var with an actual distinct recipient number to test a real send.chargeMandate/reportMandateChargeare implemented insrc/services/prava.jsbut never called anywhere in the rent/execute flow — capture is currently just a local status flip tocapturedon A2A task completion. This is a team decision, not a bug: decide whether the demo needs an actual captured charge, or whether mandate-created + approved + task-executed is sufficient.Test plan
register_agentvia MCP succeedssearchvia MCP returns the live listing for a natural-language queryrentvia MCP returns a mandate response (mock, due to the Prava 401 above)execute_rental_taskblocked with 402 before approvalexecute_rental_tasksucceeds with a real A2A response after manually approving the transaction in SQLite🤖 Generated with Claude Code